/* ==========================================================================
   Survive the Streak — Operator Site
   Stage 2 design-system foundation.

   Token architecture: a neutral, operator-grade corporate shell lives at
   :root. Sections that represent the STS product world are wrapped in
   [data-product="sts"], which overrides the accent/surface tokens with a
   warmer gold/ember palette. Every component below consumes the tokens
   (--accent / --accent-bright / --accent-warm / --color-bg / --color-surface
   etc.) rather than hardcoded colors, so a future second retention product
   can add its own [data-product="..."] block without touching component
   CSS, and the same components can be reused for a neutral corporate page
   or a cinematic product page just by which wrapper they sit inside.
   ========================================================================== */

:root {
  /* Neutral corporate foundation — near-black/charcoal, cool restrained accent */
  --color-bg: #08090b;
  --color-bg-soft: #0d0f12;
  --color-surface: #14171b;
  --color-surface-raised: #1a1e23;

  /* Stage 5B: "different shades of night," not flat black everywhere —
     each page zone gets its own near-black hue (graphite for
     commercial, blue-black for trust/control) rather than a lightness-
     only variant of the same neutral. Deliberately subtle: these are
     all still within a couple of RGB steps of --color-bg, felt on scroll
     rather than seen as a color change. */
  --color-bg-graphite: #0a0b0f;
  --color-bg-trust: #08090d;

  --color-border: rgba(160, 176, 191, 0.16);
  --color-border-strong: rgba(160, 176, 191, 0.32);

  --accent: #8CA3B8;
  --accent-bright: #B7CBDC;
  --accent-warm: #8a8f96;

  --hero-glow-1: rgba(140, 163, 184, 0.08);
  --hero-glow-2: rgba(183, 203, 220, 0.06);

  --color-text: #f2f3f5;
  --color-muted: #a7adb4;
  --color-muted-2: #767c84;

  /* State colors — fixed regardless of product theme */
  --color-success: #6fae72;
  --color-error: #d9765a;

  /* Spacing / layout */
  --section-pad-y: clamp(64px, 9vw, 120px);
  --container-max: 1180px;
  --radius: 3px;
  --radius-lg: 6px;

  /* Shadow */
  --shadow-card: 0 18px 50px rgba(0, 0, 0, 0.35);
  --shadow-cta: 0 0 0 1px rgba(140, 163, 184, 0.4), 0 12px 30px rgba(0, 0, 0, 0.24);

  /* Motion */
  --transition: 180ms ease;

  /* Stage 4F: CTA brand identity — fixed regardless of which
     [data-product] scope a button happens to sit inside. Buttons need a
     stable, coherent identity across the whole page (a "Discuss a Pilot"
     ask reads the same in the nav as it does in the footer); the
     product-world palette above is for section decoration (glows,
     borders, journey accents), not for button color, which is why
     .btn-* below consume these instead of --accent/--accent-bright. */
  --cta-gold: #d8ae5c;
  --cta-gold-bright: #f0cb7a;
  --cta-ember: #c85f2a;
}

/* STS product-world palette — warmer gold / controlled ember. Wrap any
   section that represents the Survive the Streak product experience in
   [data-product="sts"] to opt into this. A future second retention
   product would add its own [data-product="..."] block here, sharing
   every component class below unchanged. */
[data-product="sts"] {
  --color-bg: #090b0d;
  --color-bg-soft: #101317;
  --color-surface: #15191e;
  --color-surface-raised: #1a1f25;

  --color-border: rgba(216, 174, 92, 0.20);
  --color-border-strong: rgba(216, 174, 92, 0.38);

  --accent: #d8ae5c;
  --accent-bright: #f0cb7a;
  --accent-warm: #c85f2a;

  --hero-glow-1: rgba(200, 95, 42, 0.10);
  --hero-glow-2: rgba(216, 174, 92, 0.08);

  --shadow-cta: 0 0 0 1px rgba(216, 174, 92, 0.4), 0 12px 30px rgba(200, 95, 42, 0.18);
}

/* ── Base ─────────────────────────────────────────────────────────────── */

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * , *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

