/* ============================================================================
   AJ.ai — IMMERSIVE shell stylesheet
   ONE design system, mirrored 1:1 from products/ajai/index.html :root tokens.
   This file owns: tokens, the scroll-snap spine, the fixed WebGL stage, the
   per-act <section> chrome, and shared act primitives (eyebrow, act-title,
   act-lede, hairline, stat). Individual acts add only acts/act<N>.css for
   layout unique to that act — they MUST consume these tokens, never redefine
   the palette.
   ========================================================================== */

:root{
  /* ---- ink scale (dark monochrome) — identical to the flagship ---- */
  --ink-0:#08090c; --ink-100:#0c0e12; --ink-200:#11141a; --ink-300:#181c24;

  /* ---- type tones ---- (--faint = decorative ~3:1; --faint-text = readable ~4.9:1) */
  --bone:#e9ebef; --dim:#8b93a1; --faint:#565d6b; --faint-text:#828b9b;

  /* ---- hairlines ---- */
  --line:rgba(255,255,255,.07);
  --line-2:rgba(255,255,255,.12);

  /* ---- ONE accent — OKLCH calm electric blue. No purple/indigo. ---- */
  --acc:       oklch(0.78 0.12 248);
  --acc-deep:  oklch(0.62 0.16 254);
  --acc-soft:  oklch(0.78 0.12 248 / 0.15);
  --acc-line:  oklch(0.78 0.12 248 / 0.30);
  --ok:        oklch(0.82 0.11 168);

  /* ---- rgb mirror of the accent for <canvas>/WebGL (JS can't read oklch portably) ---- */
  --acc-rgb: 127, 178, 255;

  /* ---- shared rhythm ---- */
  --shell: 1320px;
  --gutter: clamp(1.25rem, 5vw, 4rem);
  --measure: 62ch;                 /* comfortable reading measure for body copy */
  --measure-tight: 46ch;           /* ledes / one-idea blurbs */

  /* ---- vertical rhythm step (use for margins between stacked blocks) ---- */
  --space-1: .5rem;
  --space-2: .85rem;
  --space-3: 1.35rem;
  --space-4: 2.1rem;
  --space-5: clamp(2.4rem, 5vh, 3.6rem);
  --space-6: clamp(3.2rem, 8vh, 5.5rem);

  /* ---- ONE readable type scale (fluid). Bigger + airier than before. ----
     Pair with line-heights below. Display = Clash, body = Satoshi, label = mono. */
  --fs-display: clamp(2.6rem, 7vw, 5.6rem);     /* act titles */
  --fs-h2:      clamp(1.9rem, 4.2vw, 3rem);      /* in-panel section heads */
  --fs-h3:      clamp(1.25rem, 2.2vw, 1.6rem);   /* sub-heads / panel titles */
  --fs-lede:    clamp(1.18rem, 1.7vw, 1.5rem);   /* the lede under a title */
  --fs-body:    clamp(1.05rem, 1.25vw, 1.18rem); /* real reading copy (was ~13px) */
  --fs-small:   clamp(.95rem, 1.1vw, 1.02rem);   /* secondary copy, captions */
  --fs-label:   clamp(11.5px, 1vw, 13px);        /* mono labels / eyebrows */

  --lh-tight: 1.05;                 /* display */
  --lh-snug:  1.3;                  /* sub-heads */
  --lh-body:  1.65;                 /* reading copy — generous for legibility */

  /* ---- motion ---- */
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --dur: .6s;
}

*,*::before,*::after{ box-sizing:border-box; }

html{
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  background:var(--ink-0);
}

body{
  margin:0;
  background:var(--ink-0);
  color:var(--bone);
  font-family:"Satoshi", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  position:relative;
  overflow-x:hidden;
}

