/* ═══════════════════════════════════════════════════════════════════
   EVERGROW MOTION GRAMMAR

   WHY THIS WAS REBUILT
   The first version bound every reveal to a scroll timeline. That was the
   wrong tool for content. A scrubbed entry never ARRIVES: it is
   perpetually mid-animation for as long as the reader keeps scrolling, so
   the System heading spent 632px of scroll travelling 45px — a 14:1 ratio
   — inside a sticky column that keeps it on screen the whole time. Text
   creeping upward at 7% of scroll speed does not read as motion design.
   It reads as a rendering fault, which is exactly what it was called.

   THE RULE NOW
   Reveals FIRE, RUN ON THEIR OWN CLOCK, AND SETTLE. An IntersectionObserver
   latches `.is-in`, a CSS transition takes 560-680ms, and the element is
   then done and stays done. Scroll position is only allowed to drive things
   that genuinely track position: the two spatial scenes and the parallax
   drift. Nothing that carries words is scrubbed.

   Transitions, not keyframes, because a transition can be interrupted and
   retargeted mid-flight — a reader who scrolls back up mid-reveal gets a
   smooth reversal rather than a restart.

   FAIL-OPEN
   Every out-state is scoped to `html.has-mot`, which only exists once JS
   has armed the system and can therefore guarantee it will also release it.
   No JS, no observer, an exception, a stale cache: content is simply
   visible. That is the whole safety story.
   ═══════════════════════════════════════════════════════════════════ */

:root {
    /* Entrances decelerate, exits accelerate. */
    --e-set: cubic-bezier(0.22, 1, 0.28, 1);
    --e-rule: cubic-bezier(0.16, 1, 0.3, 1);
    --e-lift: cubic-bezier(0.7, 0, 0.84, 0);

    --mot-line: 620ms;      /* one line of display type */
    --mot-block: 560ms;     /* a paragraph or a card */
    --mot-out: 380ms;       /* exits are shorter than entrances */
    --mot-stagger: 68ms;    /* between sibling lines */
}

/* Nothing here hides anything until JS can guarantee the release. */
[data-mot] { opacity: 1; }

/* One owner per element. The rise/stagger primitives arm the same two
   properties from a JS latch; a motion verb owns them from here. Both on
   one node means whichever resolves last wins. The verb wins. */
.stagger-armed > [data-mot],
[data-mot].rise-armed,
[data-mot].kin-fade {
    opacity: 1;
    transform: none;
    transition: none;
}