body {
  background: var(--color-bg);
  color: var(--color-text);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

/* Deliberate system-font stack (Stage 2 "Option A" — see Phase-1 audit
   finding that 'Inter' was referenced but never actually loaded). A real,
   properly self-hosted display face can be layered in later without
   touching any component below, since every heading already inherits
   from this one declaration. */
h1, h2, h3, h4 {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  font-weight: 800;
  letter-spacing: -0.015em;
  line-height: 1.08;
  margin: 0 0 0.5em;
  color: var(--color-text);
}

p { margin: 0 0 1em; color: var(--color-muted); }
a { color: var(--accent); }
a:hover { color: var(--accent-bright); }

img { max-width: 100%; height: auto; display: block; }

::selection { background: rgba(140, 163, 184, 0.3); color: var(--color-text); }

:focus-visible {
  outline: 2px solid var(--accent-bright);
  outline-offset: 3px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 2000;
  background: var(--accent);
  color: #0b0f0c;
  padding: 10px 18px;
  border-radius: var(--radius);
  font-weight: 700;
}
.skip-link:focus { left: 12px; top: 12px; }

/* ── Layout helpers ───────────────────────────────────────────────────── */

.container { max-width: var(--container-max); }

.section { padding-block: var(--section-pad-y); border-top: 1px solid var(--color-border); overflow: hidden; }
/* Stage 5: found empirically that .section's own overflow:hidden was
   silently disabling position:sticky for every descendant, including
   #experience-device — an ancestor with any overflow other than
   `visible` breaks sticky positioning entirely per spec, regardless of
   how far away that ancestor is. This ID beats the .section rule above
   on specificity, so it's a targeted opt-out for the one section that
   actually needs sticky, not a blanket change to every section's
   clipping behavior. */
#experience { overflow: visible; }
.section:first-of-type { border-top: none; }
.section-soft { background: var(--color-bg-soft); }

/* Stage 4F: three quiet rhythm groups across the page — product/emotional
   sections (hero, Player Experience, 30-Day Journey, the climax panels)
   already read distinctly via the warm [data-product="sts"] scoping and
   the climax's own glow; .section-soft (above) now consistently marks
   the two COMMERCIAL sections (Operator Value, Why STS Is Different) as
   one shaded block; .section-trust marks the TRUST/CONTROL sections
   (Integration, Controlled Pilot, and — via .trust-zone below — the
   FAQ/contact half of the final section) with a firmer top border
   instead of a background shift, since they sit on the same neutral
   background as the hero/experience sections above them and don't need
   another shade to feel distinct. Deliberately subtle — background tint
   and border weight only, no new colors, no restructured sections. */
.section-trust { border-top-color: var(--color-border-strong); background: var(--color-bg-trust); }
.trust-zone { padding-top: calc(var(--section-pad-y) * 0.6); margin-top: calc(var(--section-pad-y) * 0.6); border-top: 1px solid var(--color-border-strong); background: var(--color-bg-trust); }
#operator-value, #difference { background: var(--color-bg-graphite); }

.eyebrow {
  display: inline-block;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 16px;
}

.section-head { max-width: 780px; margin-bottom: 52px; }
.section-title { font-size: clamp(1.9rem, 3.6vw, 2.7rem); }
.section-lede { font-size: 1.08rem; max-width: 620px; }

/* Pre-Stage-6: Player Experience's rotating heading. "One" is real,
   static, always-visible text; the three phrases share one fixed-size
   box (each explicitly position:absolute, one per line-height) so the
   section never reflows as they cycle — the box height comes from the
   tallest phrase's own wrapped height, measured empirically per
   breakpoint below, not from flow (an absolutely-positioned phrase
   contributes nothing to its container's own height).
   Accessibility: .eh-visual is aria-hidden — the real heading text is
   the plain .sr-only span next to it in index.php, so a screen reader
   gets one stable sentence, never a live-updating region. */
.eh-visual { display: block; }
.eh-one { display: inline; }
.eh-rotator {
  position: relative;
  display: inline-block;
  vertical-align: top;
  height: 1.2em;
  min-width: 1px;
}
@media (min-width: 560px) { .eh-rotator { height: 1.2em; } }
.eh-phrase {
  position: absolute;
  left: 0; top: 0;
  display: inline-block;
  white-space: nowrap;
  opacity: 0;
  clip-path: inset(0 100% 0 0);
  animation-duration: 9s;
  animation-iteration-count: infinite;
  animation-timing-function: linear;
  animation-fill-mode: both;
}
/* Each phrase's own keyframes cover the full shared 9s loop, staying
   hidden except during its own ~1/3 window — explicit per-phrase
   percentages rather than one shared keyframe + negative animation-delay
   tricks, so the actual on-screen order (1, 2, 3) is simply readable
   from the keyframe rules themselves. Reveal is a smooth clip-path swipe
   (not a stepped per-character type-on) deliberately — a stepped
   monospace-style reveal is what reads as "cheap terminal," a smooth
   curtain-style reveal reads as editorial. */
@keyframes eh-reveal-1 {
  0%, 100% { opacity: 0; clip-path: inset(0 100% 0 0); }
  1% { opacity: 1; }
  12% { clip-path: inset(0 0% 0 0); }
  29% { clip-path: inset(0 0% 0 0); opacity: 1; }
  33% { opacity: 0; clip-path: inset(0 0% 0 0); }
}
@keyframes eh-reveal-2 {
  0%, 34%, 100% { opacity: 0; clip-path: inset(0 100% 0 0); }
  35% { opacity: 1; }
  45% { clip-path: inset(0 0% 0 0); }
  62% { clip-path: inset(0 0% 0 0); opacity: 1; }
  66% { opacity: 0; clip-path: inset(0 0% 0 0); }
}
@keyframes eh-reveal-3 {
  0%, 67%, 100% { opacity: 0; clip-path: inset(0 100% 0 0); }
  68% { opacity: 1; }
  78% { clip-path: inset(0 0% 0 0); }
  96% { clip-path: inset(0 0% 0 0); opacity: 1; }
  99% { opacity: 0; clip-path: inset(0 0% 0 0); }
}
.eh-phrase-1 { animation-name: eh-reveal-1; }
.eh-phrase-2 { animation-name: eh-reveal-2; }
.eh-phrase-3 { animation-name: eh-reveal-3; }
/* .section-title's own clamp() floors at 1.9rem even at narrow widths
   (3.6vw only overtakes that floor above ~845px) — fine for the
   original 3-separate-lines layout, but "One reason to return
   tomorrow." on one nowrap line at 1.9rem genuinely overflows a 390px
   viewport (confirmed: right edge at 483px). Sized down specifically
   for the rotating heading at mobile widths so the longest phrase fits
   on its one required line without wrapping mid-reveal. */
@media (max-width: 480px) { .experience-heading { font-size: 1.45rem; } }
@media (max-width: 360px) { .experience-heading { font-size: 1.3rem; } }
/* Same real overflow class as .experience-heading above, confirmed the
   same way: "One controlled audience." on one nowrap line at
   .section-title's 1.9rem mobile floor overflows a 360px viewport
   (measured: right edge at ~378px). Not an issue at 390px+ (measured
   clean), so only the 360px breakpoint is needed here. */
@media (max-width: 360px) { .pilot-heading { font-size: 1.3rem; } }
/* Reduced motion: no cycling, no typewriter — the brief explicitly wants
   a static composition containing all three ideas, not just the first
   phrase frozen mid-loop (which is what the site-wide reduced-motion
   rule's animation-iteration-count:1 override would otherwise produce).
   Falls back to the exact original three-stacked-lines layout. */
@media (prefers-reduced-motion: reduce) {
  .eh-rotator { height: auto; display: block; }
  .eh-phrase {
    position: static;
    display: block;
    opacity: 1;
    clip-path: none;
    animation: none;
    white-space: normal;
  }
}

/* Pre-Stage-6 correction: Controlled Pilot's heading gets the same
   rotating-heading language as the Player Experience heading above —
   "One" fixed, four phrases cycling through the identical clip-path
   curtain reveal (never a stepped/monospace type-on, never a blinking
   cursor). Separate class prefix (ph- vs eh-) rather than reusing the
   experience heading's classes directly: four phrases need their own
   25%-per-phrase keyframe timing (vs. three phrases at ~33% each), and
   the two headings must be free to diverge later without one's tuning
   silently affecting the other. Same layout technique as .eh-rotator:
   phrases are position:absolute + white-space:nowrap inside a
   height-only box, so only ONE line-height is ever reserved and the
   inactive (wider or narrower) phrases contribute nothing to that
   box's own size — no CLS as they cycle. */
.ph-visual { display: block; }
.ph-one { display: inline; }
.ph-rotator {
  position: relative;
  display: inline-block;
  vertical-align: top;
  height: 1.2em;
  min-width: 1px;
}
.ph-phrase {
  position: absolute;
  left: 0; top: 0;
  display: inline-block;
  white-space: nowrap;
  opacity: 0;
  clip-path: inset(0 100% 0 0);
  animation-duration: 12s;
  animation-iteration-count: infinite;
  animation-timing-function: linear;
  animation-fill-mode: both;
}
@keyframes ph-reveal-1 {
  0%, 100% { opacity: 0; clip-path: inset(0 100% 0 0); }
  1% { opacity: 1; }
  9% { clip-path: inset(0 0% 0 0); }
  21% { clip-path: inset(0 0% 0 0); opacity: 1; }
  25% { opacity: 0; clip-path: inset(0 0% 0 0); }
}
@keyframes ph-reveal-2 {
  0%, 26%, 100% { opacity: 0; clip-path: inset(0 100% 0 0); }
  27% { opacity: 1; }
  35% { clip-path: inset(0 0% 0 0); }
  46% { clip-path: inset(0 0% 0 0); opacity: 1; }
  50% { opacity: 0; clip-path: inset(0 0% 0 0); }
}
@keyframes ph-reveal-3 {
  0%, 51%, 100% { opacity: 0; clip-path: inset(0 100% 0 0); }
  52% { opacity: 1; }
  60% { clip-path: inset(0 0% 0 0); }
  71% { clip-path: inset(0 0% 0 0); opacity: 1; }
  75% { opacity: 0; clip-path: inset(0 0% 0 0); }
}
/* The final phrase ("measurable pilot.") gets a slightly stronger close —
   the same gold used for milestone/CTA emphasis elsewhere on the site,
   not a new color, and no size/weight change (restrained, not shouty). */
@keyframes ph-reveal-4 {
  0%, 76%, 100% { opacity: 0; clip-path: inset(0 100% 0 0); color: var(--color-text); text-shadow: none; }
  77% { opacity: 1; }
  85% { clip-path: inset(0 0% 0 0); color: var(--accent-bright); text-shadow: 0 0 18px rgba(216, 174, 92, 0.35); }
  96% { clip-path: inset(0 0% 0 0); opacity: 1; color: var(--accent-bright); text-shadow: 0 0 18px rgba(216, 174, 92, 0.35); }
  99% { opacity: 0; clip-path: inset(0 0% 0 0); }
}
.ph-phrase-1 { animation-name: ph-reveal-1; }
.ph-phrase-2 { animation-name: ph-reveal-2; }
.ph-phrase-3 { animation-name: ph-reveal-3; }
.ph-phrase-4 { animation-name: ph-reveal-4; }
@media (prefers-reduced-motion: reduce) {
  .ph-rotator { height: auto; display: block; }
  .ph-phrase {
    position: static;
    display: block;
    opacity: 1;
    clip-path: none;
    animation: none;
    white-space: normal;
    color: inherit;
    text-shadow: none;
  }
}

/* ── Buttons ──────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-weight: 700;
  font-size: 0.92rem;
  letter-spacing: 0.02em;
  padding: 15px 28px;
  border-radius: var(--radius);
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform var(--transition), box-shadow var(--transition), background var(--transition), border-color var(--transition);
  text-align: center;
}
.btn:hover { transform: translateY(-1px); }

/* Stage 4F CTA hierarchy — three tiers, all using the fixed --cta-* gold
   identity above rather than the scoped product palette, so a button
   looks the same whether it sits in the hero or the footer:
     .btn-primary    — PRIMARY PRODUCT: restrained warm gold fill. The
                        product-forward / conversion-terminal actions
                        (hero video CTA, final "Launch a Pilot" CTA, the
                        contact form's own submit).
     .btn-secondary  — SECONDARY: dark/neutral, warm border and text on
                        hover. Paired next to a primary action.
     .btn-corporate  — HIGH-INTENT CORPORATE: a restrained warm-neutral
                        metallic treatment — present, but deliberately
                        quieter than primary gold. The repeated "Discuss
                        a Pilot" business ask (nav, Pilot section), never
                        the product's own gold accent color, so the two
                        stay visually distinct even though both are
                        "warm." Not bright, not neon — see Stage 4F brief. */
.btn-primary {
  background: linear-gradient(180deg, var(--cta-gold-bright), var(--cta-gold));
  color: #14100a;
  box-shadow: 0 0 0 1px rgba(216, 174, 92, 0.4), 0 12px 30px rgba(200, 95, 42, 0.18);
}
.btn-primary:hover { color: #14100a; }

.btn-secondary {
  background: transparent;
  border-color: var(--color-border-strong);
  color: var(--color-text);
}
.btn-secondary:hover { border-color: var(--cta-gold-bright); color: var(--cta-gold-bright); }

.btn-corporate {
  background: linear-gradient(180deg, #3a332c, #221d18);
  border-color: rgba(216, 174, 92, 0.45);
  color: var(--cta-gold-bright);
  box-shadow: 0 0 0 1px rgba(216, 174, 92, 0.15), 0 10px 24px rgba(0, 0, 0, 0.35);
}
.btn-corporate:hover {
  border-color: var(--cta-gold-bright);
  color: var(--cta-gold-bright);
  background: linear-gradient(180deg, #453c33, #2a241d);
}

.btn-block { width: 100%; }

/* ── Nav ──────────────────────────────────────────────────────────────── */

.site-nav {
  position: sticky;
  top: 0;
  z-index: 1000;
  background: rgba(8, 9, 11, 0.72);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid transparent;
  transition: background var(--transition), border-color var(--transition);
}
.site-nav.is-scrolled {
  background: rgba(8, 9, 11, 0.94);
  border-bottom-color: var(--color-border);
}

.nav-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block: 16px;
}

.nav-brand { display: flex; align-items: baseline; gap: 8px; font-weight: 800; font-size: 1.05rem; color: var(--color-text); }
.nav-brand-sub { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.12em; color: var(--color-muted-2); }

.nav-links { display: none; align-items: center; gap: 26px; list-style: none; margin: 0; padding: 0; }
.nav-link { font-size: 0.85rem; font-weight: 600; color: var(--color-muted); }
.nav-link:hover, .nav-link.is-active { color: var(--accent-bright); }

.nav-cta { display: none; }
.nav-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  border-radius: var(--radius);
  border: 1px solid var(--color-border-strong);
  background: transparent;
  color: var(--color-text);
}

@media (min-width: 992px) {
  .nav-links { display: flex; }
  .nav-cta { display: inline-flex; }
  .nav-toggle { display: none; }
}

.mobile-menu {
  display: none;
  border-top: 1px solid var(--color-border);
  background: var(--color-bg-soft);
}
.mobile-menu.is-open { display: block; }
.mobile-menu-inner { padding: 18px 0 26px; display: flex; flex-direction: column; gap: 4px; }
.mobile-menu .nav-link { padding: 10px 4px; border-bottom: 1px solid var(--color-border); }
.mobile-menu .btn { margin-top: 14px; }

/* ── Hero (Section 1) ─────────────────────────────────────────────────── */

.hero {
  /* Stage 5E: upper clamp bound trimmed 112px -> 90px — the new
     retention-timeline graphic (see below) added real height under the
     hero device on top of a frame that was already tall enough to run
     past a 900px-tall viewport before this stage touched anything; a
     smaller top pad recovers some of that room without visibly
     cramping the headline column. */
  padding-block: clamp(64px, 8vw, 90px) clamp(56px, 8vw, 88px);
  background:
    radial-gradient(ellipse 900px 520px at 12% -10%, var(--hero-glow-1), transparent 60%),
    radial-gradient(ellipse 720px 520px at 100% 0%, var(--hero-glow-2), transparent 60%);
  /* Stage 6 QA: .hero has its own class (not .section), so the sitewide
     .section { overflow: hidden } rule never clipped it. .device-stage::
     before's ambient glow (inset: -48px) was bleeding past the section's
     own right edge wherever the grid's side margin is under 48px —
     confirmed causing real horizontal overflow (measured: exactly 32px)
     at 1024px and every narrower QA breakpoint. Clipping horizontally
     only, so nothing about the vertical glow/hero layout changes. */
  overflow-x: hidden;
}
.hero-grid { display: grid; gap: 52px; align-items: center; }
/* Stage 5G: 1.05fr/0.95fr left "Create another day." — the longer of the
   two headline lines — genuinely wrapping mid-phrase into 3 visual lines
   at every desktop width from 992px to 1440px+ (measured: the line needs
   ~630px, the old column only offered ~575px at 1440px). Widening the
   text column costs the device column nothing: .device-frame-hero has
   its own 380px cap, well below even the narrower 0.8fr column's actual
   width at any real desktop size, so the phone renders identically —
   confirmed via measurement before/after. */
@media (min-width: 992px) { .hero-grid { grid-template-columns: 1.2fr 0.8fr; } }

.hero-headline { font-size: clamp(2.4rem, 5.4vw, 4.2rem); }
.hero-body { font-size: 1.18rem; max-width: 540px; color: var(--color-text); margin-top: 20px; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 14px; margin: 28px 0 4px; }

/* ── Hero entrance (Stage 5) ─────────────────────────────────────────────
   A short, staggered resolve on first paint — headline, body, CTAs, then
   the live device with a touch of depth. ~550-850ms total, well inside
   the 600-1000ms target. The site-wide reduced-motion rule at the top of
   this file already forces every animation-duration to ~0 and iteration
   count to 1, so under prefers-reduced-motion this simply snaps straight
   to the resolved end state with no separate JS branch needed. */
@keyframes hero-rise { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
@keyframes hero-rise-depth { from { opacity: 0; transform: translateY(20px) scale(0.985); } to { opacity: 1; transform: none; } }
.hero-grid > div:first-child > .eyebrow { animation: hero-rise 480ms ease both; }
.hero-grid > div:first-child > .hero-headline { animation: hero-rise 520ms ease 90ms both; }
.hero-grid > div:first-child > .hero-body { animation: hero-rise 520ms ease 180ms both; }
.hero-grid > div:first-child > .hero-actions { animation: hero-rise 520ms ease 270ms both; }
.hero-grid .device-stage { animation: hero-rise-depth 640ms cubic-bezier(0.22, 1, 0.36, 1) 200ms both; }

/* Website-side atmosphere behind the authentic live scene — never inside
   its Shadow DOM. Cheap (background/opacity/transform only, no
   canvas/WebGL/particles): a very slow drifting warm glow, felt rather
   than noticed. Reduced-motion collapses this to a single static frame
   via the site-wide rule above. */
.device-stage::before {
  content: '';
  position: absolute;
  inset: -48px;
  z-index: -2;
  background: radial-gradient(ellipse 62% 50% at 50% 18%, var(--hero-glow-1), transparent 70%);
  opacity: 0.6;
  animation: hero-atmosphere-drift 22s ease-in-out infinite alternate;
  pointer-events: none;
}
@keyframes hero-atmosphere-drift {
  0% { transform: translate(0, 0) scale(1); opacity: 0.5; }
  100% { transform: translate(-2%, 3%) scale(1.05); opacity: 0.8; }
}
/* Stage 5B mobile pass: keep the glow (richness costs nothing extra to
   paint once), drop the 22s-loop animation driving it — one less
   continuously-compositing layer on phone hardware, per the brief's
   "don't run expensive atmospheric motion simply because desktop has
   it." */
@media (max-width: 767px) {
  .device-stage::before { animation: none; opacity: 0.55; }
}

/* Trust row directly under the hero — plain statements, not badges/seals */
.trust-row {
  list-style: none;
  margin: 40px 0 0;
  padding: 22px 0 0;
  border-top: 1px solid var(--color-border);
  display: grid;
  gap: 14px;
  grid-template-columns: 1fr;
  font-size: 0.82rem;
  color: var(--color-muted-2);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
@media (min-width: 700px) { .trust-row { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1100px) { .trust-row { grid-template-columns: repeat(4, 1fr); } }
.trust-row li { padding-left: 16px; position: relative; }
.trust-row li::before { content: ''; position: absolute; left: 0; top: 0.5em; width: 6px; height: 6px; background: var(--accent); border-radius: 50%; }

/* ── Device frame (hero + experience placeholders) ───────────────────── */
/* A game-HUD reads as one glowing surface with a few grouped numbers, not
   a stack of equal bordered boxes — deliberately avoiding a "dashboard"
   feel. .device-stage wraps the frame + its dev-note caption together so
   the note never crowds the frame itself. */

.device-stage { position: relative; }

.device-frame {
  position: relative;
  border: 1px solid var(--color-border-strong);
  border-radius: 34px;
  background: var(--color-surface);
  box-shadow: var(--shadow-card), 0 0 0 1px rgba(0, 0, 0, 0.4);
  padding: 34px 28px 28px;
  max-width: 380px;
  margin-inline: auto;
  overflow: hidden;
}
.device-frame::before {
  content: '';
  position: absolute; inset: 0;
  background: linear-gradient(160deg, var(--hero-glow-2), transparent 55%);
  pointer-events: none;
}
/* Layered depth: a second, larger, softly-blurred card sitting behind the
   frame — cheap to render, reads as "premium device on a lit stage"
   without any imagery. */
.device-frame::after {
  content: '';
  position: absolute;
  inset: -18px -14px -14px;
  z-index: -1;
  border-radius: 44px;
  background: radial-gradient(ellipse at 50% 0%, var(--hero-glow-1), transparent 70%);
  filter: blur(2px);
}
/* Stage 5G: the Stage 5F external "retention journey" rail (and its own
   ~155px of extra height under this frame) is removed — the Hero's media
   is now the authentic Day-12 result screen alone, so there's no longer a
   second element competing for vertical room below it. 380px flat is
   comfortable at ordinary desktop heights (confirmed: 380px -> ~676px
   frame height fits well inside a 900px viewport under the header and
   hero top padding); the min() with a viewport-height-derived width only
   kicks in on shorter laptop screens (~800px and below), same technique
   as .device-frame-large below. */
.device-frame-hero {
  max-width: min(380px, calc((100vh - 140px) * 9 / 16));
  padding: 40px 32px 32px;
}

.device-notch {
  width: 84px; height: 6px;
  border-radius: 999px;
  background: var(--color-border-strong);
  margin: 0 auto 26px;
  position: relative;
  z-index: 2;
}

/* Media variant: the frame becomes a bezel around a real (or pending)
   gameplay capture, edge-to-edge, notch overlaid rather than pushing
   content down with padding. */
.device-frame-media { padding: 0; }
.device-frame-media .device-notch { position: absolute; top: 16px; left: 50%; transform: translateX(-50%); margin: 0; }
.device-frame-media .media-slot { border-radius: inherit; }

.device-frame-large {
  max-width: 420px;
  width: 100%;
}
/* Stage 5C: real-desktop review found the Stage 4F sizing (440px, 480px
   from 1280px up) too dominant AND — the more serious problem — tall
   enough to overflow common 768-800px-tall laptop screens outright. A
   480px-wide frame at this 9/17.5 ratio is 933px tall; measured against
   the actual sticky offset below, that left the stage extending 250-620px
   past the bottom of the viewport on every desktop height under 900px,
   never "comfortable." Width alone can't fix that (the ratio is locked),
   so the frame's width is now the SMALLER of a flat cap (max ~440px, the
   Stage 5C target) and whatever width keeps the resulting height inside
   the real available vertical budget: viewport height, minus the sticky
   offset below (header + breathing gap, see .experience-device-sticky),
   minus a matching breathing margin at the bottom. On tall viewports the
   440px cap wins (unchanged from before); on ordinary 768-900px laptop
   screens the height budget wins and the frame shrinks accordingly —
   confirmed via Playwright measurement at 1440x900/1366x768/1280x800/
   1024x768, see the Stage 5C report for exact before/after numbers. */
@media (min-width: 992px) {
  .device-frame-large {
    aspect-ratio: 9 / 17.5;
    width: min(440px, calc((100vh - 162px) * 9 / 17.5));
  }
}

/* ── Media slots — real asset if present, honest placeholder otherwise ── */
/* See stt_media_image()/stt_media_video() in index.php. The .is-missing
   state is added automatically by each element's onerror handler; nothing
   here is toggled by hand. */

.media-slot {
  position: relative;
  width: 100%;
  overflow: hidden;
  background: var(--color-bg-soft);
}
/* Stage 5B: the Player Experience's single live stage fills its parent
   frame directly rather than setting its own aspect-ratio — that parent
   (.device-frame-large) is 9/17.5, taller than the 9/16 this slot used
   to hardcode, and the mismatch left a real empty gap between every
   scene's content and the bottom of the outer frame. */
.media-slot-experience { height: 100%; }
.media-slot img, .media-slot video {
  width: 100%; height: 100%;
  object-fit: cover; object-position: center;
  display: block;
}
/* Stage 4E — see assets/js/sts-live-scene.js and stt_media_scene() in
   index.php. <sts-live-scene> renders real exported scene UI directly
   inside its own Shadow DOM (no iframe) — the host element just needs to
   fill its .media-slot and stay non-interactive (this is a visual
   product exhibit, not a control): the custom element also sets
   aria-hidden/tabindex on itself and neutralizes every focusable element
   inside its shadow root, but pointer-events must be disabled here at
   the host-element level since Shadow DOM doesn't block mouse events by
   itself. */
sts-live-scene {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
/* .scene-fallback-note here only ever renders inside the <noscript>
   fallback (JS disabled) — the shadow-root equivalent (load failure) is
   styled by sts-live-scene.js's own adopted stylesheet, since outer page
   CSS cannot reach inside a shadow root. A deliberate, neutral "product
   preview unavailable" message either way — never a silent revert to a
   screenshot. */
.scene-fallback-note {
  position: absolute; inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center; justify-content: center;
  padding: 24px;
  text-align: center;
  background: var(--color-bg-soft);
}
.scene-fallback-note .mm-title {
  font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em;
  color: var(--color-muted-2);
}
.media-slot-thumb .scene-fallback-note .mm-title { font-size: 0.58rem; }
.media-missing-note {
  display: none;
  position: absolute; inset: 0;
  flex-direction: column;
  align-items: center; justify-content: center;
  gap: 8px;
  padding: 24px;
  text-align: center;
}
.media-slot.is-missing img,
.media-slot.is-missing video { display: none; }
.media-slot.is-missing .media-missing-note { display: flex; }
.media-missing-note .mm-title {
  font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em;
  color: var(--accent-warm);
}
.media-missing-note .mm-path {
  font-family: Consolas, 'SF Mono', monospace;
  font-size: 0.7rem;
  color: var(--color-muted-2);
  max-width: 32ch;
}

/* Pre-Stage-6: one consistent mobile media/text column system for every
   Player Experience state — replaces the old per-scene fixed-pixel
   widths (84/100 base, 148/172 for the Checkpoint pair, 260/300 for
   Torch Trial), which produced visibly inconsistent proportions state
   to state (state 1 had no media at all; Torch Trial's thumbnails ran
   ~65-70% of the row; Checkpoint's own frame rendered its real content
   as a narrow strip — a genuine scaling bug in _applyContainFit(), see
   sts-live-scene.js, not a sizing problem). The media column is now a
   fixed fraction of the row (roughly 1/3) regardless of which scene it
   holds; .media-slot-thumb simply fills whatever width that column
   gives it, and its own aspect-ratio (set inline per state, see
   stt_media_scene()) determines its height from that width. */
.experience-state-media { flex: 0 0 32%; max-width: 150px; }
@media (min-width: 480px) { .experience-state-media { max-width: 168px; } }
@media (max-width: 359px) { .experience-state-media { flex-basis: 34%; } }
.media-slot-thumb { width: 100%; border-radius: 12px; }
.media-slot-thumb .mm-title { font-size: 0.58rem; }
.media-slot-thumb .mm-path { display: none; } /* too narrow to be legible at this size */
/* Mobile Checkpoint correction: survival-before and survival-after used
   to render as two separate thumbnails side by side here — at these
   widths that meant two frames narrow enough to be genuinely hard to
   read, and confusing rather than communicating "Day 9 becomes Day 10."
   assets/js/checkpoint-mobile.js now re-targets the SAME survival-before
   element (using the unified media-column width above) to
   survival-after in place via <sts-live-scene>'s own setScene()
   crossfade, so the second thumbnail's markup (kept in the DOM — desktop
   still reads it via querySelectorAll to build its own before/after
   pair, see player-experience.js) is simply never shown here. */
@media (max-width: 991px) {
  .media-slot-thumb-next:has(sts-live-scene[data-scene="survival-after"]) {
    display: none;
  }
}

/* Stage 5D: border-radius/shadow only — sizing now lives entirely on
   .climax-panel below. This used to set its own max-width per
   breakpoint, but .media-slot-climax's only real in-flow content is a
   `sr-only` span (the actual scene is position:absolute, so it makes no
   intrinsic-size contribution at all) — inside a flex item
   (.climax-panel) with width:auto, that meant THIS element contributed
   ~nothing to its parent's shrink-to-fit width, so .climax-panel's width
   was actually being decided by its own TEXT content instead (the
   confirmed root cause of Day30 and Jackpot rendering at visibly
   different widths — 239.5px vs 326.2px at 1440px, measured before this
   fix, purely because "Jackpot Unlocked"'s copy happens to be longer
   than Day30's). Giving the width to .climax-panel itself (a definite
   value, not max-width) fixes both panels — and this frame — at once. */
.media-slot-climax { border-radius: var(--radius-lg); width: 100%; }
.media-slot-modal { max-width: 380px; margin: 0 auto; border-radius: var(--radius-lg); }

/* Dev-only placeholder marker — search the codebase for "dev-placeholder"
   to find and remove every one of these before final production launch. */
.dev-placeholder {
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.02);
}

/* ── Split (shared two-column responsibility/comparison layout) ───────── */

.split { display: grid; gap: 28px; margin-top: 40px; }
@media (min-width: 900px) { .split { grid-template-columns: 1fr 1fr; } }
.split-col {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 32px 30px;
  background: var(--color-surface);
}
.split-col.is-response { background: var(--color-surface-raised); border-color: var(--color-border-strong); }
.split-label { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.1em; color: var(--color-muted-2); margin-bottom: 16px; }
.point-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.point-list li { padding-left: 20px; position: relative; color: var(--color-text); font-size: 0.95rem; }
.point-list li::before { content: ''; position: absolute; left: 0; top: 0.55em; width: 7px; height: 7px; background: var(--accent); border-radius: 1px; }

.locked-note { margin-top: 24px; font-size: 0.88rem; font-style: italic; color: var(--color-muted-2); max-width: 640px; }

/* ── Model / process flow (Launch-Play-Measure, Pilot 6-step) ─────────── */

.model-flow { display: flex; flex-direction: column; gap: 0; margin-top: 8px; }
@media (min-width: 992px) {
  .model-flow { flex-direction: row; align-items: stretch; }
}
.model-step {
  position: relative;
  flex: 1;
  padding: 22px 20px 22px 0;
  border-left: 2px solid var(--color-border-strong);
  padding-left: 24px;
  margin-left: 14px;
}
@media (min-width: 992px) {
  .model-step {
    border-left: none;
    border-top: 2px solid var(--color-border-strong);
    padding: 28px 18px 0;
    margin-left: 0;
  }
}
.model-step-num {
  position: absolute;
  left: -15px; top: 16px;
  width: 28px; height: 28px;
  border-radius: 50%;
  background: var(--color-bg);
  border: 2px solid var(--accent);
  color: var(--accent-bright);
  font-weight: 800; font-size: 0.8rem;
  display: flex; align-items: center; justify-content: center;
}
@media (min-width: 992px) {
  .model-step-num { left: 18px; top: -15px; }
}
.model-step h3 { font-size: 1rem; margin-bottom: 6px; }
.model-step p { font-size: 0.88rem; margin: 0; }

/* Six-step pilot flow needs tighter columns than the 3-step Launch/Play/Measure */
@media (min-width: 992px) {
  .model-flow-6 .model-step { padding-inline: 12px; }
  .model-flow-6 .model-step h3 { font-size: 0.92rem; }
  .model-flow-6 .model-step p { font-size: 0.82rem; }
}

.pilot-cta { margin-top: 36px; }

/* ── Player-experience section (Section 2) ─────────────────────────────── */
/* Desktop gives the device zone equal-or-greater width than the copy
   column and a tall phone aspect ratio, plus generous vertical rhythm
   between states, so this reads as the site's main storytelling stage —
   not a small phone floating beside seven equal cards — and so Stage 5
   has real scroll distance to drive state changes against without any
   layout rework. */

.experience-grid { display: grid; gap: 44px; margin-top: 8px; }
@media (min-width: 992px) { .experience-grid { grid-template-columns: 1.1fr 0.9fr; align-items: start; } }

/* Two-layer sticky, on purpose (see index.php's comment at this markup):
   .experience-device-slot is the grid item — stretched to the FULL row
   height on desktop, matching .experience-states (the taller sibling
   column), so there's genuine room in the layout for something to stay
   stuck all the way through the last narrative state.
   .experience-device-sticky is the actual position:sticky element inside
   it, sized to its own natural (short) content. Collapsing these into
   one element was a real, confirmed bug: a sticky element that ALSO
   stretches to match its own containing block has zero spare room to
   ever move (container height - element height = 0), so it just sat at
   the top of the row and scrolled away with the page well before Day 30
   — the sticky "containing block" must be taller than the sticky
   element itself, and in a CSS Grid row that only happens if the sticky
   behavior lives on an inner element, not the stretched grid item
   itself. */
.experience-device-slot { position: relative; }
@media (min-width: 992px) { .experience-device-slot { align-self: stretch; } }
@media (max-width: 991px) { .experience-device-slot { margin-bottom: 32px; } }

.experience-device-sticky { position: static; }
/* Stage 5C: measured .site-nav's real rendered height at ~88.5px across
   every desktop breakpoint tested (it doesn't vary with viewport width).
   The old top:90px therefore left only ~1.5px of visible gap below the
   header — the product stage read as glued to the nav. top:122px is
   88.5px (header) + ~34px breathing room, landing in the requested
   30-40px visible-gap range; confirmed via Playwright measurement of the
   actual stuck position (not the sticky element's un-stuck flow
   position). This same 162px also appears in .device-frame-large's
   height-budget calc() above — 122px (this offset) + 40px (bottom
   breathing room) — the two numbers describe one shared vertical layout,
   not two independent guesses. */
@media (min-width: 992px) { .experience-device-sticky { position: sticky; top: 122px; } }

.experience-states { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 28px; counter-reset: none; }
@media (min-width: 992px) {
  /* Stage 4F: was gap:100px + min-height:32vh per state — ~2700px of
     dead vertical space across seven states, well past what's needed for
     Stage 5's future IntersectionObserver activation. A block's own
     content (num + title + copy) is naturally ~120-150px tall; a 64px
     gap on top of that lands the 180-260px per-state spacing target the
     Stage 4F brief asks for, without hardcoding height to viewport size. */
  /* padding-bottom: measured empirically against the actual sticky
     budget (outer wrapper height minus the sticky device's own height)
     needed for the device to stay stuck all the way through state 07's
     centered reading position — the two-layer split above makes real
     budget possible at all, but the container still has to be tall
     enough to cover the actual scroll distance to the last state. */
  .experience-states { gap: 64px; padding-top: 24px; padding-bottom: 450px; }
}
.experience-state {
  display: flex;
  align-items: flex-start;
  gap: 20px;
}
/* Stage 5C: measured natural content height is only ~99-123px per state
   (six of seven states wrap to a single line of body copy). Combined
   with the 64px gap, that's a ~163-187px scroll "slot" per state — short
   enough that a fast-but-ordinary wheel/trackpad scroll can move the
   viewport clean through one state's activation zone between two
   IntersectionObserver callback opportunities, which is the confirmed
   root cause of the state-skipping bug (see assets/js/player-experience.js
   and the Stage 5C report). This does not fully solve that on its own —
   the activation algorithm itself changed too — but giving every state
   real scroll territory to own makes the skip far less likely regardless
   of exactly how fast a visitor scrolls. Deliberately far short of the
   old pre-Stage-4F 32vh minimum (which this must not reintroduce). */
@media (min-width: 992px) {
  .experience-state { min-height: clamp(220px, 25vh, 280px); }
}
/* Stage 4F: on desktop there is exactly one primary product stage
   (.experience-device-slot, sticky, at left) — these per-state
   thumbnails duplicated it beside every narrative block. Below 992px
   there's no separate sticky stage in view while scrolling narrative
   copy, so each state keeps its own inline scene for now; a mobile
   orchestration decision for Stage 5 is explicitly out of scope here. A
   display:none scene never intersects the viewport, so its
   <sts-live-scene> simply never fetches/loads on desktop — no wasted
   network requests, not just a hidden box. */
@media (min-width: 992px) {
  .experience-state-media { display: none; }
}
.experience-state-body {
  border-left: 2px solid var(--color-border);
  padding-left: 22px;
  position: relative;
  flex: 1;
}
.experience-state-num {
  display: block;
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  color: var(--accent);
  margin-bottom: 6px;
}
.experience-state h3 { font-size: 1.15rem; margin-bottom: 6px; }
.experience-state p { font-size: 0.95rem; margin: 0; }
.experience-state-tagline {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  font-weight: 800;
  font-size: 1.02rem;
  color: var(--color-text);
  margin: 0 0 6px;
}
/* Pre-Stage-6 desktop polish: the narrative text block was flush against
   its own accent line (padding-left:22px, no top/bottom/right inset at
   all) with every hierarchy level packed at the same 6px margin —
   number, tagline, heading and description all read as one dense
   paragraph rather than a deliberate hierarchy. Desktop-only (mobile's
   own layout is handled separately below in the 1/3-2/3 restructure);
   editorial breathing room, not a card — no background/border added
   beyond the existing accent line. */
@media (min-width: 992px) {
  .experience-state-body {
    padding: clamp(6px, 0.8vw, 10px) clamp(20px, 1.6vw, 28px) clamp(6px, 0.8vw, 10px) clamp(28px, 2.4vw, 36px);
  }
  .experience-state-num { font-size: 0.78rem; margin-bottom: 10px; }
  .experience-state-tagline { font-size: clamp(1.05rem, 1.1vw, 1.15rem); margin-bottom: 12px; }
  .experience-state h3 { font-size: clamp(1.2rem, 1.3vw, 1.35rem); margin-bottom: 12px; }
  .experience-state p { font-size: clamp(0.96rem, 1vw, 1.04rem); line-height: 1.6; }
}

/* Stage 5: which narrative state is "current" — driven by
   player-experience.js (desktop only; below 992px every state is always
   full-strength since there's no single stage to point elsewhere).
   Inactive copy is dimmed, never hidden — the full narrative stays in
   the DOM and readable without JS or with motion disabled.
   Specificity note: .experience-state also carries the generic .reveal
   entrance-fade class, and .js-reveal-ready .reveal.is-visible{opacity:1}
   (three classes) otherwise beats a plain .experience-state.is-muted
   (two classes) regardless of source order — every selector below
   repeats .is-visible for exactly that reason, confirmed by a real
   computed-opacity check during Stage 5 testing (muting silently did
   nothing without it). */
@media (min-width: 992px) {
  .experience-state { transition: opacity 320ms ease; }
  /* Stage 5B: two mute tiers (near vs far, set as classes by
     player-experience.js from distance-to-active) instead of one flat
     0.42 — real Stage 5 screenshots showed most of the seven-state
     sequence disappearing into the background, which defeated the
     point of a scannable journey. Hierarchy comes from more than
     opacity alone: the active state also gets a brighter border and a
     brighter step number/eyebrow (below), so muted states stay legible
     without needing to be nearly full-strength. */
  .experience-state.is-far.is-visible,
  .js-reveal-ready .experience-state.is-far.is-visible { opacity: 0.55; }
  .experience-state.is-near.is-visible,
  .js-reveal-ready .experience-state.is-near.is-visible { opacity: 0.7; }
  .experience-state.is-active.is-visible,
  .js-reveal-ready .experience-state.is-active.is-visible { opacity: 1; }
  .experience-state.is-active .experience-state-body { border-left-color: var(--accent); }
  .experience-state.is-active .experience-state-num { color: var(--accent-bright); }
  .experience-state.is-active .experience-state-tagline { color: var(--color-text); }
  .experience-state:not(.is-active) .experience-state-num { color: var(--color-muted-2); }
}

/* A small "N / 7" — not pagination, not a tab strip, just a quiet
   position marker. Decorative and redundant with the narrative's own
   reading order, so aria-hidden (set in index.php) and never a tab
   stop. */
.experience-progress {
  position: absolute;
  top: 14px; right: 16px;
  z-index: 3;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--color-muted-2);
  background: rgba(0, 0, 0, 0.35);
  padding: 4px 9px;
  border-radius: 999px;
  pointer-events: none;
}

/* A brief external glow around the device frame — never inside the
   Shadow DOM — for the Checkpoint state's before/after handoff (Stage 5
   brief section 18). Opacity/box-shadow only. */
.experience-device-slot .device-frame { transition: box-shadow 900ms ease; }
.stage-pulse .device-frame { box-shadow: var(--shadow-card), 0 0 0 1px rgba(0, 0, 0, 0.4), 0 0 70px 6px var(--hero-glow-1); }

/* Quiet atmospheric progression across the seven states (Stage 5 brief
   section 22) — one felt-not-noticed background wash per state, not
   seven separate themes. Applies to the sticky stage's own backdrop
   glow, reusing the same device-stage::before technique as the hero.
   Values chosen to read as a single continuous arc: controlled -> the
   environment stirs -> strongest genuine game atmosphere (Torch Trial)
   -> warm progression -> quieter anticipation -> darker/tense -> a
   resolved warm climax. */
@media (min-width: 992px) {
  /* On .experience-device-sticky (the actual sticky element, position
     static below 992px otherwise), not the outer .experience-device-slot
     — the glow needs to visually track the pinned device as it stays in
     place, not sit fixed within the tall stretched grid-row wrapper. */
  .experience-device-sticky::before {
    content: '';
    position: absolute;
    inset: -40px;
    z-index: -1;
    border-radius: 40px;
    pointer-events: none;
    background: radial-gradient(ellipse 60% 50% at 50% 20%, var(--atmosphere-glow, var(--hero-glow-1)), transparent 70%);
    opacity: var(--atmosphere-opacity, 0.4);
    transition: opacity 900ms ease, background 900ms ease;
  }
  .experience-grid[data-atmosphere="neutral"]  { --atmosphere-opacity: 0.35; }
  .experience-grid[data-atmosphere="awaken"]   { --atmosphere-opacity: 0.5; --atmosphere-glow: rgba(200, 95, 42, 0.10); }
  .experience-grid[data-atmosphere="peak"]     { --atmosphere-opacity: 0.7; --atmosphere-glow: rgba(216, 174, 92, 0.16); }
  .experience-grid[data-atmosphere="warm"]     { --atmosphere-opacity: 0.6; --atmosphere-glow: rgba(216, 174, 92, 0.14); }
  .experience-grid[data-atmosphere="quiet"]    { --atmosphere-opacity: 0.3; }
  .experience-grid[data-atmosphere="tense"]    { --atmosphere-opacity: 0.25; --atmosphere-glow: rgba(0, 0, 0, 0.4); }
  .experience-grid[data-atmosphere="resolved"] { --atmosphere-opacity: 0.75; --atmosphere-glow: rgba(216, 174, 92, 0.20); }
}

/* Checkpoint state's mobile-inline pair (Before, then After) — a small
   arrow between them reads as "becomes," not two unrelated screenshots.
   Stage 5B: this unconditional `display: flex` was a real, confirmed
   regression — it comes after (and at equal specificity to) the Stage 4F
   rule a few lines up that hides .experience-state-media entirely on
   desktop, so it silently won the cascade and brought every per-state
   mini preview back at >=992px. Scoped to mobile only, where these
   thumbnails are the only place this pairing ever appears. */
@media (max-width: 991px) {
  .experience-state-media { position: relative; display: flex; align-items: center; gap: 8px; }
}
.media-slot-thumb-next { position: relative; }
.media-slot-thumb-next::before {
  content: '\2192';
  position: absolute;
  left: -14px; top: 50%;
  transform: translateY(-50%);
  color: var(--color-muted-2);
  font-size: 0.9rem;
}

/* ── 30-Day Survival Journey rail (Section 3) ──────────────────────────── */
/* Stage 4F: this used to be a 4-column grid holding 6 milestone stops
   plus a 7th full-width jackpot cell (grid-column:1/-1) — with 6 regular
   items across 4 columns, row 2 held only 2 of them, and the jackpot
   cell's forced full-width span left row 2's other two column tracks
   completely empty. That empty grid area was exactly the "broken gold
   rectangle" the Stage 4F brief reported. Fixed by giving the rail
   exactly as many columns as it has real milestone stops (6) so every
   row fills evenly with no remainder, and moving the jackpot attempt
   into its own block below instead of a grid cell that never fit. */
.journey-rail {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  display: grid;
  gap: 2px;
  background: var(--color-border);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  grid-template-columns: 1fr;
  position: relative;
}
@media (min-width: 560px) { .journey-rail { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1100px) { .journey-rail { grid-template-columns: repeat(6, 1fr); } }
.journey-stop {
  background: var(--color-surface);
  padding: 26px 22px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  position: relative;
}
/* A small chevron between adjacent stops reads as "→" without an actual
   arrow glyph fighting the numeric multiplier for attention. Desktop
   only, where the rail is a single continuous row. */
@media (min-width: 1100px) {
  .journey-stop:not(:last-child)::after {
    content: '';
    position: absolute; right: -1px; top: 50%; z-index: 2;
    width: 7px; height: 7px;
    transform: translate(50%, -50%) rotate(45deg);
    background: var(--color-bg);
    border-top: 1px solid var(--color-border-strong);
    border-right: 1px solid var(--color-border-strong);
  }
}
.journey-day { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.1em; color: var(--color-muted-2); }
.journey-multiplier { font-size: 1.5rem; font-weight: 800; color: var(--accent-bright); transition: font-size var(--transition); }
.journey-label { font-size: 0.85rem; color: var(--color-muted); }

/* Emotional weight grows toward Day 30 — each gate reads slightly larger
   and brighter than the last, so the climax is felt, not just labeled. */
.journey-stop:nth-child(3) .journey-multiplier { font-size: 1.65rem; }
.journey-stop:nth-child(4) .journey-multiplier { font-size: 1.8rem; }
.journey-stop:nth-child(5) .journey-multiplier { font-size: 1.95rem; }
.journey-stop.is-final { background: var(--color-surface-raised); }
.journey-stop.is-final .journey-multiplier { font-size: 2.3rem; color: var(--accent); text-shadow: 0 0 22px var(--hero-glow-1); }

/* Stage 5: the rail lights up left-to-right, once, the first time it
   scrolls into view — reusing the existing .reveal/is-visible mechanism
   (assets/js/site.js already unobserves after the first trigger, so this
   can never replay). A plain nth-child transition-delay is enough for a
   "line progression" feel without a bespoke observer of its own. */
.js-reveal-ready .journey-rail .journey-stop.reveal { transition-duration: 520ms; }
.journey-rail .journey-stop:nth-child(1) { transition-delay: 0ms; }
.journey-rail .journey-stop:nth-child(2) { transition-delay: 70ms; }
.journey-rail .journey-stop:nth-child(3) { transition-delay: 140ms; }
.journey-rail .journey-stop:nth-child(4) { transition-delay: 210ms; }
.journey-rail .journey-stop:nth-child(5) { transition-delay: 280ms; }
.journey-rail .journey-stop.is-final { transition-delay: 350ms; }
@keyframes journey-final-glow { from { box-shadow: 0 0 0 0 transparent; } to { box-shadow: 0 0 34px 2px var(--hero-glow-1); } }
.js-reveal-ready .journey-rail .journey-stop.is-final.is-visible { animation: journey-final-glow 700ms ease 480ms both; }

/* Stage 5E: the four-layer "Core Product" overview — reuses the same
   connected-list pattern as .model-flow/.model-step (border-left on
   mobile, border-top on desktop) rather than a new component, so it
   reads as part of this site's existing visual language instead of a
   bolted-on diagram. No numbered badges here on purpose — these are
   overlapping layers of the same product, not a 4-step sequence a
   player moves through once. */
.core-layers { display: flex; flex-direction: column; gap: 0; margin-top: 32px; }
@media (min-width: 992px) { .core-layers { flex-direction: row; align-items: stretch; } }
.core-layer {
  flex: 1;
  padding: 16px 18px 16px 22px;
  border-left: 2px solid var(--color-border-strong);
  margin-left: 2px;
}
@media (min-width: 992px) {
  .core-layer { border-left: none; border-top: 2px solid var(--color-border-strong); padding: 22px 18px 0; margin-left: 0; }
}
.core-layer h3 { font-size: 1rem; margin-bottom: 6px; color: var(--accent-bright); }
.core-layer p { font-size: 0.88rem; color: var(--color-muted); margin: 0; }
.core-layers-tagline {
  margin: 26px 0 0;
  text-align: center;
  font-family: 'Metal Mania', Cinzel, Georgia, serif;
  font-size: clamp(1.1rem, 2.4vw, 1.4rem);
  color: var(--cta-gold-bright);
}

/* Text-only, explicitly labelled future/optional — see
   journey.megastreak in config/content.php. Visually quieter than the
   real, current Day-30 jackpot note above (dotted border already used
   for "riding on top of the run" elements, muted label color, no accent
   gold) so it never reads as an equally-real, already-available
   feature. */
.journey-megastreak-note {
  margin-top: 14px;
  padding: 16px 22px;
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-lg);
}
.journey-megastreak-label { font-weight: 700; color: var(--color-text); margin-right: 10px; }
.journey-megastreak-status {
  font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--color-muted-2); border: 1px solid var(--color-border); border-radius: 999px; padding: 3px 10px;
}
.journey-megastreak-note p { margin: 8px 0 0; font-size: 0.86rem; color: var(--color-muted); max-width: 68ch; }

/* Small secondary-cadence line above the existing weekend cards —
   strengthens visibility per the Stage 5E brief without redesigning the
   cards themselves. */
.weekend-cadence { margin: 0 0 14px; font-size: 0.8rem; color: var(--color-muted-2); }
.weekend-cadence-step { text-transform: uppercase; letter-spacing: 0.06em; font-weight: 700; color: var(--accent); }
.weekend-cadence-arrow { margin: 0 8px; color: var(--color-border-strong); }

/* A separate epilogue, not a 7th milestone of equal weight — connected to
   Day 30 by a short arrow rather than sharing its grid. */
.journey-jackpot-note {
  margin-top: 18px;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 18px 24px;
  background: var(--color-bg-soft);
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius-lg);
}
.journey-jackpot-arrow { flex-shrink: 0; font-size: 1.3rem; color: var(--accent); }
.journey-jackpot-note .journey-day { color: var(--accent); }

/* Deliberately smaller and dashed, so these read as a layer riding on top
   of the run rather than two more milestone gates of equal weight. */
.weekend-layer { margin-top: 32px; padding-top: 28px; border-top: 1px dashed var(--color-border-strong); }
.weekend-intro { font-size: 0.86rem; font-style: italic; color: var(--color-muted-2); max-width: 560px; margin-bottom: 18px; }
.weekend-cards { display: grid; gap: 14px; grid-template-columns: 1fr; max-width: 640px; }
@media (min-width: 700px) { .weekend-cards { grid-template-columns: repeat(2, 1fr); } }
.weekend-card { border: 1px dashed var(--color-border-strong); border-radius: var(--radius); padding: 16px 18px; background: transparent; }
.weekend-card h3 { font-size: 0.9rem; color: var(--accent-bright); margin-bottom: 4px; }
.weekend-card p { font-size: 0.82rem; margin: 0; }

/* Stage 5E: the core operator question, given its own visual moment —
   "not simply a CTA headline... the economic question STS is built
   around" per the brief. Deliberately plain (no card, no border, no
   background box) so it reads as a statement the page is making, not
   another content module. */
.active-day-statement { padding-block: clamp(48px, 7vw, 80px); }
.active-day-statement-text {
  margin: 0; text-align: center;
  font-family: 'Metal Mania', Cinzel, Georgia, serif;
  font-size: clamp(1.7rem, 4vw, 2.6rem);
  line-height: 1.3;
  max-width: 900px; margin-inline: auto;
  color: var(--color-text);
}

/* ── Two commercial value engines (Section 4, Stage 5E) ────────────────── */
.value-engines { margin: 28px 0 44px; padding-bottom: 36px; border-bottom: 1px solid var(--color-border); }
.value-engines-intro { font-size: 1rem; color: var(--color-muted); margin: 0 0 20px; }
.value-engines-row {
  display: flex; flex-direction: column; align-items: stretch; gap: 16px;
}
@media (min-width: 800px) { .value-engines-row { flex-direction: row; align-items: center; gap: 24px; } }
.value-engine {
  flex: 1;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-lg);
  padding: 22px 24px;
  background: var(--color-surface);
}
.value-engine-label { display: block; font-weight: 800; font-size: 1.05rem; color: var(--accent-bright); margin-bottom: 8px; }
.value-engine p { margin: 0; font-size: 0.92rem; color: var(--color-muted); }
.value-engines-plus {
  flex-shrink: 0; align-self: center;
  font-family: 'Metal Mania', Cinzel, Georgia, serif;
  font-size: 1.4rem; color: var(--color-muted-2);
}
.value-engines-total {
  margin-top: 20px; padding-top: 18px; border-top: 1px dashed var(--color-border-strong);
  display: flex; align-items: baseline; gap: 12px; justify-content: center;
  font-weight: 800; font-size: 1.05rem; color: var(--cta-gold-bright); text-align: center;
}
.value-engines-total span[aria-hidden] { font-family: 'Metal Mania', Cinzel, Georgia, serif; font-size: 1.3rem; }

/* ── Large operator-outcomes grid (Section 4) ──────────────────────────── */

.outcomes-grid-large { display: grid; gap: 22px; grid-template-columns: 1fr; margin-top: 8px; }
@media (min-width: 800px) { .outcomes-grid-large { grid-template-columns: repeat(2, 1fr); } }
.outcome-block-large {
  border-left: 2px solid var(--color-border-strong);
  padding: 6px 0 6px clamp(20px, 2.4vw, 32px);
  background: none;
}
.outcome-block-large h3 { font-size: 1.5rem; color: var(--accent-bright); }
.outcome-block-large p { font-size: 1.02rem; margin: 0; max-width: 46ch; }

.measurement-strip { margin-top: 52px; padding-top: 32px; border-top: 1px solid var(--color-border); }
.measurement-label {
  display: inline-block; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--color-muted-2); border: 1px solid var(--color-border); border-radius: 999px; padding: 6px 14px; margin-bottom: 22px;
}
/* Plain inline tags rather than a grid of equal bordered chips — a
   measurement scope reads as rigorous without looking like a generic
   SaaS features grid. */