/* ----------------------------------------------------------------------------
   LIGHT→DARK page background — the deliberate Part I→Part II transition.
   The body keeps the DARK ink base (var(--ink-0)). A single fixed ::before paints
   the LIGHT "paper" plane ON TOP of that base; we cross-fade ITS opacity (not the
   color) so the whole page glides paper → ink across the divider with one cheap,
   GPU-friendly transition. Opacity is 1 while a light Part I section is active and
   0 over the dark Part II — driven purely by :has() on the active section below.
   z-index 0 keeps it behind the grain/vignette/gl-stage (z-index 1/1/2) and the
   #spine content (z-index 3). ---------------------------------------------------- */
body::before{
  content:""; position:fixed; inset:0; z-index:0; pointer-events:none;
  background:var(--paper, #f4f5f7);
  opacity:0;                               /* dark by default (Part II) */
  transition:opacity .9s var(--ease-out);
  will-change:opacity;
}

/* ============================================================================
   LIGHT THEME — Part I (.act--light). The business half reads as a premium,
   monochrome editorial page: paper background, near-black ink, no blue.
   (a) fade the body::before paper plane in while a light section is active,
   (b) hide the dark atmosphere (grain/vignette/shared WebGL stage) over it,
   (c) flip the design tokens to BLACK-ON-PAPER inside the section so every
   shared primitive (eyebrow/title/lede/stat/hairline/legend) + any canvas that
   reads --acc-rgb re-themes automatically.
   ========================================================================== */
body:has(.act--light[data-active])::before{ opacity:1; }
body:has(.act--light[data-active]) .grain,
body:has(.act--light[data-active]) .vignette,
body:has(.act--light[data-active]) #gl-stage{
  opacity:0; transition:opacity .9s var(--ease-out);
}
.act--light{
  --bone:#141310;                    /* primary ink — near-black (was near-white) */
  --dim:#4f4c45;                     /* secondary text */
  --faint:#aaa59b;                   /* decorative hairline text */
  --faint-text:#67635b;              /* readable small text */
  --line:rgba(18,16,13,.12);
  --line-2:rgba(18,16,13,.20);
  /* monochrome: the "accent" is just ink, so Part I is pure black & white */
  --acc:#1b1916; --acc-deep:#000; --acc-rgb:27,25,22;
  --acc-soft:rgba(27,25,22,.10); --acc-line:rgba(27,25,22,.26);
  color:#141310;
}

::selection{ background:var(--acc); color:#06080d; }

a{ color:inherit; text-decoration:inherit; }

/* keyboard focus — single, calm ring everywhere */
:focus-visible{ outline:2px solid var(--acc); outline-offset:3px; border-radius:4px; }

/* skip link */
.skip{ position:absolute; left:-999px; top:0; z-index:200; }
.skip:focus{
  left:1rem; top:1rem; background:var(--ink-200); color:var(--bone);
  padding:.6rem 1rem; border:1px solid var(--line-2); border-radius:6px;
}

/* ----------------------------------------------------------------------------
   Atmosphere — ONE static grain + ONE soft top vignette (crafted depth, cheap)
   ---------------------------------------------------------------------------- */
.grain{
  position:fixed; inset:0; z-index:1; pointer-events:none;
  opacity:.045; mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
.vignette{
  position:fixed; inset:0; z-index:1; pointer-events:none;
  background:radial-gradient(130% 80% at 50% -20%, rgba(var(--acc-rgb),.05), transparent 55%);
}

/* ----------------------------------------------------------------------------
   The fixed WebGL stage — ONE <canvas> shared by all WebGL acts (0,2,3,6).
   It sits behind the scroll spine; the scene-manager mounts/unmounts content
   into the single shared renderer. Non-WebGL acts simply leave it transparent.
   ---------------------------------------------------------------------------- */
#gl-stage{
  position:fixed; inset:0; z-index:2;
  pointer-events:none;            /* acts opt back in per-element if needed */
  /* canvas is appended here by core/renderer.js */
}
#gl-stage canvas{ display:block; width:100%; height:100%; }

/* ----------------------------------------------------------------------------
   The scroll spine — 9 full-height scroll-snap sections.
   Each act owns one <section.act>. Content sits at z-index above the stage.
   ---------------------------------------------------------------------------- */
#spine{
  position:relative; z-index:3;
  scroll-snap-type:y proximity;     /* proximity, not mandatory — never traps the user */
}

