/* ═══════════════════════════════════════════════════════════════════
   LANDING DESK — the Features and How it works scenes
   ─────────────────────────────────────────────────────────────────────
   Feature cards open on a miniature of the real member screen
   (templates/landing/features/f1–f6.html); How it works follows one
   detector-verified setup through five frames (templates/landing/how/
   h1–h5.html). Every scene obeys the SCENE CONTRACT at the top of
   static/css/landing-story.css: the base CSS is the finished picture,
   entrances hang off .has-played, loops off .is-live, only transform /
   opacity / stroke-dashoffset animate, no text inside a moving group.
   Order: layout (the only unscoped rules), one banner section per
   scene (every selector starts .scene--fN / .scene--hN, keyframes
   sfN-* / shN-*), and the reduced-motion block LAST.
   Features delays are calc(var(--st, 0ms) + N): --st is the row stagger
   set in the layout section below.
   ═══════════════════════════════════════════════════════════════════ */

/* ── Layout ─────────────────────────────────────────────────────────── */
/* Features: figure-first cards */
.features__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.feature { padding: 0; display: flex; flex-direction: column; }
.feature::before { z-index: 3; }                      /* hover hairline above the figure */
.feature__figure, .step__figure {
  position: relative; overflow: hidden;               /* the shared .scene::after sheen needs both */
  background: linear-gradient(180deg, #0c0e12, #090b0f);
}
.feature__figure { aspect-ratio: 16 / 10; border-bottom: 1px solid var(--border-2); transition: border-color 180ms ease; }
.scene__svg { display: block; width: 100%; height: 100%; }
.feature__text { padding: clamp(20px, 2.4vw, 28px); }
.features__note {
  max-width: 72rem; margin: 18px auto 0; padding: 0 clamp(20px, 4vw, 56px);
  font-family: var(--ff-mono); font-size: 12px; line-height: 1.6; letter-spacing: .02em; color: var(--fg-mute);
}
/* hover: the lift that .reveal-js .reveal.is-visible used to kill, on the independent `translate` property */
.reveal-js .feature.reveal {
  transition: opacity 500ms ease, transform 500ms ease, translate 180ms ease, border-color 180ms ease, background 180ms ease;
}
.feature:hover { translate: 0 -2px; }
.feature:hover .feature__figure { border-bottom-color: var(--border); }
/* row stagger for the figure entrances (CSP-safe: stylesheet custom property) */
.features__grid > :nth-child(3n+2) .scene { --st: 120ms; }
.features__grid > :nth-child(3n) .scene { --st: 240ms; }
@media (max-width: 1099px) {
  .features__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .features__grid > :nth-child(3n) .scene, .features__grid > :nth-child(3n+2) .scene { --st: 0ms; }
  .features__grid > :nth-child(2n) .scene { --st: 120ms; }
}
@media (max-width: 719px) {
  .features__grid { grid-template-columns: minmax(0, 520px); justify-content: center; }
  .features__grid > :nth-child(n) .scene { --st: 0ms; }
}

/* How: text | figure (+ readout) */
.step__fig { margin: 0; min-width: 0; }
.step__figure {
  aspect-ratio: 5 / 3; border: 1px solid var(--border-2); border-radius: 12px;
  box-shadow: 0 18px 44px rgba(0, 0, 0, .4), inset 0 1px 0 rgba(255, 255, 255, .04);
}
/* The readout is a <dl> of real text: term/value pairs, then the CHECK
   rule. Pairs wrap as whole units; the CHECK clauses never break inside,
   and the "·" before each clause sits in a gutter that .step__read-cls
   pulls to the left and the <dd> clips, so a line never starts or ends on
   a separator. The terse clause line is aria-hidden; screen readers get
   the plain sentence in .step__sr instead. */
.step__read {
  margin: 10px 0 0; display: flex; flex-wrap: wrap; gap: 2px 14px;
  font-family: var(--ff-mono); font-size: 12px; line-height: 1.5; color: var(--fg-mute);
  font-variant-numeric: tabular-nums;
}
.step__read > div { display: flex; gap: 5px; min-width: 0; }
.step__read dt, .step__read dd { margin: 0; }
.step__read-k { color: var(--fg-mute); }
.step__read-v { color: var(--fg); font-weight: 500; }
.step__read-v.is-sl { color: var(--red); }
.step__read-v.is-mid { color: var(--blue); }
.step__read-v.is-hh { color: var(--amber); }
.step__read-v.is-tp { color: var(--green); }
.step__read-v.is-teal { color: #26a69a; }
.step__read-v.is-sfp { color: #f0899b; }
.step__read-v.is-none { color: var(--fg-mute); font-weight: 400; }
.step__read > .step__read-rule { flex-basis: 100%; margin-top: 2px; color: var(--fg); }
.step__read-rule dd { min-width: 0; overflow: hidden; }
.step__read-cls { display: flex; flex-wrap: wrap; margin-left: -16px; }
.step__read-cl { position: relative; padding-left: 16px; white-space: nowrap; }
.step__read-cl::before { content: "·"; position: absolute; left: 5px; color: var(--fg-mute); }
.step__sr {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}
.how__out {
  max-width: 72rem; margin: clamp(28px, 4vw, 44px) auto 0; padding: 0 clamp(20px, 4vw, 56px);
  font-size: 16px; line-height: 1.6; color: var(--fg-mute);
}
.how__out strong { color: var(--fg); font-weight: 600; }
@media (min-width: 1100px) {
  .step { grid-template-columns: 120px minmax(0, 1fr) minmax(0, 1.25fr); align-items: start; }
}
@media (max-width: 1099px) {
  .step__fig { grid-column: 2; width: 100%; max-width: 560px; margin-top: 4px; }
}
/* Phones: one column. The step number becomes a small line above the
   title, so the text, the figure and the readout share one left edge
   (a 56px number column beside a full-width figure read as misaligned). */
@media (max-width: 560px) {
  .step { grid-template-columns: minmax(0, 1fr); gap: 8px; }
  .step__num { padding-top: 0; }
  .step__fig { grid-column: 1 / -1; }
  /* pairs in two even columns instead of a ragged row with an orphan.
     Flex halves, not a max-content grid: the full-width CHECK row's
     unwrapped clauses would size max-content tracks past the screen. */
  .step__read { column-gap: 12px; }
  .step__read > div:not(.step__read-rule) { flex: 0 0 calc(50% - 6px); }
}
@media (max-width: 480px) { .step__read { font-size: 11px; } }

/* ═══ FEATURE F1 — templates/landing/features/f1.html ═══ */
/* F1 — Setups blotter. Base = the finished blotter: SOL row 1 ACTIVE with its live tick.
   Entrance ≈2.0 s + --st: panel + tape → session bar grows (55% counts with it) →
   rows 2–4 settle → row 1 lands (highlight, rail, flash; ACTIVE counts 2→3) →
   its reward leg grows entry → target → the live tick slides from entry to price.
   Loops: tick drift (the one transform loop), LED blink, row-1 rail glow. */

/* Transient + loop-only layers. */
.scene--f1 .sf1-r1flash  { opacity: 0; }
.scene--f1 .sf1-r1glow-r { opacity: .5; }

/* Pivots: both bars grow from their left end (fill-box). */
.scene--f1 .sf1-wrw,
.scene--f1 .sf1-r1rw { transform-origin: 0 50%; }

/* ── Entrance ── */
/* sf1-sep = the header/row-1 rule, the one stroke outside every group: it enters
   with the panel so it never hangs alone on the empty card. */
.scene--f1.has-played .sf1-panel,
.scene--f1.has-played .sf1-sep    { animation: sf1-in 280ms var(--ease-out) var(--st, 0ms) both; }
.scene--f1.has-played .sf1-wrw    { animation: sf1-growx 800ms var(--ease-out) calc(var(--st, 0ms) + 200ms) both; }
.scene--f1.has-played .sf1-wrl    { animation: sf1-in 280ms var(--ease-out) calc(var(--st, 0ms) + 700ms) both; }
.scene--f1.has-played .sf1-r2     { animation: sf1-in 280ms var(--ease-out) calc(var(--st, 0ms) + 260ms) both; }
.scene--f1.has-played .sf1-r3     { animation: sf1-in 280ms var(--ease-out) calc(var(--st, 0ms) + 340ms) both; }
.scene--f1.has-played .sf1-r4     { animation: sf1-in 280ms var(--ease-out) calc(var(--st, 0ms) + 420ms) both; }
.scene--f1.has-played .sf1-r1hl   { animation: sf1-in 350ms var(--ease-out) calc(var(--st, 0ms) + 900ms) both; }
.scene--f1.has-played .sf1-r1rail { animation: sf1-growy 300ms var(--ease-spring) calc(var(--st, 0ms) + 900ms) both; }
.scene--f1.has-played .sf1-r1flash { animation: sf1-flash 600ms ease-out calc(var(--st, 0ms) + 950ms) both; }
.scene--f1.has-played .sf1-r1,
.scene--f1.has-played .sf1-r1glow { animation: sf1-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1000ms) both; }
.scene--f1.has-played .sf1-r1rw   { animation: sf1-growx 400ms var(--ease-out) calc(var(--st, 0ms) + 1250ms) both; }
.scene--f1.has-played .sf1-live   { animation: sf1-slide 500ms var(--ease-out) calc(var(--st, 0ms) + 1500ms) both; }
.scene--f1.has-played .sf1-tag    { animation: sf1-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1600ms) both; }

/* ── Loops (only after the entrance, only while on screen) ── */
/* The drift is one full 8.4 s sine-like cycle that starts AND ends at the resting
   position (0 → +3.5 → −3.5 → 0), so the loop never jumps the tick when it
   (re)starts. Segment easings join with matching velocity. */
.scene--f1.has-played.is-live .sf1-live-i   { animation: sf1-drift 8.4s linear calc(var(--st, 0ms) + 2100ms) infinite; }
.scene--f1.has-played.is-live .sf1-led1     { animation: sf1-blink 2.4s ease-in-out infinite; }
.scene--f1.has-played.is-live .sf1-r1glow-r { animation: sf1-glow 3.2s ease-in-out infinite alternate; }

@keyframes sf1-in    { from { opacity: 0; } }
@keyframes sf1-growx { from { opacity: 0; transform: scaleX(0); } 20% { opacity: 1; } }
@keyframes sf1-growy { from { opacity: 0; transform: scaleY(0); } 30% { opacity: 1; } }
@keyframes sf1-flash { 0% { opacity: 0; } 30% { opacity: 1; } 100% { opacity: 0; } }
@keyframes sf1-slide { from { opacity: 0; transform: translateX(-10.7px); } }
@keyframes sf1-drift {
  0%   { transform: translateX(0);      animation-timing-function: cubic-bezier(0, 0, .58, 1); }
  25%  { transform: translateX(3.5px);  animation-timing-function: cubic-bezier(.42, 0, .58, 1); }
  75%  { transform: translateX(-3.5px); animation-timing-function: cubic-bezier(.42, 0, 1, 1); }
  100% { transform: translateX(0); }
}
@keyframes sf1-blink { 50% { opacity: .3; } }
@keyframes sf1-glow  { from { opacity: .25; } to { opacity: .8; } }

/* ═══ FEATURE F2 — templates/landing/features/f2.html ═══ */
/* F2 — Discord alerts rail. Base = the finished rail: three slips (order, fill, win), the amber
   node haloed. Entrance ≈2.15 s + --st: a signal runs down the rail (d1 blue, d2 green) and each
   node POPs as it arrives; the win lands with a ping, the +2.47 counter and a hit flash.
   Loops (no transform loops): a light down the rail, the WIN node's halo breathing. */
.scene--f2 .sf2-d1, .scene--f2 .sf2-d2, .scene--f2 .sf2-rail-lt, .scene--f2 .sf2-hit, .scene--f2 .sf2-ping { opacity: 0; }
/* Named exception to the §3.1 transient-halo rule: .sf2-n3halo is NOT seeded opacity:0. It is a filled resting glow
   (fill-opacity .18), not a HALO pulse ring. The approved frame (pv-features-*.png = the reduced-motion view) shows it,
   and final/transients.css leaves it out while hiding sf5-now-halo / sf6-halo / sh5-halo. So it rests at opacity 1,
   its .s-in wrapper .sf2-n3h enters (FADE 420ms @1450), and the circle itself only BREATHEs 1↔.2 under .is-live.
   (spec.md:486 "Base opacity 0" contradicts this and is pending an owner-confirmed spec edit.) */

/* ── entrance ── */
.scene--f2.has-played .sf2-head  { animation: sf2-in 280ms var(--ease-out) var(--st, 0ms) both; }
/* slip 1: the limit order */
.scene--f2.has-played .sf2-s1    { animation: sf2-in 280ms var(--ease-out) calc(var(--st, 0ms) + 100ms) both; }
.scene--f2.has-played .sf2-n1    { animation: sf2-pop 420ms var(--ease-spring) calc(var(--st, 0ms) + 100ms) both; }
.scene--f2.has-played .sf2-g1    { animation: sf2-in 280ms var(--ease-out) calc(var(--st, 0ms) + 150ms) both; }
/* the signal runs n1 → n2; slip 2: filled, its check draws */
.scene--f2.has-played .sf2-d1    { animation: sf2-draw 380ms linear calc(var(--st, 0ms) + 450ms) both; }
.scene--f2.has-played .sf2-n2    { animation: sf2-pop 420ms var(--ease-spring) calc(var(--st, 0ms) + 750ms) both; }
.scene--f2.has-played .sf2-s2    { animation: sf2-in 280ms var(--ease-out) calc(var(--st, 0ms) + 780ms) both; }
.scene--f2.has-played .sf2-g2    { animation: sf2-in 280ms var(--ease-out) calc(var(--st, 0ms) + 820ms) both; }
.scene--f2.has-played .sf2-g2ck  { animation: sf2-drawin 300ms var(--ease-out) calc(var(--st, 0ms) + 900ms) both; }
/* n2 → n3; the rail settles in behind it; slip 3: the win */
.scene--f2.has-played .sf2-d2    { animation: sf2-draw 380ms linear calc(var(--st, 0ms) + 1100ms) both; }
.scene--f2.has-played .sf2-rail  { animation: sf2-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1400ms) both; }
.scene--f2.has-played .sf2-n3    { animation: sf2-pop 420ms var(--ease-spring) calc(var(--st, 0ms) + 1400ms) both; }
/* the ring starts visible, so it must not fill backwards over the empty node */
.scene--f2.has-played .sf2-ping  { animation: sf2-ring 650ms var(--ease-out) calc(var(--st, 0ms) + 1450ms) forwards; }
.scene--f2.has-played .sf2-n3h   { animation: sf2-in 420ms var(--ease-out) calc(var(--st, 0ms) + 1450ms) both; }
.scene--f2.has-played .sf2-s3    { animation: sf2-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1450ms) both; }
.scene--f2.has-played .sf2-g3    { animation: sf2-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1500ms) both; }
/* the +2.47 counter runs 1500–2150 (data-delay/-dur); the hit flashes behind it */
.scene--f2.has-played .sf2-hit   { animation: sf2-flash 500ms ease-out calc(var(--st, 0ms) + 1650ms) both; }

