/* ==========================================================================
   CycleCue — Motion & Cycle Experience
   --------------------------------------------------------------------------
   The organising idea: scroll IS the menstrual cycle. This file owns the
   phase colour system, the cycle dial, the hormone chart and every
   scroll-driven reveal.

   COLOUR CONTRAST CONTRACT (measured, WCAG 2.1 — see scripts note in README):
     --phase-accent  graphics/marks only.  >= 3.00:1 on white  (lowest 3.30)
     --phase-ink     text + white-on-colour. >= 4.50:1 on white (lowest 6.21)
     --phase-wash    section tint. Body ink on it >= 15.7:1
   Never put small white text on --phase-accent; use --phase-ink.
   ========================================================================== */

:root {
    /* Phase palette — one source of truth, mirrored by PHASES in cycle-motion.js */
    --menstrual-accent:  #C85B7A;  --menstrual-ink:  #9B3355;  --menstrual-wash:  #FDF2F6;
    --follicular-accent: #C97B34;  --follicular-ink: #8F5210;  --follicular-wash: #FDF6EC;
    --ovulatory-accent:  #0E8C82;  --ovulatory-ink:  #0A6B63;  --ovulatory-wash:  #ECF8F6;
    --luteal-accent:     #7A5AA8;  --luteal-ink:     #5E3F8A;  --luteal-wash:     #F5F1FB;
    --pregnancy-accent:  #B5647F;  --pregnancy-ink:  #8A3F58;  --pregnancy-wash:  #FDF3F5;

    /* Live values — JS interpolates these across the cycle section.
       Defaults equal phase 1 so the page is correct before JS runs. */
    --phase-accent: var(--menstrual-accent);
    --phase-ink:    var(--menstrual-ink);
    --phase-wash:   var(--menstrual-wash);

    /* Motion tokens */
    --ease-out-soft:  cubic-bezier(0.22, 1, 0.36, 1);
    --ease-in-out-soft: cubic-bezier(0.65, 0, 0.35, 1);
    --dur-fast:   240ms;
    --dur-base:   520ms;
    --dur-slow:   900ms;

    /* Cycle section scroll length — one viewport of scroll per phase panel */
    --cycle-panel-h: 100vh;
}

/* ==========================================================================
   1. Progressive enhancement gate
   Content is visible by default. Only once cycle-motion.js has booted does
   `.js-motion` appear on <html> and hide things ahead of their reveal.
   With JS off, or if the script fails, everything stays readable.
   ========================================================================== */

.js-motion [data-reveal] {
    opacity: 0;
    will-change: opacity, transform;
}

/* Y and scale only — deliberately no X offsets. A horizontal reveal offset on
   a full-width element pushes it past the viewport edge and creates real
   horizontal overflow (it was adding 8px on the contact form). Vertical
   offsets can never do that, and they read better against the scroll
   direction anyway. */
.js-motion [data-reveal="up"]    { transform: translate3d(0, 28px, 0); }
.js-motion [data-reveal="scale"] { transform: scale(0.94); }

/* Word-splitting for headline reveals (built by JS from text nodes only —
   never innerHTML, so it cannot introduce markup). */
.word {
    display: inline-block;
    overflow: hidden;
    vertical-align: top;
    /* Descenders (g, y, p) would be clipped by overflow:hidden without this. */
    padding-bottom: 0.12em;
    margin-bottom: -0.12em;
}

.word__inner {
    display: inline-block;
    will-change: transform;
}

/* `background-clip: text` only clips the background of the element that
   carries it. Once splitWords() wraps the text in child spans, those children
   inherit `-webkit-text-fill-color: transparent` but have no background of
   their own — so the words render completely invisible. Give each split word
   its own gradient. */
.gradient-text .word__inner {
    background: var(--gradient-primary);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
}

.js-motion .word__inner { transform: translate3d(0, 105%, 0); }

/* Lenis requirements */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-stopped { overflow: hidden; }

/* ==========================================================================
   2. Navigation — scroll state + live cycle pill
   ========================================================================== */

