The cycle tool

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Your Cycle — Cremora X Intelligence™</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Italiana&family=Jost:wght@300;400;500&display=swap" rel="stylesheet">
<style>
  :root{
    --ink:#120B10; --ink-2:#1C1218;
    --ivory:#F0E7DD; --brass:#C7A17A; --mauve:#9C7C85; --ember:#E0A96D;
    --menstrual:#5C4A66; --follicular:#7A8B69; --ovulatory:#B08D57; --luteal:#A24E3E;
    --edge:rgba(199,161,122,.20);
  }
  *{box-sizing:border-box;margin:0;padding:0}
  html,body{background:var(--ink)}
  body{
    color:var(--ivory); font-family:'Jost',-apple-system,'Segoe UI',sans-serif; font-weight:300;
    -webkit-font-smoothing:antialiased; min-height:100dvh;
    display:flex; align-items:center; justify-content:center;
    padding:clamp(1.5rem,5vw,3rem);
  }

  .cyclesync{width:100%; max-width:44rem}

  .head{text-align:center; margin-bottom:2rem}
  .eyebrow{font-size:.6rem; letter-spacing:.3em; text-transform:uppercase; color:var(--mauve)}
  h1{font-family:'Italiana',serif; font-weight:400; font-size:clamp(1.8rem,6vw,2.6rem);
     letter-spacing:.06em; margin-top:.5rem}

  /* ── date entry ── */
  .setup{
    max-width:26rem; margin:0 auto 2.5rem; padding:1.4rem 1.5rem;
    border:1px solid var(--edge); border-radius:3px; background:rgba(240,231,221,.02);
  }
  .setup .field{display:flex; flex-direction:column; gap:.45rem; margin-bottom:1rem}
  .setup .field:last-of-type{margin-bottom:1.2rem}
  .setup label{font-size:.62rem; letter-spacing:.22em; text-transform:uppercase; color:var(--mauve)}
  .setup input, .setup select{
    background:var(--ink-2); border:1px solid var(--edge); border-radius:2px;
    color:var(--ivory); font-family:'Jost',sans-serif; font-size:.95rem; padding:.7rem .8rem;
    width:100%;
  }
  .setup input:focus, .setup select:focus{outline:1px solid var(--brass); outline-offset:1px}
  .setup input::-webkit-calendar-picker-indicator{filter:invert(.7) sepia(.4)}
  .setup button{
    width:100%; padding:.85rem; cursor:pointer;
    background:var(--brass); color:var(--ink); border:none; border-radius:2px;
    font-family:'Jost',sans-serif; font-size:.7rem; font-weight:500; letter-spacing:.22em; text-transform:uppercase;
    transition:background .3s ease;
  }
  .setup button:hover{background:var(--ember)}
  .setup .hint{margin-top:.9rem; font-size:.66rem; line-height:1.5; color:rgba(156,124,133,.75); text-align:center}

  /* ── wheel ── */
  .stage{display:none}
  .stage.on{display:block; animation:fade .8s ease}
  @keyframes fade{from{opacity:0}to{opacity:1}}

  .wheelwrap{display:grid; place-items:center; margin-bottom:1.5rem}
  svg{width:min(20rem,80vw); height:auto; overflow:visible}
  .arc{cursor:pointer; transition:opacity .45s ease, transform .45s ease; transform-origin:160px 160px}
  .arc.dim{opacity:.28}
  .arc.here{opacity:1}
  .arc:hover{opacity:1}
  .center-label{fill:var(--ivory); font-family:'Italiana',serif}
  .center-day{fill:var(--brass); font-family:'Italiana',serif}
  .center-sub{fill:var(--mauve); font-family:'Jost',sans-serif}

  /* ── readout ── */
  .readout{
    max-width:30rem; margin:0 auto; text-align:center;
    padding:1.6rem 1.5rem; border:1px solid var(--edge); border-radius:3px;
    background:rgba(240,231,221,.02);
  }
  .readout .phase-name{font-family:'Italiana',serif; font-size:1.6rem; letter-spacing:.04em}
  .readout .phase-line{margin-top:.5rem; font-size:1.05rem; color:var(--ivory); opacity:.9}
  .readout .lessons{
    list-style:none; margin-top:1.3rem; display:flex; flex-wrap:wrap; gap:.5rem; justify-content:center;
  }
  .readout .lessons li{
    font-size:.68rem; letter-spacing:.08em; padding:.4rem .8rem;
    border:1px solid var(--edge); border-radius:2px; color:var(--brass);
  }
  .readout .cta{
    display:inline-block; margin-top:1.5rem; text-decoration:none;
    font-size:.64rem; letter-spacing:.22em; text-transform:uppercase; color:var(--mauve);
    border-bottom:1px solid var(--edge); padding-bottom:.3rem;
    transition:color .3s ease, border-color .3s ease;
  }
  .readout .cta:hover{color:var(--brass); border-color:var(--brass)}

  .reset{
    display:block; margin:1.5rem auto 0; background:none; border:none; cursor:pointer;
    font-family:'Jost',sans-serif; font-size:.6rem; letter-spacing:.2em; text-transform:uppercase;
    color:rgba(156,124,133,.6); transition:color .3s ease;
  }
  .reset:hover{color:var(--mauve)}

  .disclaimer{
    margin-top:2rem; text-align:center; font-size:.6rem; line-height:1.6;
    color:rgba(156,124,133,.5); max-width:28rem; margin-left:auto; margin-right:auto;
  }
