/* ============================================================
   The run — one sticky stage carrying both scrubbed acts.

   The hero and the app deck used to be two separate pinned sections.
   Handing over between two of those always shows a seam: the first has
   to unpin and slide away while the second is already pinned underneath
   it, so for a viewport's worth of scrolling you see the bottom of one
   picture above the top of the other. Running both clips through one
   canvas removes the handover — he points, jumps, lands, and the
   devices assemble around him — and the copy cross-dissolves at the
   same moment the picture does, so nothing ever looks like a cut.

   assets/js/modules/run.js drives one property, --run-p (0 to 1, the
   whole thing), and derives each act's own progress from it.

   Two states, not three. `.run.is-live` is the pinned scrub mode, added
   only once JS has decided motion is allowed. Its absence — no JS at
   all, or reduced motion — is the single fallback branch below: an
   ordinary stacked section per act, everything visible, nothing pinned.
   ============================================================ */

.run {
    --run-p: 0;
    position: relative;
    height: 820vh;
    background: var(--rose-600);
}

.run.is-live .run__stage {
    position: sticky;
    top: 0;
    height: 100vh;
    height: 100svh;
    overflow: hidden;
    isolation: isolate;
    background: var(--rose-600);
}

/* Crossfades to the deck's own tone over the join, so the ground colour
   changes with the picture instead of snapping. The frames carry a faint
   vertical ramp of their own; the gradient echoes it so the canvas never
   sits on a flatter red than the one it is drawn from. */
.run.is-live.is-deck .run__stage {
    background: linear-gradient(180deg, var(--tomato-top), var(--tomato-600) 24%);
    transition: background 0.6s var(--ease);
}

.run.is-live .run__film {
    position: absolute;
    inset: 0;
    overflow: hidden;
}

.run.is-live .run__poster,
.run.is-live .run__canvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: 50% 14%;
}

.run__canvas {
    opacity: 0;
    transition: opacity 0.5s var(--ease);
}

.run.is-ready .run__canvas {
    opacity: 1;
}

.run.is-ready .run__poster {
    opacity: 0;
}

/* Both acts occupy the same box; only the current one is interactive,
   and the outgoing one dissolves under it over the same window the
   picture cross-fades in. */
.run.is-live .run__acts {
    position: absolute;
    inset: 0;
}

.run.is-live .run__act {
    position: absolute;
    inset: 0;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.4s var(--ease);
}

.run.is-live .run__act.is-on {
    opacity: 1;
    pointer-events: auto;
}

/* Real document position for the #apps nav link — in live mode the acts
   themselves are painted inside the pinned stage, so the link needs
   something else to land on. Placed at the same fraction of the runway
   the hand-over happens at (see HERO_SHARE in run.js), minus the sticky
   viewport, so the jump lands right as the deck begins rather than
   partway through it. In the fallback layout the acts are ordinary flow
   content, so the anchor only needs to sit right before the deck act
   for the browser's own scroll-to-anchor to do the right thing. */
.run__anchor {
    position: absolute;
    top: 0;
    left: 0;
}

/* The fallback picture for each act. Default-hidden: without this an
   ordinary <figure> is display:block by default, and it was rendering
   as a static image stacked right on top of the live canvas. */
.run__fallback-fig {
    display: none;
}

.run.is-live .run__anchor {
    top: calc(0.47 * (100% - 100vh));
}

/* Dissolve into the page rather than cutting to it. */
.run.is-live .run__exit {
    position: absolute;
    inset-inline: 0;
    bottom: 0;
    height: 38vh;
    pointer-events: none;
    background: linear-gradient(180deg, transparent, var(--surface-2) 80%);
    opacity: calc((var(--run-p) - 0.94) * 16);
}

.run.is-live.is-deck .run__act.is-on {
    opacity: calc(1 - (var(--run-p) - 0.94) * 16);
}

/* ================================================================
   Hero act — the opening copy.
   ================================================================ */

.run.is-live .reel__copy {
    display: grid;
    align-content: center;
    padding-block: 6rem 5rem;
    padding-inline: env(safe-area-inset-left) env(safe-area-inset-right);
    color: #fff;
}

/* The three blocks stack in one grid row so the copy swaps whole — the
   eyebrow, the line and the paragraph together — without the buttons
   below them moving. */
.run.is-live .reel__lead {
    max-width: 38rem;
    display: grid;
    grid-template-rows: auto auto;
}