.measurement-tags { display: flex; flex-wrap: wrap; gap: 10px 8px; }
.measurement-tag {
  font-size: 0.8rem; color: var(--color-muted); padding: 6px 4px; position: relative;
}
.measurement-tag:not(:last-child)::after { content: '·'; margin-left: 14px; color: var(--color-border-strong); }

/* Stage 5B: "We can prove or disprove the value" is the strongest
   commercial statement in this section — larger type, more breathing
   room, a thin accent rule to anchor it, rather than reading as just
   another paragraph-sized line. */
.tc-headline {
  margin-top: 56px;
  padding-top: 28px;
  border-top: 1px solid var(--color-border);
  font-size: clamp(1.4rem, 2.6vw, 1.9rem);
  font-weight: 800;
  text-align: center;
  color: var(--color-text);
  max-width: 640px;
  margin-inline: auto;
}

.test-control { display: grid; gap: 16px; grid-template-columns: 1fr; align-items: center; margin-top: 24px; }
@media (min-width: 700px) { .test-control { grid-template-columns: 1fr auto 1fr; } }
.tc-box { border: 1px solid var(--color-border-strong); border-radius: var(--radius-lg); padding: 24px; background: var(--color-surface-raised); }
.tc-box-muted { border-style: dashed; background: var(--color-surface); }
.tc-label { display: block; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--accent); margin-bottom: 8px; }
.tc-box p { margin: 0; font-size: 0.9rem; }
.tc-arrow { text-align: center; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-muted-2); }
.tc-note { margin-top: 16px; font-size: 0.8rem; font-style: italic; color: var(--color-muted-2); max-width: 620px; }