</style>
</head>
<body>
<div class="cyclesync">

  <div class="head">
    <div class="eyebrow">Cremora X Intelligence™</div>
    <h1>Your Cycle</h1>
  </div>

  <!-- date entry -->
  <div class="setup" id="setup">
    <div class="field">
      <label for="start">First day of your last period</label>
      <input type="date" id="start">
    </div>
    <div class="field">
      <label for="length">Average cycle length</label>
      <select id="length">
        <option value="24">24 days</option>
        <option value="26">26 days</option>
        <option value="28" selected>28 days</option>
        <option value="30">30 days</option>
        <option value="32">32 days</option>
        <option value="34">34 days</option>
      </select>
    </div>
    <button id="sync">Sync my cycle</button>
    <p class="hint">Your date stays on your device. Nothing is sent anywhere.</p>
  </div>

  <!-- wheel + readout -->
  <div class="stage" id="stage">
    <div class="wheelwrap">
      <svg viewBox="0 0 320 320" id="wheel" role="img" aria-label="Cycle phase wheel">
        <g id="arcs"></g>
        <text class="center-label" x="160" y="150" text-anchor="middle" font-size="15" id="cLabel">—</text>
        <text class="center-day"   x="160" y="176" text-anchor="middle" font-size="26" id="cDay">—</text>
        <text class="center-sub"   x="160" y="196" text-anchor="middle" font-size="10" letter-spacing="2" id="cSub">DAY</text>
      </svg>
    </div>

    <div class="readout" id="readout"></div>
    <button class="reset" id="reset">Change my date</button>
  </div>

  <p class="disclaimer">
    Cycle phases and their timing vary from person to person, and this tool estimates
    phases from an average. It's for reflection and personal-development planning —
    not medical advice, contraception, or diagnosis.
  </p>
</div>