.navbar {
    transition: padding var(--dur-fast) var(--ease-out-soft),
                background var(--dur-fast) linear,
                box-shadow var(--dur-fast) linear;
}

.navbar.is-scrolled {
    padding: 0.45rem 0;
    background: rgba(255, 255, 255, 0.9);
    box-shadow: 0 1px 0 rgba(200, 91, 122, 0.14), 0 8px 28px rgba(200, 91, 122, 0.07);
}

/* Reading-progress hairline along the bottom edge of the navbar */
.nav-progress {
    position: absolute;
    left: 0;
    bottom: -1px;
    height: 2px;
    width: 100%;
    transform: scaleX(0);
    transform-origin: 0 50%;
    background: var(--phase-accent);
    transition: background var(--dur-base) linear;
    pointer-events: none;
}

/* The nav pill mirrors the cycle dial while the cycle section is on screen. */
.nav-cycle-pill {
    display: none;
    align-items: center;
    gap: 8px;
    padding: 6px 14px 6px 8px;
    border-radius: var(--border-radius-pill);
    background: var(--color-stone-100);
    font-size: 0.8125rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--color-stone-600);
    white-space: nowrap;
}

.nav-cycle-pill.is-active { display: inline-flex; }

.nav-cycle-pill__dot {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--phase-accent);
    flex-shrink: 0;
    transition: background var(--dur-base) linear;
}

.nav-cycle-pill__day { color: var(--color-stone-900); }

@media (max-width: 900px) {
    .nav-cycle-pill.is-active { display: none; }
}

/* ==========================================================================
   3. Hero
   ========================================================================== */

.hero { isolation: isolate; }

.hero-title .line { display: block; }

/* Slow-turning cycle ring behind the phone — the motif, stated quietly. */
.hero-ring {
    position: absolute;
    top: 50%;
    left: 50%;
    width: min(120%, 560px);
    aspect-ratio: 1;
    /* transform, not the standalone `translate` property — wider support. */
    transform: translate(-50%, -50%);
    pointer-events: none;
    z-index: 0;
    opacity: 0.85;
}

.hero-ring__track,
.hero-ring__arc {
    fill: none;
    stroke-linecap: round;
}

.hero-ring__track { stroke: var(--color-rose-100); stroke-width: 1.5; }
.hero-ring__arc   { stroke: var(--color-rose-200); stroke-width: 3; }

.hero-ring__spin { transform-origin: 160px 160px; animation: heroSpin 64s linear infinite; }
.hero-ring__spin--reverse { animation-direction: reverse; animation-duration: 96s; }

@keyframes heroSpin { to { transform: rotate(360deg); } }

.hero-ring__pip { fill: var(--color-rose-200); }

.hero-visual { position: relative; }
.phone-mockup { position: relative; z-index: 2; }

/* Scroll cue */
.hero-cue {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    margin-top: var(--spacing-lg);
    font-size: 0.8125rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--color-stone-600);
    background: none;
    border: none;
    padding: 8px 4px;
    cursor: pointer;
    font-family: var(--font-secondary);
    border-radius: var(--border-radius-sm);
}

.hero-cue:hover { color: var(--color-rose-600); }

.hero-cue__arrow {
    display: block;
    animation: cueBob 2.4s var(--ease-in-out-soft) infinite;
}

@keyframes cueBob {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(5px); }
}

/* ==========================================================================
   4. The Cycle — the centrepiece
   Pinning uses native `position: sticky` (not a JS pin) so the layout is
   correct on iOS and survives JS failure. ScrollTrigger only supplies the
   scrub progress value.
   ========================================================================== */

.cycle {
    position: relative;
    /* 5 panels: menstrual, follicular, ovulatory, luteal, late/pregnancy */
    height: calc(5 * var(--cycle-panel-h));
    background: var(--phase-wash);
    transition: background var(--dur-slow) linear;
}

.cycle__sticky {
    position: sticky;
    top: 0;
    height: 100vh;
    height: 100svh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    overflow: hidden;
}

.cycle__intro {
    text-align: center;
    padding-top: clamp(52px, 6.5vh, 84px);
    padding-bottom: var(--spacing-sm);
}