.run.is-live .reel__beat {
    grid-area: 1 / 1;
    align-self: start;
    opacity: 0;
    transform: translateY(14px);
    transition: opacity 0.5s var(--ease), transform 0.5s var(--ease);
}

.run.is-live .reel__beat.is-on {
    opacity: 1;
    transform: none;
}

.run.is-live .reel__eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    font-family: var(--font-mono);
    font-size: 0.68rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--rose-ink);
    padding: 0.4rem 0.75rem;
    border: 1px solid rgba(255, 255, 255, 0.34);
    border-radius: 999px;
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}

/* Only the first block carries the h1; the other two are paragraphs
   wearing the same face, so the document keeps exactly one h1. */
.run.is-live .reel__copy h1,
.run.is-live .reel__title {
    font-family: var(--font-display);
    font-weight: 600;
    font-size: clamp(2.15rem, 1.55rem + 2.6vw, 3.7rem);
    line-height: 1.08;
    letter-spacing: -0.02em;
    color: #fff;
    margin-block: 1.15rem 0;
    text-wrap: balance;
    overflow-wrap: normal;
    text-shadow: 0 2px 30px rgba(97, 0, 31, 0.45);
}

.run.is-live .reel__copy h1 {
    max-width: 11.5em;
    text-wrap: pretty;
}

.run.is-live .reel__copy h1 em,
.run.is-live .reel__title em {
    font-style: normal;
    color: #FFD9E5;
}

.run.is-live .reel__body {
    margin: 1.2rem 0 0;
    font-size: var(--step-0);
    line-height: 1.5;
    color: var(--rose-ink);
    max-width: 32rem;
}

.run.is-live .reel__actions {
    grid-row: 2;
    display: flex;
    flex-wrap: wrap;
    gap: 0.8rem;
    margin-top: 1.9rem;
}

/* On crimson the brass button disappears; white reads as the primary. */
.run.is-live .reel__copy .btn {
    background: #fff;
    color: var(--rose-700);
    border-color: #fff;
}

.run.is-live .reel__copy .btn:hover {
    background: var(--rose-ink);
    border-color: var(--rose-ink);
    color: var(--rose-700);
}

.run.is-live .reel__copy .btn--ghost {
    background: transparent;
    color: #fff;
    border-color: rgba(255, 255, 255, 0.5);
}

.run.is-live .reel__copy .btn--ghost:hover {
    background: rgba(255, 255, 255, 0.14);
    border-color: #fff;
    color: #fff;
}

/* ---------- Scrub rail ---------- */

.run.is-live .reel__rail {
    position: absolute;
    inset-inline: 0;
    bottom: 0;
    padding-bottom: calc(1.15rem + env(safe-area-inset-bottom));
}

.run.is-live .reel__rail .shell {
    display: grid;
    gap: 0.75rem;
}

.reel__track {
    height: 2px;
    background: rgba(255, 255, 255, 0.24);
    border-radius: 2px;
    overflow: hidden;
}

.reel__track i {
    display: block;
    height: 100%;
    background: #fff;
    transform-origin: left center;
    transform: scaleX(0);
}

.run.is-live .reel__cue {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    font-family: var(--font-mono);
    font-size: 0.64rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.8);
}

.run.is-live .reel__cue svg {
    width: 14px;
    height: 14px;
    animation: cue-drop 1.8s var(--ease-in-out) infinite;
}

@keyframes cue-drop {
    0%, 100% { transform: translateY(-2px); }
    50%      { transform: translateY(3px); }
}

/* Legibility scrim, live mode only — the frame is a mid-value crimson
   with a pale calligraphy pattern, so white type needs help. */
.run.is-live .reel__scrim {
    position: absolute;
    inset: 0;
    pointer-events: none;
    background:
        linear-gradient(180deg, rgba(97, 0, 31, 0.5), transparent 22%),
        linear-gradient(90deg, rgba(97, 0, 31, 0.72), rgba(97, 0, 31, 0.34) 34%, transparent 62%);
}

/* ================================================================
   Deck act — the feature walkthrough.
   ================================================================ */

.run.is-live .deck__panel {
    display: grid;
    align-content: center;
    padding-inline: env(safe-area-inset-left) env(safe-area-inset-right);
    color: #fff;
}

.deck__inner {
    width: min(100% - var(--gutter) * 2, var(--max));
    margin-inline: auto;
    display: grid;
    justify-items: start;
    gap: clamp(0.9rem, 2vh, 1.5rem);
}

.deck__head {
    max-width: 42rem;
}