.act{
  position:relative;
  height:100svh;                    /* STABLE one screen — never grows, so the
                                       document height is constant (kills jank);
                                       content over one screen is each act's job
                                       to fit (next phase). */
  overflow:hidden;                  /* no vertical overflow → nothing scrolls
                                       within a section → the fixed 3D + labels
                                       stay locked to the copy (no smear). */
  scroll-snap-align:start;
  display:flex;
  flex-direction:column;
  justify-content:center;
  padding-block:clamp(4rem, 12vh, 9rem);
  padding-inline:var(--gutter);
  isolation:isolate;
}

/* shell-width inner column shared by every act */
.act__inner{
  width:100%;
  max-width:var(--shell);
  max-height:100svh;                /* never exceed the screen (section is fixed
                                       at 100svh + overflow:hidden) */
  margin-inline:auto;
  position:relative;
  z-index:1;                        /* above the per-act local 2D canvas layer */
}

/* per-act LOCAL 2D drawing surface (Canvas2D/SVG acts paint here).
   Pinned to the section, behind the inner column, never grabs pointer. */
.act__layer{
  position:absolute; inset:0; z-index:0;
  pointer-events:none;
  overflow:hidden;
}
.act__layer > canvas,
.act__layer > svg{ display:block; width:100%; height:100%; }

/* ----------------------------------------------------------------------------
   Shared act typography primitives (use these; do not reinvent per act)
   ---------------------------------------------------------------------------- */
.eyebrow{
  font-family:"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size:12px; letter-spacing:.22em; text-transform:uppercase;
  color:var(--faint-text);
  display:inline-flex; align-items:center; gap:.6rem;
}
.eyebrow .sec-num{ color:var(--acc); }

.act-title{
  font-family:"Clash Display", ui-sans-serif, system-ui, sans-serif;
  font-weight:600; line-height:var(--lh-tight); letter-spacing:-.02em;
  font-size:var(--fs-display);
  margin:var(--space-2) 0 0;
  color:var(--bone);
  text-wrap:balance;
}

.act-lede{
  font-size:var(--fs-lede);
  line-height:var(--lh-body); color:var(--dim);
  max-width:var(--measure-tight); margin-top:var(--space-3);
  text-wrap:pretty;
}
.act-lede strong{ color:var(--bone); font-weight:500; }

.accent{ color:var(--acc); }

.hairline{ height:1px; background:var(--line); border:0; margin:0; }

/* mono stat block — shared across data acts */
.stat{ display:flex; flex-direction:column; gap:.25rem; }
.stat__n{
  font-family:"Clash Display", sans-serif; font-weight:600;
  font-size:clamp(1.8rem, 3vw, 2.6rem); color:var(--bone);
  font-variant-numeric:tabular-nums;
}
.stat__l{
  font-family:"JetBrains Mono", monospace; font-size:var(--fs-label);
  letter-spacing:.04em; color:var(--faint-text); text-transform:uppercase;
}

/* ============================================================================
   READABILITY LAYER — the bar: impress a senior engineer AND a business leader
   at a glance. Everything below is a SHARED utility. Acts compose these classes
   in their innerHTML; act<N>.css only adds geometry. Two voices, one system:
     • .panel--biz  → big plain copy, lots of air, ONE idea per screen.
     • .panel--tech → room for an annotated-3D diagram + a tight label legend.
   The signature device (annotated 3D reveal) is the .diagram / .legend / .leader
   primitives, generalized from the Brain act so every future act gets it free.
   ============================================================================ */

/* ---- readable copy primitives (use INSTEAD of ad-hoc 12–13px text) -------- */
/* The single reading-copy class. This is the size body text should be. */
.copy{
  font-size:var(--fs-body);
  line-height:var(--lh-body);
  color:var(--dim);                 /* readable ~ contrast; never --faint for prose */
  max-width:var(--measure);
  text-wrap:pretty;
}
.copy strong,
.copy b{ color:var(--bone); font-weight:500; }
.copy + .copy{ margin-top:var(--space-3); }
.copy--tight{ max-width:var(--measure-tight); }
.copy--lead{ font-size:var(--fs-lede); color:var(--bone); }   /* a louder first line */