/* ── loops (start once the entrance is done) ── */
.scene--f2.has-played.is-live .sf2-rail-lt { animation: sf2-light 3.2s linear calc(var(--st, 0ms) + 2150ms) infinite; }
.scene--f2.has-played.is-live .sf2-n3halo  { animation: sf2-breathe 2.6s ease-in-out calc(var(--st, 0ms) + 2150ms) infinite alternate; }

@keyframes sf2-in     { from { opacity: 0; } }
@keyframes sf2-pop    { from { opacity: 0; transform: scale(.4); } }
@keyframes sf2-draw   { 0% { opacity: 1; stroke-dashoffset: 1; } 80% { opacity: 1; stroke-dashoffset: 0; } 100% { opacity: 0; stroke-dashoffset: 0; } }
@keyframes sf2-drawin { from { opacity: 0; stroke-dashoffset: 1; } 1% { opacity: 1; } to { stroke-dashoffset: 0; } }
@keyframes sf2-ring   { from { opacity: .8; transform: scale(1); } to { opacity: 0; transform: scale(2.6); } }
@keyframes sf2-flash  { 0% { opacity: 0; } 30% { opacity: 1; } 100% { opacity: 0; } }
/* LIGHT, phase-shifted by .04 so each pass starts and ends split behind the n1 / n3 nodes (no pop) */
@keyframes sf2-light  { from { opacity: 1; stroke-dashoffset: 1.04; } to { opacity: 1; stroke-dashoffset: .04; } }
/* BREATHE .2 ↔ 1, starting from the resting 1 so the loop joins without a jump */
@keyframes sf2-breathe { from { opacity: 1; } to { opacity: .2; } }