<script>
  const PHASES = {
    menstrual:  { label:"Menstrual",  color:"#5C4A66", line:"Energy drops inward. A week to rest, reflect, and read your own data.",           lessons:["Reading your data","Subconscious download","Rest","Reflection template"] },
    follicular: { label:"Follicular", color:"#7A8B69", line:"Openness returns. Ideas come easily — a week to plan and rewrite the paradigm.", lessons:["Paradigm rewrite","Neuroplasticity","Higher-self letter","Planning"] },
    ovulatory:  { label:"Ovulatory",  color:"#B08D57", line:"Magnetism peaks. Your most visible, expressive week — direct the charge outward.", lessons:["Transmutation practice","Directing charge","Visibility work","Journaling prompts"] },
    luteal:     { label:"Luteal",     color:"#A24E3E", line:"Focus narrows and discernment sharpens. A week to edit, finish, and set boundaries.", lessons:["Physiological sigh","Affect labeling","Editing rituals","Boundary scripts"] }
  };
  const order = ['menstrual','follicular','ovulatory','luteal'];

  // ── geometry ──
  const CX=160, CY=160, R=118, W=34, GAP=5; // degrees gap between arcs
  function polar(cx,cy,r,deg){ const rad=(deg-90)*Math.PI/180; return [cx+r*Math.cos(rad), cy+r*Math.sin(rad)]; }
  function arcPath(cx,cy,rOuter,rInner,start,end){
    const [ox1,oy1]=polar(cx,cy,rOuter,start),[ox2,oy2]=polar(cx,cy,rOuter,end);
    const [ix2,iy2]=polar(cx,cy,rInner,end),[ix1,iy1]=polar(cx,cy,rInner,start);
    const large = end-start>180?1:0;
    return `M ${ox1} ${oy1} A ${rOuter} ${rOuter} 0 ${large} 1 ${ox2} ${oy2}`
         + ` L ${ix2} ${iy2} A ${rInner} ${rInner} 0 ${large} 0 ${ix1} ${iy1} Z`;
  }

  // ── phase math ──
  // Proportional model: phase boundaries scale with cycle length.
  // menstrual ~ days 1–5, follicular ~ 6–13, ovulatory ~ 14–16, luteal ~ 17–end (for a 28-day baseline)
  function phaseForDay(day, len){
    const menEnd = Math.max(4, Math.round(len*0.18));      // ~5
    const folEnd = Math.round(len*0.46);                    // ~13
    const ovEnd  = Math.round(len*0.57);                    // ~16
    if(day<=menEnd) return 'menstrual';
    if(day<=folEnd) return 'follicular';
    if(day<=ovEnd)  return 'ovulatory';
    return 'luteal';
  }

  function cycleDay(startStr, len){
    const start = new Date(startStr+'T00:00:00');
    const today = new Date(); today.setHours(0,0,0,0);
    const diff = Math.floor((today - start)/86400000);
    if(isNaN(diff)) return null;
    // wrap into 1..len
    return ((diff % len) + len) % len + 1;
  }

  // ── render ──
  const arcsG = document.getElementById('arcs');
  function drawWheel(activePhase){
    arcsG.innerHTML='';
    const span = 90 - GAP;
    order.forEach((key,i)=>{
      const start = i*90 + GAP/2;
      const p = document.createElementNS('http://www.w3.org/2000/svg','path');
      p.setAttribute('d', arcPath(CX,CY,R,R-W,start,start+span));
      p.setAttribute('fill', PHASES[key].color);
      p.setAttribute('class','arc'+(activePhase? (key===activePhase?' here':' dim') : ''));
      p.addEventListener('mouseenter',()=>showReadout(key,false));
      p.addEventListener('mouseleave',()=>{ if(activePhase) showReadout(activePhase,true); });
      p.addEventListener('click',()=>showReadout(key,false));
      arcsG.appendChild(p);
    });
  }

  const readoutEl = document.getElementById('readout');
  function showReadout(key, isCurrent){
    const p = PHASES[key];
    readoutEl.innerHTML =
      `<div class="phase-name" style="color:${p.color==='#B08D57'?'var(--brass)':p.color==='#7A8B69'?'#9DB088':p.color==='#5C4A66'?'#A594B0':'#D4796A'}">`
      + (isCurrent? "You're in your "+p.label+" phase" : p.label)
      + `</div>`
      + `<div class="phase-line">${p.line}</div>`
      + `<ul class="lessons">${p.lessons.map(l=>`<li>${l}</li>`).join('')}</ul>`
      + `<a class="cta" href="https://cremorax.com/products/cremora-x-method-the-five-pillar-course" target="_blank" rel="noopener">Go deeper in the Method →</a>`;
  }

  // ── flow ──
  const setup=document.getElementById('setup'), stage=document.getElementById('stage');
  const KEY='cremorax_cycle';

  function run(startStr, len){
    const day = cycleDay(startStr, len);
    if(day===null){ return; }
    const phase = phaseForDay(day, len);
    document.getElementById('cLabel').textContent = PHASES[phase].label;
    document.getElementById('cDay').textContent = day;
    document.getElementById('cSub').textContent = 'OF '+len;
    drawWheel(phase);
    showReadout(phase, true);
    setup.style.display='none';
    stage.classList.add('on');
  }

  document.getElementById('sync').addEventListener('click',()=>{
    const startStr=document.getElementById('start').value;
    const len=parseInt(document.getElementById('length').value,10);
    if(!startStr){ document.getElementById('start').focus(); return; }
    try{ localStorage.setItem(KEY, JSON.stringify({startStr,len})); }catch(e){}
    run(startStr, len);
  });

  document.getElementById('reset').addEventListener('click',()=>{
    try{ localStorage.removeItem(KEY); }catch(e){}
    stage.classList.remove('on');
    setup.style.display='block';
  });

  // restore
  try{
    const saved=JSON.parse(localStorage.getItem(KEY)||'null');
    if(saved && saved.startStr){
      document.getElementById('start').value=saved.startStr;
      document.getElementById('length').value=saved.len;
      run(saved.startStr, saved.len);
    }
  }catch(e){}
</script>
</body>
</html>