.cycle__eyebrow {
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--phase-ink);
    margin: 0 0 6px;
    transition: color var(--dur-base) linear;
}

.cycle__stage {
    display: grid;
    grid-template-columns: minmax(280px, 360px) minmax(0, 1fr) minmax(200px, 250px);
    gap: clamp(1.5rem, 4vw, 3.5rem);
    align-items: center;
    flex: 1 1 auto;
    min-height: 0;
    padding-bottom: var(--spacing-md);
    /* Hard backstop. The tiers below are sized so content fits, but a longer
       phase description or an unusual viewport must never let the stage paint
       over the hormone panel — the panel is a later sibling, so it wins the
       paint order and the collision looks like a rendering fault. Clipping is
       the lesser failure. */
    overflow: hidden;
}

/* ---- Dial ---------------------------------------------------------------- */

.dial {
    position: relative;
    width: 100%;
    /* Height-aware: a 360px dial is fine on a 1080px screen and 60px too tall
       on a 900px one. 34vh keeps it proportional to the room available. */
    max-width: min(360px, 34vh);
    margin-inline: auto;
    aspect-ratio: 1;
}

.dial__svg { width: 100%; height: 100%; display: block; overflow: visible; }

.dial__ring-bg {
    fill: none;
    stroke: rgba(28, 25, 23, 0.07);
    stroke-width: 10;
}

.dial__band {
    fill: none;
    stroke-width: 10;
    stroke-linecap: butt;
    opacity: 0.22;
}

.dial__band--menstrual  { stroke: var(--menstrual-accent); }
.dial__band--follicular { stroke: var(--follicular-accent); }
.dial__band--ovulatory  { stroke: var(--ovulatory-accent); }
.dial__band--luteal     { stroke: var(--luteal-accent); }

/* Progress arc. r=104 -> circumference 653.45. Rotated so day 1 sits at 12 o'clock. */
.dial__progress {
    fill: none;
    stroke: var(--phase-accent);
    stroke-width: 10;
    stroke-linecap: round;
    stroke-dasharray: 653.45;
    stroke-dashoffset: 653.45;
    transform: rotate(-90deg);
    transform-origin: 160px 160px;
    transition: stroke var(--dur-base) linear;
}

/* Overflow arc — drawn when the cycle runs past day 28 (a late period). */
.dial__overflow {
    fill: none;
    stroke: var(--pregnancy-accent);
    stroke-width: 10;
    stroke-linecap: round;
    stroke-dasharray: 4 12;
    stroke-dashoffset: 653.45;
    transform: rotate(-90deg);
    transform-origin: 160px 160px;
    opacity: 0;
}

.dial__tick {
    stroke: rgba(28, 25, 23, 0.18);
    stroke-width: 2;
    stroke-linecap: round;
    transition: stroke 300ms linear, opacity 300ms linear;
}

.dial__tick.is-passed { stroke: var(--phase-accent); }

.dial__marker { transition: none; }

.dial__marker-halo { fill: var(--phase-accent); opacity: 0.18; }
.dial__marker-dot  { fill: var(--phase-accent); stroke: #fff; stroke-width: 3; }

/* Dial centre readout */
.dial__readout {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    pointer-events: none;
}

.dial__day-label {
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--color-stone-400);
}

.dial__day-number {
    font-family: var(--font-primary);
    font-size: clamp(3rem, 7vw, 4.5rem);
    font-weight: 700;
    line-height: 1;
    color: var(--phase-ink);
    /* Digits must not reflow the layout as they change width. */
    font-variant-numeric: tabular-nums;
    transition: color var(--dur-base) linear;
}

.dial__phase-name {
    font-size: 0.8125rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--phase-ink);
    margin-top: 6px;
    transition: color var(--dur-base) linear;
}

/* ---- Phase copy ---------------------------------------------------------- */

.cycle__panels { position: relative; display: grid; }