/* ── Comparison grid (Section 5 — Why STS Is Different) ────────────────── */

.comparison-grid { display: grid; gap: 16px; grid-template-columns: 1fr; margin-top: 8px; }
@media (min-width: 700px) { .comparison-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1100px) { .comparison-grid { grid-template-columns: repeat(4, 1fr); } }
.comparison-card { border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: 24px 22px; background: var(--color-surface); }
.comparison-card h3 { font-size: 1rem; margin-bottom: 8px; }
.comparison-card p { font-size: 0.86rem; margin: 0; }
.comparison-card-sts {
  margin-top: 20px;
  padding: clamp(28px, 3vw, 40px);
  background: var(--color-surface-raised);
  border-color: var(--accent);
  box-shadow: var(--shadow-cta);
}
.comparison-card-sts h3 { font-size: 1.4rem; color: var(--accent-bright); }
.comparison-card-sts p { font-size: 1rem; max-width: 60ch; }
.comparison-card-sts .point-list { margin-top: 18px; columns: 1; }
@media (min-width: 700px) { .comparison-card-sts .point-list { columns: 2; column-gap: 32px; } }
.comparison-card-sts .point-list li { break-inside: avoid; margin-bottom: 4px; }

/* ── Trust strip (Section 6) — embedded architecture facts, not a bolted-on
   "Security" panel, so no card borders: a plain divided row instead. ──── */

