/* ════════════════════════════════════════════════════════════════════
   THE JOURNEY — animated story (landing page, #roadmap)
   ════════════════════════════════════════════════════════════════════

   STORY CONTRACT — every chapter figure follows it, so ten independent
   animations behave as one system:

   1. Markup. templates/landing/story/chNN.html holds ONE root:
        <div class="chapter__figure scene scene--NN" data-scene="NN" aria-hidden="true">
      with a single inline SVG (viewBox 0 0 560 420). Decorative only —
      the chapter text carries the meaning. No style="" attributes (the
      strict nonce CSP blocks every one), no <script>, no on*= handlers,
      no external assets. SVG ids are prefixed sNN-.

   2. States, set by landing.js (story controller) on the root:
        .has-played  added once, the first time the figure is really on
                     screen. ENTRANCE animations hang off it and run once.
        .is-live     present only while the figure is on screen. LOOPS
                     hang off it, so an off-screen chapter costs nothing.
        --p          0 → 1 as the figure travels up through the viewport
                     (0.5 = centred), ONLY on roots marked data-scrub.
                     Scrubbed, scroll-linked effects read it through
                     calc(). It is inherited, so every change re-styles
                     the whole SVG, and moving an SVG element repaints
                     the SVG: reserve it for scenes where scrolling IS
                     the idea (09 depth, 10 the climb). Defaults to 0.5.
      Counters: an SVG <text>/<tspan> with data-count="1004" (optional
      data-from, data-dur in ms, data-format int|dec1|money) counts up
      when the chapter first plays. Its markup text IS the final value.

   3. The base CSS of every element is its FINAL, resting state. An
      entrance animates FROM a start state (keyframes `from {}` + fill
      `both`). That single rule is what makes the three failure paths
      safe: no JS, JS that never arrives (reveal-fallback), and reduced
      motion all simply see the finished picture.
      Elements hidden until their entrance carry class .s-in; the hide is
      scoped to html.reveal-js exactly like the page-wide .reveal.

   4. Only transform and opacity animate (stroke-dashoffset for a line
      that draws itself is the one exception). No animated filters, no
      animated box-shadow, no layout properties. SVG elements that
      transform set transform-box: fill-box (see .scene * below).

   5. Every selector is scoped under .scene--NN and every @keyframes is
      named sNN-*. Nothing a chapter does may leak into another.
   ════════════════════════════════════════════════════════════════════ */

.scene {
  --ease-out: cubic-bezier(0.2, 0.75, 0.2, 1);
  --ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1);
  --ease-io: cubic-bezier(0.65, 0, 0.35, 1);
  --p: 0.5;
  isolation: isolate;
}
.scene svg { overflow: hidden; }
.scene svg * { transform-box: fill-box; transform-origin: center; }

/* Entrance-hidden elements: hidden only while JS is alive and the
   chapter has not played yet (same guard as the page-wide .reveal). */
.reveal-js .scene:not(.has-played) .s-in { opacity: 0; }
.reveal-js.reveal-fallback .scene .s-in { opacity: 1; }

/* An entrance still playing when its scene leaves the screen is paused
   where it is and resumes if the reader comes back. A brisk scroll would
   otherwise keep three or four scenes animating at once off screen —
   SVG animations run on the main thread, so that is where frames went.
   !important: every scene sets its entrance with the `animation`
   shorthand, which resets animation-play-state to running, and its
   selector (.scene--NN.has-played .sNN-x) out-specifies this one, so
   without it nothing was ever paused (measured: all 32 of chapter 03's
   entrances kept running off screen). Loops need .is-live anyway. */
.scene.has-played:not(.is-live),
.scene.has-played:not(.is-live) * { animation-play-state: paused !important; }

/* A soft moving sheen across every figure while it is live — the one
   shared flourish, so the ten read as one set. Pure transform. */
.scene::after {
  content: "";
  position: absolute; inset: 0;
  pointer-events: none;
  background: linear-gradient(105deg, transparent 38%, rgba(255, 255, 255, 0.035) 50%, transparent 62%);
  transform: translateX(-100%);
  z-index: 2;
}
.scene.has-played::after { animation: scene-sheen 1600ms var(--ease-io) 200ms 1 both; }
@keyframes scene-sheen { from { transform: translateX(-100%); } to { transform: translateX(100%); } }

/* The card lifts a touch with the scroll. landing.js writes this
   transform directly on the root (a composited HTML layer) rather than
   through an inherited --p, which would re-style every node of the SVG on
   every scroll frame. Only scenes that really scrub (data-scrub) get --p. */
.chapter__figure.scene {
  will-change: transform;
  transition: border-color 400ms ease, box-shadow 400ms ease;
}

/* ── Layout: two equal columns so the spine sits exactly between them ── */
.journey { position: relative; }
.chapter__inner { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: clamp(64px, 8vw, 128px); }

/* ── The spine ─────────────────────────────────────────────────────── */
.journey__spine {
  position: absolute;
  top: 0; bottom: 0;
  left: 50%;
  width: 2px;
  margin-left: -1px;
  pointer-events: none;
  z-index: 0;
  overflow: hidden;
  /* the ends fade out — a static mask, never repainted */
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 3%, #000 97%, transparent);
          mask-image: linear-gradient(to bottom, transparent, #000 3%, #000 97%, transparent);
}
/* NOTHING on the spine repaints while scrolling. The fill is a static
   phase-coloured gradient (stops placed at the real chapter positions by
   landing.js, once per layout). The veil — the untravelled rail — is a
   rail-coloured strip laid over it and slid down to the reading position
   with a direct transform, as is the head. Both are their own compositor
   layers. (A clip-path driven by a custom property made WebKit repaint a
   ~10,000px-tall element on every scroll frame.) */
.journey__fill {
  position: absolute; inset: 0;
  background: linear-gradient(to bottom,
    var(--fg-dim) 0,
    var(--fg-dim) var(--st-pivot, 12%),
    var(--amber) var(--st-pivot, 12%),
    var(--red) var(--st-build, 22%),
    var(--blue) var(--st-build, 22%),
    var(--blue) var(--st-now, 62%),
    var(--green) var(--st-now, 62%),
    var(--green) var(--st-next, 72%),
    var(--blue) var(--st-next, 72%),
    var(--fg-dim) var(--st-goal, 92%),
    var(--amber) 100%);
}
.journey__rail {                         /* the veil: the untravelled part */
  position: absolute; left: 0; right: 0; top: 0; height: 100%;
  background: var(--border);
  transform: translate3d(0, 0, 0);
  will-change: transform;
}
.journey__head {
  position: absolute;
  left: 50%; top: 0;
  width: 12px; height: 12px;
  margin: -6px 0 0 -6px;
  border-radius: 50%;
  background: var(--fg);
  box-shadow: 0 0 0 4px rgba(237, 240, 246, 0.08), 0 0 18px 4px rgba(77, 142, 255, 0.45);
  transform: translate3d(0, 0, 0);
  will-change: transform;
  opacity: 0;
  transition: opacity 300ms ease;
  z-index: 2;                            /* above the chapter nodes (z 1) */
}
.journey.is-reading .journey__head { opacity: 1; }
/* The head is wider than the 2px spine, which clips: it lives on the
   journey instead (see the markup), aligned to the spine's centre. */

/* Chapter nodes: a candle on the spine that prints when you reach it. */
.chapter__node {
  position: absolute;
  left: 50%; top: 50%;
  width: 10px; height: 34px;
  margin: -17px 0 0 -5px;
  z-index: 1;
  pointer-events: none;
}
.chapter__node::before {                 /* wick */
  content: ""; position: absolute; left: 4px; top: 0; bottom: 0; width: 2px;
  border-radius: 1px;
  background: var(--node-c, var(--fg-dim));
  opacity: 0.55;
}
.chapter__node::after {                  /* body */
  content: ""; position: absolute; left: 0; right: 0; top: 7px; bottom: 7px;
  border-radius: 2px;
  background: var(--node-c, var(--fg-dim));
  box-shadow: 0 0 0 4px var(--bg), 0 0 16px 2px var(--node-glow, transparent);
  transform-origin: center bottom;
  transform: scaleY(0.2);
  opacity: 0.35;
  transition: transform 700ms var(--ease-spring, cubic-bezier(0.34, 1.4, 0.64, 1)), opacity 400ms ease;
}
.chapter.is-reached .chapter__node::after { transform: scaleY(1); opacity: 1; }
.chapter[data-phase="origin"] { --node-c: var(--fg-mute); }
.chapter[data-phase="pivot"]  { --node-c: var(--red);   --node-glow: rgba(255, 84, 112, 0.35); }
.chapter[data-phase="build"]  { --node-c: var(--blue);  --node-glow: rgba(77, 142, 255, 0.30); }
.chapter[data-phase="now"]    { --node-c: var(--green); --node-glow: rgba(62, 213, 152, 0.45); }
.chapter[data-phase="next"],
.chapter[data-phase="later"],
.chapter[data-phase="goal"]   { --node-c: transparent; }
/* Future chapters: an outlined, not-yet-printed candle. */
.chapter[data-phase="next"]  .chapter__node::after,
.chapter[data-phase="later"] .chapter__node::after,
.chapter[data-phase="goal"]  .chapter__node::after {
  background: var(--bg);
  border: 1.5px dashed var(--fg-dim);
  transform: scaleY(1);
  opacity: 0.8;
}
.chapter[data-phase="goal"] .chapter__node::after { border-color: var(--amber); }
.chapter[data-phase="next"]  .chapter__node::before,
.chapter[data-phase="later"] .chapter__node::before { background: var(--fg-dim); }
.chapter[data-phase="goal"]  .chapter__node::before { background: var(--amber); }
/* NOW breathes: the one live candle on the spine. */
/* Opacity only, and only while the chapter is near the screen (.is-near,
   set by landing.js) — an infinite loop must never run far off screen. */
.chapter[data-phase="now"].is-reached.is-near .chapter__node::after { animation: scene-now-pulse 2400ms ease-in-out infinite; }
@keyframes scene-now-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.6; }
}