.cycle__panel {
    /* Panels stack; only the active one is shown. Grid (not absolute) so the
       tallest panel reserves the height and nothing jumps between phases. */
    grid-area: 1 / 1;
    opacity: 0;
    visibility: hidden;
    transform: translate3d(0, 14px, 0);
    transition: opacity var(--dur-base) var(--ease-out-soft),
                transform var(--dur-base) var(--ease-out-soft),
                visibility 0s linear var(--dur-base);
}

.cycle__panel.is-active {
    opacity: 1;
    visibility: visible;
    transform: none;
    transition-delay: 0s;
}

.cycle__panel-days {
    display: inline-block;
    padding: 4px 12px;
    border-radius: var(--border-radius-pill);
    background: var(--phase-ink);
    color: #fff;
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    margin-bottom: var(--spacing-sm);
    transition: background var(--dur-base) linear;
}

.cycle__panel-title {
    font-size: clamp(1.75rem, 3.4vw, 2.75rem);
    margin-bottom: var(--spacing-sm);
    color: var(--color-stone-900);
}

.cycle__panel-body {
    font-size: clamp(1rem, 1.4vw, 1.125rem);
    color: var(--color-stone-600);
    max-width: 46ch;
    margin-bottom: var(--spacing-md);
}

.cycle__cues { list-style: none; display: grid; gap: 10px; margin: 0; padding: 0; }

.cycle__cues li {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    font-size: 0.9375rem;
    color: var(--color-stone-900);
    line-height: 1.5;
}

.cycle__cues svg {
    flex-shrink: 0;
    margin-top: 3px;
    color: var(--phase-ink);
    transition: color var(--dur-base) linear;
}

/* ---- Phone ---------------------------------------------------------------- */

.cycle__phone {
    position: relative;
    /* Sized by its grid row, not by vh. A viewport-relative height cannot know
       how much room the stage actually has, so it overflowed into the hormone
       panel on short screens (-66px at 1440x900, -110px at 1600x760).
       Stretching to the row makes the row the hard ceiling: the images are
       absolutely positioned to inset:0, so they can never exceed it. */
    align-self: stretch;
    min-height: 0;
    margin-inline: auto;
    width: 100%;

    /* These are tall phone screens shown in a short box, so they have to be
       cropped — fitting one whole would make it unreadably small. Fading the
       last stretch to transparent makes the crop read as a screen continuing
       past the frame instead of an image chopped through the middle of a card.
       The drop-shadow is a filter, not a box-shadow on the image: filters are
       applied before the mask, so the shadow fades out with the artwork rather
       than being sliced off square at the mask edge. */
    -webkit-mask-image: linear-gradient(to bottom, #000 62%, rgba(0, 0, 0, 0.35) 86%, transparent 100%);
    mask-image: linear-gradient(to bottom, #000 62%, rgba(0, 0, 0, 0.35) 86%, transparent 100%);
    filter: drop-shadow(0 18px 40px rgba(200, 91, 122, 0.20));
}

.cycle__phone img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: top center;
    border-radius: 22px;
    opacity: 0;
    transform: scale(0.98);
    transition: opacity var(--dur-base) var(--ease-out-soft),
                transform var(--dur-base) var(--ease-out-soft);
}

.cycle__phone img.is-active { opacity: 1; transform: none; }

/* ---- Hormone chart -------------------------------------------------------- */

.hormones {
    padding-bottom: clamp(20px, 4vh, 40px);
}

.hormones__frame {
    position: relative;
    background: rgba(255, 255, 255, 0.72);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    border: 1px solid rgba(28, 25, 23, 0.07);
    border-radius: var(--border-radius-lg);
    padding: 14px 18px 10px;
}

.hormones__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--spacing-md);
    flex-wrap: wrap;
    margin-bottom: 8px;
}

.hormones__title {
    font-family: var(--font-secondary);
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--color-stone-600);
    margin: 0;
}

.hormones__legend { display: flex; gap: 16px; flex-wrap: wrap; }

.hormones__key {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-size: 0.75rem;
    font-weight: 600;
    /* stone-600 on the translucent white frame: 6.4:1 */
    color: var(--color-stone-600);
}

.hormones__swatch { width: 14px; height: 3px; border-radius: 2px; flex-shrink: 0; }