@media (prefers-reduced-motion: no-preference) {

    /* ── RULE · the downbeat ───────────────────────────────────────
       A hairline draws before the type beneath it sets. */
    [data-mot="rule"] {
        transform-origin: 0% 50%;
        transition: transform 520ms var(--e-rule), opacity 300ms linear;
    }
    html.has-mot [data-mot="rule"]:not(.is-in) {
        transform: scaleX(0);
        opacity: 0;
    }
    [data-mot="rule"][data-rule="centre"] { transform-origin: 50% 50%; }
    [data-mot="rule"][data-rule="down"] { transform-origin: 50% 0%; }
    html.has-mot [data-mot="rule"][data-rule="down"]:not(.is-in) {
        transform: scaleY(0);
    }
    /* Wipes from the trailing edge, faster than it drew. */
    [data-mot="rule"].is-past {
        transform-origin: 100% 50%;
        transform: scaleX(0);
        transition: transform var(--mot-out) var(--e-lift);
    }

    /* ── SET · display type, line by line ──────────────────────────
       Each line is masked by its own leading and lifts out of it, with a
       real stagger between lines rather than a scroll-position accident.
       The lines also come from a slight leading-edge offset, so the
       headline reads as type being set into a measure rather than a block
       sliding up. NO OPACITY on display type: it is never rendered at
       partial alpha, it is simply not there yet. */
    [data-mot="set"].is-split .set-line {
        display: block;
        overflow: hidden;
        overflow: clip;
    }
    [data-mot="set"].is-split .set-line > span {
        display: block;
        transition: transform var(--mot-line) var(--e-set);
        transition-delay: calc(var(--i, 0) * var(--mot-stagger));
    }
    html.has-mot [data-mot="set"].is-split:not(.is-in) .set-line > span {
        transform: translate3d(-0.16em, 1.02em, 0);
    }
    /* Leaving, the whole block goes as one. Lines rippling out
       individually reads as a fault, not a flourish. */
    [data-mot="set"].is-split.is-past .set-line > span {
        transform: translate3d(0, -0.34em, 0);
        transition: transform var(--mot-out) var(--e-lift);
        transition-delay: 0ms;
    }

    /* Unsplit — one line, or the splitter never ran. Behaves as a column. */
    [data-mot="set"]:not(.is-split) {
        transition: opacity 420ms linear, transform var(--mot-block) var(--e-set);
    }
    html.has-mot [data-mot="set"]:not(.is-split):not(.is-in) {
        opacity: 0;
        transform: translateY(16px);
    }
    [data-mot="set"]:not(.is-split).is-past {
        transform: translateY(-10px);
        transition: transform var(--mot-out) var(--e-lift);
    }

    /* ── COLUMN · body copy ────────────────────────────────────────
       Opacity finishes well before the movement does, so copy is legible
       while it is still settling. */
    [data-mot="col"] {
        transition: opacity 400ms linear, transform var(--mot-block) var(--e-set);
    }
    html.has-mot [data-mot="col"]:not(.is-in) {
        opacity: 0;
        transform: translateY(14px);
    }
    [data-mot="col"].is-past {
        transform: translateY(-10px);
        transition: transform var(--mot-out) var(--e-lift);
    }

    /* ── PLATE · surfaces land onto registration ───────────────────
       The only verb permitted to use scale. */
    [data-mot="plate"] {
        transition: opacity 420ms linear, transform 680ms var(--e-set);
    }
    html.has-mot [data-mot="plate"]:not(.is-in) {
        opacity: 0;
        transform: translateY(-1.8%) scale(1.026);
    }
    [data-mot="plate"].is-past {
        transform: translateY(-14px) scale(0.986);
        transition: transform var(--mot-out) var(--e-lift);
    }
    html.has-mot [data-mot="plate"][data-amp="50"]:not(.is-in) {
        transform: translateY(-0.9%) scale(1.013);
    }
    [data-mot="plate"][data-amp="50"].is-past {
        transform: translateY(-7px) scale(0.994);
    }

    /* A group's children land in sequence rather than as a slab. */
    [data-mot-group] > * {
        transition-delay: calc(var(--i, 0) * var(--mot-stagger));
    }

    /* The parallax exemption. These elements have their transform written
       every frame by the rAF reader; touching transform here would fight
       it, so they take light only. */
    html.has-mot [data-mot][data-kin]:not(.is-in) {
        transform: none;
        opacity: 0;
    }
    [data-mot][data-kin],
    [data-mot][data-kin].is-past {
        transform: none;
        transition: opacity 420ms linear;
    }

    /* Sections that must never dim or drift away underneath a reader. */
    #faq-section [data-mot].is-past,
    #founders-section [data-mot].is-past,
    #close-section [data-mot].is-past {
        transform: none;
    }
}

/* ── Reduced motion · calm, but still authored ─────────────────────
   Rules still arrive, they just do not sweep. Nothing moves, nothing
   dims, and no content depends on a scroll position to become visible. */
@media (prefers-reduced-motion: reduce) {
    [data-mot] { opacity: 1; transform: none; }
    [data-mot="rule"] { transition: opacity 300ms linear; }
    html.has-mot [data-mot="rule"]:not(.is-in) { opacity: 0; }
}

/* ── PRESS · the touch programme ───────────────────────────────────
   Magnetic, tilt and the two-body cursor all bail on (hover: hover) and
   leave a phone with nothing. This gives every card and button a physical
   response on every device, for the cost of one transition. */
.press { transition: transform 220ms var(--e-spring, cubic-bezier(0.34, 1.56, 0.64, 1)); }
.press:active { transform: scale(0.985); transition: transform 140ms var(--e-out); }
@media (prefers-reduced-motion: reduce) {
    .press, .press:active { transition: none; transform: none; }
}

/* ── CITE · provenance arrives with the claim ──────────────────────
   The pin draws, then the source opens. The numeral is never touched.
   Base state is the FINISHED state: a citation that never animates is
   simply a complete citation. */