/* secondary / caption copy — still readable, never decorative --faint */
.note-copy{
  font-size:var(--fs-small); line-height:1.55; color:var(--faint-text);
  max-width:var(--measure);
}

/* in-panel headings (below the act-title) ---------------------------------- */
.head-2{
  font-family:"Clash Display", ui-sans-serif, system-ui, sans-serif;
  font-weight:600; line-height:var(--lh-snug); letter-spacing:-.015em;
  font-size:var(--fs-h2); color:var(--bone); margin:0; text-wrap:balance;
}
.head-3{
  font-family:"Clash Display", ui-sans-serif, system-ui, sans-serif;
  font-weight:600; line-height:var(--lh-snug); letter-spacing:-.01em;
  font-size:var(--fs-h3); color:var(--bone); margin:0;
}

/* a small mono kicker that labels a panel/diagram region (≠ the act eyebrow) */
.kicker{
  font-family:"JetBrains Mono", ui-monospace, monospace;
  font-size:var(--fs-label); letter-spacing:.2em; text-transform:uppercase;
  color:var(--faint-text); margin:0;
}
.kicker .n{ color:var(--acc); }

/* ---- vertical rhythm helper: consistent air between stacked blocks -------- */
.flow > * + *{ margin-top:var(--space-3); }
.flow-lg > * + *{ margin-top:var(--space-4); }

/* ============================================================================
   BUSINESS PANEL — for leaders. One idea, big type, generous whitespace.
   Centered, narrow measure, nothing competing. Drop this on an act__inner for
   a clean "statement" screen. Pair copy with .copy / .copy--lead.
   ============================================================================ */
.panel--biz{
  display:flex; flex-direction:column;
  gap:var(--space-4);
  max-width:72ch;
  padding-block:clamp(1rem, 4vh, 3rem);
}
.panel--biz .act-title,
.panel--biz .head-2{ max-width:18ch; }       /* force a tall, punchy headline */
.panel--biz .copy{ max-width:54ch; }          /* one comfortable column */
.panel--biz.is-centered{
  align-items:center; text-align:center; margin-inline:auto;
}
.panel--biz.is-centered .copy{ margin-inline:auto; }

/* a single emphasized takeaway line — the sentence a leader remembers */
.takeaway{
  font-family:"Clash Display", ui-sans-serif, system-ui, sans-serif;
  font-weight:500; letter-spacing:-.01em;
  font-size:clamp(1.5rem, 3.2vw, 2.3rem); line-height:1.25;
  color:var(--bone); max-width:24ch; text-wrap:balance; margin:0;
}
.takeaway .accent{ color:var(--acc); }

/* ============================================================================
   TECH PANEL — for engineers. A two-column shell: a content/legend rail beside
   room for the annotated-3D diagram. The diagram itself lives on the shared
   WebGL canvas (acts 0/2/3/6) or in .act__layer (2D/SVG acts); this just reserves
   the space and holds the copy to one side so type never sits on the geometry.
   Default: copy LEFT, diagram room RIGHT. Add .is-flip to mirror.
   ============================================================================ */
.panel--tech{
  display:grid;
  grid-template-columns:1fr;          /* stacks on narrow screens */
  gap:var(--space-5);
  align-items:center;
}
@media (min-width:980px){
  .panel--tech{
    /* content rail ~ 50%, the rest is breathing room reserved for the diagram */
    grid-template-columns:minmax(0, 30rem) 1fr;
  }
  .panel--tech.is-flip{ grid-template-columns:1fr minmax(0, 30rem); }
  .panel--tech.is-flip .panel__rail{ order:2; }
}
/* the readable text/legend column */
.panel__rail{ min-width:0; display:flex; flex-direction:column; gap:var(--space-3); }
/* explicit slot reserving space for the diagram (keeps copy off the geometry).
   Empty by design when the visual is the shared canvas/layer behind the column. */