/* ═══ FEATURE F3 — templates/landing/features/f3.html ═══ */
/* F3 — Explorer levels on the verified SOL setup. Base = the finished chart:
   toolbar, 24 candles + the forming bar, SFP sweeps, TP / MID / HH / SL, the
   3R and 3G marks and the pinned star. Entrance ≈2.2 s + --st: toolbar,
   candles print left to right, then each level draws itself (a solid -d copy
   that hands over to the dashed line, then its label), then 3R, 3G and the
   star. Transforms: 3r-b, 3g-b, star, star-ring. Loops (opacity/dashoffset
   only): AUTO LED blink, a light along TP, the forming candle breathing.
   The 3R / 3G marker lines end at y184, the plot floor (Plotly paper y0),
   so the 3G dashes no longer strike through the ILLUSTRATIVE tag. */

/* resting transient layers */
.scene--f3 .sf3-tp-d, .scene--f3 .sf3-mid-d, .scene--f3 .sf3-hh-d, .scene--f3 .sf3-sl-d, .scene--f3 .sf3-tp-lt, .scene--f3 .sf3-star-ring { opacity: 0; }

/* ── entrance ── */
.scene--f3.has-played .sf3-tb   { animation: sf3-in 280ms var(--ease-out) var(--st, 0ms) both; }
.scene--f3.has-played .sf3-c1   { animation: sf3-in 280ms var(--ease-out) calc(var(--st, 0ms) + 100ms) both; }
.scene--f3.has-played .sf3-c2   { animation: sf3-in 280ms var(--ease-out) calc(var(--st, 0ms) + 230ms) both; }
.scene--f3.has-played .sf3-c3   { animation: sf3-in 280ms var(--ease-out) calc(var(--st, 0ms) + 360ms) both; }
.scene--f3.has-played .sf3-c4   { animation: sf3-in 280ms var(--ease-out) calc(var(--st, 0ms) + 490ms) both; }
.scene--f3.has-played .sf3-last { animation: sf3-in 280ms var(--ease-out) calc(var(--st, 0ms) + 560ms) both; }
.scene--f3.has-played .sf3-sfp  { animation: sf3-in 280ms var(--ease-out) calc(var(--st, 0ms) + 650ms) both; }

/* levels: each solid copy draws (400 ms), the dashed line takes over at 70 %, its label at 80 % */
.scene--f3.has-played .sf3-tp-d  { animation: sf3-draw 400ms linear calc(var(--st, 0ms) + 700ms) both; }
.scene--f3.has-played .sf3-tp    { animation: sf3-in 180ms var(--ease-out) calc(var(--st, 0ms) + 980ms) both; }
.scene--f3.has-played .sf3-tp-l  { animation: sf3-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1020ms) both; }
.scene--f3.has-played .sf3-mid-d { animation: sf3-draw 400ms linear calc(var(--st, 0ms) + 800ms) both; }
.scene--f3.has-played .sf3-mid   { animation: sf3-in 180ms var(--ease-out) calc(var(--st, 0ms) + 1080ms) both; }
.scene--f3.has-played .sf3-mid-l { animation: sf3-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1120ms) both; }
.scene--f3.has-played .sf3-hh-d  { animation: sf3-draw 400ms linear calc(var(--st, 0ms) + 950ms) both; }
.scene--f3.has-played .sf3-hh    { animation: sf3-in 180ms var(--ease-out) calc(var(--st, 0ms) + 1230ms) both; }
.scene--f3.has-played .sf3-hh-l  { animation: sf3-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1270ms) both; }
.scene--f3.has-played .sf3-sl-d  { animation: sf3-draw 400ms linear calc(var(--st, 0ms) + 1050ms) both; }
.scene--f3.has-played .sf3-sl    { animation: sf3-in 180ms var(--ease-out) calc(var(--st, 0ms) + 1330ms) both; }
.scene--f3.has-played .sf3-sl-l  { animation: sf3-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1370ms) both; }

/* marks: 3R, then 3G, then the star is pinned */
.scene--f3.has-played .sf3-3r-ln { animation: sf3-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1300ms) both; }
.scene--f3.has-played .sf3-3r-b  { animation: sf3-pop 420ms var(--ease-spring) calc(var(--st, 0ms) + 1350ms) both; }
.scene--f3.has-played .sf3-3r-t  { animation: sf3-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1400ms) both; }
.scene--f3.has-played .sf3-3g-ln { animation: sf3-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1500ms) both; }
.scene--f3.has-played .sf3-3g-b  { animation: sf3-pop 420ms var(--ease-spring) calc(var(--st, 0ms) + 1550ms) both; }
.scene--f3.has-played .sf3-3g-t  { animation: sf3-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1600ms) both; }
.scene--f3.has-played .sf3-star  { animation: sf3-star 400ms var(--ease-spring) calc(var(--st, 0ms) + 1750ms) both; }
/* the ring's first frame is visible, so no backwards fill: it must not sit there during its delay */
.scene--f3.has-played .sf3-star-ring { animation: sf3-ring 350ms var(--ease-out) calc(var(--st, 0ms) + 1850ms); }
.scene--f3.has-played .sf3-tag   { animation: sf3-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1900ms) both; }

/* ── loops (on screen, after the entrance) ── */
.scene--f3.has-played.is-live .sf3-led    { animation: sf3-blink 2s ease-in-out infinite; }
.scene--f3.has-played.is-live .sf3-tp-lt  { animation: sf3-light 4.8s linear calc(var(--st, 0ms) + 2200ms) infinite; }
.scene--f3.has-played.is-live .sf3-last-i { animation: sf3-breathe 1.8s ease-in-out calc(var(--st, 0ms) + 900ms) infinite alternate; }

@keyframes sf3-in      { from { opacity: 0; } }
@keyframes sf3-draw    { 0% { opacity: 1; stroke-dashoffset: 1; } 80% { opacity: 1; stroke-dashoffset: 0; } 100% { opacity: 0; stroke-dashoffset: 0; } }
@keyframes sf3-pop     { from { opacity: 0; transform: scale(.4); } }
@keyframes sf3-star    { from { opacity: 0; transform: scale(.3); } }
@keyframes sf3-ring    { from { opacity: .9; transform: scale(.6); } to { opacity: 0; transform: scale(1.7); } }
@keyframes sf3-blink   { 50% { opacity: .3; } }
@keyframes sf3-light   { from { opacity: 1; stroke-dashoffset: 1; } to { opacity: 1; stroke-dashoffset: 0; } }
@keyframes sf3-breathe { from { opacity: 1; } to { opacity: .6; } }

/* ═══ FEATURE F4 — templates/landing/features/f4.html ═══ */
/* F4 — Markets. Base = the finished picture: three venue lanes of chips, the
   class labels under FXCM, and the EXPLORER ALSO CHARTS list. The scan beam
   and the twelve hit outlines are transient layers (base opacity 0).
   Entrance ≈1.73 s + --st: head and LED, lanes, then the chips in a diagonal
   wave (250 + 90·column + 120·lane ms), class labels, the divider draws, the
   Explorer list. Loops: the beam sweeps the lanes (the one transform loop, a
   text-free rect) and lights each chip as its centre passes — hit delay =
   400 + 3600 × (chip cx − 53) / 278 ms, so beam and outlines share one 3.6 s
   period — plus the LED blink. DODO (h4) and SUI (h8) are cropped at the
   viewBox edge, so they use the centre of their visible part (x 288/294 to
   320) and peak on the crossing (−216 ms): with the plain formula they peaked
   after the beam had wrapped to the left edge. */
.scene--f4 .sf4-beam, .scene--f4 .sf4-h1, .scene--f4 .sf4-h2, .scene--f4 .sf4-h3, .scene--f4 .sf4-h4, .scene--f4 .sf4-h5, .scene--f4 .sf4-h6, .scene--f4 .sf4-h7, .scene--f4 .sf4-h8, .scene--f4 .sf4-h9, .scene--f4 .sf4-h10, .scene--f4 .sf4-h11, .scene--f4 .sf4-h12 { opacity: 0; }