.deck__eyebrow {
    display: block;
    font-family: var(--font-mono);
    font-size: 0.68rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--tomato-ink);
    margin-bottom: 0.7rem;
}

.deck h2 {
    color: #fff;
    font-size: var(--step-2);
    line-height: 1.15;
    margin: 0;
    text-wrap: balance;
}

.run:not(.is-live) .deck h2 {
    color: var(--ink);
}

/* ---------- Screen cards — three across ---------- */

.deck__toc {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0.7rem;
    width: min(42rem, 100%);
}

.deck__point {
    display: grid;
    align-content: start;
    gap: 0.28rem;
    min-height: 6.4rem;
    padding: 0.95rem 1rem 1.05rem;
    border-radius: var(--r-md);
    background: rgba(255, 255, 255, 0.08);
    border: 1px solid rgba(255, 255, 255, 0.14);
    color: rgba(255, 255, 255, 0.62);
    transition: color 0.35s var(--ease), background 0.35s var(--ease),
                border-color 0.35s var(--ease);
}

.deck__no {
    font-family: var(--font-mono);
    font-size: 0.68rem;
    letter-spacing: 0.12em;
    font-weight: 500;
}

.deck__point strong {
    font-size: var(--step-0);
    font-weight: 650;
    letter-spacing: -0.02em;
    color: inherit;
}

.deck__hint {
    font-size: 0.78rem;
    font-weight: 400;
    line-height: 1.35;
    color: rgba(255, 255, 255, 0.48);
}

.run.is-live .deck__point.is-on {
    background: rgba(255, 255, 255, 0.18);
    border-color: rgba(255, 255, 255, 0.42);
    color: #fff;
}

.run.is-live .deck__point.is-on .deck__hint {
    color: rgba(255, 255, 255, 0.84);
}

.deck__caption {
    margin: 0;
    max-width: 42rem;
    font-size: var(--step--1);
    line-height: 1.5;
    color: rgba(255, 255, 255, 0.72);
}

/* ---------- Counter ---------- */

.deck__meter {
    display: grid;
    gap: 0.6rem;
    width: min(42rem, 100%);
}

.deck__count {
    margin: 0;
    font-family: var(--font-mono);
    font-size: 0.68rem;
    letter-spacing: 0.14em;
    color: rgba(255, 255, 255, 0.75);
}

.deck__track {
    height: 2px;
    background: rgba(255, 255, 255, 0.26);
    border-radius: 2px;
    overflow: hidden;
}

.deck__track i {
    display: block;
    height: 100%;
    background: #fff;
    transform-origin: left center;
    transform: scaleX(0);
}

/* ================================================================
   Live mode, responsive.
   ================================================================ */

/* Below 1025px the deck's copy no longer fits beside the subject, so it
   moves to a band under him; the hero already sits under him at this
   width via its own square, character-tracking crop. */
@media (max-width: 1024px) {
    .run.is-live .deck__panel {
        position: relative;
        align-content: end;
        height: 100%;
        padding-bottom: calc(1.4rem + env(safe-area-inset-bottom));
    }

    .run.is-live .deck__toc,
    .run.is-live .deck__meter {
        width: min(100%, 42rem);
    }

    .run.is-live .deck__point {
        min-height: 0;
        padding: 0.75rem 0.8rem 0.85rem;
    }
}

@media (max-width: 900px) {
    /* .reel__rail (the "scroll" cue + progress line) is a separate
       element pinned to this same act's bottom edge, not a row inside
       this grid — so it and the bottom-aligned copy only avoid each
       other if this reserves real room for it. 1.6rem wasn't enough:
       the rail's own line height + gap + track + padding runs to
       roughly 3.5rem, and the "See every screen" button was landing
       right on top of the "Scroll" cue. */
    .run.is-live .reel__copy {
        align-content: end;
        padding-block: 0 calc(5rem + env(safe-area-inset-bottom));
    }

    .run.is-live .reel__scrim {
        background: linear-gradient(180deg, rgba(97, 0, 31, 0.5), transparent 26%);
    }

    .run.is-live .reel__lead {
        max-width: none;
    }

    .run.is-live .reel__copy h1,
    .run.is-live .reel__title {
        margin-block: 0.7rem 0;
        font-size: clamp(1.85rem, 5.5vw, 2.6rem);
        line-height: 1.1;
    }

    .run.is-live .reel__body {
        margin-top: 0.9rem;
        font-size: var(--step-0);
    }

    .run.is-live .reel__beat {
        font-size: var(--step-0);
    }

    .run.is-live .reel__actions {
        margin-top: 1.4rem;
    }

    .run.is-live .reel__actions .btn {
        flex: 1 1 12rem;
        justify-content: center;
    }
}