.panel__diagram{
  position:relative; min-width:0;
  min-height:clamp(320px, 48vh, 540px);
}

/* ============================================================================
   ANNOTATED-3D PRIMITIVES — the signature device.
   A .legend is the tight label rail beside a 3D object; each .legend__item is a
   labeled part that lights as its cluster reveals on scroll (toggle .is-active).
   A .leader is a thin leader-line+label you can absolutely-position over a
   diagram to point at a part. Generalized from act2's brain-legend so any act
   can annotate a 3D object consistently.
   ============================================================================ */
.legend{
  list-style:none; margin:0; padding:0;
  display:flex; flex-direction:column; gap:var(--space-2);
}
.legend__item{
  display:flex; align-items:flex-start; gap:.85rem;
  padding:.8rem 1rem;
  border:1px solid var(--line);
  border-radius:10px;
  background:linear-gradient(180deg, rgba(255,255,255,.012), transparent);
  opacity:.6;                          /* resting (un-revealed) state */
  transition:border-color .4s var(--ease-out), background .4s var(--ease-out),
             transform .4s var(--ease-out), opacity .4s var(--ease-out);
}
/* the resting marker dot — a part's "off" state */
.legend__dot{
  flex:0 0 auto; width:9px; height:9px; margin-top:.5rem;
  border-radius:50%;
  background:rgba(var(--acc-rgb), .28);
  box-shadow:0 0 0 1px rgba(var(--acc-rgb), .18);
  transition:background .4s var(--ease-out), box-shadow .4s var(--ease-out),
             transform .4s var(--ease-out);
}
.legend__body{ display:flex; flex-direction:column; gap:.22rem; min-width:0; }
.legend__head{ display:flex; align-items:baseline; gap:.7rem; flex-wrap:wrap; }
.legend__name{
  font-family:"Clash Display", ui-sans-serif, system-ui, sans-serif;
  font-weight:600; font-size:var(--fs-h3); letter-spacing:-.01em;
  line-height:1.1; color:var(--bone);
}
.legend__role{
  font-family:"JetBrains Mono", ui-monospace, monospace;
  font-size:var(--fs-label); letter-spacing:.06em; text-transform:uppercase;
  color:var(--faint-text);
}
.legend__note{
  font-size:var(--fs-small); line-height:1.5; color:var(--dim);
}
/* ACTIVE part — the legend row whose 3D cluster is currently lit */
.legend__item.is-active{
  opacity:1; border-color:var(--acc-line);
  background:linear-gradient(180deg, rgba(var(--acc-rgb), .07), rgba(var(--acc-rgb), .02));
  transform:translateX(4px);
}
.legend__item.is-active .legend__dot{
  background:var(--acc);
  box-shadow:0 0 10px rgba(var(--acc-rgb), .8), 0 0 0 1px var(--acc);
  transform:scale(1.15);
}
.legend__item.is-active .legend__note{ color:var(--bone); }

/* leader-line label — absolutely positioned over a .panel__diagram to point at
   a part. Set left/top inline (or via act CSS); .is-active reveals it. The line
   is a thin rule; the dot sits at the anchor end. Default points up-left. */
.leader{
  position:absolute; display:flex; align-items:center; gap:.6rem;
  opacity:0; transform:translateY(6px);
  transition:opacity .5s var(--ease-out), transform .5s var(--ease-out);
  pointer-events:none;
}
.leader.is-active{ opacity:1; transform:none; }
.leader__line{
  width:clamp(28px, 6vw, 72px); height:1px;
  background:linear-gradient(90deg, var(--acc-line), var(--acc));
  flex:0 0 auto;
}
.leader__dot{
  width:7px; height:7px; border-radius:50%;
  background:var(--acc); box-shadow:0 0 10px var(--acc);
  flex:0 0 auto;
}
.leader__label{ display:flex; flex-direction:column; gap:.1rem; }
.leader__label .t{
  font-family:"Clash Display", ui-sans-serif, system-ui, sans-serif;
  font-weight:600; font-size:1rem; line-height:1.1; color:var(--bone);
}
.leader__label .s{
  font-family:"JetBrains Mono", ui-monospace, monospace;
  font-size:var(--fs-label); letter-spacing:.04em; color:var(--faint-text);
}