.cite-pin {
    display: inline-block;
    vertical-align: super;
    width: 10px;
    height: 12px;
    margin-left: 0.18em;
    color: var(--gold, #D5B473);
    opacity: 0.55;
    transition: opacity 300ms linear;
}
.cite-pin path, .cite-pin circle, .cite-pin line {
    stroke-dasharray: 26;
    stroke-dashoffset: 0;
    transition: stroke-dashoffset 460ms var(--e-set);
}
.cite-rule {
    display: block;
    height: 1px;
    width: min(180px, 62%);
    margin: 0.85rem auto 0.5rem;
    background: currentColor;
    color: var(--gold, #D5B473);
    opacity: 0.5;
    transform: scaleX(1);
    transform-origin: 0 50%;
    transition: transform 420ms var(--e-rule);
}
.cite-src {
    display: block;
    overflow: hidden;
    overflow: clip;
}
.cite-src > span {
    display: block;
    transition: transform 420ms var(--e-set);
}

@media (prefers-reduced-motion: no-preference) {
    html.has-mot [data-cite]:not(.is-in) .cite-pin { opacity: 0; }
    html.has-mot [data-cite]:not(.is-in) .cite-pin path,
    html.has-mot [data-cite]:not(.is-in) .cite-pin circle,
    html.has-mot [data-cite]:not(.is-in) .cite-pin line { stroke-dashoffset: 26; }
    html.has-mot [data-cite]:not(.is-in) .cite-rule { transform: scaleX(0); }
    html.has-mot [data-cite]:not(.is-in) .cite-src > span { transform: translateY(100%); }
    /* Provenance lands after the claim has been read, never before. */
    [data-cite].is-in .cite-rule { transition-delay: 160ms; }
    [data-cite].is-in .cite-src > span { transition-delay: 300ms; }
}

/* Not a scroll container. Tailwind's overflow-hidden makes one, which
   breaks sticky descendants and any position-tracking inside. */
.mot-clip { overflow: hidden; overflow: clip; }

/* ── Mobile · its own signature ────────────────────────────────────
   A phone is a single measure of type, which is already a printed page.
   Section rules run full bleed and content sets against them — something
   the constrained desktop measure structurally cannot do. */
@media (max-width: 767px) {
    :root {
        --mot-line: 540ms;
        --mot-block: 500ms;
        --mot-stagger: 54ms;   /* a phone headline is 3-4 lines, not 2 */
    }
    [data-mot="rule"][data-rule="lead"] {
        width: 100vw;
        margin-left: calc(50% - 50vw);
        margin-right: calc(50% - 50vw);
    }
    html.has-mot [data-mot="plate"]:not(.is-in) {
        transform: translateY(-0.9%) scale(1.013);
    }
    [data-mot="plate"].is-past { transform: translateY(-8px) scale(0.992); }
}

/* ── The relay · the baton is visible ──────────────────────────────
   initRelay owns .rl-card's transform (it writes the per-card tilt inline
   every frame), so everything here works in properties it does not touch:
   light, edge, and the card's own children. */
.rl-card {
    transition: opacity 520ms linear,
                border-color 420ms var(--e-out),
                box-shadow 520ms var(--e-out);
}
.rl-card .rl-icon {
    transition: transform 520ms var(--e-set), background-color 420ms var(--e-out),
                color 420ms var(--e-out), border-color 420ms var(--e-out);
}
html.has-mot #relay .rl-card:not(.is-in) { opacity: 0; }
html.has-mot #relay .rl-card:not(.is-in) .rl-icon { transform: scale(0.72); }
#relay .rl-card { transition-delay: calc(var(--i, 0) * 70ms); }
#relay .rl-card .rl-icon { transition-delay: calc(var(--i, 0) * 70ms + 90ms); }

/* The baton is here. Edge and light only — never the card's transform. */
.rl-card.is-focus {
    border-color: rgba(213, 180, 115, 0.62);
    box-shadow: 0 40px 90px -42px rgba(0, 0, 0, 0.6),
                0 0 0 1px rgba(213, 180, 115, 0.22);
}
.rl-card.is-focus .rl-icon {
    background-color: rgba(213, 180, 115, 0.16);
    border-color: rgba(213, 180, 115, 0.55);
    color: var(--gold, #D5B473);
}

/* The baton has passed. A completed step, not a dimmed one: nothing that
   carries words loses contrast on the way past. */
.rl-card.is-done .rl-step {
    color: rgba(213, 180, 115, 0.85);
    border-color: rgba(213, 180, 115, 0.35);
}
.rl-card.is-done .rl-icon {
    border-color: rgba(213, 180, 115, 0.34);
}

@media (prefers-reduced-motion: reduce) {
    .rl-card, .rl-card .rl-icon { transition: none; transition-delay: 0ms; }
    html.has-mot #relay .rl-card:not(.is-in) { opacity: 1; }
    html.has-mot #relay .rl-card:not(.is-in) .rl-icon { transform: none; }
}