@media (max-width: 620px) {
    .deck__head {
        max-width: none;
    }

    .deck h2 {
        font-size: var(--step-1);
    }

    .run.is-live .deck__toc {
        gap: 0.45rem;
    }

    .run.is-live .deck__point {
        padding: 0.65rem 0.65rem 0.75rem;
        min-height: 0;
    }

    .deck__point strong {
        font-size: 0.86rem;
    }

    .deck__hint {
        font-size: 0.7rem;
    }

    .deck__caption {
        font-size: 0.78rem;
        display: none;
    }

    .deck__inner {
        gap: 0.8rem;
    }
}

@media (prefers-reduced-motion: reduce) {
    .reel__cue svg { animation: none; }
    .reel__beat, .deck__point { transition: none; }
}

/* ================================================================
   Fallback — no JS, or motion turned down.

   Both acts render as plain stacked content: a rounded picture, then the
   copy underneath in the page's own colours rather than white-on-photo,
   so nothing depends on a scrim being right. This is also what a
   reduced-motion visitor with JS running sees; `showAll()` in run.js
   just makes sure nothing that would only get an `.is-on` class from a
   scroll handler is left invisible.
   ================================================================ */

.run:not(.is-live) {
    height: auto;
    background: none;
}

.run:not(.is-live) .run__stage {
    position: static;
    height: auto;
    display: block;
}

.run:not(.is-live) .run__film,
.run:not(.is-live) .run__exit,
.run:not(.is-live) .reel__rail,
.run:not(.is-live) .deck__meter,
.run:not(.is-live) .deck__caption {
    display: none;
}

.run:not(.is-live) .run__acts {
    display: block;
}

.run:not(.is-live) .run__act {
    display: block;
    padding-block: clamp(2.5rem, 6vw, 4rem);
}

.run:not(.is-live) .run__fallback-fig {
    display: block;
    margin: 0 0 1.75rem;
    border-radius: var(--r-lg);
    overflow: hidden;
    aspect-ratio: 16 / 9;
    background: var(--surface-2);
}

.run:not(.is-live) .run__fallback-fig img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.run:not(.is-live)[data-run] .run__fallback-fig img {
    object-position: 50% 14%;
}

.run:not(.is-live) [data-run-act="deck"] .run__fallback-fig img {
    object-position: 50% 100%;
}

/* Hero copy, in the page's own colours instead of white-on-photo. */
.run:not(.is-live) .reel__copy {
    color: var(--ink);
}

.run:not(.is-live) .reel__eyebrow {
    color: var(--ink-4);
    border-color: var(--line);
}

.run:not(.is-live) .reel__lead {
    max-width: 40rem;
}

.run:not(.is-live) .reel__beat {
    opacity: 1;
}

.run:not(.is-live) .reel__beat + .reel__beat {
    margin-top: 2rem;
}

.run:not(.is-live) .reel__copy h1,
.run:not(.is-live) .reel__title {
    color: var(--ink);
    font-family: var(--font-display);
    font-weight: 600;
    font-size: clamp(2.15rem, 1.55rem + 2.6vw, 3.7rem);
    line-height: 1.08;
    margin-block: 1.1rem 0;
    text-wrap: balance;
}

.run:not(.is-live) .reel__beat + .reel__beat .reel__title {
    font-size: var(--step-2);
}

.run:not(.is-live) .reel__copy h1 em,
.run:not(.is-live) .reel__title em {
    font-style: normal;
    color: var(--rose-700);
}

.run:not(.is-live) .reel__body {
    color: var(--ink-3);
    margin-top: 1rem;
}

.run:not(.is-live) .reel__actions {
    margin-top: 1.75rem;
}

/* Deck copy: numbered contents list, same as the live overlay. */
.run:not(.is-live) .deck__panel {
    color: var(--ink);
}

.run:not(.is-live) .deck__inner {
    justify-items: stretch;
    gap: 1.75rem;
}

.run:not(.is-live) .deck__toc {
    width: 100%;
    max-width: 42rem;
}

.run:not(.is-live) .deck__point {
    color: var(--ink);
    background: var(--surface);
    border: 1px solid var(--line);
}

.run:not(.is-live) .deck__hint {
    color: var(--ink-3);
}