/* ============================================================================
   PART I → PART II DIVIDER — "Under the hood — for the engineers".
   A full-height interstitial section announcing the shift from the business
   narrative (Part I) to the technical deep-dive (Part II). Center it; let it
   breathe. Compose on an .act__inner.
   ============================================================================ */
/* the divider lives in its own full-height section (.act--divider) between the
   two tracks. It deliberately has NO .act__inner — its .divider child is not
   subject to the [data-active] inner-fade, so the interstitial stays fully
   visible as you scroll the handoff (it is a breath between Part I and Part II,
   not a destination act). It is not registered in the manifest and has no rail
   tick. .act already gives it 100svh + vertical centering; we just center the
   block horizontally and keep snap rhythm consistent with the acts. */
.act--divider{ align-items:center; }

.divider{
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  text-align:center; gap:var(--space-4);
  max-width:60ch; margin-inline:auto;
  padding-block:clamp(2rem, 8vh, 5rem);
}
.divider__rule{
  display:flex; align-items:center; gap:1rem; width:min(100%, 30rem);
  color:var(--faint-text);
}
.divider__rule::before,
.divider__rule::after{
  content:""; flex:1; height:1px;
  background:linear-gradient(90deg, transparent, var(--line-2));
}
.divider__rule::after{ background:linear-gradient(90deg, var(--line-2), transparent); }
/* the Part marker — e.g. "PART II" */
.divider__part{
  font-family:"JetBrains Mono", ui-monospace, monospace;
  font-size:var(--fs-label); letter-spacing:.32em; text-transform:uppercase;
  color:var(--acc); white-space:nowrap;
}
.divider__title{
  font-family:"Clash Display", ui-sans-serif, system-ui, sans-serif;
  font-weight:600; letter-spacing:-.02em; line-height:1.06;
  font-size:clamp(2rem, 5.5vw, 3.8rem); color:var(--bone);
  margin:0; text-wrap:balance;
}
.divider__sub{
  font-size:var(--fs-lede); line-height:1.55; color:var(--dim);
  max-width:42ch; margin:0; text-wrap:pretty;
}
.divider__sub strong{ color:var(--bone); font-weight:500; }
/* a calm down-cue echoing act0's scroll cue */
.divider__cue{
  width:1px; height:46px; margin-top:var(--space-2);
  background:linear-gradient(var(--acc-line), transparent);
  position:relative; overflow:hidden;
}
.divider__cue::after{
  content:""; position:absolute; left:-1px; top:0;
  width:3px; height:10px; border-radius:2px;
  background:var(--acc); box-shadow:0 0 8px var(--acc);
  animation:divider-cue 2.4s var(--ease-out) infinite;
}
@keyframes divider-cue{
  0%{ transform:translateY(-12px); opacity:0; }
  25%{ opacity:1; } 75%{ opacity:1; }
  100%{ transform:translateY(46px); opacity:0; }
}

/* reduced motion: freeze divider + leader reveals as static, fully-visible */
@media (prefers-reduced-motion: reduce){
  .legend__item, .legend__dot, .leader{ transition:none; }
  .legend__item.is-active{ transform:none; }
  .legend__item.is-active .legend__dot{ transform:none; }
  .divider__cue::after{ animation:none; top:0; opacity:.9; }
}

/* ----------------------------------------------------------------------------
   Scroll progress rail + act index (fixed UI chrome, above everything)
   ---------------------------------------------------------------------------- */