/* ── entrance ── */
.scene--f4.has-played .sf4-head  { animation: sf4-in 280ms var(--ease-out) var(--st, 0ms) both; }
.scene--f4.has-played .sf4-led   { animation: sf4-pop 420ms var(--ease-spring) var(--st, 0ms) both; }
.scene--f4.has-played .sf4-lane1 { animation: sf4-in 280ms var(--ease-out) calc(var(--st, 0ms) + 150ms) both; }
.scene--f4.has-played .sf4-lane2 { animation: sf4-in 280ms var(--ease-out) calc(var(--st, 0ms) + 250ms) both; }
.scene--f4.has-played .sf4-lane3 { animation: sf4-in 280ms var(--ease-out) calc(var(--st, 0ms) + 350ms) both; }
/* chips: a diagonal wave, left to right and lane by lane */
.scene--f4.has-played .sf4-ch1   { animation: sf4-in 280ms var(--ease-out) calc(var(--st, 0ms) + 250ms) both; }
.scene--f4.has-played .sf4-ch2   { animation: sf4-in 280ms var(--ease-out) calc(var(--st, 0ms) + 340ms) both; }
.scene--f4.has-played .sf4-ch3   { animation: sf4-in 280ms var(--ease-out) calc(var(--st, 0ms) + 430ms) both; }
.scene--f4.has-played .sf4-ch4   { animation: sf4-in 280ms var(--ease-out) calc(var(--st, 0ms) + 520ms) both; }
.scene--f4.has-played .sf4-ch5   { animation: sf4-in 280ms var(--ease-out) calc(var(--st, 0ms) + 370ms) both; }
.scene--f4.has-played .sf4-ch6   { animation: sf4-in 280ms var(--ease-out) calc(var(--st, 0ms) + 460ms) both; }
.scene--f4.has-played .sf4-ch7   { animation: sf4-in 280ms var(--ease-out) calc(var(--st, 0ms) + 550ms) both; }
.scene--f4.has-played .sf4-ch8   { animation: sf4-in 280ms var(--ease-out) calc(var(--st, 0ms) + 640ms) both; }
.scene--f4.has-played .sf4-ch9   { animation: sf4-in 280ms var(--ease-out) calc(var(--st, 0ms) + 490ms) both; }
.scene--f4.has-played .sf4-ch10  { animation: sf4-in 280ms var(--ease-out) calc(var(--st, 0ms) + 580ms) both; }
.scene--f4.has-played .sf4-ch11  { animation: sf4-in 280ms var(--ease-out) calc(var(--st, 0ms) + 670ms) both; }
.scene--f4.has-played .sf4-ch12  { animation: sf4-in 280ms var(--ease-out) calc(var(--st, 0ms) + 760ms) both; }
.scene--f4.has-played .sf4-cls   { animation: sf4-in 280ms var(--ease-out) calc(var(--st, 0ms) + 900ms) both; }
/* the divider draws left to right, then the Explorer list reads in */
.scene--f4.has-played .sf4-div   { animation: sf4-drawin 300ms var(--ease-out) calc(var(--st, 0ms) + 1000ms) both; }
.scene--f4.has-played .sf4-x1    { animation: sf4-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1150ms) both; }
.scene--f4.has-played .sf4-x2    { animation: sf4-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1300ms) both; }
.scene--f4.has-played .sf4-x3    { animation: sf4-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1450ms) both; }

/* ── loops (only while on screen) ── */
.scene--f4.has-played.is-live .sf4-beam  { animation: sf4-beam 3.6s linear calc(var(--st, 0ms) + 400ms) infinite; }
/* each outline flashes as the beam's centre crosses its chip */
.scene--f4.has-played.is-live .sf4-h1    { animation: sf4-hit 3.6s linear calc(var(--st, 0ms) + 1086ms) infinite; }
.scene--f4.has-played.is-live .sf4-h2    { animation: sf4-hit 3.6s linear calc(var(--st, 0ms) + 1967ms) infinite; }
.scene--f4.has-played.is-live .sf4-h3    { animation: sf4-hit 3.6s linear calc(var(--st, 0ms) + 2886ms) infinite; }
.scene--f4.has-played.is-live .sf4-h4    { animation: sf4-hit 3.6s linear calc(var(--st, 0ms) + 3434ms) infinite; }
.scene--f4.has-played.is-live .sf4-h5    { animation: sf4-hit 3.6s linear calc(var(--st, 0ms) + 1086ms) infinite; }
.scene--f4.has-played.is-live .sf4-h6    { animation: sf4-hit 3.6s linear calc(var(--st, 0ms) + 2045ms) infinite; }
.scene--f4.has-played.is-live .sf4-h7    { animation: sf4-hit 3.6s linear calc(var(--st, 0ms) + 3003ms) infinite; }
.scene--f4.has-played.is-live .sf4-h8    { animation: sf4-hit 3.6s linear calc(var(--st, 0ms) + 3473ms) infinite; }
.scene--f4.has-played.is-live .sf4-h9    { animation: sf4-hit 3.6s linear calc(var(--st, 0ms) + 1009ms) infinite; }
.scene--f4.has-played.is-live .sf4-h10   { animation: sf4-hit 3.6s linear calc(var(--st, 0ms) + 1773ms) infinite; }
.scene--f4.has-played.is-live .sf4-h11   { animation: sf4-hit 3.6s linear calc(var(--st, 0ms) + 2537ms) infinite; }
.scene--f4.has-played.is-live .sf4-h12   { animation: sf4-hit 3.6s linear calc(var(--st, 0ms) + 3340ms) infinite; }
.scene--f4.has-played.is-live .sf4-led-c { animation: sf4-blink 2s ease-in-out infinite; }

@keyframes sf4-in     { from { opacity: 0; } }
@keyframes sf4-pop    { from { opacity: 0; transform: scale(.4); } }
@keyframes sf4-drawin { from { opacity: 0; stroke-dashoffset: 1; } 1% { opacity: 1; } to { stroke-dashoffset: 0; } }
@keyframes sf4-beam   { 0% { opacity: 0; transform: translateX(40px); } 8%, 92% { opacity: 1; } 100% { opacity: 0; transform: translateX(318px); } }
@keyframes sf4-hit    { 0% { opacity: 0; } 6% { opacity: .9; } 20% { opacity: 0; } 100% { opacity: 0; } }
@keyframes sf4-blink  { 50% { opacity: .3; } }

/* ═══ FEATURE F5 — templates/landing/features/f5.html ═══ */
/* F5 — Elliott Wave and Alpha on EUR/USD 1h. Base = the finished chart: the (0)–(4) count,
   IF WRONG 1.0826 (end of W1), the 1.0932–1.0978 target zone, the ALPHA badge and the
   projection from the now-dot into the zone.
   Entrance ≈2.2 s + --st: candles sweep in left to right, the count draws itself and each
   label lands as the pen passes its pivot, the invalidation line, the badge (counter 0→72),
   then the projection draws from the now-dot and the zone pops where it lands.
   Loops (no transform loop): zone breathe, a light running up the projection, now-dot halo. */
.scene--f5 .sf5-ew-d, .scene--f5 .sf5-proj-d, .scene--f5 .sf5-proj-lt, .scene--f5 .sf5-now-halo { opacity: 0; }
/* chart */
.scene--f5.has-played .sf5-base   { animation: sf5-in 280ms var(--ease-out) var(--st, 0ms) both; }
.scene--f5.has-played .sf5-c1     { animation: sf5-in 280ms var(--ease-out) calc(var(--st, 0ms) + 100ms) both; }
.scene--f5.has-played .sf5-c2     { animation: sf5-in 280ms var(--ease-out) calc(var(--st, 0ms) + 220ms) both; }
.scene--f5.has-played .sf5-c3     { animation: sf5-in 280ms var(--ease-out) calc(var(--st, 0ms) + 340ms) both; }
/* the count: solid pen 500–1140, fades as the dashed count takes over; labels follow the pen */
.scene--f5.has-played .sf5-ew-d   { animation: sf5-draw 800ms linear calc(var(--st, 0ms) + 500ms) both; }
.scene--f5.has-played .sf5-ew     { animation: sf5-in 180ms var(--ease-out) calc(var(--st, 0ms) + 1150ms) both; }
.scene--f5.has-played .sf5-w0     { animation: sf5-in 280ms var(--ease-out) calc(var(--st, 0ms) + 520ms) both; }
.scene--f5.has-played .sf5-w1     { animation: sf5-in 280ms var(--ease-out) calc(var(--st, 0ms) + 680ms) both; }
.scene--f5.has-played .sf5-w2     { animation: sf5-in 280ms var(--ease-out) calc(var(--st, 0ms) + 800ms) both; }
.scene--f5.has-played .sf5-w3     { animation: sf5-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1040ms) both; }
.scene--f5.has-played .sf5-w4     { animation: sf5-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1200ms) both; }
.scene--f5.has-played .sf5-inv    { animation: sf5-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1250ms) both; }
/* Alpha: badge, its text (counter 1500 + 700), then the projection into the zone */
.scene--f5.has-played .sf5-bdg    { animation: sf5-bdg 350ms var(--ease-out) calc(var(--st, 0ms) + 1400ms) both; }
.scene--f5.has-played .sf5-bt     { animation: sf5-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1500ms) both; }
.scene--f5.has-played .sf5-now    { animation: sf5-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1650ms) both; }
.scene--f5.has-played .sf5-proj-d { animation: sf5-draw 350ms linear calc(var(--st, 0ms) + 1700ms) both; }
.scene--f5.has-played .sf5-zone   { animation: sf5-pop 350ms var(--ease-spring) calc(var(--st, 0ms) + 1850ms) both; }
.scene--f5.has-played .sf5-zl, .scene--f5.has-played .sf5-head, .scene--f5.has-played .sf5-tag
                                  { animation: sf5-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1900ms) both; }