.hormones__swatch--estrogen     { background: #0E8C82; }
.hormones__swatch--progesterone { background: #7A5AA8; }
.hormones__swatch--lh           { background: #C97B34; }

.hormones__chart { display: block; width: 100%; height: clamp(58px, 8.5vh, 104px); overflow: visible; }

.hormones__grid { stroke: rgba(28, 25, 23, 0.08); stroke-width: 0.6; }

.hormones__curve {
    fill: none;
    stroke-width: 2.4;
    stroke-linecap: round;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
}

.hormones__curve--estrogen     { stroke: #0E8C82; }
.hormones__curve--progesterone { stroke: #7A5AA8; }
.hormones__curve--lh           { stroke: #C97B34; }

.hormones__playhead {
    stroke: var(--phase-ink);
    stroke-width: 1.4;
    stroke-dasharray: 3 3;
    opacity: 0;
    transition: stroke var(--dur-base) linear;
}

.hormones__axis {
    display: flex;
    justify-content: space-between;
    margin-top: 4px;
    font-size: 0.6875rem;
    font-weight: 600;
    /* Axis labels are text: stone-600 on white = 6.4:1 */
    color: var(--color-stone-600);
}

.hormones__note {
    font-size: 0.6875rem;
    color: var(--color-stone-600);
    margin: 6px 0 0;
    line-height: 1.5;
}

/* No-JS / static fallback: show the curves fully drawn and the panels stacked. */
.no-motion .cycle { height: auto; }
.no-motion .cycle__sticky { position: static; height: auto; padding-bottom: var(--spacing-xl); }
.no-motion .cycle__stage { grid-template-columns: 1fr; }
.no-motion .cycle__panels { display: grid; gap: var(--spacing-xl); }
.no-motion .cycle__panel {
    opacity: 1;
    visibility: visible;
    transform: none;
    grid-area: auto;
}
.no-motion .cycle__phone,
.no-motion .dial { display: none; }
.no-motion .cycle__phone img:first-child { opacity: 1; }

/* ==========================================================================
   5. Feature cards — icon draw-in + lift
   ========================================================================== */

.feature-card {
    position: relative;
    overflow: hidden;
}

.feature-card::after {
    /* Hairline that sweeps in on hover — cheap, GPU-only. */
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    height: 3px;
    width: 100%;
    background: var(--gradient-primary);
    transform: scaleX(0);
    transform-origin: 0 50%;
    transition: transform 420ms var(--ease-out-soft);
}

.feature-card:hover::after,
.feature-card:focus-within::after { transform: scaleX(1); }

.icon-circle { transition: transform 420ms var(--ease-out-soft), background 240ms linear; }
.feature-card:hover .icon-circle { transform: scale(1.08) rotate(-4deg); }

/* ==========================================================================
   6. Screenshot marquee
   Two identical tracks scroll continuously; the second is aria-hidden so the
   duplicate images are announced once, not twice.
   ========================================================================== */

.marquee {
    position: relative;
    overflow: hidden;
    -webkit-mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
    mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
}

/* No gap at track level: the two groups must be exactly equal width for the
   -50% loop to be seamless, so the spacing lives inside each group instead. */
.marquee__track {
    display: flex;
    width: max-content;
    animation: marqueeSlide 64s linear infinite;
}

.marquee__group {
    display: flex;
    gap: 1.5rem;
    padding-right: 1.5rem;
    align-items: flex-start;
}

.marquee:hover .marquee__track,
.marquee:focus-within .marquee__track { animation-play-state: paused; }

@keyframes marqueeSlide {
    /* Track holds two identical groups, so -50% is one seamless loop. */
    from { transform: translate3d(0, 0, 0); }
    to   { transform: translate3d(-50%, 0, 0); }
}

/* Fixed height / auto width: the screenshots have mixed aspect ratios, so
   sizing by height reserves space before decode (no layout shift) without
   cropping any of them. */
.marquee .screenshot-item { width: auto; flex: 0 0 auto; }

.marquee .screenshot-item img {
    height: var(--shot-h, 400px);
    width: auto;
    margin-bottom: var(--spacing-sm);
    transition: transform 420ms var(--ease-out-soft), box-shadow 420ms var(--ease-out-soft);
}

.marquee .screenshot-item:hover img { transform: translateY(-6px) scale(1.02); box-shadow: var(--shadow-lg); }

.marquee__hint {
    text-align: center;
    font-size: 0.8125rem;
    color: var(--color-stone-600);
    margin-top: var(--spacing-sm);
}

/* ==========================================================================
   7. Counters + misc
   ========================================================================== */

.stat-number { font-variant-numeric: tabular-nums; }

.btn { position: relative; overflow: hidden; }

.btn::before {
    content: '';
    position: absolute;
    inset: 0;
    background: radial-gradient(circle at var(--mx, 50%) var(--my, 50%), rgba(255, 255, 255, 0.28), transparent 60%);
    opacity: 0;
    transition: opacity 260ms linear;
    pointer-events: none;
}

.btn:hover::before { opacity: 1; }

/* .back-to-top lives in components.css — not redefined here. */

/* ==========================================================================
   8. Responsive
   ========================================================================== */

@media (max-width: 1180px) {
    .cycle__stage { grid-template-columns: minmax(240px, 300px) minmax(0, 1fr); }
    .cycle__phone { display: none; }
}

/* Mobile: everything below has to fit inside one 100svh sticky stage without
   the hormone panel riding over the copy. Sizes are budgeted, not guessed —
   intro ~152px + dial ~150px + panel ~180px + hormones ~160px + gaps ~60px,
   which clears a 844px viewport (iPhone 14/15) with room to spare. */
@media (max-width: 860px) {
    :root { --cycle-panel-h: 72vh; }

    .cycle__sticky { justify-content: flex-start; }

    .cycle__intro {
        padding-top: clamp(72px, 10vh, 92px);
        padding-bottom: var(--spacing-sm);
    }

    .cycle__intro .section-title { font-size: 1.35rem; margin-bottom: 0; }
    .cycle__eyebrow { font-size: 0.6875rem; letter-spacing: 0.14em; }

    .cycle__stage {
        grid-template-columns: 1fr;
        gap: var(--spacing-sm);
        align-content: start;
    }

    .dial { max-width: min(40vw, 150px); }
    .dial__day-number { font-size: 2.5rem; }
    .dial__phase-name { font-size: 0.6875rem; margin-top: 2px; }

    .cycle__panel-days { margin-bottom: 6px; }
    .cycle__panel-title { font-size: 1.5rem; margin-bottom: 6px; }

    .cycle__panel-body {
        font-size: 0.9375rem;
        margin-bottom: var(--spacing-sm);
        /* Hard guarantee against overflow: however long a phase description
           gets, it can never push the hormone panel off the stage. */
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 4;
        line-clamp: 4;
        overflow: hidden;
    }

    .cycle__cues li { font-size: 0.8125rem; }
    .cycle__cues li:nth-child(n+3) { display: none; }

    .hormones__frame { padding: 10px 12px 8px; }
    .hormones__chart { height: 64px; }
    .hormones__note { font-size: 0.625rem; margin-top: 4px; }
    .hero-ring { display: none; }
}

/* Short viewports — the sticky stage must still fit in 100svh, so drop the
   least essential rows first, in order of what a visitor can most afford to
   lose: the section heading, then the disclaimer, then the cues, then the
   chart. The phase copy and the dial never go.

   Short *desktop* is the common laptop case (1366x768, 1440x800, 1600x760).
   The phase copy is the tall row there — measured at 419px inside a 348px
   stage — so the heading and body are what get trimmed, not the dial. */
@media (min-width: 861px) and (max-height: 880px) {
    .cycle__intro { padding-top: clamp(44px, 5vh, 64px); padding-bottom: 4px; }
    .cycle__intro .section-title { font-size: clamp(1.5rem, 2.2vw, 1.9rem); margin-bottom: 4px; }

    .cycle__panel-title { font-size: clamp(1.4rem, 2.4vw, 1.9rem); margin-bottom: 6px; }

    .cycle__panel-body {
        font-size: 1rem;
        margin-bottom: var(--spacing-sm);
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 3;
        line-clamp: 3;
        overflow: hidden;
    }

    .cycle__cues li { font-size: 0.875rem; }
    .hormones__chart { height: clamp(52px, 7vh, 76px); }
    .hormones__note { display: none; }
}

@media (min-width: 861px) and (max-height: 660px) {
    .cycle__intro .section-title { display: none; }
    .cycle__cues li:nth-child(n+3) { display: none; }
}

/* iPhone SE and similar (<= 700px tall). The section heading is restated by
   the eyebrow and the phase titles, so it is the cheapest row to remove. */
@media (max-height: 700px) and (max-width: 860px) {
    .cycle__intro .section-title { display: none; }
    .cycle__intro { padding-bottom: 4px; }
    .dial { max-width: min(30vw, 112px); }
    .dial__day-number { font-size: 2rem; }
    .cycle__panel-body { -webkit-line-clamp: 3; line-clamp: 3; }
    .cycle__cues li:nth-child(n+2) { display: none; }
    .hormones__chart { height: 54px; }
    .hormones__note { display: none; }
}

/* Small Android portrait (360x640 and similar) — 6px short with the tier
   above, so the body loses one more line rather than clipping. */
@media (max-width: 860px) and (max-height: 660px) {
    .cycle__panel-body { -webkit-line-clamp: 2; line-clamp: 2; }
    .hormones__chart { height: 48px; }
}

@media (max-height: 560px) and (max-width: 860px) {
    .cycle__cues { display: none; }
    .hormones { display: none; }
    /* Landscape phones: 72px of navbar clearance out of a 390px viewport
       leaves very little, so the copy gets the last trim available. */
    .cycle__intro { padding-top: clamp(60px, 16vh, 72px); }
    .dial { max-width: min(22vw, 92px); }
    .dial__day-number { font-size: 1.75rem; }
    .cycle__panel-body { -webkit-line-clamp: 2; line-clamp: 2; }
}

@media (max-width: 480px) {
    .dial { max-width: 170px; }
    .hormones__legend { gap: 10px; }
    .hormones__key { font-size: 0.6875rem; }
    .marquee { --shot-h: 280px; }
}

/* ==========================================================================
   9. Reduced motion
   Everything that moves stops. The cycle section collapses to the readable
   static layout rather than a pinned scrubber, because scroll-hijacking is
   itself a motion trigger for people with vestibular disorders.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
    .js-motion [data-reveal] { opacity: 1 !important; transform: none !important; }
    .js-motion .word__inner  { transform: none !important; }

    .hero-ring__spin,
    .hero-cue__arrow,
    .marquee__track { animation: none !important; }

    .marquee {
        overflow-x: auto;
        scroll-snap-type: x mandatory;
        -webkit-mask-image: none;
        mask-image: none;
    }

    .marquee__track { width: auto; }
    .marquee .screenshot-item { scroll-snap-align: start; }

    .cycle { height: auto; background: var(--menstrual-wash); }
    .cycle__sticky { position: static; height: auto; padding-bottom: var(--spacing-xl); }
    .cycle__stage { grid-template-columns: 1fr; }
    .cycle__panels { gap: var(--spacing-xl); }
    .cycle__panel {
        opacity: 1 !important;
        visibility: visible !important;
        transform: none !important;
        grid-area: auto;
    }
    .cycle__phone, .dial { display: none; }
    .nav-cycle-pill.is-active { display: none; }
    .feature-card:hover .icon-circle { transform: none; }
}

/* ==========================================================================
   10. Print
   ========================================================================== */

@media print {
    .cycle { height: auto; }
    .cycle__sticky { position: static; height: auto; }
    .cycle__panel { opacity: 1 !important; visibility: visible !important; transform: none !important; grid-area: auto; }
    .js-motion [data-reveal] { opacity: 1 !important; transform: none !important; }
    .js-motion .word__inner { transform: none !important; }
    .nav-progress, .back-to-top, .hero-ring { display: none !important; }
}