#rail{
  position:fixed; right:clamp(.9rem,2vw,1.6rem); top:50%;
  transform:translateY(-50%); z-index:40;
  display:flex; flex-direction:column; align-items:center; gap:.75rem;
  pointer-events:auto;
}
/* faint connecting spine behind the dots — reads as a single nav rail */
#rail::before{
  content:""; position:absolute; top:4px; bottom:4px; left:50%;
  width:1px; transform:translateX(-50%);
  background:linear-gradient(var(--line-2), transparent 8%, transparent 92%, var(--line-2));
  pointer-events:none;
}
#rail .tick{
  position:relative;
  width:10px; height:10px; border-radius:50%;
  border:1px solid var(--line-2); background:var(--ink-0);
  padding:0; cursor:pointer; transition:transform .35s var(--ease-out),
    background .35s var(--ease-out), border-color .35s var(--ease-out),
    box-shadow .35s var(--ease-out);
}
#rail .tick[aria-current="true"]{
  background:var(--acc); border-color:var(--acc);
  box-shadow:0 0 12px var(--acc); transform:scale(1.18);
}
#rail .tick:hover{ border-color:var(--acc); }
/* generous invisible hit target so the 10px dot is easy to tap/click */
#rail .tick::after{ content:""; position:absolute; inset:-9px; border-radius:50%; }

/* ---- two-track grouping on the rail (Part I dots, then Part II dots) ------
   The DIVIDER section between the tracks is not a tick; instead the rail shows
   the split with a roman-numeral part marker at the top of each group and a
   short gap rule between them. Decorative (aria-hidden) — the per-tick
   aria-labels already announce "Part I/II · n. <name>" to assistive tech. */
#rail .rail__part{
  font-family:"JetBrains Mono", ui-monospace, monospace;
  font-size:10px; letter-spacing:.12em; line-height:1;
  color:var(--faint-text);
  user-select:none;
}
#rail .rail__part--ii{ color:var(--acc); }   /* the technical track is accented */
#rail .rail__part--iii{ color:var(--acc); }  /* Part III (the closer) — also accented */
/* the gap rule separating the two groups of dots */
#rail .rail__split{
  width:1px; height:18px; margin:.15rem 0;
  background:linear-gradient(var(--line-2), transparent 45%, transparent 55%, var(--line-2));
}

#progress{
  position:fixed; left:0; top:0; height:2px; z-index:41;
  width:0%; background:linear-gradient(90deg, var(--acc-deep), var(--acc));
  box-shadow:0 0 10px var(--acc-soft);
}

/* ----------------------------------------------------------------------------
   Shell-level section transition — the active act's content settles in.
   Driven by [data-active], which the scene-manager sets on the live <section>.
   Inactive sections hold their content slightly lowered + dimmed so entering an
   act always reads as a soft arrival (independent of any per-act GSAP).
   ---------------------------------------------------------------------------- */
.act__inner{
  transition:opacity .7s var(--ease-out), transform .7s var(--ease-out);
}
html.imm-ready .act:not([data-active]) .act__inner{
  opacity:.0;
  transform:translateY(26px);
}
html.imm-ready .act[data-active] .act__inner{
  opacity:1;
  transform:none;
}
/* the shared 2D fallback backdrop, parented into a degraded act's layer */
.act__layer > canvas[data-imm-fallback]{
  opacity:0; transition:opacity .8s var(--ease-out);
}
.act[data-active] .act__layer > canvas[data-imm-fallback]{ opacity:1; }

/* ----------------------------------------------------------------------------
   Mobile / narrow viewports (tested at 375px).
   • The vertical right-edge rail becomes a slim HORIZONTAL bar pinned bottom-
     center, so it NEVER overlaps the copy column on a narrow screen.
   • Copy stays comfortably readable: the act lede may run full measure, type
     sizes already use clamp() so they shrink gracefully.
   • Scenes downscale automatically (DPR is capped lower on coarse pointers in
     capabilities.js; WebGL/2D surfaces fill the section either way).
   ---------------------------------------------------------------------------- */