.scene--f5.has-played .sf5-proj   { animation: sf5-in 180ms var(--ease-out) calc(var(--st, 0ms) + 1950ms) both; }
/* loops — start together once the picture is complete, so each halo pulse sends a light up the projection */
.scene--f5.has-played.is-live .sf5-now-halo { animation: sf5-halo 2.4s var(--ease-out) calc(var(--st, 0ms) + 2200ms) infinite; }
.scene--f5.has-played.is-live .sf5-proj-lt { animation: sf5-light 2.4s linear calc(var(--st, 0ms) + 2200ms) infinite; }
.scene--f5.has-played.is-live .sf5-zone-r  { animation: sf5-breathe 2.8s ease-in-out calc(var(--st, 0ms) + 2200ms) infinite alternate; }
@keyframes sf5-in      { from { opacity: 0; } }
@keyframes sf5-draw    { 0% { opacity: 1; stroke-dashoffset: 1; } 80% { opacity: 1; stroke-dashoffset: 0; } 100% { opacity: 0; stroke-dashoffset: 0; } }
@keyframes sf5-bdg     { from { opacity: 0; transform: scale(.92); } }
@keyframes sf5-pop     { from { opacity: 0; transform: scale(.6); } }
@keyframes sf5-halo    { 0% { opacity: .6; } 100% { opacity: 0; } }
@keyframes sf5-light   { from { opacity: 1; stroke-dashoffset: 1; } to { opacity: 1; stroke-dashoffset: 0; } }
@keyframes sf5-breathe { from { opacity: 1; } to { opacity: .65; } }

/* ═══ FEATURE F6 — templates/landing/features/f6.html ═══ */
/* F6 — Track Record modal. Base = the finished card: 30D tab selected, the
   16-trade equity line drawn over its green area, end dot on the last value.
   Entrance ≈2.2 s + --st: the pill slides 7D → 30D, the tiles count up while
   the line draws itself left to right, then the dot lands.
   Loops (opacity / dashoffset only): end-dot halo, a light running the line. */
.scene--f6 .sf6-eq-lt, .scene--f6 .sf6-halo { opacity: 0; }
.scene--f6.has-played .sf6-chrome { animation: sf6-in 280ms var(--ease-out) var(--st, 0ms) both; }
.scene--f6.has-played .sf6-tabpill{ animation: sf6-tab 400ms var(--ease-out) calc(var(--st, 0ms) + 250ms) both; }
.scene--f6.has-played .sf6-vals   { animation: sf6-in 280ms var(--ease-out) calc(var(--st, 0ms) + 450ms) both; }
.scene--f6.has-played .sf6-tab30  { animation: sf6-in 280ms var(--ease-out) calc(var(--st, 0ms) + 500ms) both; }
.scene--f6.has-played .sf6-zero   { animation: sf6-in 280ms var(--ease-out) calc(var(--st, 0ms) + 700ms) both; }
.scene--f6.has-played .sf6-eq     { animation: sf6-drawin 1200ms var(--ease-io) calc(var(--st, 0ms) + 800ms) both; }
/* the area eases in on the line's own S-curve, ~500 ms behind the pen, so its
   silhouette never shows the curve ahead of the drawing tip */
.scene--f6.has-played .sf6-area   { animation: sf6-in 700ms var(--ease-io) calc(var(--st, 0ms) + 1300ms) both; }
.scene--f6.has-played .sf6-foot   { animation: sf6-in 280ms var(--ease-out) calc(var(--st, 0ms) + 1900ms) both; }
.scene--f6.has-played .sf6-dot    { animation: sf6-pop 250ms var(--ease-spring) calc(var(--st, 0ms) + 1950ms) both; }
.scene--f6.has-played.is-live .sf6-halo  { animation: sf6-halo 2.6s ease-out calc(var(--st, 0ms) + 2200ms) infinite; }
.scene--f6.has-played.is-live .sf6-eq-lt { animation: sf6-light 5s linear calc(var(--st, 0ms) + 2600ms) infinite; }
@keyframes sf6-in     { from { opacity: 0; } }
@keyframes sf6-tab    { from { opacity: 0; transform: translateX(-33px); } }
@keyframes sf6-pop    { from { opacity: 0; transform: scale(.4); } }
@keyframes sf6-drawin { from { opacity: 0; stroke-dashoffset: 1; } 1% { opacity: 1; } to { stroke-dashoffset: 0; } }
@keyframes sf6-halo   { 0% { opacity: .6; } 100% { opacity: 0; } }
@keyframes sf6-light  { from { opacity: 1; stroke-dashoffset: 1; } to { opacity: 1; stroke-dashoffset: 0; } }

/* ═══ STEP H1 — templates/landing/how/h1.html ═══ */
/* H1 — Swing structure. Base = the finished frame: bars 0–12 printed, pivots
   ringed, the swing low at 103.10 projected right to its teal LOW tag, chip
   SCANNING. Entrance ≈1.88 s: the grid settles → bars print left to right,
   60 ms apart, and each pivot POPs the moment the bar after it prints (a
   pivot is only confirmed once the next candle has closed) → SWING HIGHS →
   the now-cursor and the hatched forming slot → the SP 1 bracket draws under
   bar 11 → the swing-low level draws out to the tag, the dashed line
   settling in behind the solid draw → label, tag, chip.
   Transforms: the five pivot POPs only (text-free circles). Loops (no
   transforms): a light runs along the swing-low level, the forming slot
   breathes, the chip LED blinks. */

/* Transient + loop-only layers. */
.scene--h1 .sh1-lq-d,
.scene--h1 .sh1-lq-lt { opacity: 0; }

/* ── Entrance ── */
.scene--h1.has-played .sh1-base  { animation: sh1-in 280ms var(--ease-out) both; }

/* Bars print, 100 + 60i ms. */
.scene--h1.has-played .sh1-c0  { animation: sh1-in 280ms var(--ease-out) 100ms both; }
.scene--h1.has-played .sh1-c1  { animation: sh1-in 280ms var(--ease-out) 160ms both; }
.scene--h1.has-played .sh1-c2  { animation: sh1-in 280ms var(--ease-out) 220ms both; }
.scene--h1.has-played .sh1-c3  { animation: sh1-in 280ms var(--ease-out) 280ms both; }
.scene--h1.has-played .sh1-c4  { animation: sh1-in 280ms var(--ease-out) 340ms both; }
.scene--h1.has-played .sh1-c5  { animation: sh1-in 280ms var(--ease-out) 400ms both; }
.scene--h1.has-played .sh1-c6  { animation: sh1-in 280ms var(--ease-out) 460ms both; }
.scene--h1.has-played .sh1-c7  { animation: sh1-in 280ms var(--ease-out) 520ms both; }
.scene--h1.has-played .sh1-c8  { animation: sh1-in 280ms var(--ease-out) 580ms both; }
.scene--h1.has-played .sh1-c9  { animation: sh1-in 280ms var(--ease-out) 640ms both; }
.scene--h1.has-played .sh1-c10 { animation: sh1-in 280ms var(--ease-out) 700ms both; }
.scene--h1.has-played .sh1-c11 { animation: sh1-in 280ms var(--ease-out) 760ms both; }
.scene--h1.has-played .sh1-c12 { animation: sh1-in 280ms var(--ease-out) 820ms both; }

/* Pivots confirm with the bar after them: p2 with c3, p3 with c4, p4 with c5,
   p6 with c7, p11 with c12. 300 ms POPs, so the five run 280–1120 ms (at
   most four at once, 520–580 ms: the storyboard starts are 60 ms apart). */