.trust-strip {
  display: grid; gap: 22px 28px; grid-template-columns: 1fr;
  margin-top: 44px; padding-top: 28px; border-top: 1px solid var(--color-border);
}
@media (min-width: 700px) { .trust-strip { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1100px) { .trust-strip { grid-template-columns: repeat(4, 1fr); } }
.trust-strip-item { padding: 0; background: none; }
.trust-strip-item h3 { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--accent); margin-bottom: 6px; }
.trust-strip-item p { font-size: 0.84rem; margin: 0; }

/* ── Climax / final conversion block (Section 8) ───────────────────────── */

.climax { text-align: center; }
.climax { position: relative; }
.climax::before {
  content: '';
  position: absolute; inset: 0; z-index: -1;
  background: radial-gradient(ellipse 800px 500px at 50% 20%, var(--hero-glow-1), transparent 65%);
}
.climax .section-title { max-width: 760px; margin-inline: auto; font-size: clamp(2rem, 4vw, 3rem); }

/* Two distinct, sequential climax states — never one ambiguous image. */
.climax-sequence {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  margin-bottom: 48px;
}
@media (min-width: 800px) { .climax-sequence { flex-direction: row; justify-content: center; align-items: flex-start; gap: 28px; } }
/* Stage 5D: width (not max-width) — .climax-panel is a flex item of
   .climax-sequence, so width:auto (what max-width alone left it with)
   made its actual rendered size a shrink-to-fit calculation over its
   own text content, not a real shared dimension. Day30's and Jackpot's
   copy are different lengths, so the two panels — and the product-stage
   frame inside each, which has no sizing of its own anymore (see
   .media-slot-climax above) — rendered at visibly different widths.
   One definite width, used identically by both, is what actually makes
   them "the same shared frame." Mobile keeps the old 280px value (still
   comfortable inside typical side margins at 390/360); desktop uses a
   single flat value rather than the old three-tier progression — the
   brief's ~360-380px target is a single number, not something that
   needs to keep growing above 800px, and a flat value is what keeps the
   two frames matched at every desktop breakpoint tested. */
