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>