.scene--h1.has-played .sh1-p2  { animation: sh1-pop 300ms var(--ease-spring) 280ms both; }
.scene--h1.has-played .sh1-p3  { animation: sh1-pop 300ms var(--ease-spring) 340ms both; }
.scene--h1.has-played .sh1-p4  { animation: sh1-pop 300ms var(--ease-spring) 400ms both; }
.scene--h1.has-played .sh1-p6  { animation: sh1-pop 300ms var(--ease-spring) 520ms both; }
.scene--h1.has-played .sh1-p11 { animation: sh1-pop 300ms var(--ease-spring) 820ms both; }

.scene--h1.has-played .sh1-sh  { animation: sh1-in 280ms var(--ease-out) 600ms both; }
.scene--h1.has-played .sh1-now { animation: sh1-in 280ms var(--ease-out) 800ms both; }
.scene--h1.has-played .sh1-forming,
.scene--h1.has-played .sh1-t   { animation: sh1-in 280ms var(--ease-out) 850ms both; }

/* SP bracket draws under bar 11 (pathLength 1), then its label. */
.scene--h1.has-played .sh1-sp  { animation: sh1-drawin 300ms var(--ease-out) 900ms both; }
.scene--h1.has-played .sh1-spl { animation: sh1-in 280ms var(--ease-out) 1000ms both; }

/* The swing-low level: a solid copy draws x(11) → tag (1150–1550), the dashed
   level fades in behind it at 70% of the draw, the copy fades out. */
.scene--h1.has-played .sh1-lq-d { animation: sh1-draw 500ms linear 1150ms both; }
.scene--h1.has-played .sh1-sl   { animation: sh1-in 280ms var(--ease-out) 1450ms both; }
.scene--h1.has-played .sh1-lq   { animation: sh1-in 180ms var(--ease-out) 1500ms both; }
.scene--h1.has-played .sh1-tag  { animation: sh1-in 280ms var(--ease-out) 1550ms both; }
.scene--h1.has-played .sh1-chip { animation: sh1-in 280ms var(--ease-out) 1600ms both; }



/* ── Loops (only after the entrance, only while on screen; no transforms) ── */
/* Each starts once its element has entered, and each starts from the resting
   value, so nothing jumps when a loop (re)starts. */
.scene--h1.has-played.is-live .sh1-lq-lt     { animation: sh1-light 3.2s linear 1700ms infinite; }
.scene--h1.has-played.is-live .sh1-forming-r { animation: sh1-breathe 1.8s ease-in-out 1150ms infinite alternate; }
.scene--h1.has-played.is-live .sh1-led       { animation: sh1-blink 1.6s ease-in-out 1900ms infinite; }

@keyframes sh1-in      { from { opacity: 0; } }
@keyframes sh1-pop     { from { opacity: 0; transform: scale(.4); } }
@keyframes sh1-drawin  { from { opacity: 0; stroke-dashoffset: 1; } 1% { opacity: 1; } to { stroke-dashoffset: 0; } }
@keyframes sh1-draw    {
  0%   { opacity: 1; stroke-dashoffset: 1; }
  80%  { opacity: 1; stroke-dashoffset: 0; }
  100% { opacity: 0; stroke-dashoffset: 0; }
}
@keyframes sh1-light   { from { opacity: 1; stroke-dashoffset: 1; } to { opacity: 1; stroke-dashoffset: 0; } }
@keyframes sh1-breathe { from { opacity: 1; } to { opacity: .35; } }
@keyframes sh1-blink   { 50% { opacity: .3; } }

/* ═══ STEP H2 — templates/landing/how/h2.html ═══ */
/* H2 — Liquidity sweep. Base = the finished frame: bars 0–16, the sweep of the
   103.10 swing low marked (glow, ring, SWEEP 102.70), chip SFP LOW, and both
   SFP-high extremes (107.70, 104.80) carried to the right edge as red dashed
   lines with their outline tags.
   Entrance ≈1.73 s: the step-1 chart settles → now-cursor + forming slot →
   bar 13 prints through the swing-low line and the ring snaps onto the wick →
   chip, label, time → bars 14–16 print (p14 confirms on bar 15) → each SFP high
   lights its stub, then a solid red line draws from its extreme to the edge
   and settles into the dashed level, then its tag.
   One transform (the ring SNAP). Loops: sweep glow breathes, a light runs the
   107.70 level, the forming slot breathes. */

/* Transient + loop-only layers. */
.scene--h2 .sh2-x1-d, .scene--h2 .sh2-x2-d, .scene--h2 .sh2-x1-lt { opacity: 0; }

/* ── Entrance ── */
.scene--h2.has-played .sh2-base    { animation: sh2-in 280ms var(--ease-out) both; }
.scene--h2.has-played .sh2-now,
.scene--h2.has-played .sh2-forming { animation: sh2-in 280ms var(--ease-out) 150ms both; }
/* bar 13 pierces the swing low; the PL segment it pierces arrives with it */
.scene--h2.has-played .sh2-c13,
.scene--h2.has-played .sh2-pl      { animation: sh2-in 280ms var(--ease-out) 300ms both; }
.scene--h2.has-played .sh2-gl      { animation: sh2-in 350ms var(--ease-out) 350ms both; }
.scene--h2.has-played .sh2-ring    { animation: sh2-snap 350ms var(--ease-out) 350ms both; }
.scene--h2.has-played .sh2-c14,
.scene--h2.has-played .sh2-chip    { animation: sh2-in 280ms var(--ease-out) 450ms both; }
.scene--h2.has-played .sh2-c15,
.scene--h2.has-played .sh2-sw,
.scene--h2.has-played .sh2-t       { animation: sh2-in 280ms var(--ease-out) 600ms both; }
.scene--h2.has-played .sh2-p14     { animation: sh2-in 280ms var(--ease-out) 700ms both; }
.scene--h2.has-played .sh2-c16     { animation: sh2-in 280ms var(--ease-out) 750ms both; }
/* SFP high 1 (107.70): stub + ring, draw, dashed twin at ~70–80% of the draw, tag */
.scene--h2.has-played .sh2-hi1     { animation: sh2-in 280ms var(--ease-out) 850ms both; }
.scene--h2.has-played .sh2-x1-d    { animation: sh2-draw 450ms linear 900ms both; }
.scene--h2.has-played .sh2-x1      { animation: sh2-in 180ms var(--ease-out) 1250ms both; }
.scene--h2.has-played .sh2-tag1    { animation: sh2-in 280ms var(--ease-out) 1300ms both; }
/* SFP high 2 (104.80): same beat, 150 ms behind */
.scene--h2.has-played .sh2-hi2     { animation: sh2-in 280ms var(--ease-out) 1000ms both; }
.scene--h2.has-played .sh2-x2-d    { animation: sh2-draw 450ms linear 1050ms both; }
.scene--h2.has-played .sh2-x2      { animation: sh2-in 180ms var(--ease-out) 1400ms both; }
.scene--h2.has-played .sh2-tag2    { animation: sh2-in 280ms var(--ease-out) 1450ms both; }

/* ── Loops (only after the entrance, only while on screen) ──
   Both BREATHEs start at the resting opacity 1, so a loop that (re)starts never
   pops. The light waits for its dashed level to have settled; no fill mode, so
   it stays at its base opacity 0 until then. */
.scene--h2.has-played.is-live .sh2-gl-c      { animation: sh2-breathe-gl 2.8s ease-in-out 700ms infinite alternate; }
.scene--h2.has-played.is-live .sh2-x1-lt     { animation: sh2-light 4s linear 1600ms infinite; }
.scene--h2.has-played.is-live .sh2-forming-r { animation: sh2-breathe-f 1.8s ease-in-out 450ms infinite alternate; }

@keyframes sh2-in   { from { opacity: 0; } }
@keyframes sh2-snap { from { opacity: 0; transform: scale(2); } }
@keyframes sh2-draw {
  0%   { opacity: 1; stroke-dashoffset: 1; }
  80%  { opacity: 1; stroke-dashoffset: 0; }
  100% { opacity: 0; stroke-dashoffset: 0; }
}
@keyframes sh2-light     { from { opacity: 1; stroke-dashoffset: 1; } to { opacity: 1; stroke-dashoffset: 0; } }
@keyframes sh2-breathe-gl { from { opacity: 1; } to { opacity: .6; } }
@keyframes sh2-breathe-f  { from { opacity: 1; } to { opacity: .35; } }