.climax-panel { width: min(280px, 100%); text-align: center; }
@media (min-width: 800px) { .climax-panel { width: 370px; } }
.climax-panel .media-slot-climax { box-shadow: 0 0 60px var(--hero-glow-1); margin-bottom: 14px; }
.climax-panel-label {
  display: block;
  font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em;
  color: var(--accent); margin-bottom: 4px;
}
.climax-panel-sublabel { display: block; font-size: 1.1rem; font-weight: 800; color: var(--accent-bright); margin-bottom: 8px; }
.climax-panel-text { font-size: 0.86rem; color: var(--color-muted); margin: 0; }
/* Discloses climax_b's illustrative concept status — see
   assets/scenes/README.md, "Concept scene". Part of the accessible
   parent-page narrative (the scene's shadow host itself is aria-hidden).
   Remove alongside content.php's climax_b.concept_note once a real
   implementation replaces the concept. */
.climax-panel-concept-note {
  font-size: 0.7rem;
  font-style: italic;
  color: var(--color-muted-2);
  margin: 8px 0 0;
}
.climax-connector {
  align-self: center;
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.1em; color: var(--color-muted-2);
  padding: 6px 14px; border: 1px solid var(--color-border); border-radius: 999px;
}
/* Stage 5D: align-self:center above centers the connector against the
   whole ROW's cross-axis span (from the shorter of the two panels' top
   to the taller one's bottom) — not against the frames specifically.
   Day30 and Jackpot's supporting copy runs a slightly different number
   of lines below each frame, which pulled that row-center a measured
   ~25px below the frames' own true vertical center. Now that both
   frames are pinned to the same width/height (see .climax-panel and
   .media-slot-climax above), that center is a known, fixed offset from
   the row's own top: label block height (~22.4px) + half the frame's
   height (370px wide at the shared 1080/1920 ratio = 657.8px tall) minus
   half the connector's own height — 335px, confirmed by direct
   measurement against the rendered frame. Re-measure and adjust this if
   the shared frame width, label font size, or connector padding ever
   change. Mobile is unaffected (.climax-sequence stacks in a plain
   column below 800px, where the connector already sits naturally
   between the two stacked panels). */