@media (max-width: 640px){
  .act{
    padding-block:clamp(3.5rem, 12vh, 6rem);
    /* keep content clear of the bottom rail */
    padding-bottom:5.5rem;
  }
  .act-lede{ max-width:none; }              /* full measure on small screens */

  /* readability utilities run full measure on narrow screens */
  .copy, .note-copy, .panel--biz .copy{ max-width:none; }
  .panel--biz{ gap:var(--space-3); }

  /* leader-lines can't reliably point at a stacked diagram on a phone — on the
     tech panel they re-flow into a static inline label rail under the copy. */
  .panel--tech{ gap:var(--space-4); }
  .panel__diagram{ min-height:clamp(240px, 40vh, 360px); }
  .panel__diagram .leader{
    position:static; opacity:1; transform:none;
    margin-top:.5rem;
  }
  .panel__diagram .leader__line{ display:none; }

  /* rail → horizontal, bottom-center, with a backplate so it reads on any scene */
  #rail{
    right:auto; left:50%; top:auto; bottom:max(.7rem, env(safe-area-inset-bottom));
    transform:translateX(-50%);
    flex-direction:row; gap:.85rem;
    padding:.5rem .8rem; border-radius:999px;
    background:color-mix(in oklab, var(--ink-100) 78%, transparent);
    border:1px solid var(--line-2);
    backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
  }
  #rail::before{                            /* horizontal connector */
    top:50%; bottom:auto; left:8px; right:8px; width:auto; height:1px;
    transform:translateY(-50%);
    background:linear-gradient(90deg, var(--line-2), transparent 10%, transparent 90%, var(--line-2));
  }
  #rail .tick{ width:11px; height:11px; }    /* a touch larger for touch */
  #rail .tick::after{ inset:-7px; }

  /* horizontal rail: drop the roman-numeral part labels (the aria-labels still
     announce the part), and turn the group separator into a slim vertical gap. */
  #rail .rail__part{ display:none; }
  #rail .rail__split{
    width:1px; height:14px; margin:0 .15rem;
    background:linear-gradient(var(--line-2), transparent 45%, transparent 55%, var(--line-2));
  }

  #progress{ height:3px; }                    /* a hair thicker so it's visible */

  /* the hero stat strip and dense act layouts already wrap via their own CSS;
     this just guarantees the shell never forces horizontal scroll */
  .act__inner{ max-width:100%; }
}

/* very small / old phones: make the entrance translate gentler (less reflow) */
@media (max-width: 380px){
  html.imm-ready .act:not([data-active]) .act__inner{ transform:translateY(16px); }
}

/* ----------------------------------------------------------------------------
   Degrade-to-A — running the shared 2D fallback for the WebGL acts.
   The fallback backdrop lives in .act__layer; nothing GPU is created. Keep the
   atmosphere a touch stronger so a fallback act still feels composed.
   ---------------------------------------------------------------------------- */
html.imm-degraded .vignette{
  background:radial-gradient(130% 80% at 50% -10%, rgba(var(--acc-rgb),.08), transparent 58%);
}

/* ----------------------------------------------------------------------------
   Reduced motion — the spine still works; we just FREEZE animation and keep
   all content. WebGL acts read ctx.reducedMotion and skip their rAF loops; the
   2D fallback paints a single static frame; shell transitions are removed so
   content is simply present (no fade/slide), never hidden.
   ---------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce){
  #spine{ scroll-snap-type:none; }
  html{ scroll-behavior:auto; }
}
html.imm-reduced #spine{ scroll-snap-type:none; }

/* freeze shell transitions: content is fully visible and in place, no motion */
html.imm-reduced .act__inner,
html.imm-reduced .act__layer > canvas[data-imm-fallback],
html.imm-reduced #rail .tick{
  transition:none !important;
}
html.imm-reduced.imm-ready .act:not([data-active]) .act__inner{
  opacity:1; transform:none;            /* never hide content under reduced motion */
}
html.imm-reduced .act__layer > canvas[data-imm-fallback]{ opacity:1; }

/* boot state: hide ONLY the chrome until the controller is live (no FOUC of the
   rail/progress). Content sections stay in normal flow so the page is usable
   even if the module fails to boot. */
html:not(.imm-ready) #rail,
html:not(.imm-ready) #progress{ opacity:0; }