/* ═══ STEP H3 — templates/landing/how/h3.html ═══ */
/* H3 — 3R flush. Base = the finished frame: bars 0–19, the three red
   candles stepping down with their wick chain, the touch on the sweep
   low, the 3R mark, TP / MID / HH / SL with their tags, chip WAITING.
   Entrance ≈2.18 s, in the engine's order: reds → 3R → levels.
   0–1100 ms is only the carried-over chart, the three reds DROPping one
   by one (each wick link lights as the next bar lands) and the touch.
   Then the 3R line draws down and its badge pops; TP and MID are fixed
   AT the 3R (engine step 8), so they land with the badge — before that
   H2 showed these prices only as SFP extremes. The blue MID 104.80 tag
   gives its slot to HH 104.80 as the amber line draws to it, then the
   stop draws in under the reds. Transforms: c17, c18, c19, 3rb.
   Loops (opacity/dashoffset only): a light along SL, the chip LED, the
   forming slot breathing. */

/* resting transient layers */
.scene--h3 .sh3-3r-d, .scene--h3 .sh3-hh-d, .scene--h3 .sh3-sl-d, .scene--h3 .sh3-sl-lt, .scene--h3 .sh3-midt { opacity: 0; }

/* ── entrance ── */
.scene--h3.has-played .sh3-base  { animation: sh3-in 280ms var(--ease-out) both; }
.scene--h3.has-played .sh3-now,
.scene--h3.has-played .sh3-forming { animation: sh3-in 280ms var(--ease-out) 150ms both; }

/* three reds step down, each wick link lights once the next bar has landed */
.scene--h3.has-played .sh3-c17   { animation: sh3-drop 280ms var(--ease-out) 300ms both; }
.scene--h3.has-played .sh3-c18   { animation: sh3-drop 280ms var(--ease-out) 520ms both; }
.scene--h3.has-played .sh3-c19   { animation: sh3-drop 280ms var(--ease-out) 740ms both; }
.scene--h3.has-played .sh3-k1    { animation: sh3-in 280ms var(--ease-out) 560ms both; }
.scene--h3.has-played .sh3-k2    { animation: sh3-in 280ms var(--ease-out) 780ms both; }
.scene--h3.has-played .sh3-k3    { animation: sh3-in 280ms var(--ease-out) 1000ms both; }
.scene--h3.has-played .sh3-touch { animation: sh3-in 280ms var(--ease-out) 1050ms both; }

/* 3R: the solid copy draws down (300 ms), the dashed line takes over, badge pops */
.scene--h3.has-played .sh3-3r-d  { animation: sh3-draw 300ms linear 1100ms both; }
.scene--h3.has-played .sh3-t     { animation: sh3-in 280ms var(--ease-out) 1100ms both; }
.scene--h3.has-played .sh3-chip  { animation: sh3-in 280ms var(--ease-out) 1200ms both; }
.scene--h3.has-played .sh3-3rb   { animation: sh3-pop 420ms var(--ease-spring) 1250ms both; }
.scene--h3.has-played .sh3-3rt   { animation: sh3-in 280ms var(--ease-out) 1300ms both; }
.scene--h3.has-played .sh3-3r    { animation: sh3-in 180ms var(--ease-out) 1320ms both; }

/* the levels are fixed at the 3R: TP and MID land with the badge, never before it */
.scene--h3.has-played .sh3-tp,
.scene--h3.has-played .sh3-tpt,
.scene--h3.has-played .sh3-mid   { animation: sh3-in 280ms var(--ease-out) 1250ms both; }
/* the transient MID tag arrives with the levels (1250) and lets go of its slot as the
   HH line leaves the 3R bar (1560–1660); HH takes the slot from 1640, so the two labels
   barely overlap and never ghost through each other */
.scene--h3.has-played .sh3-midt  { animation: sh3-midt 410ms linear 1250ms both; }

/* HH draws to its tag, then the stop draws under the reds */
.scene--h3.has-played .sh3-hh-d  { animation: sh3-draw 400ms linear 1500ms both; }
.scene--h3.has-played .sh3-sl-d  { animation: sh3-draw 400ms linear 1600ms both; }
.scene--h3.has-played .sh3-hht   { animation: sh3-in 280ms var(--ease-out) 1640ms both; }
.scene--h3.has-played .sh3-hh    { animation: sh3-in 180ms var(--ease-out) 1800ms both; }
.scene--h3.has-played .sh3-sl    { animation: sh3-in 180ms var(--ease-out) 1900ms both; }
.scene--h3.has-played .sh3-slt   { animation: sh3-in 280ms var(--ease-out) 1900ms both; }

/* ── loops (on screen, after the entrance) ── */
.scene--h3.has-played.is-live .sh3-sl-lt     { animation: sh3-light 3.6s linear 2200ms infinite; }
.scene--h3.has-played.is-live .sh3-led       { animation: sh3-blink 1.6s ease-in-out infinite; }
.scene--h3.has-played.is-live .sh3-forming-r { animation: sh3-breathe 1.8s ease-in-out infinite alternate; }

@keyframes sh3-in      { from { opacity: 0; } }
@keyframes sh3-drop    { from { opacity: 0; transform: translateY(-6px); } }
@keyframes sh3-pop     { from { opacity: 0; transform: scale(.4); } }
@keyframes sh3-draw    { 0% { opacity: 1; stroke-dashoffset: 1; } 80% { opacity: 1; stroke-dashoffset: 0; } 100% { opacity: 0; stroke-dashoffset: 0; } }
@keyframes sh3-midt    { 0% { opacity: 0; animation-timing-function: cubic-bezier(0.2, 0.75, 0.2, 1); } 45%, 75% { opacity: 1; } 100% { opacity: 0; } }
@keyframes sh3-light   { from { opacity: 1; stroke-dashoffset: 1; } to { opacity: 1; stroke-dashoffset: 0; } }
@keyframes sh3-blink   { 50% { opacity: .3; } }
@keyframes sh3-breathe { from { opacity: 1; } to { opacity: .35; } }

/* ═══ STEP H4 — templates/landing/how/h4.html ═══ */
/* H4 — 3G breakout. Base = the finished frame: bars 0–23, the dimmed 3R mark,
   TP / MID / HH / SL with their tags, the breakout ring on HH, the upper-wick
   connectors, the 3G marker and badge, the green glow on the fire bar, the
   ACTIVE chip and 12:23. Entrance ≈1.67 s: the chart, then the three green
   candles rise one by one (the first snaps the HH ring as it closes through
   it, each upper wick is then tied into the next body), the fire bar glows,
   the 3G line draws itself down from under its badge, which pops, and the WAITING
   chip carried over from H3 hands over to ACTIVE. Transforms: c20–c23 (RISE),
   bo (SNAP), 3gb (POP), each ≤420 ms, at most 3 at once. Loops (opacity /
   dashoffset only): the glow breathes, a light runs up the 3G line, the
   forming slot breathes. */

/* resting transient layers */
.scene--h4 .sh4-3g-d, .scene--h4 .sh4-3g-lt, .scene--h4 .sh4-chipw { opacity: 0; }

/* ── entrance ── */
.scene--h4.has-played .sh4-base    { animation: sh4-in 280ms var(--ease-out) both; }
.scene--h4.has-played .sh4-now,
.scene--h4.has-played .sh4-forming { animation: sh4-in 280ms var(--ease-out) 150ms both; }
/* the WAITING chip H3 ended on: in with the chart (0–280 ms), out 1180–1350 ms
   so ACTIVE fades into an empty slot — two different words crossfading on
   the same spot read as a garble */
.scene--h4.has-played .sh4-chipw   { animation: sh4-chipw 1350ms linear both; }

/* the three greens (and bar 20 before them) print left to right */
.scene--h4.has-played .sh4-c20 { animation: sh4-rise 280ms var(--ease-out) 300ms both; }
.scene--h4.has-played .sh4-c21 { animation: sh4-rise 280ms var(--ease-out) 480ms both; }
.scene--h4.has-played .sh4-c22 { animation: sh4-rise 280ms var(--ease-out) 660ms both; }
.scene--h4.has-played .sh4-c23 { animation: sh4-rise 280ms var(--ease-out) 840ms both; }
/* bar 21 closes above HH: the breakout ring snaps onto the level */
.scene--h4.has-played .sh4-bo  { animation: sh4-snap 340ms var(--ease-out) 560ms both; }
/* each upper wick lands inside the next body */
.scene--h4.has-played .sh4-k1  { animation: sh4-in 280ms var(--ease-out) 760ms both; }
.scene--h4.has-played .sh4-k2  { animation: sh4-in 280ms var(--ease-out) 940ms both; }