@media (min-width: 800px) {
  .climax-connector { align-self: flex-start; margin-top: 335px; }
}
.final-cta-actions { display: flex; flex-wrap: wrap; gap: 14px; justify-content: center; margin-top: 30px; }

/* ── FAQ ──────────────────────────────────────────────────────────────── */

.faq-list { border-top: 1px solid var(--color-border); max-width: 800px; margin: 56px auto 0; text-align: left; }
.faq-item { border-bottom: 1px solid var(--color-border); }
.faq-question {
  width: 100%; text-align: left; background: none; border: none; color: var(--color-text);
  font-size: 1rem; font-weight: 700; padding: 20px 4px; display: flex; align-items: center; justify-content: space-between; gap: 16px;
  cursor: pointer;
}
.faq-question:hover { color: var(--accent-bright); }
.faq-icon { flex-shrink: 0; transition: transform var(--transition); color: var(--accent); }
.faq-question[aria-expanded="true"] .faq-icon { transform: rotate(45deg); }
.faq-answer { max-height: 0; overflow: hidden; transition: max-height var(--transition); }
.faq-answer-inner { padding: 0 4px 20px; font-size: 0.92rem; color: var(--color-muted); max-width: 760px; }

/* ── Contact form ─────────────────────────────────────────────────────── */