/* ── Chapter text: a quieter, more editorial entrance ───────────────── */
.chapter__num {
  font-variant-numeric: tabular-nums;
}
.chapter__title {
  background: linear-gradient(90deg, var(--fg) 0%, var(--fg) 60%, var(--fg-mute) 100%);
  -webkit-background-clip: text; background-clip: text;
  color: transparent;
}
.chapter[data-phase="goal"] .chapter__title {
  background: linear-gradient(90deg, var(--amber) 0%, #ffd08a 100%);
  -webkit-background-clip: text; background-clip: text;
  color: transparent;
}
.chapter__text { position: relative; }
.chapter__text::after {                  /* underline that draws in */
  content: "";
  display: block;
  width: 56px; height: 2px;
  margin-top: clamp(18px, 2vw, 26px);
  border-radius: 2px;
  background: var(--node-c, var(--fg-dim));
  transform-origin: left center;
  transform: scaleX(1);
}
.chapter[data-phase="next"] .chapter__text::after,
.chapter[data-phase="later"] .chapter__text::after { background: var(--fg-dim); }
.chapter[data-phase="goal"] .chapter__text::after { background: var(--amber); }
.reveal-js .chapter.reveal:not(.is-visible) .chapter__text::after { transform: scaleX(0); }
/* JS never arrived: .reveal is force-shown without .is-visible — so is this. */
.reveal-js.reveal-fallback .chapter.reveal .chapter__text::after { transform: scaleX(1); animation: none; }
.chapter.reveal.is-visible .chapter__text::after {
  animation: scene-underline 900ms var(--ease-out, ease) 250ms both;
}
@keyframes scene-underline { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* ── Phone / narrow: the spine moves to the left edge ──────────────── */
@media (max-width: 860px) {
  .chapter__inner { grid-template-columns: 1fr; gap: 24px; }
  .journey { padding-left: clamp(40px, 10vw, 48px); }
  .journey__spine { left: clamp(18px, 5vw, 22px); }
  .journey__head { left: clamp(18px, 5vw, 22px); }
  .chapter__node { left: calc(-1 * clamp(40px, 10vw, 48px) + clamp(18px, 5vw, 22px)); top: 0; margin-top: 4px; }
  .chapter__figure.scene,
  .chapter:hover .chapter__figure.scene { transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════
   CHAPTER 01 — templates/landing/story/ch01.html
   ═══════════════════════════════════════════════════════════════════ */
/* Chapter 01 — Origin: one chart, one timeframe. Scoped under .scene--01;
   keyframes s01-*. Base = the finished picture.
   Entrance (.has-played, ~2.7 s): the 5m tab lights, the other timeframes
   settle in dim; candles print left to right down into a structural low;
   the low is marked; the SDT candle stabs below it and closes back up —
   flash, glow, one ripple, the 5m tab pings; the FIRED tag drops on its
   leader; the risk/reward projection draws; the reversal prints; the
   indicator pane ticks in; the next chart (ETHUSDT) waits to be opened by
   hand; SCANNER is struck out and flares red.
   Loops (.is-live): the crosshair drifts, the fired glow breathes, the
   live candle ticks. Scrubbed: the grid drifts. */

.scene--01 .s01-ring,
.scene--01 .s01-flash { opacity: 0; }
.scene--01 .s01-tabring { opacity: 0; transform: scale(1.4, 1.6); }
.scene--01 .s01-u,
.scene--01 .s01-sdt { transform-origin: 50% 100%; }
.scene--01 .s01-d,
.scene--01 .s01-leader,
.scene--01 .s01-live { transform-origin: 50% 0; }
.scene--01 .s01-tab5,
.scene--01 .s01-low,
.scene--01 .s01-zone { transform-origin: 0 50%; }

/* ── Entrance ── */
.scene--01.has-played .s01-tab5 { animation: s01-wipe 450ms var(--ease-out) 80ms both; }
.scene--01.has-played .s01-tab { animation: s01-fade 380ms ease-out 220ms both; }
.scene--01.has-played .s01-tab + .s01-tab { animation-delay: 300ms; }
.scene--01.has-played .s01-tab + .s01-tab + .s01-tab { animation-delay: 380ms; }

.scene--01.has-played .s01-k { animation: s01-print 420ms var(--ease-out) both; }
.scene--01.has-played .s01-pre .s01-k:nth-child(1)  { animation-delay: 150ms; }
.scene--01.has-played .s01-pre .s01-k:nth-child(2)  { animation-delay: 210ms; }
.scene--01.has-played .s01-pre .s01-k:nth-child(3)  { animation-delay: 270ms; }
.scene--01.has-played .s01-pre .s01-k:nth-child(4)  { animation-delay: 330ms; }
.scene--01.has-played .s01-pre .s01-k:nth-child(5)  { animation-delay: 390ms; }
.scene--01.has-played .s01-pre .s01-k:nth-child(6)  { animation-delay: 450ms; }
.scene--01.has-played .s01-pre .s01-k:nth-child(7)  { animation-delay: 510ms; }
.scene--01.has-played .s01-pre .s01-k:nth-child(8)  { animation-delay: 570ms; }
.scene--01.has-played .s01-pre .s01-k:nth-child(9)  { animation-delay: 630ms; }
.scene--01.has-played .s01-pre .s01-k:nth-child(10) { animation-delay: 690ms; }
.scene--01.has-played .s01-pre .s01-k:nth-child(11) { animation-delay: 750ms; }
.scene--01.has-played .s01-pre .s01-k:nth-child(12) { animation-delay: 810ms; }
.scene--01.has-played .s01-pre .s01-k:nth-child(13) { animation-delay: 870ms; }
.scene--01.has-played .s01-pre .s01-k:nth-child(14) { animation-delay: 930ms; }
.scene--01.has-played .s01-pre .s01-k:nth-child(15) { animation-delay: 990ms; }
.scene--01.has-played .s01-pre .s01-k:nth-child(16) { animation-delay: 1050ms; }
.scene--01.has-played .s01-post .s01-k:nth-child(1) { animation-delay: 1780ms; }
.scene--01.has-played .s01-post .s01-k:nth-child(2) { animation-delay: 1850ms; }
.scene--01.has-played .s01-post .s01-k:nth-child(3) { animation-delay: 1920ms; }
.scene--01.has-played .s01-post .s01-k:nth-child(4) { animation-delay: 1990ms; }
.scene--01.has-played .s01-post .s01-k:nth-child(5) { animation-delay: 2060ms; }
.scene--01.has-played .s01-post .s01-k:nth-child(6) { animation-delay: 2130ms; }
.scene--01.has-played .s01-post .s01-k:nth-child(7) { animation-delay: 2200ms; }

/* The sweep and the fire */
.scene--01.has-played .s01-low   { animation: s01-wipe 380ms var(--ease-out) 1000ms both; }
.scene--01.has-played .s01-sdt   { animation: s01-ignite 780ms var(--ease-out) 1150ms both; }
.scene--01.has-played .s01-flash { animation: s01-flash 650ms ease-out 1480ms both; }
.scene--01.has-played .s01-glow  { animation: s01-bloom 800ms var(--ease-out) 1460ms both; }
.scene--01.has-played .s01-ring  { animation: s01-ripple 1050ms var(--ease-out) 1500ms both; }
.scene--01.has-played .s01-tabring { animation: s01-ping 900ms var(--ease-out) 1530ms both; }
.scene--01.has-played .s01-leader { animation: s01-grow 280ms ease-out 1620ms both; }
.scene--01.has-played .s01-tag   { animation: s01-tag 520ms var(--ease-spring) 1700ms both; }
.scene--01.has-played .s01-zone  { animation: s01-wipe 650ms var(--ease-out) 1720ms both; }
.scene--01.has-played .s01-ln    { animation: s01-draw 650ms var(--ease-out) 1720ms both; }
.scene--01.has-played .s01-ax    { animation: s01-slide 360ms var(--ease-out) 2000ms both; }
.scene--01.has-played .s01-ax + .s01-ax { animation-delay: 2080ms; }

/* The pane ticks in, then the one-by-one workflow and the verdict */
.scene--01.has-played .s01-v  { animation: s01-rise 380ms var(--ease-out) 1560ms both; }
.scene--01.has-played .s01-st { animation: s01-pop 480ms var(--ease-spring) 1700ms both; }
.scene--01.has-played .s01-cross { animation: s01-fade 450ms ease-out 2050ms both; }
.scene--01.has-played .s01-next  { animation: s01-slide 400ms var(--ease-out) 1980ms both; }
.scene--01.has-played .s01-strike { animation-duration: 340ms; animation-delay: 2150ms; }
.scene--01.has-played .s01-scanx { animation: s01-fade 320ms ease-out 2380ms both; }
.scene--01.has-played .s01-scan  { animation: s01-shake 300ms ease-out 2390ms both; }

@keyframes s01-print  { from { opacity: 0; transform: scaleY(0.06); } 45% { opacity: 1; } }
/* Stab below the low, then close back up through it. */
@keyframes s01-ignite {
  from { opacity: 0; transform: translateY(4px) scaleY(0.14); }
  30%  { opacity: 1; transform: translateY(9px) scaleY(0.2); animation-timing-function: cubic-bezier(0.3, 0, 0.2, 1); }
  74%  { transform: scaleY(1.06); }
}
@keyframes s01-flash  { from { opacity: 0; } 22% { opacity: 0.9; } }
@keyframes s01-bloom  { from { opacity: 0; transform: scale(0.2); } }
@keyframes s01-ripple { from { opacity: 0; transform: scale(0.15); } 12% { opacity: 0.9; } }
@keyframes s01-ping   { from { opacity: 0; transform: none; } 15% { opacity: 1; } }
@keyframes s01-grow   { from { opacity: 0; transform: scaleY(0); } }
@keyframes s01-tag    { from { opacity: 0; transform: translateY(-12px) scale(0.9); } }
@keyframes s01-wipe   { from { opacity: 0; transform: scaleX(0); } }
@keyframes s01-draw   { from { stroke-dashoffset: 1; opacity: 0; } 6% { opacity: 1; } }
@keyframes s01-slide  { from { opacity: 0; transform: translateX(-8px); } }
@keyframes s01-rise   { from { opacity: 0; transform: translateY(6px); } }
@keyframes s01-pop    { from { opacity: 0; transform: scale(0.5); } }
@keyframes s01-fade   { from { opacity: 0; } }
@keyframes s01-shake  { from { transform: none; } 25% { transform: translateX(2px); } 55% { transform: translateX(-2px); } 80% { transform: translateX(1px); } }

/* ── Loops: only while on screen ── */
.scene--01.is-live .s01-vl,
.scene--01.is-live .s01-xd { animation: s01-sweep 7.5s ease-in-out 2600ms infinite alternate; }
.scene--01.is-live .s01-hl { animation: s01-bob 6.2s ease-in-out 2600ms infinite alternate; }
.scene--01.is-live .s01-breathe { animation: s01-breathe 3.6s ease-in-out 2400ms infinite; }
.scene--01.is-live .s01-live { animation: s01-tick 2.8s ease-in-out 2600ms infinite; }

@keyframes s01-sweep   { to { transform: translateX(-156px); } }
@keyframes s01-bob     { to { transform: translateY(20px); } }
@keyframes s01-breathe { 50% { opacity: 0.55; transform: scale(1.14); } }
@keyframes s01-tick    { 35% { transform: scaleY(1.9); } 70% { transform: scaleY(0.7); } }

/* ── Scrubbed: the grid drifts a few px against the candles ── */
@media (prefers-reduced-motion: no-preference) {
  .scene--01 .s01-grid { }
}

/* ═══════════════════════════════════════════════════════════════════
   CHAPTER 02 — templates/landing/story/ch02.html
   ═══════════════════════════════════════════════════════════════════ */
/* Chapter 02 — The wall. Scoped under .scene--02; keyframes s02-*.
   Base = the finished picture: dimmed wall of charts, 480,000, a full
   10/10 meter, the barrier, the queue, the modal, the X.
   Entrance (.has-played, ~2.6 s): the charts light up; the equation builds
   term by term and 480,000 counts up; the ten slots fill with the counter;
   alert #11 slams into the barrier (shake + red flash), the charts die,
   the queue backs up, the modal springs up, the X draws itself.
   Loops (.is-live): the queue bumps the wall every few seconds, the
   barrier flares, 480,000 shimmers with heat. Scrubbed: the tile wall. */

.scene--02 .s02-row { opacity: 0.2; }
.scene--02 .s02-fla { opacity: 0.5; }
.scene--02 .s02-flo { opacity: 0.7; }
.scene--02 .s02-eq  { transform-origin: 50% 50%; }
.scene--02 .s02-c11 { transform-origin: 0 50%; }

/* ── Entrance ── */
/* Rows light top to bottom 70 ms apart, hold, and die in the same order
   from the moment #11 hits the wall. */
.scene--02.has-played .s02-row { animation: s02-tiles 2200ms ease-out both; }
.scene--02.has-played .s02-row:nth-child(2) { animation-delay: 70ms; }
.scene--02.has-played .s02-row:nth-child(3) { animation-delay: 140ms; }
.scene--02.has-played .s02-row:nth-child(4) { animation-delay: 210ms; }
.scene--02.has-played .s02-row:nth-child(5) { animation-delay: 280ms; }
.scene--02.has-played .s02-row:nth-child(6) { animation-delay: 350ms; }
.scene--02.has-played .s02-row:nth-child(7) { animation-delay: 420ms; }
.scene--02.has-played .s02-row:nth-child(8) { animation-delay: 490ms; }

.scene--02.has-played .s02-t { animation: s02-rise 420ms var(--ease-out) both; }
.scene--02.has-played .s02-t:nth-child(1) { animation-delay: 120ms; }
.scene--02.has-played .s02-t:nth-child(2) { animation-delay: 220ms; }
.scene--02.has-played .s02-t:nth-child(3) { animation-delay: 300ms; }
.scene--02.has-played .s02-t:nth-child(4) { animation-delay: 400ms; }
.scene--02.has-played .s02-t:nth-child(5) { animation-delay: 480ms; }
.scene--02.has-played .s02-eq  { animation: s02-wipe 300ms var(--ease-out) 620ms both; }
.scene--02.has-played .s02-big { animation: s02-pop 520ms var(--ease-spring) 700ms both; }
.scene--02.has-played .s02-hgi { animation: s02-bloom 900ms var(--ease-out) 820ms both; }
.scene--02.has-played .s02-sub { animation: s02-fade 400ms ease-out 900ms both; }

/* Slot i lands when the eased counter shows i. */
.scene--02.has-played .s02-f { animation: s02-slot 300ms var(--ease-spring) both; }
.scene--02.has-played .s02-f:nth-child(1)  { animation-delay: 430ms; }
.scene--02.has-played .s02-f:nth-child(2)  { animation-delay: 485ms; }
.scene--02.has-played .s02-f:nth-child(3)  { animation-delay: 545ms; }
.scene--02.has-played .s02-f:nth-child(4)  { animation-delay: 615ms; }
.scene--02.has-played .s02-f:nth-child(5)  { animation-delay: 690ms; }
.scene--02.has-played .s02-f:nth-child(6)  { animation-delay: 775ms; }
.scene--02.has-played .s02-f:nth-child(7)  { animation-delay: 870ms; }
.scene--02.has-played .s02-f:nth-child(8)  { animation-delay: 990ms; }
.scene--02.has-played .s02-f:nth-child(9)  { animation-delay: 1150ms; }
.scene--02.has-played .s02-f:nth-child(10) { animation-delay: 1410ms; }

/* #11 slams the barrier at ~1.73 s */
.scene--02.has-played .s02-c11  { animation: s02-slam 640ms linear 1380ms both; }
.scene--02.has-played .s02-wall { animation: s02-shake 420ms ease-out 1730ms both; }
.scene--02.has-played .s02-fla  { animation: s02-flash 900ms ease-out 1720ms both; }
.scene--02.has-played .s02-cq   { animation: s02-queue 420ms var(--ease-out) 1950ms both; }
.scene--02.has-played .s02-q3 .s02-cq { animation-delay: 2080ms; }
.scene--02.has-played .s02-modal { animation: s02-modal 540ms var(--ease-spring) 1880ms both; }
.scene--02.has-played .s02-x { animation: s02-draw 360ms var(--ease-io) 2050ms both; }
.scene--02.has-played .s02-x + .s02-x { animation-delay: 2250ms; }

@keyframes s02-tiles { from { opacity: 0; } 14% { opacity: 1; } 79% { opacity: 1; } }
@keyframes s02-rise  { from { opacity: 0; transform: translateY(10px); } }
@keyframes s02-wipe  { from { opacity: 0; transform: scaleX(0); } }
@keyframes s02-pop   { from { opacity: 0; transform: scale(0.82); } }
@keyframes s02-bloom { from { opacity: 0; transform: scale(0.4); } }
@keyframes s02-fade  { from { opacity: 0; } }
@keyframes s02-slot  { from { opacity: 0; transform: scale(0.4); } 60% { opacity: 1; transform: scale(1.1); } }
@keyframes s02-slam {
  from { opacity: 0; transform: translateX(150px); animation-timing-function: cubic-bezier(0.5, 0, 1, 0.6); }
  15%  { opacity: 1; }
  55%  { transform: translateX(-9px) scaleX(0.94); animation-timing-function: var(--ease-out); }
  70%  { transform: translateX(9px); }
  85%  { transform: translateX(-2px); }
}
@keyframes s02-shake { from { transform: translateX(0); } 20% { transform: translateX(-4px); } 45% { transform: translateX(2px); } 70% { transform: translateX(-1px); } }
@keyframes s02-flash { from { opacity: 0; } 10% { opacity: 1; } }
@keyframes s02-queue { from { opacity: 0; transform: translateX(36px); } }
@keyframes s02-modal { from { opacity: 0; transform: translateY(26px) scale(0.94); } }
@keyframes s02-draw  { from { stroke-dashoffset: 1; opacity: 0; } 5% { opacity: 1; } }

/* ── Loops: only while on screen ── */
.scene--02.is-live .s02-q    { animation: s02-bump 4.2s var(--ease-io) 2900ms infinite; }
.scene--02.is-live .s02-q2   { animation-delay: 3020ms; }
.scene--02.is-live .s02-q3   { animation-delay: 3140ms; }
.scene--02.is-live .s02-wallo { animation: s02-nudge 4.2s ease-out 2900ms infinite; }
.scene--02.is-live .s02-flo  { animation: s02-flare 4.2s ease-out 2900ms infinite; }
.scene--02.is-live .s02-shim { animation: s02-shim 2.8s ease-in-out 2000ms infinite; }
.scene--02.is-live .s02-hg   { animation: s02-heat 3.6s ease-in-out 2200ms infinite; }

@keyframes s02-bump  { 0%, 38% { transform: translateX(0); } 48% { transform: translateX(-8.5px); } 56% { transform: translateX(4px); } 64%, 100% { transform: translateX(0); } }
@keyframes s02-nudge { 0%, 47% { transform: translateX(0); } 50% { transform: translateX(-2.5px); } 56% { transform: translateX(1px); } 62%, 100% { transform: translateX(0); } }
@keyframes s02-flare { 0%, 46% { opacity: 0.7; } 50% { opacity: 1; } 66%, 100% { opacity: 0.7; } }
@keyframes s02-shim  { 50% { opacity: 0.8; } }
@keyframes s02-heat  { 50% { opacity: 0.55; transform: scale(1.08); } }

/* ── Scrubbed: the tile wall drifts against the foreground ── */
@media (prefers-reduced-motion: no-preference) {
  .scene--02 .s02-tiles { }
}

/* ═══════════════════════════════════════════════════════════════════
   CHAPTER 03 — templates/landing/story/ch03.html
   ═══════════════════════════════════════════════════════════════════ */
/* Chapter 03 — From Pine to Python. Scoped under .scene--03; keyframes s03-*.
   Base = the finished picture. Entrance (.has-played, done by 2.78 s): the
   Pine card flips over into the Python card (same tokens, Python-indented);
   only once Python has landed does the terminal type the first scan —
   progress + counter, three fires flashing amber, 14.2 s — and the
   TradingView check traces back to the Pine original, which gets its tick.
   Loops (.is-live): cursor, a slow scanline, the port arrow's pulse, the
   Python card's status ring and its executing line. */

/* ── resting state ───────────────────────────────────────────── */
.scene--03 .s03-pine-card { opacity: 0.62; }
.scene--03 .s03-shade,
.scene--03 .s03-shade2 { opacity: 0; }
.scene--03 .s03-burst { opacity: 0; transform: scale(1.6); }
.scene--03 .s03-flow { stroke-dashoffset: -1; }
.scene--03 .s03-ring { opacity: 0; }
.scene--03 .s03-exec { opacity: 0; }
.scene--03 .s03-hl { opacity: 0.08; }
.scene--03 .s03-bar { transform-origin: 0 50%; }
.scene--03 .s03-cov { transform-origin: 100% 50%; transform: scaleX(0); }
.scene--03 .s03-caret { opacity: 0; transform: translateX(139px); }
.scene--03 .s03-scan { transform: translateY(-64px); }
.scene--03 .s03-badge { transform-origin: 50% 50%; }

/* ── entrance: the port first (0–1.3 s), then the run (0.84–2.78 s) ── */
.scene--03.has-played .s03-term { animation: s03-rise 450ms var(--ease-out) 40ms both; }
.scene--03.has-played .s03-pine-card { animation: s03-fold 900ms linear both; }
.scene--03.has-played .s03-shade { animation: s03-dark 900ms linear both; }
.scene--03.has-played .s03-py { animation: s03-unfold 940ms linear both; }
.scene--03.has-played .s03-shade2 { animation: s03-light 940ms linear both; }
.scene--03.has-played .s03-burst { animation: s03-burst 820ms linear both; }
.scene--03.has-played .s03-amb { animation: s03-bloom 600ms var(--ease-out) 560ms both; }
.scene--03.has-played .s03-mid { animation: s03-fade 350ms ease-out 820ms both; }
.scene--03.has-played .s03-arrow { animation: s03-draw 450ms var(--ease-io) 860ms both; }
.scene--03.has-played .s03-head { animation: s03-nudge 280ms var(--ease-spring) 1200ms both; }

.scene--03.has-played .s03-caret { animation: s03-caret 1360ms linear both; }
.scene--03.has-played .s03-c1 { animation: s03-type 360ms steps(21, end) 840ms both; }
.scene--03.has-played .s03-c2 { animation: s03-type 200ms steps(11, end) 1230ms both; }
.scene--03.has-played .s03-c3 { animation: s03-type 190ms steps(10, end) 1340ms both; }
.scene--03.has-played .s03-row4 { animation: s03-fade 200ms ease-out 1420ms both; }
.scene--03.has-played .s03-bar { animation: s03-fill 640ms cubic-bezier(0.33, 1, 0.68, 1) 1440ms both; }
.scene--03.has-played .s03-f1 { animation: s03-land 300ms var(--ease-out) 1540ms both; }
.scene--03.has-played .s03-f2 { animation: s03-land 300ms var(--ease-out) 1680ms both; }
.scene--03.has-played .s03-f3 { animation: s03-land 300ms var(--ease-out) 1820ms both; }
.scene--03.has-played .s03-h1 { animation: s03-flash 560ms ease-out 1540ms both; }
.scene--03.has-played .s03-h2 { animation: s03-flash 560ms ease-out 1680ms both; }
.scene--03.has-played .s03-h3 { animation: s03-flash 560ms ease-out 1820ms both; }
.scene--03.has-played .s03-m1 { animation: s03-mark 300ms var(--ease-spring) 1540ms both; }
.scene--03.has-played .s03-m2 { animation: s03-mark 300ms var(--ease-spring) 1680ms both; }
.scene--03.has-played .s03-m3 { animation: s03-mark 300ms var(--ease-spring) 1820ms both; }
.scene--03.has-played .s03-c7 { animation: s03-type 180ms steps(9, end) 2060ms both; }
.scene--03.has-played .s03-c8 { animation: s03-type 180ms steps(10, end) 2170ms both; }
.scene--03.has-played .s03-tick { animation: s03-pop 240ms var(--ease-spring) 2310ms both; }
.scene--03.has-played .s03-c9 { animation: s03-type 100ms steps(2, end) 2330ms both; }
.scene--03.has-played .s03-link { animation: s03-fade 160ms ease-out 2340ms both; }
.scene--03.has-played .s03-trace { animation: s03-draw 280ms var(--ease-io) 2340ms both; }
.scene--03.has-played .s03-badge { animation: s03-pop 240ms var(--ease-spring) 2540ms both; }

@keyframes s03-rise { from { opacity: 0; transform: translateY(14px); } }
@keyframes s03-fade { from { opacity: 0; } }
@keyframes s03-bloom { from { opacity: 0; transform: scale(0.5); } }
@keyframes s03-draw { from { stroke-dashoffset: 1; } }
@keyframes s03-nudge { from { opacity: 0; transform: translateX(-8px); } }
@keyframes s03-type { from { transform: scaleX(1); } }
@keyframes s03-fill { from { transform: scaleX(0); } }
@keyframes s03-flash { from { opacity: 0; } 20% { opacity: 0.3; } }
@keyframes s03-land { from { opacity: 0; transform: translateX(-12px); } }
@keyframes s03-mark { from { opacity: 0; transform: scaleY(0); } }
@keyframes s03-pop { from { opacity: 0; transform: scale(0); } }
/* The Pine card slides toward the middle and folds edge-on (a flip),
   darkening as it turns away… */
@keyframes s03-fold {
  from { opacity: 0; transform: translateY(8px); animation-timing-function: cubic-bezier(0.2, 0.75, 0.2, 1); }
  18% { opacity: 1; transform: none; }
  32% { opacity: 1; transform: none; animation-timing-function: cubic-bezier(0.55, 0, 0.8, 0.3); }
  50% { opacity: 1; transform: translateX(80px) scaleX(0.62) skewY(-4deg); animation-timing-function: linear; }
  64% { opacity: 1; transform: translateX(155px) scaleX(0) skewY(-6deg); }
  64.1% { opacity: 0; transform: none; animation-timing-function: ease-out; }
}
@keyframes s03-dark { from, 32% { opacity: 0; } 64% { opacity: 0.65; } 64.1% { opacity: 0; } }
/* …and comes out the other side as Python, catching the light as it lands. */
@keyframes s03-unfold {
  from, 60.8% { opacity: 0; transform: translateX(-155px) scaleX(0) skewY(6deg); }
  61% { opacity: 1; transform: translateX(-155px) scaleX(0) skewY(6deg); animation-timing-function: cubic-bezier(0.2, 0.75, 0.2, 1); }
  78% { opacity: 1; transform: translateX(-58px) scaleX(0.82) skewY(2deg); animation-timing-function: cubic-bezier(0.34, 1.35, 0.64, 1); }
}
@keyframes s03-light { from, 61% { opacity: 0.65; } 84% { opacity: 0; } }
@keyframes s03-burst {
  from, 56% { opacity: 0; transform: scale(0.3); }
  70% { opacity: 1; transform: scale(1); animation-timing-function: ease-out; }
}
/* The caret waits at the prompt (one blink) while the port lands, then
   rides the typing of the command. */
@keyframes s03-caret {
  from, 33.1% { opacity: 0; transform: translateX(0); }
  33.2%, 50% { opacity: 1; }
  50.1%, 55.9% { opacity: 0; }
  56% { opacity: 1; }
  61.76% { opacity: 1; transform: translateX(0); animation-timing-function: steps(21, end); }
  88.2% { opacity: 1; transform: translateX(139px); }
}

/* ── loops (only while on screen) ────────────────────────────── */
.scene--03.is-live .s03-cur { animation: s03-blink 2400ms step-end infinite; }
.scene--03.is-live .s03-scan { animation: s03-sweep 7200ms linear 2600ms infinite; }
.scene--03.is-live .s03-flow { animation: s03-flow 3200ms var(--ease-io) 1400ms infinite; }
.scene--03.is-live .s03-ring { animation: s03-ring 2800ms ease-out 1000ms infinite; }
.scene--03.is-live .s03-exec { animation: s03-exec 3600ms var(--ease-io) 1200ms infinite; }

@keyframes s03-blink { 0% { opacity: 1; } 25% { opacity: 0; } 50% { opacity: 1; } 75%, 100% { opacity: 0; } }
@keyframes s03-sweep { from { transform: translateY(-56px); } to { transform: translateY(292px); } }
@keyframes s03-flow { 0% { stroke-dashoffset: 0.14; } 55%, 100% { stroke-dashoffset: -1; } }
@keyframes s03-ring { from { opacity: 0.75; transform: scale(1); } to { opacity: 0; transform: scale(2.8); } }
/* the interpreter stepping through the three lines */
@keyframes s03-exec {
  0% { opacity: 0; transform: translateY(0); }
  6%, 28% { opacity: 1; transform: translateY(0); }
  36%, 58% { transform: translateY(10px); }
  66%, 88% { opacity: 1; transform: translateY(20px); }
  96%, 100% { opacity: 0; transform: translateY(20px); }
}

@media (prefers-reduced-motion: reduce) {
  .scene--03 .s03-top, .scene--03 .s03-termp, .scene--03 .s03-proof { transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════
   CHAPTER 04 — templates/landing/story/ch04.html
   ═══════════════════════════════════════════════════════════════════ */
/* Chapter 04 — From laptop to Frankfurt. Scoped under .scene--04; keyframes s04-*.
   Base = the finished picture. Entrance (.has-played, done by 2.34 s): the
   globe, the Frankfurt pin and the rack arrive; a packet flies from the
   laptop to the pin and the laptop goes to sleep; the rack's LEDs come up
   in order, the 24/7 orbit closes around the globe and the badge lands; the
   first alert runs down to Discord and the bubble springs in. Loops
   (.is-live): the globe turns (five meridians handing off 36° slots), the
   orbit sweeps, the pin pulses, an alert packet flows every 3.2 s. Scroll
   (--p): the loose badge floats against the wired-up scene; the orbit turns. */

/* ── resting state ───────────────────────────────────────────── */
.scene--04 .s04-back { }
.scene--04 .s04-front { }
.scene--04 .s04-orbit { }
.scene--04 .s04-sweep { transform-box: view-box; transform-origin: 268px 192px; }
.scene--04 .s04-mer { transform-origin: 0 50%; }
/* the resting pose = 30% into the turn cycle (asymmetric, reads as a sphere) */
.scene--04 .s04-m1 { transform: scaleX(-0.981); opacity: 0.406; }
.scene--04 .s04-m2 { transform: scaleX(-0.683); opacity: 0.81; }
.scene--04 .s04-m3 { transform: scaleX(-0.124); opacity: 0.945; }
.scene--04 .s04-m4 { transform: scaleX(0.481); opacity: 0.888; }
.scene--04 .s04-m5 { transform: scaleX(0.903); opacity: 0.618; }
.scene--04 .s04-pring { opacity: 0; }
.scene--04 .s04-hit { opacity: 0; transform: scale(1.8); }
.scene--04 .s04-pk,
.scene--04 .s04-apk1,
.scene--04 .s04-apk2 { stroke-dashoffset: -1; }
.scene--04 .s04-lchart { opacity: 0.35; }
.scene--04 .s04-bub { transform-origin: 52% 0; }

/* ── entrance: move (0–1.2 s) → server up (1.2–1.98 s) → first fire ── */
.scene--04.has-played .s04-globe { animation: s04-grow 650ms var(--ease-out) both; }
.scene--04.has-played .s04-lap { animation: s04-left 480ms var(--ease-out) 60ms both; }
.scene--04.has-played .s04-pin { animation: s04-drop 520ms var(--ease-spring) 240ms both; }
.scene--04.has-played .s04-rack { animation: s04-down 460ms var(--ease-out) 300ms both; }
.scene--04.has-played .s04-flabel { animation: s04-left 380ms var(--ease-out) 420ms both; }
.scene--04.has-played .s04-leader { animation: s04-fade 320ms ease-out 560ms both; }
.scene--04.has-played .s04-trail { animation: s04-draw 700ms var(--ease-io) 520ms both; }
.scene--04.has-played .s04-pk { animation: s04-fly 700ms var(--ease-io) 520ms both; }
.scene--04.has-played .s04-hit { animation: s04-hit 520ms ease-out 1160ms both; }
.scene--04.has-played .s04-lchart { animation: s04-dim 520ms ease-out 1100ms both; }
.scene--04.has-played .s04-moon { animation: s04-moon 420ms var(--ease-spring) 1240ms both; }
.scene--04.has-played .s04-ringin { animation: s04-spin 800ms var(--ease-out) 1180ms both; }
.scene--04.has-played .s04-l1 { animation: s04-led 220ms ease-out 1240ms both; }
.scene--04.has-played .s04-l2 { animation: s04-led 220ms ease-out 1320ms both; }
.scene--04.has-played .s04-l3 { animation: s04-led 220ms ease-out 1400ms both; }
.scene--04.has-played .s04-l4 { animation: s04-led 220ms ease-out 1480ms both; }
.scene--04.has-played .s04-rec { animation: s04-up 360ms var(--ease-out) 1500ms both; }
.scene--04.has-played .s04-chip { animation: s04-up 420ms var(--ease-out) 1560ms both; }
.scene--04.has-played .s04-apath { animation: s04-fade 300ms ease-out 1380ms both; }
.scene--04.has-played .s04-apk1 { animation: s04-alert 440ms cubic-bezier(0.5, 0, 0.75, 0.6) 1440ms both; }
.scene--04.has-played .s04-bub { animation: s04-pop 520ms var(--ease-spring) 1820ms both; }
.scene--04.has-played .s04-plabel { animation: s04-up 320ms var(--ease-out) 2000ms both; }

@keyframes s04-grow { from { opacity: 0; transform: scale(0.9); } }
@keyframes s04-spin { from { opacity: 0; transform: rotate(-70deg) scale(0.94); } }
@keyframes s04-up { from { opacity: 0; transform: translateY(8px); } }
@keyframes s04-down { from { opacity: 0; transform: translateY(-12px); } }
@keyframes s04-left { from { opacity: 0; transform: translateX(-14px); } }
@keyframes s04-drop { from { opacity: 0; transform: translateY(-22px) scale(0.6); } }
@keyframes s04-fade { from { opacity: 0; } }
@keyframes s04-draw { from { stroke-dashoffset: 1; } }
@keyframes s04-fly { from { stroke-dashoffset: 0.1; } }
@keyframes s04-alert { from { stroke-dashoffset: 0.12; } }
@keyframes s04-hit { from { opacity: 0; transform: scale(0.3); } 30% { opacity: 1; transform: scale(1); } }
@keyframes s04-dim { from { opacity: 1; } }
@keyframes s04-moon { from { opacity: 0; transform: scale(0.4) rotate(-50deg); } }
@keyframes s04-led { from { opacity: 0.12; transform: scale(0.5); } }
@keyframes s04-pop { from { opacity: 0; transform: translateY(14px) scale(0.72); } 40% { opacity: 1; } }

/* ── loops (only while on screen) ────────────────────────────── */
/* The globe turns west to east: each meridian walks one 36° slot per
   cycle, scaleX following sin(longitude) and fading toward the limb; the
   hand-off from slot to slot is invisible. -1440ms = the resting pose. */
.scene--04.is-live .s04-m1 { animation: s04-k1 4800ms linear -1440ms infinite; }
.scene--04.is-live .s04-m2 { animation: s04-k2 4800ms linear -1440ms infinite; }
.scene--04.is-live .s04-m3 { animation: s04-k3 4800ms linear -1440ms infinite; }
.scene--04.is-live .s04-m4 { animation: s04-k4 4800ms linear -1440ms infinite; }
.scene--04.is-live .s04-m5 { animation: s04-k5 4800ms linear -1440ms infinite; }
.scene--04.is-live .s04-sweep { animation: s04-orbit 24000ms linear infinite; }
.scene--04.is-live .s04-pring { animation: s04-ring 2800ms ease-out 800ms infinite; }
.scene--04.is-live .s04-apk2 { animation: s04-flow 3200ms linear 2400ms infinite; }

@keyframes s04-k1 { 0% { transform: scaleX(-1); opacity: 0; } 25% { transform: scaleX(-0.988); opacity: 0.376; } 50% { transform: scaleX(-0.951); opacity: 0.528; } 75% { transform: scaleX(-0.891); opacity: 0.64; } 100% { transform: scaleX(-0.809); opacity: 0.728; } }
@keyframes s04-k2 { 0% { transform: scaleX(-0.809); opacity: 0.728; } 25% { transform: scaleX(-0.707); opacity: 0.799; } 50% { transform: scaleX(-0.588); opacity: 0.854; } 75% { transform: scaleX(-0.454); opacity: 0.897; } 100% { transform: scaleX(-0.309); opacity: 0.926; } }
@keyframes s04-k3 { 0% { transform: scaleX(-0.309); opacity: 0.926; } 25% { transform: scaleX(-0.156); opacity: 0.944; } 50% { transform: scaleX(0.004); opacity: 0.95; } 75% { transform: scaleX(0.156); opacity: 0.944; } 100% { transform: scaleX(0.309); opacity: 0.926; } }
@keyframes s04-k4 { 0% { transform: scaleX(0.309); opacity: 0.926; } 25% { transform: scaleX(0.454); opacity: 0.897; } 50% { transform: scaleX(0.588); opacity: 0.854; } 75% { transform: scaleX(0.707); opacity: 0.799; } 100% { transform: scaleX(0.809); opacity: 0.728; } }
@keyframes s04-k5 { 0% { transform: scaleX(0.809); opacity: 0.728; } 25% { transform: scaleX(0.891); opacity: 0.64; } 50% { transform: scaleX(0.951); opacity: 0.528; } 75% { transform: scaleX(0.988); opacity: 0.376; } 100% { transform: scaleX(1); opacity: 0; } }
@keyframes s04-orbit { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes s04-ring { from { opacity: 0.8; transform: scale(1); } to { opacity: 0; transform: scale(3.6); } }
@keyframes s04-flow {
  0% { stroke-dashoffset: 0.12; animation-timing-function: cubic-bezier(0.5, 0, 0.75, 0.6); }
  16%, 100% { stroke-dashoffset: -1; }
}

@media (prefers-reduced-motion: reduce) {
  .scene--04 .s04-back, .scene--04 .s04-front, .scene--04 .s04-orbit { transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════
   CHAPTER 05 — templates/landing/story/ch05.html
   ═══════════════════════════════════════════════════════════════════ */
/* Ch 05 — THE PRODUCT. Base = finished dashboard (lock open, access granted).
   Entrances: .has-played; loops: .is-live; depth: --p. */
.scene--05 svg { font-family: "JetBrains Mono", ui-monospace, monospace; }
.scene--05 .s05-i { font-family: Inter, system-ui, sans-serif; }
.scene--05 .s05-t { font-family: "Inter Tight", Inter, sans-serif; font-weight: 700; }
.scene--05 .s05-up { fill: #26a69a; stroke: #26a69a; }
.scene--05 .s05-dn { fill: #ef5350; stroke: #ef5350; }

/* resting transient layers */
.scene--05 .s05-us, .scene--05 .s05-sb, .scene--05 .s05-fb, .scene--05 .s05-halo, .scene--05 .s05-ck { opacity: 0; }
.scene--05 .s05-cur { opacity: 0; transform: translate(8px, 12px); transform-origin: 0 0; }
.scene--05 .s05-ring { opacity: 0; transform: scale(1.9); }
.scene--05 .s05-us { transform: translateY(-7px); }
.scene--05 .s05-lg { opacity: .45; }
.scene--05 .s05-cs > *, .scene--05 .s05-lb { transform-origin: 50% 100%; }
.scene--05 .s05-ln { transform-origin: 0 50%; }
/* shackle rests OPEN, swung about its left foot (352.5,355) */
.scene--05 .s05-shk { transform-origin: 0 14.5px; transform: translateY(-6px) rotate(-18deg); }

/* depth: the card floats */
.scene--05 .s05-float { }

/* ── entrance (done by ≈2.65 s) ── */
.scene--05.has-played .s05-win { animation:s05-win 550ms var(--ease-out) both; }
.scene--05.has-played .s05-nav { animation:s05-down 400ms var(--ease-out) 150ms both; }
/* nav switches first (150–750 ms) */
.scene--05.has-played .s05-sb { animation:s05-off 600ms linear 150ms both; }
.scene--05.has-played .s05-eb { animation:s05-on 600ms linear 150ms both; }
.scene--05.has-played .s05-us { animation:s05-uout 600ms linear 150ms both; }
.scene--05.has-played .s05-ue { animation:s05-uin 600ms linear 150ms both; }
.scene--05.has-played .s05-ul { animation:s05-ul 600ms linear 150ms both; }
.scene--05.has-played .s05-cur { animation:s05-cur 900ms linear 60ms both; }
.scene--05.has-played .s05-ck { animation:s05-ck 380ms var(--ease-out) 450ms both; }
/* then Explorer fills in */
.scene--05.has-played .s05-sh,
.scene--05.has-played .s05-ct { animation:s05-fade 400ms var(--ease-out) 700ms both; }
.scene--05.has-played .s05-rows > g { animation:s05-left 420ms var(--ease-out) 740ms both; }
.scene--05.has-played .s05-rows>g:nth-child(2){animation-delay:790ms}
.scene--05.has-played .s05-rows>g:nth-child(3){animation-delay:840ms}
.scene--05.has-played .s05-rows>g:nth-child(4){animation-delay:890ms}
.scene--05.has-played .s05-rows>g:nth-child(5){animation-delay:940ms}
.scene--05.has-played .s05-rows>g:nth-child(6){animation-delay:990ms}

/* candles print, 35 ms apart */
.scene--05.has-played .s05-cs > * { animation:s05-print 400ms var(--ease-spring) 780ms both; }
.scene--05.has-played .s05-cs>:nth-child(2){animation-delay:815ms}
.scene--05.has-played .s05-cs>:nth-child(3){animation-delay:850ms}
.scene--05.has-played .s05-cs>:nth-child(4){animation-delay:885ms}
.scene--05.has-played .s05-cs>:nth-child(5){animation-delay:920ms}
.scene--05.has-played .s05-cs>:nth-child(6){animation-delay:955ms}
.scene--05.has-played .s05-cs>:nth-child(7){animation-delay:990ms}
.scene--05.has-played .s05-cs>:nth-child(8){animation-delay:1025ms}
.scene--05.has-played .s05-cs>:nth-child(9){animation-delay:1060ms}
.scene--05.has-played .s05-cs>:nth-child(10){animation-delay:1095ms}
.scene--05.has-played .s05-cs>:nth-child(11){animation-delay:1130ms}
.scene--05.has-played .s05-cs>:nth-child(12){animation-delay:1165ms}
.scene--05.has-played .s05-cs>:nth-child(13){animation-delay:1200ms}
.scene--05.has-played .s05-cs>:nth-child(14){animation-delay:1235ms}
.scene--05.has-played .s05-cs>:nth-child(15){animation-delay:1270ms}
.scene--05.has-played .s05-cs>:nth-child(16){animation-delay:1305ms}
.scene--05.has-played .s05-cs>:nth-child(17){animation-delay:1340ms}
.scene--05.has-played .s05-cs>:nth-child(18){animation-delay:1375ms}

/* levels: entry, SL, TP; then tags */
.scene--05.has-played .s05-le { animation:s05-draw 550ms var(--ease-out) 1250ms both; }
.scene--05.has-played .s05-sl { animation:s05-draw 550ms var(--ease-out) 1400ms both; }
.scene--05.has-played .s05-tp { animation:s05-draw 550ms var(--ease-out) 1550ms both; }
.scene--05.has-played .s05-et,
.scene--05.has-played .s05-st { animation:s05-pop 350ms var(--ease-spring) 1750ms both; }
.scene--05.has-played .s05-tt { animation:s05-pop 350ms var(--ease-spring) 1850ms both; }
.scene--05.has-played .s05-rp { animation:s05-pop 450ms var(--ease-spring) 1500ms both; }
.scene--05.has-played .s05-lp { animation:s05-fade 400ms var(--ease-out) 1500ms both; }

/* gate: key in, turn, lock springs open */
.scene--05.has-played .s05-card { animation:s05-card 400ms var(--ease-out) 1600ms both; }
.scene--05.has-played .s05-key { animation:s05-key 350ms var(--ease-out) 1800ms both; }
.scene--05.has-played .s05-kt { animation:s05-turn 220ms var(--ease-io) 2100ms both; }
.scene--05.has-played .s05-shk { animation:s05-open 380ms var(--ease-spring) 2300ms both; }
.scene--05.has-played .s05-gg { animation:s05-fade 260ms var(--ease-out) 2300ms both; }
.scene--05.has-played .s05-lg { animation:s05-flash 320ms var(--ease-out) 2300ms both; }
.scene--05.has-played .s05-ring { animation:s05-ring 340ms var(--ease-out) 2280ms both; }
.scene--05.has-played .s05-sub { animation:s05-right 260ms var(--ease-out) 2360ms both; }
.scene--05.has-played .s05-ah { animation:s05-fade 350ms var(--ease-out) 1700ms both; }
.scene--05.has-played .s05-aud > g { animation:s05-left 300ms var(--ease-out) 1900ms both; }
.scene--05.has-played .s05-aud>g:nth-child(2){animation-delay:2100ms}
.scene--05.has-played .s05-aud>g:nth-child(3){animation-delay:2300ms}

@keyframes s05-win { from { opacity: 0; transform: translateY(10px) scale(.975); } }
@keyframes s05-down { from { opacity: 0; transform: translateY(-5px); } }
@keyframes s05-fade { from { opacity: 0; } }
@keyframes s05-left { from { opacity: 0; transform: translateX(-16px); } }
@keyframes s05-right { from { opacity: 0; transform: translateX(-6px); } }
@keyframes s05-off { from { opacity: 0; } 25%, 55% { opacity: 1; } }
@keyframes s05-on { from, 55% { opacity: 0; } }
@keyframes s05-uout { from { opacity: 0; transform: none; } 25%, 55% { opacity: 1; transform: none; } }
@keyframes s05-uin { from, 55% { opacity: 0; transform: translateY(7px); } }
@keyframes s05-ul {
  from { opacity: 0; transform: translateX(-55px) scaleX(.77); }
  25% { opacity: 1; transform: translateX(-55px) scaleX(.77); }
  55% { transform: translateX(-55px) scaleX(.77); animation-timing-function: cubic-bezier(.65, 0, .35, 1); }
}
@keyframes s05-cur {
  from { opacity: 0; transform: translate(70px, 64px); }
  12% { opacity: 1; transform: translate(70px, 64px); animation-timing-function: cubic-bezier(.45, 0, .2, 1); }
  42% { transform: none; }
  47% { transform: scale(.82); }
  54% { opacity: 1; transform: none; animation-timing-function: ease-in; }
}
@keyframes s05-ck { from { opacity: .9; transform: scale(.3); } }
@keyframes s05-print { from { opacity: 0; transform: scaleY(.05); } }
@keyframes s05-draw { from { transform: scaleX(0); } }
@keyframes s05-pop { from { opacity: 0; transform: scale(.4); } }
@keyframes s05-card { from { opacity: 0; transform: translateY(14px) scale(.96); } }
@keyframes s05-key { from { opacity: 0; transform: translateX(36px); } }
@keyframes s05-turn { from { transform: scaleY(-1); } }
@keyframes s05-open { from { transform: none; } }
@keyframes s05-flash { from { opacity: 0; transform: scale(.3); } 50% { opacity: 1; transform: scale(1.2); } }
@keyframes s05-ring { from { opacity: 0; transform: scale(.7); } 15% { opacity: .9; transform: scale(.8); } }

/* ── loops (on screen only) ── */
.scene--05.is-live .s05-halo { animation:s05-halo 2400ms var(--ease-out) infinite; }
.scene--05.is-live .s05-lb { animation:s05-breathe 3200ms ease-in-out 2000ms infinite; }
.scene--05.is-live .s05-lpi { animation:s05-tick 3200ms ease-in-out 2000ms infinite; }
/* WATCH → ACTIVE only; the reset hides inside a row dim */
.scene--05.is-live .s05-fb { animation:s05-fb 20s linear infinite; }
.scene--05.is-live .s05-rd { animation:s05-rd 20s linear infinite; }
.scene--05.is-live .s05-rows>:nth-child(4) .s05-fb,
.scene--05.is-live .s05-rows>:nth-child(4) .s05-rd{animation-delay:5s}

@keyframes s05-halo { 0% { opacity: .7; transform: scale(1); } 70%, 100% { opacity: 0; transform: scale(3.2); } }
@keyframes s05-breathe { 0%, 100% { transform: scaleY(1); } 50% { transform: scaleY(.7); } }
@keyframes s05-tick { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(5.25px); } }
@keyframes s05-fb { 0%, 18% { opacity: 0; transform: translateY(3px); } 20%, 71% { opacity: 1; transform: none; } 72%, 100% { opacity: 0; } }
@keyframes s05-rd { 0%, 69% { opacity: 1; } 71.5% { opacity: .25; } 74%, 100% { opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
  .scene--05 .s05-float { transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════
   CHAPTER 06 — templates/landing/story/ch06.html
   ═══════════════════════════════════════════════════════════════════ */
/* Ch 06 — LAUNCH: the site goes live, the server lights up, invites land as
   members. Base = finished picture. Entrances: .has-played; loops: .is-live;
   depth: --p. Keyframes s06-*. */
.scene--06 svg { font-family: "JetBrains Mono", ui-monospace, monospace; }
.scene--06 .s06-i { font-family: Inter, system-ui, sans-serif; }
.scene--06 .s06-t { font-family: "Inter Tight", Inter, sans-serif; font-weight: 700; }

/* resting transient layers */
.scene--06 .s06-hg { opacity: .55; }
.scene--06 .s06-fl, .scene--06 .s06-bb, .scene--06 .s06-v1, .scene--06 .s06-v2 { opacity: 0; }
.scene--06 .s06-bb { transform: scale(.7); }
.scene--06 .s06-mp, .scene--06 .s06-lp { opacity: 0; transform: scale(3.2); }
/* envelopes rest at their avatar, spent: x and y on nested groups = an arc */
.scene--06 .s06-tk { opacity: 0; transform: translateY(0) scale(.3) rotate(0deg); }
.scene--06 .s06-ex { transform: translateX(131px); }
.scene--06 .s06-ey { transform: translateY(var(--dy)); }
.scene--06 .s06-ex:nth-child(1) { --dy: -120px; }
.scene--06 .s06-ex:nth-child(2) { --dy: -82px; }
.scene--06 .s06-ex:nth-child(3) { --dy: -44px; }
/* zoom anchors on the latest bar (the canvas's right edge) */
.scene--06 .s06-zm { transform-box: view-box; transform-origin: 540px 72px; }

/* depth: the chart card floats; scrolling pans its canvas */
.scene--06 .s06-float { }
.scene--06 .s06-pan { }

/* ── entrance (done by ≈2.7 s) ── */
.scene--06.has-played .s06-win { animation: s06-win 500ms var(--ease-out) both; }
.scene--06.has-played .s06-hi { animation: s06-pop 450ms var(--ease-spring) 120ms both; }
.scene--06.has-played .s06-hg { animation: s06-glow 800ms var(--ease-out) 200ms both; }
/* the site goes live first */
.scene--06.has-played .s06-cv { animation: s06-drop 450ms var(--ease-out) 200ms both; }
.scene--06.has-played .s06-lp { animation: s06-ping 700ms var(--ease-out) 550ms both; }
.scene--06.has-played .s06-ln { animation: s06-draw 900ms var(--ease-io) 700ms both; }
.scene--06.has-played .s06-ar { animation: s06-fade 400ms var(--ease-out) 1300ms both; }
.scene--06.has-played .s06-xo { animation: s06-fade 300ms var(--ease-out) 1600ms both; }
.scene--06.has-played .s06-fps { animation: s06-pop 350ms var(--ease-spring) 1600ms both; }
/* the server, its channels, the bot's card */
.scene--06.has-played .s06-ch { animation: s06-left 450ms var(--ease-out) 250ms both; }
.scene--06.has-played .s06-hd { animation: s06-fade 400ms var(--ease-out) 300ms both; }
.scene--06.has-played .s06-c1 { animation: s06-up 350ms var(--ease-out) 400ms both; }
.scene--06.has-played .s06-c2 { animation: s06-up 350ms var(--ease-out) 500ms both; }
.scene--06.has-played .s06-bot { animation: s06-up 350ms var(--ease-out) 450ms both; }
.scene--06.has-played .s06-card { animation: s06-in 450ms var(--ease-out) 550ms both; }
.scene--06.has-played .s06-fl { animation: s06-flash 800ms ease-out 800ms both; }
/* invites fly chip → avatar, 400 ms apart; each lands, types, posts */
.scene--06.has-played .s06-ex { animation: s06-ex 600ms cubic-bezier(.2, .6, .35, 1) 650ms both; }
.scene--06.has-played .s06-ey { animation: s06-ey 600ms cubic-bezier(.5, 0, .5, 1) 650ms both; }
.scene--06.has-played .s06-tk { animation: s06-fly 600ms linear 650ms both; }
.scene--06.has-played .s06-ex:nth-child(2),.scene--06.has-played .s06-ex:nth-child(2) *{animation-delay:1050ms}
.scene--06.has-played .s06-ex:nth-child(3),.scene--06.has-played .s06-ex:nth-child(3) *{animation-delay:1450ms}
.scene--06.has-played .s06-mp { animation: s06-mp 1200ms var(--ease-out) 1150ms both; }
.scene--06.has-played .s06-av { animation: s06-pop 400ms var(--ease-spring) 1150ms both; }
.scene--06.has-played .s06-bb { animation: s06-bub 400ms var(--ease-out) 1180ms both; }
.scene--06.has-played .s06-m { animation: s06-up 330ms var(--ease-out) 1520ms both; }
.scene--06.has-played .s06-av:nth-child(5){animation-delay:1550ms}
.scene--06.has-played .s06-av:nth-child(6){animation-delay:1950ms}
.scene--06.has-played .s06-bb:nth-child(2){animation-delay:1580ms}
.scene--06.has-played .s06-bb:nth-child(3){animation-delay:1980ms}
.scene--06.has-played .s06-m:nth-child(8){animation-delay:1920ms}
.scene--06.has-played .s06-m:nth-child(9){animation-delay:2320ms}
.scene--06.has-played .s06-lv { animation: s06-fade 300ms var(--ease-out) 2400ms both; }

@keyframes s06-win { from { opacity: 0; transform: translateY(10px) scale(.98); } }
@keyframes s06-pop { from { opacity: 0; transform: scale(.5); } }
@keyframes s06-glow { from { opacity: 0; transform: scale(.4); } 45% { opacity: 1; transform: scale(1.35); } }
@keyframes s06-left { from { opacity: 0; transform: translateX(-8px); } }
@keyframes s06-fade { from { opacity: 0; } }
@keyframes s06-up { from { opacity: 0; transform: translateY(6px); } }
@keyframes s06-in { from { opacity: 0; transform: translateX(22px); } }
@keyframes s06-drop { from { opacity: 0; transform: translateY(-14px); } }
@keyframes s06-flash { from { opacity: 0; } 25% { opacity: 1; } }
@keyframes s06-bub { from { opacity: 0; transform: scale(.6); } 25%, 70% { opacity: 1; transform: none; } }
@keyframes s06-draw { from { stroke-dashoffset: 1; } }
@keyframes s06-ping { from { opacity: .9; transform: scale(1); } }
@keyframes s06-mp { 0%, 33%, 67% { opacity: .9; transform: scale(1); } 25%, 32%, 58%, 66%, 92% { opacity: 0; transform: scale(3.2); } }
@keyframes s06-ex { from { transform: none; } }
@keyframes s06-ey { from { transform: none; } }
@keyframes s06-fly {
  from { opacity: 0; transform: translateY(0) scale(.6) rotate(0deg); }
  14% { opacity: 1; transform: translateY(-18px) scale(1.12) rotate(-10deg); }
  78% { opacity: 1; transform: translateY(0) scale(1) rotate(6deg); animation-timing-function: ease-in; }
}

/* ── loops (on screen only) ── */
.scene--06.is-live .s06-x { animation: s06-glide 12s linear 2000ms infinite; }
.scene--06.is-live .s06-zm { animation: s06-zoom 9s var(--ease-io) 2000ms infinite; }
.scene--06.is-live .s06-v1 { animation: s06-v1 10s linear 2700ms infinite; }
.scene--06.is-live .s06-v2 { animation: s06-v2 10s linear 2700ms infinite; }
.scene--06.is-live .s06-ty { animation: s06-ty 10s linear 2700ms infinite; }
.scene--06.is-live .s06-d { animation: s06-dot 10s ease-in-out 2700ms infinite; }
.scene--06.is-live .s06-d:nth-child(2){animation-delay:2850ms}
.scene--06.is-live .s06-d:nth-child(3){animation-delay:3000ms}

/* the crosshair rides the line, vertex to vertex (base = the x=482 vertex) */
@keyframes s06-glide {
  0%, 100% { transform: translate(0, 0); }
  8.33% { transform: translate(20px, 4px); animation-timing-function: ease-out; }
  16.67% { transform: translate(40px, -5px); animation-timing-function: ease-in; }
  25% { transform: translate(20px, 4px); }
  33.33% { transform: translate(0, 0); }
  41.67%, 91.67% { transform: translate(-20px, 10px); }
  50%, 83.33% { transform: translate(-40px, 7px); }
  58.33% { transform: translate(-60px, 15px); animation-timing-function: ease-out; }
  66.67% { transform: translate(-80px, 12px); animation-timing-function: ease-in; }
  75% { transform: translate(-60px, 15px); }
}
@keyframes s06-zoom { 50% { transform: scaleX(1.08); } }
/* slot: typing (rest) → ary_0017 → typing → skyobtc → typing */
@keyframes s06-ty { 0%, 18%, 50%, 68%, 100% { opacity: 1; } 20%, 48%, 70%, 98% { opacity: 0; } }
@keyframes s06-v1 { 0%, 18% { opacity: 0; transform: translateY(5px); } 21%, 46% { opacity: 1; transform: none; } 48%, 100% { opacity: 0; } }
@keyframes s06-v2 { 0%, 68% { opacity: 0; transform: translateY(5px); } 71%, 96% { opacity: 1; transform: none; } 98%, 100% { opacity: 0; } }
@keyframes s06-dot { 0%, 8%, 16%, 50%, 58%, 66%, 100% { transform: none; } 4%, 12%, 54%, 62% { transform: translateY(-3px); } }

@media (prefers-reduced-motion: reduce) {
  .scene--06 .s06-float, .scene--06 .s06-pan { transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════
   CHAPTER 07 — templates/landing/story/ch07.html
   ═══════════════════════════════════════════════════════════════════ */
/* Chapter 07 — Live research desk. Scoped under .scene--07; keyframes s07-*.
   Base = the finished board. Entrance (.has-played, ~2.6 s): status strip,
   the radar opens ring by ring, the exchange bezel turns into place and its
   ten nodes light clockwise, stat tiles rise and count, the review gate is
   built, the activity line draws. Loops (.is-live, 8 elements): one sweep
   arm, three blips lit as the sweep passes them, a setup token that stops
   at REVIEW (APPROVED flashes) before it reaches MEMBERS, the scrolling
   activity line, the LIVE ping. Scroll (--p): the bezel turns a little and
   the radar and the panels drift apart. */

.scene--07 .s07-mono { font-family: 'JetBrains Mono', ui-monospace, monospace; }

/* ── resting state (+ scroll-scrubbed depth) ─────────────────── */
.scene--07 .s07-par  { }
.scene--07 .s07-col  { }
.scene--07 .s07-dial { }
.scene--07 .s07-ping { opacity: 0.35; }
.scene--07 .s07-gate { transform-origin: 50% 100%; }

/* ── entrance ────────────────────────────────────────────────── */
.scene--07.has-played .s07-hdr     { animation: s07-drop 600ms var(--ease-out) both; }
.scene--07.has-played .s07-disc    { animation: s07-grow 900ms var(--ease-out) 80ms both; }
.scene--07.has-played .s07-cross   { animation: s07-fade 600ms ease-out 300ms both; }
.scene--07.has-played .s07-ring--1 { animation: s07-ring 800ms var(--ease-out) 150ms both; }
.scene--07.has-played .s07-ring--2 { animation: s07-ring 800ms var(--ease-out) 230ms both; }
.scene--07.has-played .s07-ring--3 { animation: s07-ring 800ms var(--ease-out) 310ms both; }
.scene--07.has-played .s07-ticks   { animation: s07-turn 1100ms var(--ease-out) 200ms both; }
.scene--07.has-played .s07-bezel   { animation: s07-turn 1200ms var(--ease-out) 300ms both; }
.scene--07.has-played .s07-node    { animation: s07-pop 500ms var(--ease-spring) both; }
.scene--07.has-played .s07-node:nth-child(1)  { animation-delay: 450ms; }
.scene--07.has-played .s07-node:nth-child(2)  { animation-delay: 505ms; }
.scene--07.has-played .s07-node:nth-child(3)  { animation-delay: 560ms; }
.scene--07.has-played .s07-node:nth-child(4)  { animation-delay: 615ms; }
.scene--07.has-played .s07-node:nth-child(5)  { animation-delay: 670ms; }
.scene--07.has-played .s07-node:nth-child(6)  { animation-delay: 725ms; }
.scene--07.has-played .s07-node:nth-child(7)  { animation-delay: 780ms; }
.scene--07.has-played .s07-node:nth-child(8)  { animation-delay: 835ms; }
.scene--07.has-played .s07-node:nth-child(9)  { animation-delay: 890ms; }
.scene--07.has-played .s07-node:nth-child(10) { animation-delay: 945ms; }
.scene--07.has-played .s07-sweepin { animation: s07-fade 500ms ease-out 800ms both; }
.scene--07.has-played .s07-blips   { animation: s07-fade 500ms ease-out 1000ms both; }
.scene--07.has-played .s07-cap     { animation: s07-rise 600ms var(--ease-out) 900ms both; }

.scene--07.has-played .s07-tile--1 { animation: s07-rise 600ms var(--ease-out) 200ms both; }
.scene--07.has-played .s07-tile--2 { animation: s07-rise 600ms var(--ease-out) 290ms both; }
.scene--07.has-played .s07-tile--3 { animation: s07-rise 600ms var(--ease-out) 380ms both; }
.scene--07.has-played .s07-tile--4 { animation: s07-rise 600ms var(--ease-out) 470ms both; }
.scene--07.has-played .s07-rev     { animation: s07-rise 600ms var(--ease-out) 600ms both; }
.scene--07.has-played .s07-track   { animation: s07-draw 700ms var(--ease-io) 800ms both; }
.scene--07.has-played .s07-gate    { animation: s07-post 550ms var(--ease-spring) 1000ms both; }
.scene--07.has-played .s07-out     { animation: s07-draw 500ms var(--ease-io) 1400ms both; }
.scene--07.has-played .s07-chal    { animation: s07-slide 500ms var(--ease-out) 1500ms both; }
.scene--07.has-played .s07-spk     { animation: s07-rise 600ms var(--ease-out) 800ms both; }
.scene--07.has-played .s07-spline  { animation: s07-draw 1600ms var(--ease-io) 1000ms both; }
.scene--07.has-played .s07-sarea   { animation: s07-fade 700ms ease-out 1500ms both; }

@keyframes s07-fade  { from { opacity: 0; } }
@keyframes s07-drop  { from { opacity: 0; transform: translateY(-8px); } }
@keyframes s07-rise  { from { opacity: 0; transform: translateY(12px); } }
@keyframes s07-slide { from { opacity: 0; transform: translateX(-8px); } }
@keyframes s07-grow  { from { opacity: 0; transform: scale(0.6); } }
@keyframes s07-ring  { from { opacity: 0; transform: scale(0.3); } }
@keyframes s07-turn  { from { opacity: 0; transform: rotate(-40deg); } }
@keyframes s07-pop   { from { opacity: 0; transform: scale(0); } }
@keyframes s07-post  { from { opacity: 0; transform: scaleY(0); } }
@keyframes s07-draw  { from { stroke-dashoffset: 1; } }

/* ── loops: only while on screen ─────────────────────────────── */
/* One sweep turn = 6 s, starting pointing up at 0.9 s. A blip at screen
   angle a is lit at 0.9 + ((a + 90) mod 360) / 360 × 6 s; the delays below
   are that moment minus two periods. Blip 1 (300°) fires at 1.4 s and the
   review token leaves FIRED at 1.5 s, so the board tells one story. */
.scene--07.is-live .s07-sweep  { animation: s07-spin 6000ms linear 900ms infinite; }
.scene--07.is-live .s07-blip   { animation: s07-blip 6000ms linear infinite both; }
.scene--07.is-live .s07-blip--1 { animation-delay: -10600ms; }
.scene--07.is-live .s07-blip--2 { animation-delay: -8600ms; }
.scene--07.is-live .s07-blip--3 { animation-delay: -6767ms; }
.scene--07.is-live .s07-token  { animation: s07-token 6000ms var(--ease-io) -4500ms infinite both; }
.scene--07.is-live .s07-flash  { animation: s07-flash 6000ms ease-in-out -4500ms infinite both; }
.scene--07.is-live .s07-scroll { animation: s07-scroll 16000ms linear 1200ms infinite; }
.scene--07.is-live .s07-ping   { animation: s07-ping 2400ms ease-out infinite; }

@keyframes s07-spin { to { transform: rotate(360deg); } }
@keyframes s07-blip {
  0%        { opacity: 1; transform: scale(0.4); }
  7%        { opacity: 1; transform: scale(1.2); }
  14%       { opacity: 1; transform: scale(1); }
  60%, 100% { opacity: 0.22; transform: scale(1); }
}
/* Token rests at x 478: FIRED is −154, the gate −52, MEMBERS +42. */
@keyframes s07-token {
  0%        { opacity: 0; transform: translateX(-154px); }
  6%        { opacity: 1; }
  28%, 46%  { transform: translateX(-52px); }
  68%       { opacity: 1; transform: translateX(42px); }
  78%, 100% { opacity: 0; transform: translateX(42px); }
}
/* The mark exists only while the token sits in the gate (base = 1, so
   the resting picture still shows the approved state). */
@keyframes s07-flash {
  0%, 26%   { opacity: 0; }
  32%, 52%  { opacity: 1; }
  66%, 100% { opacity: 0; }
}
@keyframes s07-scroll { to { transform: translateX(-224px); } }
@keyframes s07-ping {
  0%        { opacity: 0.7; transform: scale(1); }
  70%, 100% { opacity: 0; transform: scale(2.8); }
}

@media (prefers-reduced-motion: reduce) {
  .scene--07 .s07-par, .scene--07 .s07-col, .scene--07 .s07-dial { transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════
   CHAPTER 08 — templates/landing/story/ch08.html
   ═══════════════════════════════════════════════════════════════════ */
/* Chapter 08 — Phones. Prop-firm. Scoped .scene--08, keyframes s08-*.
   Blueprint of what ships next. Base = the finished picture. Entrance
   (.has-played, 2.7 s): phone rises, alerts stack, rules tick, leader
   links PUSH ALERTS to the phone, shield ticks, needle settles safe.
   Loops (.is-live, 8): a push every 4 s (packet, stack, new alert); four
   live FX chips orbit the phone's base with depth. --p: phone floats. */

.scene--08 .s08-mono { font-family: 'JetBrains Mono', ui-monospace, monospace; }
.scene--08 .s08-ui { font-family: 'Inter', system-ui, sans-serif; }
.scene--08 .s08-head { font-family: 'Inter Tight', 'Inter', system-ui, sans-serif; }

/* ── resting state (+ scroll depth) ── */
.scene--08 .s08-float { }
/* Cards are drawn at slot 0; stacked here. */
.scene--08 .s08-card--b { transform: translateY(66px) scale(0.95); opacity: 0.62; }
.scene--08 .s08-card--c { transform: translateY(132px) scale(0.9); opacity: 0.3; }
/* Chips sit at the orbit centre; θ puts each on the ellipse. */
/* The ellipse squash is a plain SVG transform about the orbit centre. The
   scene-wide transform-box: fill-box would pivot it on the chips' own
   centroid — harmless while the chips sat symmetrically, wrong once they
   all sit on the front arc. */
.scene--08 .s08-squash { transform-box: view-box; transform-origin: 0 0; }
.scene--08 .s08-chip--1 { transform: rotate(20deg) translateX(136px) rotate(-20deg); opacity: 1; }
.scene--08 .s08-chip--2 { transform: rotate(160deg) translateX(136px) rotate(-160deg); opacity: 1; }
/* Stations at the sides of the track, two in front and two behind, all
   clear of the phone (which covers the middle of the track) and of the
   card's bottom edge. The travelling light carries the orbit. */
.scene--08 .s08-chip--3 { transform: rotate(200deg) translateX(136px) rotate(-200deg) scale(0.86); opacity: 0.75; }
.scene--08 .s08-chip--4 { transform: rotate(340deg) translateX(136px) rotate(-340deg) scale(0.86); opacity: 0.75; }
.scene--08 .s08-pkt { opacity: 0; }
.scene--08 .s08-needle { transform: rotate(-48deg); }
.scene--08 .s08-strike { transform-origin: 0 50%; }

/* ── entrance ── */
.scene--08.has-played .s08-grid { animation: s08-fade 900ms ease-out both; }
.scene--08.has-played .s08-tag { animation: s08-fade 500ms ease-out 200ms both; }
.scene--08.has-played .s08-phone { animation: s08-lift 1000ms cubic-bezier(0.22, 1.15, 0.36, 1) 60ms both; }
.scene--08.has-played .s08-ghost { animation: s08-ghost 900ms var(--ease-out) 450ms both; }
.scene--08.has-played .s08-time { animation: s08-fade 500ms ease-out 550ms both; }
.scene--08.has-played .s08-orbin { animation: s08-grow 1000ms var(--ease-out) 350ms both; }
.scene--08.has-played .s08-fxl { animation: s08-slide 500ms var(--ease-out) 900ms both; }
.scene--08.has-played .s08-in--c { animation: s08-drop 600ms var(--ease-spring) 900ms both; }
.scene--08.has-played .s08-in--b { animation: s08-drop 600ms var(--ease-spring) 1150ms both; }
.scene--08.has-played .s08-in--a { animation: s08-drop 600ms var(--ease-spring) 1400ms both; }

.scene--08.has-played .s08-fp { animation: s08-rise 600ms var(--ease-out) 300ms both; }
.scene--08.has-played .s08-row--1 { animation: s08-slide 450ms var(--ease-out) 650ms both; }
.scene--08.has-played .s08-row--2 { animation: s08-slide 450ms var(--ease-out) 760ms both; }
.scene--08.has-played .s08-row--3 { animation: s08-slide 450ms var(--ease-out) 870ms both; }
.scene--08.has-played .s08-strike { animation: s08-strike 380ms var(--ease-io) 1200ms both; }
.scene--08.has-played .s08-dot--p { animation: s08-pop 400ms var(--ease-spring) 1600ms both; }
.scene--08.has-played .s08-lead { animation: s08-draw 600ms var(--ease-io) 1700ms both; }
.scene--08.has-played .s08-dot--f { animation: s08-pop 400ms var(--ease-spring) 2250ms both; }

.scene--08.has-played .s08-sp { animation: s08-rise 600ms var(--ease-out) 450ms both; }
.scene--08.has-played .s08-shield { animation: s08-draw 900ms var(--ease-io) 750ms both; }
.scene--08.has-played .s08-sfill { animation: s08-fade 500ms ease-out 1450ms both; }
.scene--08.has-played .s08-check { animation: s08-draw 380ms var(--ease-out) 1600ms both; }
.scene--08.has-played .s08-slbl { animation: s08-slide 500ms var(--ease-out) 1250ms both; }

.scene--08.has-played .s08-gp { animation: s08-rise 600ms var(--ease-out) 600ms both; }
.scene--08.has-played .s08-band--1 { animation: s08-draw 450ms var(--ease-out) 1300ms both; }
.scene--08.has-played .s08-band--2 { animation: s08-draw 400ms var(--ease-out) 1500ms both; }
.scene--08.has-played .s08-band--3 { animation: s08-draw 400ms var(--ease-out) 1680ms both; }
.scene--08.has-played .s08-needle { animation: s08-settle 1000ms linear 1700ms both; }
.scene--08.has-played .s08-glbl { animation: s08-fade 500ms ease-out 1400ms both; }

@keyframes s08-fade { from { opacity: 0; } }
@keyframes s08-rise { from { opacity: 0; transform: translateY(12px); } }
@keyframes s08-slide { from { opacity: 0; transform: translateX(10px); } }
@keyframes s08-lift { from { opacity: 0; transform: translateY(80px); } 40% { opacity: 1; } }
@keyframes s08-ghost { from { opacity: 0; transform: scale(1.08); } }
@keyframes s08-grow { from { opacity: 0; transform: scale(0.55); } }
@keyframes s08-pop { from { opacity: 0; transform: scale(0); } }
@keyframes s08-drop { from { opacity: 0; transform: translateY(-28px) scale(0.94); } 30% { opacity: 1; } }
@keyframes s08-strike { from { transform: scaleX(0); } }
@keyframes s08-draw { from { stroke-dashoffset: 1; } }
/* Swings in, settles damped in the safe band (rest −48°). */
@keyframes s08-settle {
  from { opacity: 0; transform: rotate(-90deg); animation-timing-function: cubic-bezier(0.3, 0, 0.3, 1); }
  10% { opacity: 1; }
  45% { transform: rotate(-26deg); animation-timing-function: ease-in-out; }
  68% { transform: rotate(-60deg); animation-timing-function: ease-in-out; }
  86% { transform: rotate(-43deg); animation-timing-function: ease-in-out; }
}

/* ── loops: only while on screen ── */
/* Push every 4 s (t = 3, 7, 11 …), card cycle 12 s; phases at t=0 (a 1 s,
   b 5 s, c 9 s) = the resting stack. Per push: packet lands (−0.6 s),
   oldest falls away (−0.3 s), the others step down (0.32 s) and 60 ms in
   the recycled card springs into the top slot. Cards never overlap (SVG
   paint order is fixed) and the top slot is empty for ~3 frames. */
.scene--08.is-live .s08-card { animation: s08-stack 12000ms linear infinite both; }
.scene--08.is-live .s08-card--a { animation-delay: -1000ms; }
.scene--08.is-live .s08-card--b { animation-delay: -5000ms; }
.scene--08.is-live .s08-card--c { animation-delay: -9000ms; }
.scene--08.is-live .s08-pkt { animation: s08-push 4000ms linear 2400ms infinite; }
/* 40 s a turn; delay −θ/360 × 40 s = resting θ. */
/* The orbit is a light travelling round the dashed track (a paint-only
   stroke-dashoffset loop). Rotating the four chips themselves made the
   browser re-lay out their text on every frame — ~10x the layout cost of
   any other scene. The chips sit at their stations on the track. */
.scene--08 .s08-orblight { stroke-dashoffset: 0; }
.scene--08.is-live .s08-orblight { animation: s08-orbit 12000ms linear infinite; }

@keyframes s08-stack {
  0%, 0.5% { opacity: 0; transform: translateY(-16px); animation-timing-function: var(--ease-spring); }
  2.5% { opacity: 1; }
  5.5%, 33.33% { opacity: 1; transform: translateY(0); animation-timing-function: var(--ease-out); }
  36%, 66.67% { opacity: 0.62; transform: translateY(66px); animation-timing-function: var(--ease-out); }
  69.33%, 97.5% { opacity: 0.3; transform: translateY(132px); animation-timing-function: ease-in; }
  100% { opacity: 0; transform: translateY(142px); }
}
/* PUSH ALERTS → phone side button, then a soft bloom. */
@keyframes s08-push {
  0% { opacity: 0; transform: translate(0, 0) scale(0.6); animation-timing-function: var(--ease-io); }
  5% { opacity: 1; }
  14% { opacity: 1; transform: translate(-98px, 46px) scale(1); animation-timing-function: ease-out; }
  24%, 100% { opacity: 0; transform: translate(-98px, 46px) scale(2.4); }
}
/* Depth: scale .9 + .1 sinθ, opacity .8 + .2 sinθ (front θ = 90°). */
@keyframes s08-orbit { from { stroke-dashoffset: 0; } to { stroke-dashoffset: -1; } }

@media (prefers-reduced-motion: reduce) {
  .scene--08 .s08-float { transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════
   CHAPTER 09 — templates/landing/story/ch09.html
   ═══════════════════════════════════════════════════════════════════ */
/* Chapter 09 — LATER: the road into the uncharted. Scoped .scene--09,
   keyframes s09-*, base = final picture. Entrance: ridges rise; sparks
   race up the road edges while a mask wipe paves behind them and lane
   marks print as the tip passes; the pass lights; the stops float in.
   Loops (.is-live only). --p drives the depth parallax. */

.scene--09 .s09-m { font-family: "JetBrains Mono", ui-monospace, monospace; }
.scene--09 .s09-t { font-family: "Inter Tight", Inter, sans-serif; font-weight: 600; }
.scene--09 .s09-spark { opacity: 0; stroke-dashoffset: -0.965; }
/* Spin about the dial centre, not the label-inclusive bbox. */
.scene--09 .s09-cmp { transform-box: view-box; transform-origin: 500px 400px; }

/* ── Entrance ─────────────────────────────────────────────────────── */
.scene--09.has-played .s09-stars { animation: s09-fade 1400ms ease-out both; }
.scene--09.has-played .s09-haze  { animation: s09-bloom 1600ms var(--ease-out) 100ms both; }
.scene--09.has-played .s09-ifar  { animation: s09-rise 1100ms var(--ease-out) both; }
.scene--09.has-played .s09-imid  { animation: s09-rise 1100ms var(--ease-out) 120ms both; }
.scene--09.has-played .s09-inear { animation: s09-rise 1100ms var(--ease-out) 240ms both; }
.scene--09.has-played .s09-grid  { animation: s09-fade 900ms ease-out 150ms both; }
.scene--09.has-played .s09-edge  { animation: s09-draw 1400ms cubic-bezier(0.3, 0.1, 0.2, 1) 250ms both; }
.scene--09.has-played .s09-spark { animation: s09-spark 1400ms cubic-bezier(0.3, 0.1, 0.2, 1) 250ms both; }
.scene--09.has-played .s09-wipe  { animation: s09-wipe 1400ms linear 250ms both; }
.scene--09.has-played .s09-lane > * { animation: s09-dash 380ms var(--ease-out) 524ms both; }
.scene--09.has-played .s09-lane>:nth-child(2){animation-delay:676ms}
.scene--09.has-played .s09-lane>:nth-child(3){animation-delay:740ms}
.scene--09.has-played .s09-lane>:nth-child(4){animation-delay:794ms}
.scene--09.has-played .s09-lane>:nth-child(5){animation-delay:858ms}
.scene--09.has-played .s09-lane>:nth-child(6){animation-delay:910ms}
.scene--09.has-played .s09-lane>:nth-child(7){animation-delay:946ms}
.scene--09.has-played .s09-lane>:nth-child(8){animation-delay:966ms}
.scene--09.has-played .s09-lane>:nth-child(9){animation-delay:984ms}
.scene--09.has-played .s09-lane>:nth-child(10){animation-delay:1006ms}
.scene--09.has-played .s09-lane>:nth-child(11){animation-delay:1034ms}
.scene--09.has-played .s09-lane>:nth-child(12){animation-delay:1062ms}
.scene--09.has-played .s09-tv  { animation: s09-fade 500ms ease-out 640ms both; }
.scene--09.has-played .s09-ign { animation: s09-ign 1100ms var(--ease-out) 1250ms both; }
.scene--09.has-played .s09-bf  { animation: s09-float 1000ms var(--ease-out) 1000ms both; }
.scene--09.has-played .s09-ba  { animation: s09-float 1000ms var(--ease-out) 1150ms both; }
.scene--09.has-played .s09-br  { animation: s09-float 1000ms var(--ease-out) 1300ms both; }
.scene--09.has-played .s09-cmp { animation: s09-spin 1000ms var(--ease-out) 1450ms both; }
.scene--09.has-played .s09-unc { animation: s09-up 850ms var(--ease-out) 1650ms both; }

@keyframes s09-fade  { from { opacity: 0; } }
@keyframes s09-bloom { from { opacity: 0; transform: scale(0.6); } }
@keyframes s09-rise  { from { opacity: 0; transform: translateY(26px); } }
@keyframes s09-draw  { from { stroke-dashoffset: 1; } }
@keyframes s09-spark { from { opacity: 1; stroke-dashoffset: 0.035; } 72% { opacity: 1; } }
/* Paving front = measured tip of the edges, 5 units behind. */
@keyframes s09-wipe {
  0%{transform:translateY(200.7px)} 10%{transform:translateY(178.7px)} 20%{transform:translateY(145.7px)}
  30%{transform:translateY(105px)} 40%{transform:translateY(70.4px)} 50%{transform:translateY(48.2px)}
  60%{transform:translateY(37px)} 70%{transform:translateY(27.6px)} 80%{transform:translateY(23.7px)}
  90%{transform:translateY(20.5px)}
}
@keyframes s09-dash  { from { opacity: 0; transform: scale(0.3); } }
@keyframes s09-ign   { from { opacity: 0; transform: scale(0.2); } 45% { opacity: 1; transform: scale(1.4); animation-timing-function: cubic-bezier(0.45, 0, 0.3, 1); } }
@keyframes s09-float { from { opacity: 0; transform: translateY(18px) scale(0.94); } }
@keyframes s09-spin  { from { opacity: 0; transform: rotate(-120deg) scale(0.7); } }
@keyframes s09-up    { from { opacity: 0; transform: translateY(6px); } }

/* ── Loops (only while on screen) ─────────────────────────────────── */
.scene--09.is-live .s09-bob1   { animation: s09-bob 6.4s ease-in-out 2.4s infinite; }
.scene--09.is-live .s09-bob2   { animation: s09-bob 7.6s ease-in-out 2.3s infinite; }
.scene--09.is-live .s09-bob3   { animation: s09-bob 8.4s ease-in-out 2.5s infinite; }
.scene--09.is-live .s09-head   { animation: s09-replay 5.6s cubic-bezier(0.3, 0, 0.2, 1) 1.9s infinite backwards; }
.scene--09.is-live .s09-pkt    { animation: s09-pkt 3.2s var(--ease-io) 2.4s infinite backwards; }
.scene--09.is-live .s09-tw1    { animation: s09-tw 4.2s ease-in-out 1.4s infinite; }
.scene--09.is-live .s09-needle { animation: s09-hunt 6s ease-in-out 2.5s infinite; }
.scene--09.is-live .s09-trav   { animation: s09-trav 9s linear 2.6s infinite; }

@keyframes s09-bob { 50% { transform: translateY(-4px); } }
/* Curtain + playhead + fill + knob: one bar (19u) per eased step, hold,
   then an eased rewind sweep. */
@keyframes s09-replay {
  0%, 5.55% { transform: translateX(-152px); }
  7.75%, 13.3% { transform: translateX(-133px); }
  15.5%, 21.05% { transform: translateX(-114px); }
  23.25%, 28.8% { transform: translateX(-95px); }
  31%, 36.55% { transform: translateX(-76px); }
  38.75%, 44.3% { transform: translateX(-57px); }
  46.5%, 52.05% { transform: translateX(-38px); }
  54.25%, 59.8% { transform: translateX(-19px); }
  62% { transform: none; }
  84% { transform: none; animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1); }
  100% { transform: translateX(-152px); }
}
@keyframes s09-pkt {
  0%   { opacity: 0; transform: translateX(-13px); }
  20%  { opacity: 1; }
  55%  { opacity: 1; transform: translateX(13px); }
  70%, 100% { opacity: 0; transform: translateX(13px); }
}
@keyframes s09-tw   { 50% { opacity: 0.25; } }
@keyframes s09-hunt { 30% { transform: rotate(-9deg); } 70% { transform: rotate(6deg); } }
/* Traveller: sampled from the road's own ground-plane projection (right
   lane), shrinks with depth, fades at the pass, reappears at the start. */
@keyframes s09-trav {
  3.6%{transform:translate(2.6px,-1.2px)scale(.99)} 7.2%{transform:translate(10.4px,-5px)scale(.96)}
  10.8%{transform:translate(22.9px,-11.3px)scale(.91)} 14.4%{transform:translate(37.7px,-20px)scale(.84)}
  18%{transform:translate(49.7px,-30.4px)scale(.75)} 21.6%{transform:translate(51.6px,-42.1px)scale(.65)}
  25.2%{transform:translate(38.1px,-54.1px)scale(.55)} 28.8%{transform:translate(12.2px,-65.6px)scale(.46)}
  32.4%{transform:translate(-12.5px,-76px)scale(.37)} 36%{transform:translate(-22.6px,-84.8px)scale(.3)}
  39.6%{transform:translate(-16.3px,-92px)scale(.24)} 43.2%{transform:translate(-3.5px,-97.4px)scale(.2);opacity:1}
  46.8%{transform:translate(6.1px,-101.5px)scale(.16)} 50.4%{transform:translate(9.5px,-104.5px)scale(.14)}
  54%{transform:translate(8.7px,-106.7px)scale(.12)} 57.6%{transform:translate(6.1px,-108.2px)scale(.11)}
  61.2%{transform:translate(3.6px,-109.2px)scale(.1)} 64.8%{transform:translate(1.8px,-109.9px)scale(.09)}
  72%{transform:translate(.4px,-110.4px)scale(.09);opacity:0}
  80%{transform:none;opacity:0}
}

/* ── Scroll parallax: far layers lag, near layers lead ────────────── */
@media (prefers-reduced-motion: no-preference) {
  .scene--09 .s09-psky { transform: translate(0, calc((var(--p) - 0.5) * 22px)); }
  .scene--09 .s09-pfar { transform: translate(0, calc((var(--p) - 0.5) * 14px)); }
  .scene--09 .s09-pmid { transform: translate(0, calc((var(--p) - 0.5) * 6px)); }
  .scene--09 .s09-pnear { transform: translate(0, calc((0.5 - var(--p)) * 18px)); }
  .scene--09 .s09-pf { transform: translate(0, calc((var(--p) - 0.5) * 16px)); }
  .scene--09 .s09-pa { transform: translate(0, calc((0.5 - var(--p)) * 6px)); }
  .scene--09 .s09-pr { transform: translate(0, calc((0.5 - var(--p)) * 15px)); }
}

/* ═══════════════════════════════════════════════════════════════════
   CHAPTER 10 — templates/landing/story/ch10.html
   ═══════════════════════════════════════════════════════════════════ */
/* Chapter 10 — NORTH STAR: the summit, climbed by scrolling.
   Scoped under .scene--10; keyframes named s10-*. Base = final picture
   (trail drawn, climber at the top, ring full, "live" lit).
   Entrance (.has-played): dawn and stars, ranges and summit rise, the
   dawn rim-light runs down both ridges, the route and climber appear,
   the pole grows and the flag is hoisted, the $100K medallion and the
   north star arrive, the kicker settles (≈2.7 s).
   Scrub (--p → --k): the trail draws and the climber walks the
   switchbacks, waypoints light as it passes, the evaluation ring fills
   toward its target, and at the top the summit glows and "live" lights.
   Loops (.is-live): dawn breathes, stars twinkle, the north star pulses,
   the flag flies, the medallion blooms, the climber pings. */

.scene--10 {
  --k: 1;
  --s1: clamp(0, var(--k) / 0.2768, 1);
  --s2: clamp(0, (var(--k) - 0.2768) / 0.2202, 1);
  --s3: clamp(0, (var(--k) - 0.497) / 0.2261, 1);
  --s4: clamp(0, (var(--k) - 0.7231) / 0.1536, 1);
  --s5: clamp(0, (var(--k) - 0.8767) / 0.1233, 1);
}
.scene--10 .s10-m { font-family: "JetBrains Mono", ui-monospace, monospace; }

/* ── Scroll-driven climb (base values = summit reached) ───────────── */
.scene--10 .s10-c1 { transform: translate(calc(var(--s1) * 182px), calc(var(--s1) * -58px)); }
.scene--10 .s10-c2 { transform: translate(calc(var(--s2) * -142px), calc(var(--s2) * -54px)); }
.scene--10 .s10-c3 { transform: translate(calc(var(--s3) * 144px), calc(var(--s3) * -60px)); }
.scene--10 .s10-c4 { transform: translate(calc(var(--s4) * -90px), calc(var(--s4) * -56px)); }
.scene--10 .s10-c5 { transform: translate(calc(var(--s5) * 42px), calc(var(--s5) * -74px)); }
.scene--10 .s10-trav,
.scene--10 .s10-ring { stroke-dashoffset: calc(1 - var(--k)); }
.scene--10 .s10-wp1 { opacity: clamp(0, (var(--k) - 0.2768) * 40, 1); }
.scene--10 .s10-wp2 { opacity: clamp(0, (var(--k) - 0.497) * 40, 1); }
.scene--10 .s10-wp3 { opacity: clamp(0, (var(--k) - 0.7231) * 40, 1); }
.scene--10 .s10-wp4 { opacity: clamp(0, (var(--k) - 0.8767) * 40, 1); }
.scene--10 .s10-live { opacity: clamp(0, (var(--k) - 0.97) * 34, 1); }
.scene--10 .s10-sglow { opacity: calc(0.3 + 0.7 * clamp(0, (var(--k) - 0.9) * 10, 1)); }
/* On arrival the climber hands its ping to the "live" dot. */
.scene--10 .s10-pw { opacity: calc(1 - clamp(0, (var(--k) - 0.97) * 34, 1)); }

@media (prefers-reduced-motion: no-preference) {
  /* Only once the controller is running (has-played) does the scroll
     position take over; no-JS and reduced motion keep the summit. */
  .scene--10.has-played { --k: clamp(0.06, (var(--p) - 0.08) * 2.7, 1); }
  .scene--10 .s10-psky  { transform: translate(0, calc((var(--p) - 0.5) * 20px)); }
  .scene--10 .s10-pglow { transform: translate(0, calc((var(--p) - 0.5) * 12px)); }
  .scene--10 .s10-pback { transform: translate(0, calc((var(--p) - 0.5) * 7px)); }
  .scene--10 .s10-pmed  { transform: translate(0, calc((0.5 - var(--p)) * 10px)); }
}

/* ── Entrance ─────────────────────────────────────────────────────── */
.scene--10.has-played .s10-dawn  { animation: s10-bloom 1600ms var(--ease-out) both; }
.scene--10.has-played .s10-stars { animation: s10-fade 1400ms ease-out 100ms both; }
.scene--10.has-played .s10-back  { animation: s10-rise 1100ms var(--ease-out) both; }
.scene--10.has-played .s10-mtn   { animation: s10-rise 1200ms var(--ease-out) 120ms both; }
.scene--10.has-played .s10-rimd  { animation: s10-draw 1300ms var(--ease-io) 700ms both; }
.scene--10.has-played .s10-route,
.scene--10.has-played .s10-sglow { animation: s10-fade 800ms ease-out 800ms both; }
.scene--10.has-played .s10-me    { animation: s10-pop 700ms var(--ease-spring) 1000ms both; }
.scene--10.has-played .s10-pole  { animation: s10-grow 700ms var(--ease-out) 900ms both; }
.scene--10.has-played .s10-hoist { animation: s10-hoist 1000ms var(--ease-out) 1400ms both; }
.scene--10.has-played .s10-med   { animation: s10-pop 900ms var(--ease-out) 1100ms both; }
.scene--10.has-played .s10-north { animation: s10-star 1100ms var(--ease-out) 1500ms both; }
.scene--10.has-played .s10-nlab  { animation: s10-up 800ms var(--ease-out) 1800ms both; }
.scene--10.has-played .s10-kick  { animation: s10-up 800ms var(--ease-out) 1900ms both; }
.scene--10.has-played .s10-lin   { animation: s10-up 600ms var(--ease-out) 2000ms both; }
.scene--10 .s10-pole  { transform-origin: 50% 100%; }
.scene--10 .s10-cloth { transform-origin: 0 50%; }

@keyframes s10-fade  { from { opacity: 0; } }
@keyframes s10-bloom { from { opacity: 0; transform: scale(0.7); } }
@keyframes s10-rise  { from { opacity: 0; transform: translateY(30px); } 35% { opacity: 1; } }
@keyframes s10-draw  { from { stroke-dashoffset: 1; } }
@keyframes s10-pop   { from { opacity: 0; transform: scale(0.6); } }
@keyframes s10-grow  { from { transform: scaleY(0); } }
@keyframes s10-hoist { from { opacity: 0; transform: translateY(40px); } 30% { opacity: 1; } }
@keyframes s10-star  { from { opacity: 0; transform: scale(0.2) rotate(-90deg); } }
@keyframes s10-up    { from { opacity: 0; transform: translateY(6px); } }

/* ── Loops (only while on screen) ─────────────────────────────────── */
.scene--10 .s10-ping { opacity: 0.45; }
.scene--10.is-live .s10-breathe { animation: s10-breathe 7s ease-in-out 1.6s infinite; }
.scene--10.is-live .s10-tw1   { animation: s10-tw 4.4s ease-in-out 1.4s infinite; }
.scene--10.is-live .s10-tw2   { animation: s10-tw 6s ease-in-out 2.6s infinite; }
.scene--10.is-live .s10-halo  { animation: s10-halo 3.6s ease-in-out 2.6s infinite; }
.scene--10.is-live .s10-cloth { animation: s10-wave 3.2s ease-in-out 2.4s infinite; }
.scene--10.is-live .s10-bloom { animation: s10-glow 4.8s ease-in-out 2s infinite; }
.scene--10.is-live .s10-ping  { animation: s10-ping 2.6s ease-out 1.7s infinite; }
.scene--10.is-live .s10-lping { animation: s10-ping 2.6s ease-out 1.7s infinite; }

@keyframes s10-breathe { 50% { opacity: 0.78; transform: scale(1.04); } }
@keyframes s10-tw      { 50% { opacity: 0.25; } }
@keyframes s10-halo    { 50% { opacity: 0.55; transform: scale(0.8); } }
@keyframes s10-wave    { 25% { transform: skewY(-6deg) scaleX(0.95); } 75% { transform: skewY(5deg) scaleX(1); } }
@keyframes s10-glow    { 50% { opacity: 0.6; transform: scale(0.92); } }
@keyframes s10-ping    { from { opacity: 0.8; transform: scale(1); } to { opacity: 0; transform: scale(2.8); } }

/* ── Reduced motion: the finished picture, nothing moving ──────────── */
@media (prefers-reduced-motion: reduce) {
  .scene *, .scene::after, .chapter__node::after, .chapter__text::after {
    animation: none !important;
    transition: none !important;
  }
  /* Selectors at least as specific as the hide rules above, or those win
     and reduced-motion visitors wait for the 50% trigger to see anything. */
  .reveal-js .scene:not(.has-played) .s-in,
  .reveal-js .scene .s-in { opacity: 1; }
  .reveal-js .chapter.reveal:not(.is-visible) .chapter__text::after,
  .reveal-js .chapter.reveal .chapter__text::after { transform: scaleX(1); }
  .chapter__node::after { transform: scaleY(1); opacity: 1; }
  .chapter__figure.scene, .chapter:hover .chapter__figure.scene { transform: none; }
  .journey__rail { transform: translate3d(0, 100%, 0) !important; }
  .journey__head { display: none; }
}