/* the third green closes: glow, the 3G line draws, its badge pops */
.scene--h4.has-played .sh4-gl   { animation: sh4-in 400ms var(--ease-out) 1000ms both; }
.scene--h4.has-played .sh4-3g-d { animation: sh4-draw 300ms linear 1100ms both; }
.scene--h4.has-played .sh4-t    { animation: sh4-in 280ms var(--ease-out) 1100ms both; }
.scene--h4.has-played .sh4-3gb  { animation: sh4-pop 420ms var(--ease-spring) 1250ms both; }
.scene--h4.has-played .sh4-3gt  { animation: sh4-in 280ms var(--ease-out) 1300ms both; }
.scene--h4.has-played .sh4-3g   { animation: sh4-in 180ms var(--ease-out) 1320ms both; }
.scene--h4.has-played .sh4-chip { animation: sh4-in 300ms var(--ease-out) 1350ms both; }

/* ── loops (on screen, after the entrance) ── */
.scene--h4.has-played.is-live .sh4-forming-r { animation: sh4-breathe-f 1.8s ease-in-out 430ms infinite alternate; }
.scene--h4.has-played.is-live .sh4-gl-c      { animation: sh4-breathe-g 2.6s ease-in-out 1400ms infinite alternate; }
.scene--h4.has-played.is-live .sh4-3g-lt     { animation: sh4-light 3s linear 1700ms infinite; }

@keyframes sh4-in        { from { opacity: 0; } }
@keyframes sh4-rise      { from { opacity: 0; transform: translateY(6px); } }
@keyframes sh4-snap      { from { opacity: 0; transform: scale(2); } }
@keyframes sh4-pop       { from { opacity: 0; transform: scale(.4); } }
@keyframes sh4-draw      { 0% { opacity: 1; stroke-dashoffset: 1; } 80% { opacity: 1; stroke-dashoffset: 0; } 100% { opacity: 0; stroke-dashoffset: 0; } }
@keyframes sh4-chipw     { 0% { opacity: 0; } 21%, 87% { opacity: 1; } 100% { opacity: 0; } }
@keyframes sh4-light     { from { opacity: 1; stroke-dashoffset: 1; } to { opacity: 1; stroke-dashoffset: 0; } }
@keyframes sh4-breathe-f { from { opacity: 1; } to { opacity: .35; } }
@keyframes sh4-breathe-g { from { opacity: 1; } to { opacity: .55; } }

/* ═══ STEP H5 — templates/landing/how/h5.html ═══ */
/* H5 — The trade. Base = the finished frame: bars 0–31, the 50% limit at
   104.175 filled on bar 26 (ring, FILLED, 12:26), reward and risk boxes from
   the fill, the target touched on bar 31 (win ring, glow, 12:31) and chip
   WIN +2.04R; 3R, 3G and HH stay dimmed in the step-4 chart underneath.
   Entrance ≈1.95 s: the step-4 chart settles → now-cursor → the post-3G
   high and its HI tag → the 50% bracket draws from that high down to the
   stop, its midpoint tick marking the entry → the entry line draws out
   from the 3G bar to the bracket → entry tag, 50% LIMIT → bars 24–31
   print, 110 ms apart → bar 26 dips into the limit and the fill ring snaps
   onto it (FILLED, 12:26, the reward and risk boxes open from the fill) →
   bar 31 tags the target and the win ring snaps on, the glow comes up
   (12:31) → the chip pops, its text last.
   Transforms: the fill SNAP, the win SNAP and the chip pill (text-free
   circles and a rect; the chip text only fades). Loops (no transforms):
   the target glow breathes, a halo pulses on the win ring. */

/* Transient layer: the halo exists only as a loop. */
.scene--h5 .sh5-halo { opacity: 0; }

/* ── Entrance ── */
.scene--h5.has-played .sh5-base { animation: sh5-in 280ms var(--ease-out) both; }
.scene--h5.has-played .sh5-now  { animation: sh5-in 280ms var(--ease-out) 150ms both; }

/* The limit is set. The post-3G high comes first, then the 50% bracket
   draws from it down to the stop (its midpoint tick marks the entry), then
   the entry line draws out from the 3G bar to the bracket (pathLength 1). */
.scene--h5.has-played .sh5-hi   { animation: sh5-in 280ms var(--ease-out) 250ms both; }
.scene--h5.has-played .sh5-br   { animation: sh5-drawin 400ms var(--ease-out) 300ms both; }
.scene--h5.has-played .sh5-en   { animation: sh5-drawin 400ms var(--ease-out) 400ms both; }
.scene--h5.has-played .sh5-ent  { animation: sh5-in 280ms var(--ease-out) 550ms both; }
.scene--h5.has-played .sh5-lim  { animation: sh5-in 280ms var(--ease-out) 600ms both; }

/* Bars 24–31 print, 700 + 110k ms. */
.scene--h5.has-played .sh5-c24  { animation: sh5-in 280ms var(--ease-out) 700ms both; }
.scene--h5.has-played .sh5-c25  { animation: sh5-in 280ms var(--ease-out) 810ms both; }
.scene--h5.has-played .sh5-c26  { animation: sh5-in 280ms var(--ease-out) 920ms both; }
.scene--h5.has-played .sh5-c27  { animation: sh5-in 280ms var(--ease-out) 1030ms both; }
.scene--h5.has-played .sh5-c28  { animation: sh5-in 280ms var(--ease-out) 1140ms both; }
.scene--h5.has-played .sh5-c29  { animation: sh5-in 280ms var(--ease-out) 1250ms both; }
.scene--h5.has-played .sh5-c30  { animation: sh5-in 280ms var(--ease-out) 1360ms both; }
.scene--h5.has-played .sh5-c31  { animation: sh5-in 280ms var(--ease-out) 1470ms both; }

/* Bar 26 fills the limit: ring snaps on, the position opens. */
.scene--h5.has-played .sh5-fill { animation: sh5-snap 300ms var(--ease-out) 950ms both; }
.scene--h5.has-played .sh5-filled,
.scene--h5.has-played .sh5-t26  { animation: sh5-in 280ms var(--ease-out) 1000ms both; }
.scene--h5.has-played .sh5-rw,
.scene--h5.has-played .sh5-rk   { animation: sh5-in 400ms var(--ease-out) 1000ms both; }

/* Bar 31 tags the target: win ring, glow, time, then the chip. */
.scene--h5.has-played .sh5-win  { animation: sh5-snap 320ms var(--ease-out) 1500ms both; }
.scene--h5.has-played .sh5-gl   { animation: sh5-in 400ms var(--ease-out) 1500ms both; }
.scene--h5.has-played .sh5-t31  { animation: sh5-in 280ms var(--ease-out) 1520ms both; }
.scene--h5.has-played .sh5-chipbg { animation: sh5-chip 300ms var(--ease-spring) 1650ms both; }
.scene--h5.has-played .sh5-chipt  { animation: sh5-in 250ms var(--ease-out) 1700ms both; }

/* ── Loops (only after the entrance, only while on screen; no transforms) ──
   The glow BREATHE starts at its resting opacity 1, so a loop that (re)starts
   never pops; it waits for the glow's fade to finish. The halo has no fill
   mode, so it stays at its base opacity 0 until the win ring has landed. */
.scene--h5.has-played.is-live .sh5-gl-c { animation: sh5-breathe 2.8s ease-in-out 1900ms infinite alternate; }
.scene--h5.has-played.is-live .sh5-halo { animation: sh5-halo 2.6s ease-out 1850ms infinite; }

@keyframes sh5-in      { from { opacity: 0; } }
@keyframes sh5-snap    { from { opacity: 0; transform: scale(2); } }
@keyframes sh5-chip    { from { opacity: 0; transform: scale(.8); } }
@keyframes sh5-drawin  { from { opacity: 0; stroke-dashoffset: 1; } 1% { opacity: 1; } to { stroke-dashoffset: 0; } }
@keyframes sh5-breathe { from { opacity: 1; } to { opacity: .5; } }
@keyframes sh5-halo    { 0% { opacity: .6; } 100% { opacity: 0; } }

/* ── Reduced motion ──────────────────────────────────────────────────
   The scenes themselves are stilled by landing-story.css's block
   (.scene * { animation: none }); this only stops the card hover lift. */
@media (prefers-reduced-motion: reduce) {
  .feature, .feature:hover { translate: none; }
  .reveal-js .feature.reveal, .feature__figure { transition: none; }
}