.contact-card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  padding: clamp(24px, 4vw, 44px);
  max-width: 760px;
  margin: 44px auto 0;
  text-align: left;
}
.form-row { display: grid; gap: 18px; grid-template-columns: 1fr; margin-bottom: 18px; }
@media (min-width: 700px) { .form-row.cols-2 { grid-template-columns: 1fr 1fr; } }
.form-group { display: flex; flex-direction: column; gap: 6px; }
.form-label { font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--color-muted-2); }
.form-label .req { color: var(--accent-warm); }
.form-control {
  background: var(--color-bg-soft);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  color: var(--color-text);
  padding: 12px 14px;
  font-size: 0.95rem;
  font-family: inherit;
  width: 100%;
}
.form-control:focus { border-color: var(--accent); outline: none; }
textarea.form-control { resize: vertical; min-height: 120px; }
.form-check { display: flex; align-items: flex-start; gap: 10px; font-size: 0.85rem; color: var(--color-muted); margin: 6px 0 22px; }
.form-check input { margin-top: 3px; }
.form-error { color: var(--color-error); font-size: 0.8rem; margin-top: 4px; }
.form-alert { border: 1px solid var(--color-border-strong); border-radius: var(--radius); padding: 14px 16px; font-size: 0.88rem; margin: 44px auto 0; max-width: 760px; text-align: left; }
.form-alert.is-error { border-color: rgba(217, 118, 90, 0.5); color: var(--color-error); }
.form-alert.is-success { border-color: rgba(111, 174, 114, 0.5); color: var(--color-success); }
.honeypot-field { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }
.turnstile-field { margin: 4px 0 22px; }

/* ── Screen-reader-only text ──────────────────────────────────────────── */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ── 60-second experience video modal ────────────────────────────────── */

.video-modal {
  position: fixed; inset: 0; z-index: 3000;
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
.video-modal[hidden] { display: none; }
.video-modal-backdrop {
  position: absolute; inset: 0;
  background: rgba(4, 5, 6, 0.88);
}
.video-modal-panel {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 420px;
}
.video-modal-close {
  position: absolute;
  top: -44px; right: 0;
  width: 36px; height: 36px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius);
  background: var(--color-surface);
  color: var(--color-text);
  cursor: pointer;
}
.video-modal-close:hover { color: var(--accent-bright); border-color: var(--accent); }

/* ── Footer ───────────────────────────────────────────────────────────── */

.site-footer { padding-block: 34px; border-top: 1px solid var(--color-border); font-size: 0.82rem; color: var(--color-muted-2); }
.site-footer .footer-inner { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 14px; }
.footer-links { list-style: none; display: flex; flex-wrap: wrap; gap: 20px; margin: 0; padding: 0; }
.footer-links a { color: var(--color-muted-2); }
.footer-links a:hover { color: var(--accent-bright); }

/* ── Access screen ────────────────────────────────────────────────────── */

.access-screen {
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px 16px;
  background: radial-gradient(ellipse 900px 600px at 50% 0%, var(--hero-glow-1), transparent 60%);
}
.access-card {
  width: 100%; max-width: 420px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  padding: clamp(28px, 5vw, 44px);
  text-align: center;
  box-shadow: var(--shadow-card);
}
.access-mark { font-size: 0.8rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--accent); margin-bottom: 18px; }
.access-card h1 { font-size: 1.5rem; }
.access-card p.access-message { font-size: 0.92rem; margin-bottom: 24px; }
.access-card form { text-align: left; display: flex; flex-direction: column; gap: 14px; }
.access-footer { margin-top: 22px; font-size: 0.82rem; color: var(--color-muted-2); }

/* ── 404 ──────────────────────────────────────────────────────────────── */
.error-screen { min-height: 100dvh; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 32px 16px; }
.error-code { font-size: 0.85rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--accent); margin-bottom: 12px; }

/* ── Reveal-on-scroll (progressive enhancement; content is visible without JS) ── */
.reveal { opacity: 1; transform: none; }
.js-reveal-ready .reveal { opacity: 0; transform: translateY(14px); transition: opacity 480ms ease, transform 480ms ease; }
.js-reveal-ready .reveal.is-visible { opacity: 1; transform: none; }

/* Stage 5 section rhythm: motion intensity follows the three groups
   Stage 4F established (see the .section-soft/.section-trust/.trust-zone
   comment further up) — product/emotional sections (hero, Player
   Experience, Journey, the climax panels) get the default .reveal above,
   the strongest of the three; commercial sections settle for a shorter,
   smaller move; trust/control sections barely move at all. Not seven
   different animations, one shared mechanism with three calibrations. */
.js-reveal-ready #operator-value .reveal,
.js-reveal-ready #difference .reveal {
  transform: translateY(10px);
  transition-duration: 400ms;
}
.js-reveal-ready .section-trust .reveal,
.js-reveal-ready .trust-zone .reveal {
  transform: translateY(6px);
  transition-duration: 300ms;
}
