Files
cycle-atlas/index.html
T
Claude 95826a1f19 Self-host fonts (Fraunces, Work Sans, IBM Plex Mono); drop Google Fonts
No third-party requests remain; CSP tightened to font-src/style-src 'self'.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LSDgjk1Agjy9Z2UH2n8bc3
2026-10-15 00:31:30 +00:00

546 lines
26 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en-AU">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data:; connect-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'">
<title>CycleWise AU</title>
<link rel="stylesheet" href="fonts/fonts.css">
<style>
:root{
--paper:#FBF7F2;
--surface:#FFFFFF;
--surface-sunken:#F1E9E1;
--ink:#251C1F;
--ink-soft:#6B5D5F;
--line:rgba(37,28,31,0.14);
--line-strong:rgba(37,28,31,0.28);
--period:#A93349;
--period-strong:#7E2438;
--period-soft:#F3DCE0;
--ovulation:#B87A1F;
--ovulation-strong:#8F5E14;
--ovulation-soft:#F4E3C4;
--fertile:#3B7A6C;
--fertile-strong:#265A4F;
--fertile-soft:#DCEBE5;
--pregnancy:#5B4B8A;
--pregnancy-strong:#453768;
--pregnancy-soft:#E5E0F2;
--shadow: 0 1px 2px rgba(37,28,31,0.06), 0 8px 24px -12px rgba(37,28,31,0.18);
}
@media (prefers-color-scheme: dark){
:root:not([data-theme="light"]){
--paper:#1B1417;
--surface:#241B1F;
--surface-sunken:#2C2226;
--ink:#F3E9E6;
--ink-soft:#B8A6AA;
--line:rgba(243,233,230,0.14);
--line-strong:rgba(243,233,230,0.26);
--period:#E58A96;
--period-strong:#F0AEB6;
--period-soft:#402129;
--ovulation:#E3AC5C;
--ovulation-strong:#EFC585;
--ovulation-soft:#3E2F19;
--fertile:#7FC4B2;
--fertile-strong:#A3D8C9;
--fertile-soft:#1E332D;
--pregnancy:#B0A0E0;
--pregnancy-strong:#CBC0EC;
--pregnancy-soft:#2C2540;
--shadow: 0 1px 2px rgba(0,0,0,0.3), 0 12px 28px -14px rgba(0,0,0,0.6);
}
}
:root[data-theme="dark"]{
--paper:#1B1417;
--surface:#241B1F;
--surface-sunken:#2C2226;
--ink:#F3E9E6;
--ink-soft:#B8A6AA;
--line:rgba(243,233,230,0.14);
--line-strong:rgba(243,233,230,0.26);
--period:#E58A96;
--period-strong:#F0AEB6;
--period-soft:#402129;
--ovulation:#E3AC5C;
--ovulation-strong:#EFC585;
--ovulation-soft:#3E2F19;
--fertile:#7FC4B2;
--fertile-strong:#A3D8C9;
--fertile-soft:#1E332D;
--pregnancy:#B0A0E0;
--pregnancy-strong:#CBC0EC;
--pregnancy-soft:#2C2540;
--shadow: 0 1px 2px rgba(0,0,0,0.3), 0 12px 28px -14px rgba(0,0,0,0.6);
}
*{box-sizing:border-box;}
body{
margin:0;
background:var(--paper);
color:var(--ink);
font-family:"Work Sans",system-ui,sans-serif;
-webkit-font-smoothing:antialiased;
}
h1,h2,h3,.display{
font-family:"Fraunces",Georgia,serif;
text-wrap:balance;
margin:0;
}
.mono{font-family:"IBM Plex Mono",ui-monospace,monospace;}
a{color:inherit;}
button{font-family:inherit;}
::selection{background:var(--period-soft);}
.app{max-width:1180px;margin:0 auto;padding:0 clamp(16px,4vw,40px) 96px;}
/* ---------- topbar ---------- */
.topbar{
display:flex;align-items:center;justify-content:space-between;
gap:20px;padding:28px 0 20px;flex-wrap:wrap;
}
.wordmark{display:flex;align-items:baseline;gap:10px;}
.wordmark .glyph{
width:30px;height:30px;flex:none;
}
.wordmark .name{font-size:1.35rem;font-weight:600;letter-spacing:-0.01em;}
.wordmark .tag{
font-family:"IBM Plex Mono",monospace;font-size:0.68rem;color:var(--ink-soft);
text-transform:uppercase;letter-spacing:0.09em;
}
.mode-switch{
display:flex;gap:4px;background:var(--surface-sunken);padding:4px;border-radius:999px;
border:1px solid var(--line);
}
.mode-btn{
border:none;background:transparent;color:var(--ink-soft);
padding:9px 18px;border-radius:999px;font-size:0.86rem;font-weight:600;
cursor:pointer;transition:background .18s ease,color .18s ease;
font-family:"Work Sans",sans-serif;
}
.mode-btn:hover{color:var(--ink);}
.mode-btn:focus-visible{outline:2px solid var(--ink);outline-offset:2px;}
.mode-btn[data-mode="period"].active{background:var(--period);color:#fff;}
.mode-btn[data-mode="ovulation"].active{background:var(--ovulation);color:#fff;}
.mode-btn[data-mode="pregnancy"].active{background:var(--pregnancy);color:#fff;}
/* ---------- panels ---------- */
.mode-panel{display:none;}
.mode-panel.active{display:block;animation:rise .4s ease both;}
@keyframes rise{from{opacity:0;transform:translateY(6px);}to{opacity:1;transform:none;}}
@media (prefers-reduced-motion: reduce){.mode-panel.active{animation:none;}}
@media (prefers-color-scheme: dark){.mode-btn[data-mode="pregnancy"].active{color:#1B1417;}}
.hero{
display:grid;grid-template-columns:1.3fr 1fr;gap:40px;
padding:36px 0 28px;border-bottom:1px solid var(--line);
align-items:center;
}
@media (max-width:820px){.hero{grid-template-columns:1fr;}}
.eyebrow{
font-family:"IBM Plex Mono",monospace;font-size:0.72rem;letter-spacing:0.12em;
text-transform:uppercase;font-weight:500;margin-bottom:10px;display:inline-block;
}
.hero h1{font-size:clamp(1.9rem,3.4vw,2.7rem);font-weight:600;line-height:1.08;}
.hero p.lede{color:var(--ink-soft);font-size:1.02rem;line-height:1.6;max-width:52ch;margin-top:14px;}
.hero-art{display:flex;justify-content:center;position:relative;}
.hero-art.has-3d{align-items:center;}
.hero-art.has-3d .ring3d{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);}
.hero-art.has-3d svg{position:relative;max-width:300px;}
.hero-art.has-3d svg path,.hero-art.has-3d svg circle{display:none;}
.hero-art svg{width:100%;max-width:220px;height:auto;}
.stat-row{display:flex;gap:12px;flex-wrap:wrap;margin-top:22px;}
.stat-tile{
background:var(--surface);border:1px solid var(--line);border-radius:14px;
padding:12px 16px;min-width:118px;box-shadow:var(--shadow);
}
.stat-tile .num{font-family:"Fraunces",serif;font-size:1.5rem;font-weight:600;line-height:1;}
.stat-tile .lbl{font-size:0.72rem;color:var(--ink-soft);margin-top:4px;text-transform:uppercase;letter-spacing:0.05em;}
.controls{display:flex;gap:16px;flex-wrap:wrap;margin-top:22px;}
.field{display:flex;flex-direction:column;gap:6px;}
.field label{font-size:0.72rem;text-transform:uppercase;letter-spacing:0.06em;color:var(--ink-soft);font-weight:600;}
.field input{
font-family:"IBM Plex Mono",monospace;font-size:0.88rem;padding:9px 11px;
border:1px solid var(--line-strong);border-radius:9px;background:var(--surface);color:var(--ink);
width:150px;
}
.field input:focus-visible{outline:2px solid var(--ink);outline-offset:1px;}
.example-note{font-size:0.76rem;color:var(--ink-soft);margin-top:14px;font-style:italic;}
/* ---------- calendar layout ---------- */
.workbench{
display:grid;grid-template-columns:1.5fr 1fr;gap:28px;padding:32px 0;
}
@media (max-width:900px){.workbench{grid-template-columns:1fr;}}
.cal-card{
background:var(--surface);border:1px solid var(--line);border-radius:20px;
padding:22px 22px 18px;box-shadow:var(--shadow);
}
.cal-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px;}
.cal-head h3{font-size:1.15rem;font-weight:600;}
.cal-nav{display:flex;gap:6px;}
.nav-btn{
width:32px;height:32px;border-radius:9px;border:1px solid var(--line-strong);
background:var(--surface);color:var(--ink);cursor:pointer;font-size:1rem;
display:flex;align-items:center;justify-content:center;
}
.nav-btn:hover{background:var(--surface-sunken);}
.weekday-row{display:grid;grid-template-columns:repeat(7,1fr);gap:6px;margin-bottom:6px;}
.weekday-row span{
text-align:center;font-size:0.68rem;color:var(--ink-soft);
text-transform:uppercase;letter-spacing:0.05em;font-weight:600;
}
.day-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:6px;}
.day-cell{
aspect-ratio:1;border-radius:10px;border:1px solid transparent;
background:var(--surface-sunken);cursor:pointer;position:relative;
display:flex;flex-direction:column;align-items:center;justify-content:center;
font-family:"IBM Plex Mono",monospace;font-size:0.78rem;color:var(--ink);
transition:transform .12s ease;
}
.day-cell:hover{transform:translateY(-1px);border-color:var(--line-strong);}
.day-cell:focus-visible{outline:2px solid var(--ink);outline-offset:1px;}
.day-cell.pad{visibility:hidden;pointer-events:none;}
.day-cell .dot-row{display:flex;gap:2px;position:absolute;bottom:4px;}
.day-cell .dot{width:4px;height:4px;border-radius:50%;}
.day-cell.today{box-shadow:inset 0 0 0 2px var(--ink);}
.day-cell.selected{box-shadow:inset 0 0 0 2px var(--ink); background:var(--surface);}
.day-cell.is-period{background:var(--period-soft);}
.day-cell.is-fertile{background:var(--fertile-soft);}
.day-cell.is-ovulation{background:var(--ovulation-soft);box-shadow:inset 0 0 0 2px var(--ovulation-strong);}
.day-cell.is-fertile.is-ovulation{box-shadow:inset 0 0 0 2px var(--ovulation-strong);}
.legend{display:flex;gap:14px;flex-wrap:wrap;margin-top:16px;font-size:0.76rem;color:var(--ink-soft);}
.legend span{display:inline-flex;align-items:center;gap:6px;}
.legend i{width:10px;height:10px;border-radius:3px;display:inline-block;}
.detail-card{
background:var(--surface);border:1px solid var(--line);border-radius:20px;
padding:24px;box-shadow:var(--shadow);display:flex;flex-direction:column;gap:14px;
}
.detail-card .d-date{font-family:"IBM Plex Mono",monospace;font-size:0.76rem;color:var(--ink-soft);text-transform:uppercase;letter-spacing:0.06em;}
.detail-card .d-phase{font-size:1.4rem;font-weight:600;font-family:"Fraunces",serif;}
.pill{
display:inline-flex;align-self:flex-start;padding:4px 12px;border-radius:999px;
font-size:0.72rem;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;
}
.d-desc{color:var(--ink-soft);line-height:1.6;font-size:0.94rem;}
.d-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:4px;}
.d-mini{background:var(--surface-sunken);border-radius:12px;padding:10px 12px;}
.d-mini .k{font-size:0.66rem;text-transform:uppercase;letter-spacing:0.06em;color:var(--ink-soft);}
.d-mini .v{font-size:0.92rem;font-weight:600;margin-top:2px;}
/* ---------- reference cards ---------- */
.ref-heading{padding:8px 0 18px;}
.ref-heading h2{font-size:1.4rem;font-weight:600;}
.ref-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;}
@media (max-width:900px){.ref-grid{grid-template-columns:repeat(2,1fr);}}
@media (max-width:520px){.ref-grid{grid-template-columns:1fr;}}
.ref-card{
background:var(--surface);border:1px solid var(--line);border-radius:16px;padding:18px;
display:flex;flex-direction:column;gap:8px;box-shadow:var(--shadow);
}
.ref-card .r-num{font-family:"IBM Plex Mono",monospace;font-size:0.7rem;color:var(--ink-soft);}
.ref-card h4{font-size:1.05rem;font-weight:600;font-family:"Fraunces",serif;}
.ref-card p{font-size:0.86rem;color:var(--ink-soft);line-height:1.55;margin:0;}
.ref-card .tag-row{display:flex;gap:6px;flex-wrap:wrap;margin-top:2px;}
.tagchip{font-size:0.68rem;background:var(--surface-sunken);padding:3px 9px;border-radius:999px;color:var(--ink-soft);}
/* ---------- ovulation timeline ---------- */
.timeline{display:flex;gap:6px;overflow-x:auto;padding:6px 2px 16px;}
.tl-btn{
flex:none;min-width:56px;border-radius:12px;border:1px solid var(--line);
background:var(--surface);padding:10px 4px;cursor:pointer;text-align:center;
}
.tl-btn .tl-day{font-family:"IBM Plex Mono",monospace;font-size:0.72rem;color:var(--ink-soft);}
.tl-btn .tl-fert{width:8px;height:8px;border-radius:50%;margin:6px auto 0;}
.tl-btn.active{box-shadow:inset 0 0 0 2px var(--ovulation-strong);background:var(--ovulation-soft);}
/* ---------- pregnancy week scrubber ---------- */
.trimester-track{position:relative;margin:26px 0 8px;}
.trimester-labels{display:flex;justify-content:space-between;font-size:0.72rem;color:var(--ink-soft);
text-transform:uppercase;letter-spacing:0.05em;margin-bottom:8px;font-weight:600;}
input[type="range"].week-slider{
width:100%;-webkit-appearance:none;appearance:none;height:8px;border-radius:999px;
background:linear-gradient(to right, var(--pregnancy) 0, var(--pregnancy) calc(11px + (100% - 22px) * var(--f,0)), var(--pregnancy-soft) calc(11px + (100% - 22px) * var(--f,0)), var(--pregnancy-soft) 100%);
outline:none;cursor:pointer;
}
input[type="range"].week-slider::-webkit-slider-thumb{
-webkit-appearance:none;width:22px;height:22px;border-radius:50%;
background:var(--pregnancy);border:3px solid var(--surface);box-shadow:var(--shadow);cursor:grab;
}
input[type="range"].week-slider::-moz-range-thumb{
width:22px;height:22px;border-radius:50%;background:var(--pregnancy);
border:3px solid var(--surface);box-shadow:var(--shadow);cursor:grab;
}
.week-ticks{display:flex;justify-content:space-between;margin-top:6px;}
.week-ticks span{font-size:0.64rem;color:var(--ink-soft);font-family:"IBM Plex Mono",monospace;}
.week-panel{
display:grid;grid-template-columns:0.9fr 1.4fr;gap:28px;margin-top:26px;
}
@media (max-width:820px){.week-panel{grid-template-columns:1fr;}}
.size-card{
background:var(--surface);border:1px solid var(--line);border-radius:20px;
padding:26px;box-shadow:var(--shadow);text-align:center;
display:flex;flex-direction:column;align-items:center;gap:10px;justify-content:center;
}
.size-card .blob{display:flex;align-items:center;justify-content:center;height:120px;}
.size-card .cmp{font-family:"Fraunces",serif;font-size:1.3rem;font-weight:600;}
.size-card .metric{font-family:"IBM Plex Mono",monospace;font-size:0.82rem;color:var(--ink-soft);}
.size-card .weeknum{font-size:0.72rem;text-transform:uppercase;letter-spacing:0.08em;color:var(--pregnancy);font-weight:700;}
.week-info{display:flex;flex-direction:column;gap:16px;}
.info-block{background:var(--surface);border:1px solid var(--line);border-radius:16px;padding:16px 18px;box-shadow:var(--shadow);}
.info-block h5{font-size:0.72rem;text-transform:uppercase;letter-spacing:0.07em;color:var(--pregnancy-strong);margin-bottom:6px;font-weight:700;}
.info-block p{margin:0;font-size:0.92rem;line-height:1.55;color:var(--ink);}
.filmstrip{display:flex;gap:8px;overflow-x:auto;padding:4px 2px 20px;margin-top:8px;}
.film-item{
color:var(--ink);
flex:none;width:64px;height:64px;border-radius:12px;border:1px solid var(--line);
background:var(--surface);display:flex;flex-direction:column;align-items:center;justify-content:center;
cursor:pointer;font-family:"IBM Plex Mono",monospace;
}
.film-item .fw{font-size:0.62rem;color:var(--ink-soft);}
.film-item .fn{font-size:1rem;font-weight:700;}
.film-item.active{background:var(--pregnancy-soft);box-shadow:inset 0 0 0 2px var(--pregnancy);}
footer.note{
border-top:1px solid var(--line);margin-top:40px;padding-top:20px;
font-size:0.78rem;color:var(--ink-soft);line-height:1.6;
}
</style>
</head>
<body>
<div class="app">
<header class="topbar">
<div class="wordmark">
<svg class="glyph" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M23.96,85.24 A44,44 0 0 1 52.36,16.67" stroke="#1B2A57" stroke-width="14" stroke-linecap="round"/>
<path d="M93.70,88.28 A44,44 0 0 1 28.89,91.11" stroke="#AAD4F5" stroke-width="14" stroke-linecap="round"/>
<path d="M67.64,16.67 A44,44 0 0 1 98.10,38.00" stroke="#F4623A" stroke-width="14" stroke-linecap="round"/>
<circle cx="103.04" cy="50.85" r="7.7" fill="#F4623A"/>
</svg>
<span class="name">CycleWise<span style="color:#F4623A"> AU</span></span>
<span class="tag">Menstrual &amp; Pregnancy Charting</span>
</div>
<nav class="mode-switch" role="tablist" aria-label="Tracking mode">
<button class="mode-btn active" id="tab-period" aria-controls="mode-period" data-mode="period" role="tab" aria-selected="true">Period</button>
<button class="mode-btn" id="tab-ovulation" aria-controls="mode-ovulation" data-mode="ovulation" role="tab" aria-selected="false">Ovulation</button>
<button class="mode-btn" id="tab-pregnancy" aria-controls="mode-pregnancy" data-mode="pregnancy" role="tab" aria-selected="false">Pregnancy</button>
</nav>
</header>
<main>
<!-- ================= PERIOD ================= -->
<section id="mode-period" class="mode-panel active" role="tabpanel" aria-labelledby="tab-period">
<div class="hero">
<div>
<span class="eyebrow" style="color:var(--period)">01 — Menstrual Cycle</span>
<h1>Chart your cycle, one day at a time.</h1>
<p class="lede">Log the first day of your last period and your typical cycle length — the calendar plots your period, fertile window, and ovulation day automatically, and updates every time you adjust the numbers.</p>
<div class="controls">
<div class="field">
<label for="p-start">Last period started</label>
<input type="date" id="p-start">
</div>
<div class="field">
<label for="p-cyclelen">Cycle length (days)</label>
<input type="number" id="p-cyclelen" min="21" max="35" value="28">
</div>
<div class="field">
<label for="p-periodlen">Period length (days)</label>
<input type="number" id="p-periodlen" min="2" max="10" value="5">
</div>
</div>
<p class="example-note" id="p-example-note">Prefilled with an example date — enter your own to personalize the calendar.</p>
<div class="stat-row" id="period-stats"></div>
</div>
<div class="hero-art">
<svg viewBox="0 0 200 200" aria-hidden="true">
<circle cx="100" cy="100" r="88" fill="none" stroke="var(--line-strong)" stroke-width="1.5"/>
<path id="phase-arc-menstrual" fill="none" stroke="var(--period)" stroke-width="10" stroke-linecap="round"/>
<path id="phase-arc-follicular" fill="none" stroke="var(--ovulation-soft)" stroke-width="10" stroke-linecap="round"/>
<path id="phase-arc-fertile" fill="none" stroke="var(--fertile)" stroke-width="10" stroke-linecap="round"/>
<path id="phase-arc-luteal" fill="none" stroke="var(--pregnancy-soft)" stroke-width="10" stroke-linecap="round"/>
<circle id="today-marker" cx="100" cy="12" r="6" fill="var(--ink)"/>
<text x="100" y="96" text-anchor="middle" class="mono" font-size="11" fill="var(--ink-soft)">CYCLE DAY</text>
<text id="cycle-day-big" x="100" y="122" text-anchor="middle" font-family="Fraunces,serif" font-size="34" font-weight="700" fill="var(--ink)">1</text>
</svg>
</div>
</div>
<div class="workbench">
<div class="cal-card">
<div class="cal-head">
<h3 id="p-cal-title">Month</h3>
<div class="cal-nav">
<button class="nav-btn" id="p-prev" aria-label="Previous month">‹</button>
<button class="nav-btn" id="p-next" aria-label="Next month">›</button>
</div>
</div>
<div class="weekday-row"><span>S</span><span>M</span><span>T</span><span>W</span><span>T</span><span>F</span><span>S</span></div>
<div class="day-grid" id="p-daygrid"></div>
<div class="legend">
<span><i style="background:var(--period-soft)"></i>Period</span>
<span><i style="background:var(--fertile-soft)"></i>Fertile window</span>
<span><i style="background:var(--ovulation-soft);box-shadow:inset 0 0 0 2px var(--ovulation-strong)"></i>Ovulation</span>
<span><i style="background:var(--surface-sunken)"></i>Other</span>
</div>
</div>
<div class="detail-card" id="p-detail"></div>
</div>
<div class="ref-heading">
<span class="eyebrow" style="color:var(--period)">Reference</span>
<h2>The four phases</h2>
</div>
<div class="ref-grid" id="phase-ref-grid"></div>
</section>
<!-- ================= OVULATION ================= -->
<section id="mode-ovulation" class="mode-panel" role="tabpanel" aria-labelledby="tab-ovulation">
<div class="hero">
<div>
<span class="eyebrow" style="color:var(--ovulation)">02 — Fertile Window</span>
<h1>Six days a cycle actually matter.</h1>
<p class="lede">Conception is only possible in the days leading up to and including ovulation. This view uses the same cycle dates to surface your fertile window, plus what basal body temperature and cervical mucus typically do each day around it.</p>
<div class="stat-row" id="ovulation-stats"></div>
</div>
<div class="hero-art">
<svg viewBox="0 0 200 200" aria-hidden="true">
<circle cx="100" cy="100" r="86" fill="none" stroke="var(--fertile-soft)" stroke-width="16"/>
<circle cx="100" cy="100" r="86" fill="none" stroke="var(--ovulation)" stroke-width="16"
stroke-dasharray="49 490" stroke-dashoffset="-405" stroke-linecap="round"/>
<circle cx="100" cy="100" r="54" fill="var(--surface)" stroke="var(--line)" />
<text x="100" y="96" text-anchor="middle" class="mono" font-size="10" fill="var(--ink-soft)">DAY VS.</text>
<text x="100" y="112" text-anchor="middle" class="mono" font-size="10" fill="var(--ink-soft)">OVULATION</text>
</svg>
</div>
</div>
<div class="ref-heading" style="padding-bottom:4px;">
<span class="eyebrow" style="color:var(--ovulation)">Timeline</span>
<h2>Day by day around ovulation</h2>
</div>
<div class="timeline" id="ov-timeline"></div>
<div class="workbench">
<div class="cal-card">
<div class="cal-head">
<h3 id="o-cal-title">Month</h3>
<div class="cal-nav">
<button class="nav-btn" id="o-prev" aria-label="Previous month">‹</button>
<button class="nav-btn" id="o-next" aria-label="Next month">›</button>
</div>
</div>
<div class="weekday-row"><span>S</span><span>M</span><span>T</span><span>W</span><span>T</span><span>F</span><span>S</span></div>
<div class="day-grid" id="o-daygrid"></div>
<div class="legend">
<span><i style="background:var(--fertile-soft)"></i>Fertile window</span>
<span><i style="background:var(--ovulation-soft);box-shadow:inset 0 0 0 2px var(--ovulation-strong)"></i>Ovulation day</span>
<span><i style="background:var(--period-soft)"></i>Period</span>
</div>
</div>
<div class="detail-card" id="o-detail"></div>
</div>
</section>
<!-- ================= PREGNANCY ================= -->
<section id="mode-pregnancy" class="mode-panel" role="tabpanel" aria-labelledby="tab-pregnancy">
<div class="hero">
<div>
<span class="eyebrow" style="color:var(--pregnancy)">03 — Forty Weeks</span>
<h1>Every week of pregnancy, mapped.</h1>
<p class="lede">Enter the first day of your last menstrual period (LMP) — pregnancy is dated from there, not from conception. The tracker converts today's date into a gestational week and your estimated due date.</p>
<div class="controls">
<div class="field">
<label for="pr-lmp">Last menstrual period</label>
<input type="date" id="pr-lmp">
</div>
</div>
<p class="example-note" id="pr-example-note">Prefilled with an example LMP — enter your own for your real week and due date.</p>
<div class="stat-row" id="pregnancy-stats"></div>
</div>
<div class="hero-art">
<svg viewBox="0 0 200 200" aria-hidden="true">
<path d="M20 160 C 60 150, 60 60, 100 40 C 140 20, 160 60, 180 30" fill="none" stroke="var(--pregnancy-soft)" stroke-width="10" stroke-linecap="round"/>
<path id="pregnancy-progress-path" d="M20 160 C 60 150, 60 60, 100 40 C 140 20, 160 60, 180 30" fill="none" stroke="var(--pregnancy)" stroke-width="10" stroke-linecap="round" stroke-dasharray="230" stroke-dashoffset="230"/>
<circle id="pregnancy-progress-dot" cx="20" cy="160" r="7" fill="var(--pregnancy)"/>
</svg>
</div>
</div>
<div class="trimester-track">
<div class="trimester-labels">
<span style="color:var(--pregnancy)">Trimester 1 · Weeks 1–13</span>
<span style="color:var(--pregnancy)">Trimester 2 · Weeks 14–27</span>
<span style="color:var(--pregnancy)">Trimester 3 · Weeks 28–40</span>
</div>
<input type="range" class="week-slider" id="week-slider" aria-label="Pregnancy week, 1 to 40" min="1" max="40" value="20">
<div class="week-ticks"><span>Wk 1</span><span>Wk 13</span><span>Wk 27</span><span>Wk 40</span></div>
</div>
<div class="week-panel">
<div class="size-card">
<span class="weeknum" id="wk-badge">WEEK 20</span>
<div class="blob" id="wk-blob"></div>
<div class="cmp" id="wk-compare">size of a banana</div>
<div class="metric" id="wk-metric">≈ 16.5 cm · 300 g</div>
</div>
<div class="week-info">
<div class="info-block"><h5>Baby's development</h5><p id="wk-dev"></p></div>
<div class="info-block"><h5>Your body</h5><p id="wk-body"></p></div>
<div class="info-block"><h5>This week's note</h5><p id="wk-tip"></p></div>
</div>
</div>
<div class="ref-heading" style="padding-bottom:4px;">
<span class="eyebrow" style="color:var(--pregnancy)">Jump to a week</span>
<h2>All 40 weeks</h2>
</div>
<div class="filmstrip" id="week-filmstrip"></div>
<div class="workbench" style="grid-template-columns:1fr;">
<div class="cal-card">
<div class="cal-head">
<h3 id="pr-cal-title">Month</h3>
<div class="cal-nav">
<button class="nav-btn" id="pr-prev" aria-label="Previous month">‹</button>
<button class="nav-btn" id="pr-next" aria-label="Next month">›</button>
</div>
</div>
<div class="weekday-row"><span>S</span><span>M</span><span>T</span><span>W</span><span>T</span><span>F</span><span>S</span></div>
<div class="day-grid" id="pr-daygrid"></div>
<div class="legend">
<span><i style="background:var(--pregnancy-soft)"></i>Each date labeled with its gestational week</span>
</div>
</div>
</div>
</section>
</main>
<footer class="note">
CycleWise AU gives general educational estimates based on average cycle math — not medical advice or a diagnosis. Actual timing varies by body. Speak with a clinician for guidance specific to you. Dates you enter are stored only in this browser, on this device.
</footer>
</div>
<script src="app.js"></script>
<script src="vendor/gsap.min.js"></script>
<script src="vendor/ScrollTrigger.min.js"></script>
<script src="ring3d.js"></script>
<script src="animations.js"></script>
<script src="timeline.js"></script>
</body>
</html>