/* ============================================================
   0. SELF-HOSTED FONTS (Pass 10) — Anton 400 + Sora variable
   (400-800). Verbatim Google Fonts css2 output with URLs
   rewritten to /assets/fonts/ — subsets + unicode-range kept
   intact. Replaces the render-blocking fonts.googleapis.com
   stylesheet + two preconnects (see index.html).
   ============================================================ */
/* vietnamese */
@font-face {
  font-family: 'Anton';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/assets/fonts/1Ptgg87LROyAm3K8-C8CSKlvPfE.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Anton';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/assets/fonts/1Ptgg87LROyAm3K9-C8CSKlvPfE.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Anton';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/assets/fonts/1Ptgg87LROyAm3Kz-C8CSKlv.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/assets/fonts/xMQ9uFFYT72X5wkB_18qmnndmSdSnh2BAfO5mnuyOo1lfiQwWa-xo6eeI7fM.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/assets/fonts/xMQ9uFFYT72X5wkB_18qmnndmSdSnh2BAfO5mnuyOo1lfiQwV6-xo6eeIw.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/assets/fonts/xMQ9uFFYT72X5wkB_18qmnndmSdSnh2BAfO5mnuyOo1lfiQwWa-xo6eeI7fM.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/assets/fonts/xMQ9uFFYT72X5wkB_18qmnndmSdSnh2BAfO5mnuyOo1lfiQwV6-xo6eeIw.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/assets/fonts/xMQ9uFFYT72X5wkB_18qmnndmSdSnh2BAfO5mnuyOo1lfiQwWa-xo6eeI7fM.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/assets/fonts/xMQ9uFFYT72X5wkB_18qmnndmSdSnh2BAfO5mnuyOo1lfiQwV6-xo6eeIw.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/assets/fonts/xMQ9uFFYT72X5wkB_18qmnndmSdSnh2BAfO5mnuyOo1lfiQwWa-xo6eeI7fM.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/assets/fonts/xMQ9uFFYT72X5wkB_18qmnndmSdSnh2BAfO5mnuyOo1lfiQwV6-xo6eeIw.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url(/assets/fonts/xMQ9uFFYT72X5wkB_18qmnndmSdSnh2BAfO5mnuyOo1lfiQwWa-xo6eeI7fM.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url(/assets/fonts/xMQ9uFFYT72X5wkB_18qmnndmSdSnh2BAfO5mnuyOo1lfiQwV6-xo6eeIw.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ==========================================================================
   SHOWCALL — design system + cue-driven "Run of Show" engine styles.
   "The show is called." Obsidian black, bone white, one amber cue.

   ARCHITECTURE: 7 full-viewport <section class="cue"> scenes. By default
   (no JS, or prefers-reduced-motion:reduce) they render as normal stacked,
   anchored, scrollable sections — a fully accessible linear page, and the
   top nav (.topnav) is the working "dock" (real anchor links).
   Only when `html.js` is present AND the OS allows motion do cues become
   position:fixed full-screen scenes with exactly one active at a time,
   and the fixed console dock (.dock) takes over navigation.
   All non-trivial motion is gated behind prefers-reduced-motion:no-preference.
   ========================================================================== */

/* ---------- 1. RESET & BASE ---------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: auto; }
html:focus-within { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html:focus-within { scroll-behavior: auto; } }
img, svg, canvas, video { display: block; max-width: 100%; }
input, button, textarea, select { font: inherit; color: inherit; }
button { cursor: pointer; background: none; border: 0; }
a { color: inherit; text-decoration: none; }
ul, ol { list-style: none; }

.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;
}

/* ---------- 2. DESIGN TOKENS ---------- */
:root {
  /* palette */
  --black: #08080a;
  --surface: #0d0d10;
  --surface-2: #131316;
  --surface-3: #1b1b1f;
  --steel: #3a3a40;
  --steel-dim: #232326;
  --bone: #f4f2ee;
  --bone-dim: #a8a6a1;
  --bone-faint: #86847e; /* AA-passing (~5.3:1 on --black) at small/micro text sizes */
  --amber: #ffb020;
  --amber-hi: #ffc85c;
  --amber-dim: rgba(255, 176, 32, 0.16);
  --amber-glow: rgba(255, 176, 32, 0.45);
  --amber-line: rgba(255, 176, 32, 0.28);
  /* shared bloom scale (Visual Pass 4) — every "light source" element on the
     site (GO button ring, veil bloom, cue-glow, headline em, the cursor
     reticle) draws its glow from this same three-step scale + --amber-glow
     color, so they read as one lighting model instead of independently
     tuned effects. See §26/§27. */
  --bloom-sm: 0 0 10px var(--amber-glow);
  --bloom-md: 0 0 22px var(--amber-glow);
  --bloom-lg: 0 0 40px var(--amber-glow);

  /* type */
  --f-display: "Anton", "Archivo Black", Impact, sans-serif;
  --f-body: "Sora", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --f-mono: ui-monospace, "SF Mono", "Cascadia Code", Menlo, Consolas, monospace;

  --fs-hero: clamp(2.9rem, 5vw + 1.1rem, 7rem);
  --fs-xl: clamp(2rem, 2.2vw + 1.1rem, 3.6rem);
  --fs-lg: clamp(1.5rem, 1.1vw + 0.95rem, 2.2rem);
  --fs-md: clamp(1.15rem, 0.5vw + 0.95rem, 1.4rem);
  --fs-body-lg: clamp(1rem, 0.25vw + 0.9rem, 1.2rem);
  --fs-body: 1rem;
  --fs-small: 0.875rem;
  --fs-micro: 0.72rem;

  /* spacing */
  --sp-1: 0.25rem; --sp-2: 0.5rem; --sp-3: 0.75rem; --sp-4: 1rem;
  --sp-5: 1.25rem; --sp-6: 1.5rem; --sp-8: 2rem; --sp-12: 3rem; --sp-16: 4rem;
  --sp-24: 6rem; --sp-32: 8rem;

  /* layout */
  --container-w: 1400px;
  --edge: clamp(1.25rem, 4vw, 4rem);
  --radius: 2px;
  --dock-h: 5.5rem;
  --topnav-h: 4.5rem;
  /* iPhone notch/home-indicator clearance (Pass 6 — mobile craft). Only
     nonzero when <meta viewport> carries viewport-fit=cover (see
     index.html) AND the device actually has a safe-area inset; falls back
     to 0px everywhere else (desktop, Android, non-notched iOS), so every
     calc() built on these is a silent no-op off-device. Derived totals so
     every fixed top/bottom bar's clearance math lives in one place. */
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --topnav-total-h: calc(var(--topnav-h) + var(--safe-t));
  --dock-total-h: calc(var(--dock-h) + var(--safe-b));

  /* motion */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-inout: cubic-bezier(0.65, 0, 0.35, 1);
  --ease-stamp: cubic-bezier(0.34, 1.56, 0.64, 1); /* overshoot — the veil readout's "impact" */
  --dur-fast: 180ms;
  --dur-med: 480ms;
  --dur-slow: 900ms;
  --veil-snap: 90ms;    /* the curtain itself: instant blackout, not a fade */
  --veil-in: 260ms;     /* the "call" window — bloom/sweep/readout play on top of solid black */
  --veil-out: 320ms;    /* curtain lifts + incoming cue cross-fades in */
  --veil-bloom-out: 480ms; /* amber afterglow lingers + settles over the arriving cue — the ignite handoff */
  /* Visual Pass 5 — the arriving cue's own ignition (see §23b). Both run
     concurrently with --veil-out, starting the instant the curtain lifts,
     so the whole call (blackout snap → bloom/sweep/readout → stage ignite)
     lands at 90+260+420 ≈ 770ms worst case, 500-750ms in the common case —
     one theatrical beat, not two stacked animations. */
  --ignite-sweep: 420ms;  /* the follow-spot beam continuing onto the newly-lit backdrop */
  --ignite-flare: 480ms;  /* haze catching that light and settling to rest */
}

/* ---------- 3. TYPOGRAPHY ---------- */
html { font-size: 100%; }
body {
  background: var(--black);
  color: var(--bone);
  font-family: var(--f-body);
  font-size: var(--fs-body);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

h1, h2, h3, .display {
  font-family: var(--f-display);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.01em;
  line-height: 0.96;
  color: var(--bone);
}

h1 { font-size: var(--fs-hero); }
h2 { font-size: var(--fs-xl); }
h3 { font-size: var(--fs-lg); }

p { color: var(--bone-dim); max-width: 60ch; }
p.lede { font-size: var(--fs-body-lg); color: var(--bone); }

/* idle bloom on every amber emphasis word — the first thread of the
   "one lighting model": headline em's now sit at a low resting glow even
   before their kinetic snap fires (§24) or under reduced motion, tying
   them visually to the GO button / cue-glow / veil bloom below (§26/§27). */
em { color: var(--amber); font-style: normal; text-shadow: var(--bloom-sm); }

/* ---------- 4. LAYOUT ---------- */
.container {
  max-width: var(--container-w);
  margin-inline: auto;
  padding-inline: var(--edge);
  width: 100%;
}
section { position: relative; }
.section-num {
  font-family: var(--f-mono); font-size: var(--fs-micro);
  color: var(--bone-faint); letter-spacing: 0.15em;
  display: inline-block; margin-bottom: var(--sp-3);
}
.section-head { display: flex; flex-direction: column; gap: var(--sp-3); max-width: 46rem; margin-bottom: var(--sp-8); }

/* Every content cue's "Cue N / Title" tag carries a small live tally
   light — the same "always on air" signal as the hero's cue-tag dot,
   the caller's headset dot, and the dock's rail dot. Gives cues 1-6 (and
   the quote result state) a continuously-living element even where there
   is no meter/node system of their own (the Model + Proof cues had none).
   Cue-caller already runs its own dedicated status-line dot immediately
   below, so it's excluded here to avoid two pulsing dots stacked in one
   column. */
.cue-body .section-num {
  display: inline-flex; align-items: center; gap: 0.55em;
  /* Pass 8: bumped from the base rule's --bone-faint to --bone-dim.
     section-num sits directly over cue photography (not a solid card
     background like the rundown's own .section-num use), same real-photo
     contrast risk as §29's scrim fix below — --bone-faint alone left too
     thin a margin against a bright highlight in the source photo. Same
     reasoning p.lede (§3) already got the brighter --bone treatment for. */
  color: var(--bone-dim);
}
.cue-body .section-num::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0;
  background: var(--amber); box-shadow: 0 0 6px var(--amber-glow);
}
.caller-copy .section-num::before { display: none; }
@media (prefers-reduced-motion: no-preference) {
  html.js .cue.is-active .section-num::before { animation: pulse-dot 2.4s var(--ease-inout) infinite; animation-delay: -0.5s; }
}
@media (prefers-reduced-motion: reduce) {
  .cue-body .section-num::before { animation: none; }
}

/* The cue-slate headline frame — a consistent amber box around every
   content cue's h2 (cues 1-6; the cue-0 hero keeps its own wipe-in
   treatment at hero scale). Gives each cue a strong, repeatable
   compositional anchor and doubles as the cue's "locked-in" marker. */
.cue-body h2 {
  display: block;
  width: fit-content;
  border: 2px solid var(--amber);
  padding: 0.32em 0.7em;
  margin-bottom: var(--sp-4);
}

/* ---------- 5. BUTTONS & CONTROLS ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  font-family: var(--f-body); font-weight: 700; font-size: var(--fs-small);
  letter-spacing: 0.04em; text-transform: uppercase;
  padding: 1rem 1.75rem; border-radius: var(--radius);
  border: 1px solid transparent;
  transition: transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.btn-primary {
  background: var(--amber); color: var(--black);
}
.btn-primary:hover, .btn-primary:focus-visible {
  background: var(--amber-hi);
  box-shadow: 0 0 0 1px var(--amber), 0 0 24px var(--amber-glow);
  transform: translateY(-1px);
}
.btn-ghost {
  background: transparent; color: var(--bone); border-color: var(--steel);
}
.btn-ghost:hover, .btn-ghost:focus-visible {
  border-color: var(--amber); color: var(--amber);
  transform: translateY(-1px);
  box-shadow: 0 0 0 1px var(--amber-line), 0 6px 20px rgba(255, 176, 32, 0.14);
}
.btn-primary:active, .btn-ghost:active { transform: translateY(0) scale(0.97); }
.btn-sm { padding: 0.7rem 1.1rem; font-size: var(--fs-micro); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn:disabled:hover, .btn:disabled:active { transform: none; box-shadow: none; }

a.btn:focus-visible, button:focus-visible, input:focus-visible,
select:focus-visible, textarea:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--amber); outline-offset: 3px;
}

/* ---------- 6. FALLBACK TOP NAV (always-working anchor nav) ---------- */
.topnav {
  position: fixed; inset: 0 0 auto 0; z-index: 90;
  display: flex; align-items: center; justify-content: space-between;
  padding: calc(var(--sp-4) + var(--safe-t)) var(--edge) var(--sp-4);
  background: rgba(8, 8, 10, 0.86);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--steel-dim);
}
.brand {
  display: inline-flex; align-items: center; gap: var(--sp-3);
  font-family: var(--f-display); font-size: 1.15rem; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--bone);
}
.brand-mark { width: 26px; height: 26px; flex-shrink: 0; }
.brand-mark circle.aperture { fill: none; stroke: var(--amber); stroke-width: 1.4; }
.brand-mark .live-dot { fill: var(--amber); }
@media (prefers-reduced-motion: no-preference) {
  /* -0.9s: knocks this out of phase with the other "always visible"
     living dots (dock rail-dot, chat launcher, active cue-tag) below,
     so the console reads as several independently-breathing lights
     instead of one synchronized blink — see §23 for the full rationale. */
  .brand-mark .live-dot { animation: brand-heartbeat 2.6s var(--ease-inout) infinite; animation-delay: -0.9s; transform-origin: center; transform-box: fill-box; }
}
@keyframes brand-heartbeat { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.55; transform: scale(0.82); } }

.topnav-links { display: flex; align-items: center; gap: var(--sp-6); font-size: var(--fs-small); font-weight: 600; }
.topnav-links a { color: var(--bone-dim); transition: color var(--dur-fast) var(--ease-out); position: relative; }
.topnav-links a:hover, .topnav-links a:focus-visible { color: var(--amber); }
.topnav-cta { display: flex; align-items: center; gap: var(--sp-4); }

/* min-width/height: the hamburger is the ONLY way to reach the other six
   cues in the reduced-motion/no-JS fallback (this Mac defaults to reduced
   motion, so this path sees real traffic) — the 22px-of-bars + 8px padding
   box was ~38x32px, under the 44px touch minimum. justify/align-content
   keep the three bars centered in the now-larger box. */
.nav-toggle { display: none; flex-direction: column; gap: 5px; padding: 8px; min-width: 44px; min-height: 44px; align-items: center; justify-content: center; }
.nav-toggle span { width: 22px; height: 2px; background: var(--bone); transition: transform var(--dur-fast) var(--ease-out), opacity var(--dur-fast) var(--ease-out); }
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

.mobile-nav {
  position: fixed; inset: 0; z-index: 130;
  background: var(--black);
  display: none; flex-direction: column; justify-content: center; gap: var(--sp-6);
  padding: var(--sp-16) var(--edge);
  transform: translateY(-100%);
  transition: transform var(--dur-med) var(--ease-inout);
}
.mobile-nav:not([hidden]) { display: flex; }
.mobile-nav.is-open { transform: translateY(0); }
@media (prefers-reduced-motion: reduce) { .mobile-nav { transition: none; } }
.mobile-nav ol { display: flex; flex-direction: column; gap: var(--sp-6); }
.mobile-nav a {
  font-family: var(--f-display); font-size: clamp(1.8rem, 8vw, 3rem);
  text-transform: uppercase; display: flex; align-items: baseline; gap: var(--sp-4);
}
.mobile-nav .mnum { font-family: var(--f-mono); font-size: var(--fs-small); color: var(--amber); }
.mobile-nav-close { position: absolute; top: calc(var(--sp-4) + var(--safe-t)); right: var(--edge); min-width: 44px; min-height: 44px; }

@media (max-width: 860px) {
  .topnav-links { display: none; }
  .topnav-cta .btn-ghost.btn-sm span,
  .topnav-cta > .btn.btn-ghost.btn-sm { display: none; }
  .nav-toggle { display: flex; }
  /* the 44px hamburger (above) is a few px taller than the topnav's old
     reserved height — bump the reservation so cue-body content (which
     pads its top by --topnav-total-h) never sits a hair under the bar. */
  :root { --topnav-h: 4.8rem; }
}
@media (max-width: 560px) {
  .topnav-cta > a.btn.btn-primary.btn-sm { padding: 0.6rem 0.9rem; min-height: 44px; }
}

/* Hide the fallback top nav once JS + motion have taken over navigation. */
@media (prefers-reduced-motion: no-preference) {
  html.js .topnav { display: none; }
}

/* ============================================================
   7. THE CUE ENGINE — core stacking rules.
   Default: normal document flow, full-bleed sections, anchored,
   scrollable — the accessible baseline for no-JS AND reduced motion.
   ============================================================ */
.cue {
  position: relative;
  min-height: 100svh;
  display: flex;
  overflow: hidden;
  background: var(--black);
  scroll-margin-top: var(--topnav-total-h);
}
.cue-backdrop, .cue-scrim {
  position: absolute; inset: 0; width: 100%; height: 100%;
  background-size: cover; background-position: center;
}
.cue-backdrop { z-index: 1; }
.cue-body {
  position: relative; z-index: 4; width: 100%;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding-top: calc(var(--topnav-total-h) + var(--sp-8));
  padding-bottom: var(--sp-16);
  min-height: 100svh;
}
.cue-content { width: 100%; }

@media (prefers-reduced-motion: no-preference) {
  html.js .cue {
    position: fixed; inset: 0; min-height: 0; height: 100svh;
    opacity: 0; visibility: hidden; pointer-events: none; z-index: 1;
    transition: opacity var(--veil-out) linear, visibility 0s linear var(--veil-out);
  }
  html.js .cue.is-active {
    opacity: 1; visibility: visible; pointer-events: auto; z-index: 2;
    transition: opacity var(--veil-out) linear;
  }
  html.js .cue-body { padding-top: calc(var(--sp-8) + 2.5rem + var(--safe-t)); padding-bottom: calc(var(--dock-total-h) + var(--sp-6)); justify-content: center; overflow-y: auto; max-height: 100svh; }
  html.js body { overflow: hidden; height: 100svh; }

  /* THE BOARD is the one cue whose content (6-channel grid) reliably
     overflows a phone/tablet viewport. The engine .cue-body above centers
     its content vertically (justify-content:center), which is right for
     every cue that FITS — but when a scrollable flex column's content is
     taller than the box, center-alignment pushes the START of the content
     ABOVE the top padding edge and CLIPS it unreachably: scrollTop:0 is
     already the top of the scrollable range, so there is no way to scroll
     up to reveal it. That put the board's "EVERY DEPARTMENT COVERED."
     headline ~397px above the viewport on a 390px phone, off-screen and
     unreachable. `safe center` is the native fix for exactly this: it is
     byte-identical to `center` whenever the content fits (the DESKTOP
     board, 6-col — unchanged) and falls back to start-alignment ONLY when
     the content would otherwise overflow and clip (mobile/tablet board),
     so the headline sits at the TOP of the scroll flow, the grid follows
     below, and the user scrolls down (with the "MORE BELOW" hint). It is
     self-scoping to the actual overflow condition, so no fixed breakpoint
     can miss a width or over-reach. Scoped to #cue-board so no other cue's
     vertical centering changes. Browsers without `safe` alignment (iOS
     Safari <16.4) simply drop this one rule and inherit the base `center`
     via the cascade — no worse than the current shipped behavior. Keeps
     the scrollTop=0-on-activate reset + MORE-BELOW hint already built. */
  html.js #cue-board .cue-body { justify-content: safe center; }
}

/* ---------- 8. CUE VEIL (the "call" — a real console calling a cue).
   Sequence: crisp blackout snap → 1-frame white-hot pop → amber bloom +
   hot-core follow-spot sweep → the readout stamps down with authority →
   curtain lifts fast while the amber afterglow lingers a beat longer,
   settling over the incoming cue like it just got lit. Runs only inside
   the cue engine (JS + motion allowed); under reduced motion the whole
   veil is display:none and cues swap via native anchor jumps, so nothing
   here needs its own gate logic beyond the blanket rule at the bottom. */
.cue-veil {
  position: fixed; inset: 0; z-index: 140; background: var(--black);
  opacity: 0; pointer-events: none;
  transition: opacity var(--veil-out) var(--ease-inout);
  display: flex; align-items: center; justify-content: center; overflow: hidden;
}
/* going in: a hard snap to black, not a fade — the "call" lands instantly,
   everything else (flash/bloom/sweep/readout) plays on top of solid black. */
.cue-veil.is-flashing { opacity: 1; transition: opacity var(--veil-snap) var(--ease-inout); pointer-events: auto; }

/* the 1-frame white-hot pop: sells the "light just snapped on" impact
   right as blackout lands, ahead of the slower amber bloom/sweep. */
.cue-veil-flash {
  position: absolute; inset: 0; z-index: 0; background: var(--bone); opacity: 0;
}
.cue-veil.is-flashing .cue-veil-flash { animation: veil-flash var(--veil-in) var(--ease-out) both; }
@keyframes veil-flash {
  0% { opacity: 0; }
  14% { opacity: 0.9; }
  45% { opacity: 0; }
  100% { opacity: 0; }
}

/* the bloom: a soft radial glow that punches in (with a slight overshoot)
   at the peak of blackout, then lingers and settles over the incoming cue
   after the curtain lifts — the "backdrop igniting" handoff. Two speeds:
   fast in (is-flashing), slower dissipate on the way back out (base rule). */
.cue-veil-bloom {
  position: absolute; z-index: 1; width: 70vmax; height: 70vmax; border-radius: 50%;
  background: radial-gradient(circle, var(--amber-glow) 0%, transparent 68%);
  transform: scale(0.7); opacity: 0;
  transition: transform var(--veil-bloom-out) var(--ease-out), opacity var(--veil-bloom-out) var(--ease-out);
}
.cue-veil.is-flashing .cue-veil-bloom {
  transform: scale(1.08); opacity: 1;
  transition: transform var(--veil-in) var(--ease-out), opacity var(--veil-in) var(--ease-out);
}

/* the sweep: a hot-core follow-spot wipe that snaps across the blackout —
   amber ramp, a white-hot dead center, amber-hi falloff — the "cue is
   being called" beat. */
.cue-veil-sweep {
  position: absolute; inset: -10% -10%; z-index: 2;
  background: linear-gradient(100deg,
    transparent 0%, transparent 40%,
    var(--amber) 46%, var(--bone) 50%, var(--amber-hi) 54%,
    transparent 60%, transparent 100%);
  transform: translateX(-130%); opacity: 0;
}
.cue-veil.is-flashing .cue-veil-sweep {
  transform: translateX(130%); opacity: 1;
  transition: transform var(--veil-in) var(--ease-out), opacity 80ms linear;
}

/* the readout: STANDBY/GO stamps down with impact (overshoot easing) right
   as the frame hits solid black, then a small "CUE n/7" caption ticks in
   underneath a beat later — a real console confirming the call. Text is
   set via JS (Showcall cue engine) before the flash starts. */
.cue-veil-readout-group { position: relative; z-index: 3; display: flex; flex-direction: column; align-items: center; }
.cue-veil-readout {
  font-family: var(--f-mono); font-size: var(--fs-body-lg); font-weight: 700;
  letter-spacing: 0.4em; color: var(--amber);
  text-shadow: 0 0 20px var(--amber-glow);
  opacity: 0; transform: scale(1.7);
}
.cue-veil.is-flashing .cue-veil-readout {
  opacity: 1; transform: scale(1);
  transition: opacity 190ms var(--ease-out) 70ms, transform 220ms var(--ease-stamp) 70ms;
}
.cue-veil-sub {
  display: block; margin-top: var(--sp-3); text-align: center;
  font-family: var(--f-mono); font-size: var(--fs-micro); letter-spacing: 0.25em;
  color: var(--bone-faint);
  opacity: 0; transform: translateY(6px);
}
.cue-veil.is-flashing .cue-veil-sub {
  opacity: 1; transform: translateY(0);
  transition: opacity 180ms var(--ease-out) 170ms, transform 180ms var(--ease-out) 170ms;
}
@media (prefers-reduced-motion: reduce) {
  .cue-veil, .cue-veil-bloom, .cue-veil-sweep, .cue-veil-readout, .cue-veil-flash, .cue-veil-sub, .cue-veil-readout-group { display: none; }
}

/* ---------- 9. CUE NEXT (the little "call the show" affordance) ---------- */
.cue-next {
  position: absolute; right: var(--edge); bottom: var(--sp-8); z-index: 5;
  display: flex; align-items: center; gap: var(--sp-3);
  font-family: var(--f-mono); font-size: var(--fs-micro); letter-spacing: 0.15em;
  color: var(--bone-faint); text-transform: uppercase;
  transition: color var(--dur-fast) var(--ease-out);
}
.cue-next:hover, .cue-next:focus-visible { color: var(--amber); }
.cue-next-bar { width: 28px; height: 1px; background: linear-gradient(to right, transparent, var(--amber)); transition: box-shadow var(--dur-fast) var(--ease-out); }
/* the little "call the show" bar energizes on hover/focus — same --amber-glow
   bloom token as every other light source on the site (§ shared bloom scale),
   so the affordance itself feels live, not just the text color beside it.
   Excluded from .cue-next-restart (below), which stays deliberately muted —
   "reset to standby" is a step back, not a call, and shouldn't borrow the
   same energized glow as every forward "GO" link. */
.cue-next:not(.cue-next-restart):hover .cue-next-bar,
.cue-next:not(.cue-next-restart):focus-visible .cue-next-bar { box-shadow: var(--bloom-sm); }
html.js .cue-next { bottom: calc(var(--dock-total-h) + var(--sp-6)); }
@media (max-width: 700px) { .cue-next { display: none; } }

/* ---------- 10. REVEAL SYSTEM — visible by default; animates only when
   its cue becomes active (JS + motion-allowed). Replays each time the
   cue is re-entered, echoing "the cue lights up again." ---------- */
.reveal { opacity: 1; transform: none; }
@media (prefers-reduced-motion: no-preference) {
  html.js .cue .reveal {
    opacity: 0; transform: translateY(26px);
    transition: opacity 0.7s var(--ease-out), transform 0.7s var(--ease-out);
  }
  html.js .cue.is-active .reveal { opacity: 1; transform: none; }
  html.js .cue.is-active .reveal-1 { transition-delay: 0.08s; }
  html.js .cue.is-active .reveal-2 { transition-delay: 0.16s; }
  html.js .cue.is-active .reveal-3 { transition-delay: 0.24s; }
  html.js .cue.is-active .reveal-4 { transition-delay: 0.32s; }
}

/* ---------- 11. CUE 0 / STANDBY (hero + ignition) ---------- */
.cue-backdrop-standby {
  background-image: url('/assets/hero-stage.jpg'), linear-gradient(160deg, var(--surface-3), var(--black));
}
.ignite-canvas { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 2; mix-blend-mode: screen; pointer-events: none; }
.cue-scrim-standby {
  z-index: 3; pointer-events: none;
  background:
    radial-gradient(ellipse at 50% 100%, rgba(8,8,10,0) 0%, rgba(8,8,10,0.55) 55%, rgba(8,8,10,0.96) 100%),
    linear-gradient(to top, rgba(8,8,10,0.95) 0%, rgba(8,8,10,0.1) 40%);
}
.cue-content-standby { padding-block: var(--sp-8); }
.cue-tag {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  font-family: var(--f-mono); font-size: var(--fs-micro); letter-spacing: 0.2em;
  color: var(--bone-faint); border: 1px solid var(--steel); border-radius: var(--radius);
  padding: 0.4rem 0.75rem; margin-bottom: var(--sp-6);
  transition: color var(--dur-slow) var(--ease-out), border-color var(--dur-slow) var(--ease-out);
}
.cue-dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--steel); flex-shrink: 0;
  transition: background-color var(--dur-slow) var(--ease-out), box-shadow var(--dur-slow) var(--ease-out);
}
#cue-standby.is-ignited .cue-tag { color: var(--amber); border-color: var(--amber-line); }
#cue-standby.is-ignited .cue-dot { background: var(--amber); box-shadow: 0 0 8px var(--amber-glow); }

/* Title-sequence cascade: JS fires "is-ignited" almost immediately (a short
   power-on beat, decoupled from the slower canvas beam warm-up — see
   initHeroIgnition) so the whole reveal below peaks well inside 1.5s
   instead of waiting on the beams to finish warming first. */
.hero-headline { max-width: 15ch; }
.hero-headline .line { display: block; overflow: hidden; }
/* html.js-scoped (not just is-ignited-scoped): without it, a true no-JS
   visitor — .js never gets added, so is-ignited never fires either — would
   be stuck staring at a permanently opacity:0 headline. Scoping the HIDDEN
   state itself behind html.js means the no-JS baseline is simply "never
   animate, always show the text," matching the §7/§23 pattern the rest of
   the engine already uses, instead of relying on is-ignited to rescue it. */
html.js .hero-headline .line span {
  display: block; transform: translateY(110%); opacity: 0; letter-spacing: 0.09em;
  transition: transform 0.85s var(--ease-out), opacity 0.85s var(--ease-out), letter-spacing 0.85s var(--ease-out);
}
html.js #cue-standby.is-ignited .hero-headline .line:nth-child(1) span { transition-delay: 0.02s; }
html.js #cue-standby.is-ignited .hero-headline .line:nth-child(2) span { transition-delay: 0.15s; }
html.js #cue-standby.is-ignited .hero-headline .line span { transform: translateY(0); opacity: 1; letter-spacing: 0.01em; }

/* The "landing" word — hero is the most dramatic beat in the kinetic-type
   system, so on top of the shared line-wipe it gets its own harder snap
   (bigger scale kick, brighter glow) plus a signature underline that draws
   in right as the word lands. Both are exclusive to the hero; every other
   cue's kinetic headline (below) uses the calmer shared .kinetic-h em. */
.hero-headline em { display: inline-block; position: relative; }
.hero-headline em::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -0.07em; height: 3px;
  background: var(--amber); box-shadow: 0 0 12px var(--amber-glow);
  transform: scaleX(0); transform-origin: left;
}
@media (prefers-reduced-motion: no-preference) {
  html.js #cue-standby.is-ignited .hero-headline em {
    animation: hero-word-snap 0.5s var(--ease-stamp) 0.78s both;
  }
  html.js #cue-standby.is-ignited .hero-headline em::after {
    animation: hero-underline-draw 0.4s var(--ease-out) 0.86s both;
  }
}
@keyframes hero-word-snap {
  0% { transform: scale(1); text-shadow: 0 0 0 transparent; }
  55% { transform: scale(1.08); text-shadow: 0 0 22px var(--amber-glow); }
  100% { transform: scale(1); text-shadow: 0 0 0 transparent; }
}
@keyframes hero-underline-draw { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@media (prefers-reduced-motion: reduce) {
  .hero-headline em::after { transform: scaleX(1); }
}

.hero-sub {
  margin-top: var(--sp-6); font-size: var(--fs-body-lg); max-width: 42ch;
  opacity: 0; transform: translateY(14px);
  transition: opacity 0.75s var(--ease-out) 0.36s, transform 0.75s var(--ease-out) 0.36s;
}
#cue-standby.is-ignited .hero-sub { opacity: 1; transform: translateY(0); }

.hero-actions { display: flex; flex-wrap: wrap; gap: var(--sp-4); margin-top: var(--sp-8); }
/* Only the standby hero delays+wipes this row in on ignition; reused
   elsewhere (cue-caller), .hero-actions should just be visible immediately
   and ride its ancestor .reveal fade like everything else — it was
   previously stuck at opacity:0 forever outside #cue-standby, since
   "is-ignited" is a hero-only state. */
#cue-standby .hero-actions {
  opacity: 0; transform: translateY(14px);
  transition: opacity 0.75s var(--ease-out) 0.48s, transform 0.75s var(--ease-out) 0.48s;
}
#cue-standby.is-ignited .hero-actions { opacity: 1; transform: translateY(0); }

@media (prefers-reduced-motion: reduce) {
  .hero-headline .line span, .hero-sub, .hero-actions { transition: none !important; opacity: 1 !important; transform: none !important; }
  .cue-tag, .cue-dot { transition: none !important; }
}

.hero-video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 1; display: none; opacity: 1; }
@media (min-width: 900px) and (prefers-reduced-motion: no-preference) {
  .hero-video { display: block; }
  /* Ignition: the loop starts dimmed and rides the same power-on beat as
     the canvas beams + title cascade, so the stage reads as warming up as
     one coordinated moment instead of a background that was already lit.
     Scoped to html.js — under no-JS this media query still shows the video
     element, but with no <source> (attached by JS) it just renders the
     poster, so it must stay at full brightness there, matching the
     pre-existing no-JS-fallback behavior every other hero element uses.
     scale/translate ride the same rule (not a separate one — see §23) so
     there is exactly one `transition` declaration for this element and the
     opacity ignition beat, the depth-settle, and the pointer parallax
     never fight over which one wins the cascade. translate defaults to
     0,0 (--px/--py unset) until initPointerParallax() starts writing them,
     and only does so on hover-capable fine-pointer desktop with motion
     allowed — inert everywhere else. */
  html.js .hero-video {
    opacity: 0.42; scale: 1.06; translate: calc(var(--px, 0) * 7px) calc(var(--py, 0) * 5px);
    transition: opacity 1.3s var(--ease-out), scale 1.15s var(--ease-out), translate 0.6s var(--ease-out);
  }
  html.js #cue-standby.is-ignited .hero-video { opacity: 1; }
  html.js #cue-standby.is-active .hero-video { scale: 1; }
}

/* ---------- 12. CUE 1 / THE MODEL ---------- */
.cue-backdrop-model {
  background-image: url('/assets/venue-ballroom.jpg'), linear-gradient(160deg, var(--surface-3) 0%, var(--surface) 55%, var(--black) 100%);
  filter: saturate(0.85) brightness(0.55);
}
.cue-scrim-side { z-index: 2; pointer-events: none; background: linear-gradient(100deg, rgba(8,8,10,0.94) 0%, rgba(8,8,10,0.82) 42%, rgba(8,8,10,0.4) 75%, rgba(8,8,10,0.15) 100%); }
/* Mirrored scrim for cues whose readable copy sits in the RIGHT-hand
   column (model, proof) — the plain .cue-scrim-side above fades out
   exactly where their body text lives, which was borderline-illegible
   over a bright backdrop. Same curve, opposite side. */
.cue-scrim-side-r { z-index: 2; pointer-events: none; background: linear-gradient(260deg, rgba(8,8,10,0.94) 0%, rgba(8,8,10,0.82) 42%, rgba(8,8,10,0.4) 75%, rgba(8,8,10,0.15) 100%); }
.model-grid { display: grid; grid-template-columns: 0.8fr 1.2fr; gap: var(--sp-16); align-items: center; }
@media (max-width: 900px) { .model-grid { grid-template-columns: 1fr; gap: var(--sp-8); } }
.model-lead h2 { max-width: 13ch; }
.model-points { display: flex; flex-direction: column; gap: var(--sp-6); }
.model-point {
  display: grid; grid-template-columns: auto 1fr; gap: var(--sp-4);
  padding-block: var(--sp-6); border-top: 1px solid var(--steel-dim);
}
.model-point:first-child { border-top: none; padding-top: 0; }
.model-point-num { font-family: var(--f-mono); font-size: var(--fs-small); color: var(--amber); padding-top: 0.2em; }
.model-point h3 { font-size: var(--fs-md); text-transform: none; font-family: var(--f-body); font-weight: 700; margin-bottom: var(--sp-2); letter-spacing: 0; }
.model-point p { font-size: var(--fs-small); }

/* ---------- 13. CUE 2 / THE BOARD ---------- */
.cue-backdrop-board {
  background-image: url('/assets/disc-lighting.jpg'), linear-gradient(160deg, var(--surface-3) 0%, var(--black) 100%);
  filter: saturate(0.6) brightness(0.3) blur(2px);
}
.cue-scrim-full { z-index: 2; pointer-events: none; background: rgba(8,8,10,0.78); }
.board-head { margin-bottom: var(--sp-6); }
.channel-bank {
  display: grid; grid-template-columns: repeat(6, 1fr); gap: 1px;
  background: var(--steel-dim); border: 1px solid var(--steel-dim);
}
@media (max-width: 1000px) { .channel-bank { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 600px) { .channel-bank { grid-template-columns: repeat(2, 1fr); } }

.channel {
  position: relative;
  background: var(--surface); padding: var(--sp-4) var(--sp-3) var(--sp-6);
  display: flex; flex-direction: column; gap: var(--sp-3);
  opacity: 0.55; filter: saturate(0.5);
  transition: opacity 0.7s var(--ease-out), filter 0.7s var(--ease-out),
              transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
html.js .cue.is-active .channel { opacity: 1; filter: saturate(1); }
/* --ch-phase feeds the VU-meter ripple below (§13) — each channel's bars
   fire 90ms after the previous, the same beat the reveal-in stagger uses,
   so the board reads as one wave sweeping left-to-right, not six identical
   meters flashing in unison. */
html.js .cue.is-active .channel:nth-child(1) { transition-delay: 0ms; --ch-phase: 0s; }
html.js .cue.is-active .channel:nth-child(2) { transition-delay: 90ms; --ch-phase: 0.09s; }
html.js .cue.is-active .channel:nth-child(3) { transition-delay: 180ms; --ch-phase: 0.18s; }
html.js .cue.is-active .channel:nth-child(4) { transition-delay: 270ms; --ch-phase: 0.27s; }
html.js .cue.is-active .channel:nth-child(5) { transition-delay: 360ms; --ch-phase: 0.36s; }
html.js .cue.is-active .channel:nth-child(6) { transition-delay: 450ms; --ch-phase: 0.45s; }
.channel:hover, .channel:focus-within {
  transform: translateY(-4px);
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.5), inset 0 0 0 1px var(--amber-line);
  z-index: 2;
}

.channel-shot {
  aspect-ratio: 3/4; margin-bottom: var(--sp-1); position: relative; overflow: hidden; border-radius: var(--radius);
  background-image: linear-gradient(155deg, var(--surface-3) 0%, var(--surface) 55%, var(--black) 100%);
  background-size: cover; background-position: center;
  transition: transform var(--dur-med) var(--ease-out);
}
.channel:hover .channel-shot, .channel:focus-within .channel-shot { transform: scale(1.03); }
.channel[data-disc="audio"] .channel-shot { background-image: url('/assets/disc-audio.jpg'), linear-gradient(155deg, var(--surface-3), var(--black)); }
.channel[data-disc="video"] .channel-shot { background-image: url('/assets/disc-video.jpg'), linear-gradient(155deg, var(--surface-3), var(--black)); }
.channel[data-disc="led"] .channel-shot { background-image: url('/assets/disc-led.jpg'), linear-gradient(155deg, var(--surface-3), var(--black)); }
.channel[data-disc="lighting"] .channel-shot { background-image: url('/assets/disc-lighting.jpg'), linear-gradient(155deg, var(--surface-3), var(--black)); }
.channel[data-disc="staging"] .channel-shot { background-image: url('/assets/disc-staging.jpg'), linear-gradient(155deg, var(--surface-3), var(--black)); }
.channel[data-disc="scenic"] .channel-shot { background-image: url('/assets/disc-scenic.jpg'), linear-gradient(155deg, var(--surface-3), var(--black)); }

.ph-frame-corners { position: absolute; inset: 8px; pointer-events: none; }
.ph-frame-corners i { position: absolute; width: 14px; height: 14px; border: 1px solid var(--amber-line); }
.ph-frame-corners i:nth-child(1) { top: 0; left: 0; border-right: 0; border-bottom: 0; }
.ph-frame-corners i:nth-child(2) { top: 0; right: 0; border-left: 0; border-bottom: 0; }
.ph-frame-corners i:nth-child(3) { bottom: 0; left: 0; border-right: 0; border-top: 0; }
.ph-frame-corners i:nth-child(4) { bottom: 0; right: 0; border-left: 0; border-top: 0; }

.channel-head { display: flex; align-items: center; justify-content: space-between; }
.channel-num { font-family: var(--f-mono); font-size: var(--fs-micro); color: var(--bone-faint); }
.channel-icon { width: 20px; height: 20px; color: var(--steel); transition: color 0.7s var(--ease-out); }
html.js .cue.is-active .channel .channel-icon { color: var(--amber); }
.channel h3 { font-size: 1.05rem; font-family: var(--f-body); font-weight: 700; text-transform: none; letter-spacing: 0; }
.channel p { font-size: var(--fs-micro); line-height: 1.4; }

.meter { display: flex; align-items: flex-end; gap: 3px; height: 16px; margin-top: auto; }
.meter i { width: 4px; background: var(--steel); border-radius: 1px; height: 30%; }
/* --bar-phase (per-bar, below) + --ch-phase (per-channel, set on .channel
   above) combine into one delay — the four bars still tick unevenly like a
   real meter, and the whole meter is offset by which channel it's in, so
   the six meters ripple across the board instead of ticking in lockstep. */
.meter i:nth-child(1) { --bar-phase: 0.1s; }
.meter i:nth-child(2) { --bar-phase: 0.4s; }
.meter i:nth-child(3) { --bar-phase: 0.7s; }
.meter i:nth-child(4) { --bar-phase: 0.25s; }
html.js .cue.is-active .channel .meter i {
  background: var(--amber);
  animation: meter-tick 1.8s var(--ease-inout) infinite;
  animation-delay: calc(var(--bar-phase, 0s) + var(--ch-phase, 0s));
}
@keyframes meter-tick { 0%, 100% { height: 20%; } 50% { height: 85%; } }

@media (prefers-reduced-motion: reduce) {
  .channel { transition: none !important; opacity: 1; filter: saturate(1); }
  .channel .channel-icon { transition: none !important; }
  .channel .meter i { background: var(--amber); animation: none; height: 55%; }
}

/* -- CHANNEL SOLO (Visual Pass 5) — hover/focus one channel and it reads
   like pulling its fader solo on the desk: the channel itself lifts
   (existing :hover rule above) and its meter runs hot, while every other
   channel dims and its meter cools — the board briefly acts like ONE
   live signal instead of six identical loops. Pure :has(), no JS, no new
   markup — a native platform feature covers this outright (ponytail).
   Desktop hover only in practice (no focusable element lives inside a
   .channel today), matching the existing :hover/:focus-within pairing
   above; content is never hidden, siblings only dim to 0.4 opacity. -- */
@media (prefers-reduced-motion: no-preference) {
  html.js .channel-bank:has(.channel:hover) .channel:not(:hover):not(:focus-within),
  html.js .channel-bank:has(.channel:focus-within) .channel:not(:hover):not(:focus-within) {
    opacity: 0.4; filter: saturate(0.3);
    transition: opacity var(--dur-fast) var(--ease-out), filter var(--dur-fast) var(--ease-out);
  }
  html.js .channel-bank:has(.channel:hover) .channel:hover .meter i,
  html.js .channel-bank:has(.channel:focus-within) .channel:focus-within .meter i {
    animation-duration: 0.85s; /* hot — the soloed fader is live */
  }
}

/* -- MOBILE SCROLL CRAFT (Pass 6) — The Board is the one cue whose content
   (6 channels) can't fit a phone viewport in one screen; it already
   scrolls internally (cue-body's engine-mode overflow-y:auto, §7). Two
   fixes make that feel intentional instead of like a broken full-viewport
   cue:
   1. extra bottom clearance so the last channel row never scrolls up
      underneath the floating chat launcher (bottom-right, fixed above the
      dock — see §21) — the launcher's own footprint needs ~dock-h+~90px
      of clearance from the true bottom, well past the flat dock-h+sp-6
      every other cue reserves. Scoped to board only so the other five
      cues (which don't overflow) don't gain empty bottom space they
      don't need.
   2. a small "more below" hint (markup: index.html, right after
      #cue-board's .cue-body) — see the JS-driven fade rules further down
      this file and initBoardScrollHint() in showcall.js. -- */
@media (max-width: 600px) {
  html.js #cue-board .cue-body { padding-bottom: calc(var(--dock-total-h) + var(--sp-24)); }
}

.board-scroll-hint { display: none; }
@media (max-width: 600px) and (prefers-reduced-motion: no-preference) {
  html.js #cue-board .board-scroll-hint {
    display: flex; align-items: center; justify-content: center; gap: 6px;
    position: absolute; left: 0; right: 0; z-index: 6; pointer-events: none;
    bottom: calc(var(--dock-total-h) + var(--sp-3));
    font-family: var(--f-mono); font-size: var(--fs-micro); letter-spacing: 0.12em; text-transform: uppercase;
    color: var(--bone-faint);
    opacity: 0; transition: opacity 0.4s var(--ease-out);
  }
  /* visible once the board is the active cue and JS has determined its
     content actually overflows (is-overflow, set by initBoardScrollHint);
     fades back out once the user scrolls near the end (is-scrolled-end). */
  html.js #cue-board.is-active.is-overflow:not(.is-scrolled-end) .board-scroll-hint { opacity: 1; }
  .board-scroll-hint svg { width: 12px; height: 12px; animation: hint-bounce 1.6s var(--ease-inout) infinite; }
}
@keyframes hint-bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(4px); } }

/* ---------- 14. CUE 3 / RUN OF SHOW ---------- */
.cue-backdrop-run {
  background-image: url('/assets/crew-foh.jpg'), linear-gradient(160deg, var(--surface-3) 0%, var(--black) 100%);
  filter: saturate(0.7) brightness(0.4);
}
.how-track { display: flex; flex-direction: row; gap: var(--sp-6); align-items: stretch; padding-block: var(--sp-6); }
@media (max-width: 1000px) { .how-track { flex-direction: column; gap: 0; padding-block: 0; } }
.how-step { display: flex; flex-direction: column; gap: var(--sp-4); flex: 1; }
@media (max-width: 1000px) {
  .how-step { flex-direction: row; gap: var(--sp-4); padding-block: var(--sp-4); }
}
.how-rail { display: flex; flex-direction: row; align-items: center; }
@media (max-width: 1000px) { .how-rail { flex-direction: column; align-self: stretch; } }
.how-node {
  width: 12px; height: 12px; border-radius: 50%; background: var(--surface-3);
  border: 1px solid var(--steel); flex-shrink: 0;
  transition: background-color 0.5s var(--ease-out), border-color 0.5s var(--ease-out), box-shadow 0.5s var(--ease-out);
}
html.js .cue.is-active .how-step .how-node { background: var(--amber); border-color: var(--amber); box-shadow: 0 0 10px var(--amber-glow); }
html.js .cue.is-active .how-step:nth-child(1) .how-node { transition-delay: 0ms; }
html.js .cue.is-active .how-step:nth-child(2) .how-node { transition-delay: 140ms; }
html.js .cue.is-active .how-step:nth-child(3) .how-node { transition-delay: 280ms; }
html.js .cue.is-active .how-step:nth-child(4) .how-node { transition-delay: 420ms; }
html.js .cue.is-active .how-step:nth-child(5) .how-node { transition-delay: 560ms; }
.how-line { height: 1px; flex: 1; background: var(--steel-dim); position: relative; overflow: hidden; margin-inline: var(--sp-2); }
@media (max-width: 1000px) { .how-line { width: 1px; height: auto; flex: 1; margin-inline: 0; margin-block: var(--sp-2); } }
.how-line::after {
  content: ""; position: absolute; top: 0; left: 0; width: 0; height: 100%;
  background: var(--amber); transition: width 0.6s var(--ease-out);
}
@media (max-width: 1000px) { .how-line::after { width: 100%; height: 0; transition: height 0.6s var(--ease-out); } }
html.js .cue.is-active .how-step .how-line::after { width: 100%; }
@media (max-width: 1000px) { html.js .cue.is-active .how-step .how-line::after { height: 100%; width: 100%; } }
.how-step:last-child .how-line { display: none; }
.how-copy .step-tag { font-family: var(--f-mono); font-size: var(--fs-micro); color: var(--bone-faint); letter-spacing: 0.15em; }
.how-copy h3 { text-transform: none; font-family: var(--f-body); font-weight: 700; font-size: var(--fs-md); letter-spacing: 0; margin-block: var(--sp-1) var(--sp-2); }
.how-copy p { font-size: var(--fs-small); max-width: 26ch; }
@media (prefers-reduced-motion: reduce) {
  .how-node, .how-line::after { transition: none !important; }
  .how-step .how-node { background: var(--amber); border-color: var(--amber); }
  .how-step .how-line::after { width: 100%; height: 100%; }
}

/* ---------- 15. CUE 4 / PROOF ---------- */
.cue-backdrop-proof {
  background-image: url('/assets/disc-staging.jpg'), linear-gradient(160deg, var(--surface-3) 0%, var(--black) 100%);
  filter: saturate(0.8) brightness(0.4);
}
.proof-grid { display: grid; grid-template-columns: 0.9fr 1.1fr; gap: var(--sp-12); align-items: center; }
@media (max-width: 950px) { .proof-grid { grid-template-columns: 1fr; gap: var(--sp-8); } }
.proof-lead .lede { margin-bottom: var(--sp-6); }
.proof-shots { display: flex; gap: var(--sp-3); }
.proof-shot {
  width: 120px; aspect-ratio: 4/5; border-radius: var(--radius); border: 1px solid var(--steel-dim);
  background-size: cover; background-position: center;
  background-image: linear-gradient(155deg, var(--surface-3), var(--black));
}
.proof-shot-crew { background-image: url('/assets/crew-foh.jpg'), linear-gradient(155deg, var(--surface-3), var(--black)); }
.proof-shot-venue { background-image: url('/assets/venue-ballroom.jpg'), linear-gradient(155deg, var(--surface-3), var(--black)); }
.badge-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--sp-3); }
.badge {
  border: 1px solid var(--steel-dim); border-radius: var(--radius); background: rgba(13,13,16,0.7);
  padding: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-2);
  transition: border-color var(--dur-fast) var(--ease-out);
}
.badge:hover { border-color: var(--amber-line); }
.badge-icon { width: 20px; height: 20px; color: var(--amber); }
.badge h3 { font-size: 0.95rem; font-family: var(--f-body); font-weight: 700; text-transform: none; letter-spacing: 0; }
.badge p { font-size: var(--fs-micro); }

.indiana-motif { position: absolute; right: var(--edge); top: calc(var(--topnav-total-h) + var(--sp-6)); width: 110px; opacity: 0.14; pointer-events: none; z-index: 3; }
@media (max-width: 900px) { .indiana-motif { display: none; } }
.indiana-motif .in-dot { fill: var(--amber); animation: pulse-dot 3s var(--ease-inout) infinite; }
.indiana-motif .in-dot:nth-child(2) { animation-delay: 0.6s; }
.indiana-motif .in-dot:nth-child(3) { animation-delay: 1.2s; }
@media (prefers-reduced-motion: reduce) { .indiana-motif .in-dot { animation: none; } }
@keyframes pulse-dot { 0%, 100% { opacity: 0.5; } 50% { opacity: 1; } }

/* ---------- 16. CUE 5 / SHOW CALLER ---------- */
.cue-backdrop-caller {
  background-image: url('/assets/caller-headset.jpg'), linear-gradient(160deg, var(--surface-3) 0%, var(--black) 100%);
  filter: saturate(0.8) brightness(0.42);
}
.caller-grid { display: grid; grid-template-columns: 1.1fr 0.9fr; gap: var(--sp-12); align-items: center; }
@media (max-width: 900px) { .caller-grid { grid-template-columns: 1fr; gap: var(--sp-6); } }
.status-line {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  font-family: var(--f-mono); font-size: var(--fs-micro); letter-spacing: 0.15em; color: var(--amber);
  margin-bottom: var(--sp-4);
}
.status-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--amber); box-shadow: 0 0 8px var(--amber-glow); animation: pulse-dot 2.4s var(--ease-inout) infinite; animation-delay: -1.7s; }
@media (prefers-reduced-motion: reduce) { .status-dot { animation: none; } }

.cue-card {
  border: 1px solid var(--steel-dim); border-radius: var(--radius);
  background: rgba(19,19,22,0.85); backdrop-filter: blur(6px); padding: var(--sp-5);
}
.cue-card-label {
  font-family: var(--f-mono); font-size: var(--fs-micro); color: var(--bone-faint);
  letter-spacing: 0.15em; margin-bottom: var(--sp-3); display: block;
}
.cue-card .msg { display: flex; gap: var(--sp-3); margin-bottom: var(--sp-3); }
.cue-card .msg:last-child { margin-bottom: 0; }
.cue-card .who { font-family: var(--f-mono); font-size: var(--fs-micro); color: var(--amber); flex-shrink: 0; width: 4.5rem; padding-top: 0.15em; }
.cue-card .txt { font-size: var(--fs-small); color: var(--bone-dim); }

/* ---------- 17. CUE 6 / GO (quote + close credits) ---------- */
.cue-backdrop-go {
  background-image: url('/assets/disc-lighting.jpg'), linear-gradient(160deg, var(--surface-3) 0%, var(--black) 100%);
  filter: saturate(0.9) brightness(0.32);
}
/* padding-top must repeat the FULL §7 calc (…+ var(--safe-t)), not just the
   part this override actually changes (justify-content) — an earlier pass's
   override silently dropped the safe-area-inset-top term the general rule
   carries, which would sit the quote form var(--safe-t) too high on a
   notched device reporting a nonzero inset. Every other cue inherits §7's
   padding-top untouched; only this one needed a copy because it's the one
   cue that overrides justify-content away from center. */
@media (prefers-reduced-motion: no-preference) {
  html.js #cue-go .cue-body { justify-content: flex-start; padding-top: calc(var(--sp-8) + 2.5rem + var(--safe-t)); }
}
.quote-wrap { display: grid; grid-template-columns: 0.8fr 1.2fr; gap: var(--sp-12); }
@media (max-width: 950px) { .quote-wrap { grid-template-columns: 1fr; gap: var(--sp-6); } }
.quote-aside p { margin-bottom: var(--sp-4); }
.reassure {
  display: flex; gap: var(--sp-3); padding: var(--sp-3) var(--sp-4); border-left: 2px solid var(--amber);
  background: rgba(13,13,16,0.7); font-size: var(--fs-small); color: var(--bone-dim);
}
/* Shown only when a chat-widget handoff (sessionStorage "showcall_chat_lead")
   prefilled name/email — a small continuity cue so the jump from chat to
   form doesn't feel like starting over. Never claims the chat's free-text
   "need" was captured into a field it isn't (see initQuoteForm). */
.q-continuity {
  margin-top: var(--sp-4); padding: var(--sp-3) var(--sp-4); border: 1px dashed var(--amber-line);
  background: var(--amber-dim); color: var(--amber-hi); font-size: var(--fs-small); border-radius: var(--radius);
}

.qform { border: 1px solid var(--steel-dim); background: rgba(13,13,16,0.85); backdrop-filter: blur(6px); padding: var(--sp-6); border-radius: var(--radius); }
.qprogress { display: none; margin-bottom: var(--sp-6); }
.js-enhanced .qprogress { display: block; }
.qprogress-bar { height: 4px; background: var(--steel-dim); position: relative; overflow: hidden; border-radius: 2px; }
.qprogress-fill { position: absolute; inset: 0 auto 0 0; width: 12.5%; background: var(--amber); transition: width var(--dur-med) var(--ease-out); }
/* Eight faint tick marks segment the bar into its real step count — "how
   many left" reads spatially at a glance instead of only from the text
   label below. Pure decoration layered above the fill; z-index keeps it
   visible over the amber. */
.qprogress-bar::after {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background-image: repeating-linear-gradient(to right, transparent 0, transparent calc(12.5% - 1px), var(--black) calc(12.5% - 1px), var(--black) 12.5%);
}
.qprogress-label { display: flex; justify-content: space-between; margin-top: var(--sp-3); font-family: var(--f-mono); font-size: var(--fs-micro); color: var(--bone-faint); letter-spacing: 0.1em; }
.qprogress-label #qstepName { color: var(--bone-dim); }

.qstep { display: block; }
.js-enhanced .qstep:not(.js-active) { display: none; }
.js-enhanced .qstep.js-active { animation: step-in 0.4s var(--ease-out) both; }
@keyframes step-in { from { opacity: 0; transform: translateX(10px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .js-enhanced .qstep.js-active { animation: none; } }

.qstep + .qstep, .qform fieldset + fieldset { margin-top: var(--sp-6); }
.field-label { display: block; font-size: var(--fs-small); font-weight: 700; margin-bottom: var(--sp-2); }
.field-hint { font-size: var(--fs-micro); color: var(--bone-faint); margin-top: var(--sp-2); }

.text-input, .select-input {
  width: 100%; padding: 0.8rem 1rem; background: var(--surface-2);
  border: 1px solid var(--steel); color: var(--bone); border-radius: var(--radius);
  transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.text-input:focus, .select-input:focus { border-color: var(--amber); }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); }
@media (max-width: 600px) { .field-row { grid-template-columns: 1fr; } }

/* ---- forgiving inline validation (JS: setFieldError/clearFieldError) ----
   Reuses --amber (brand law: one hue only) rather than a second "error red";
   distinguished from the plain focus ring by the added icon + explicit
   message text below, not by color alone. Native reportValidity() still
   fires alongside this for assistive tech — this is a visual/textual layer
   on top, not a replacement. */
.text-input.is-invalid, .select-input.is-invalid {
  border-color: var(--amber); box-shadow: 0 0 0 1px var(--amber-line);
}
.field-error {
  display: flex; align-items: flex-start; gap: 0.45em;
  margin-top: var(--sp-2); font-size: var(--fs-micro); color: var(--amber-hi); line-height: 1.4;
}
.field-error::before { content: "!"; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center;
  width: 14px; height: 14px; border-radius: 50%; border: 1px solid var(--amber-hi); font-family: var(--f-mono);
  font-size: 0.6rem; font-weight: 700; margin-top: 0.1em; }
.field-error[hidden] { display: none; }
@media (prefers-reduced-motion: no-preference) {
  @keyframes qstep-shake {
    0%, 100% { transform: translateX(0); }
    20% { transform: translateX(-6px); }
    40% { transform: translateX(5px); }
    60% { transform: translateX(-3px); }
    80% { transform: translateX(2px); }
  }
  /* Specificity note: `.js-enhanced .qstep.js-active` (§17, step-in entrance)
     already sets `animation` on this element at (0,3,0). A plain `.qstep.is-
     shake` rule at (0,2,0) would lose that cascade fight and silently never
     play — matching the selector chain exactly plus the extra class wins
     cleanly instead of relying on source order. */
  .js-enhanced .qstep.js-active.is-shake { animation: qstep-shake 380ms var(--ease-out); }
}

.chip-group { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.chip { position: relative; }
.chip input { position: absolute; opacity: 0; width: 100%; height: 100%; cursor: pointer; }
.chip span {
  display: inline-flex; align-items: center; min-height: 44px; padding: 0.6rem 1rem;
  border: 1px solid var(--steel); border-radius: var(--radius); font-size: var(--fs-small);
  transition: border-color var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.chip input:checked + span { border-color: var(--amber); background: var(--amber-dim); color: var(--amber-hi); }
.chip input:focus-visible + span { outline: 2px solid var(--amber); outline-offset: 2px; }

/* :not([hidden]) matters here — JS toggles the `hidden` attribute to switch
   between the Back/Next row and the final-step submit row, but an author
   rule that sets `display` unconditionally beats the UA `[hidden]{display:none}`
   rule regardless of specificity, which was defeating the hidden attribute
   and showing "Send it to Showcall" on every step. */
.qnav:not([hidden]) { display: flex; justify-content: space-between; margin-top: var(--sp-6); }
/* Back/Next were the site's one sub-44px tap target (.btn-sm's ~36px was
   fine for a hover-driven desktop control, not for the primary way a
   phone visitor moves through an 8-step form) — floor them at a confident
   48px without touching every other .btn-sm on the page (dock/topnav
   already carry their own, separately-verified sizing). */
.qnav .btn-sm { min-height: 48px; padding-inline: 1.3rem; }
.js-enhanced .qsubmit-row:not([hidden]) { display: flex; flex-direction: column; align-items: flex-end; gap: var(--sp-3); margin-top: var(--sp-6); }
.qsubmit-row { margin-top: var(--sp-6); }
.qsubmit-row #qSubmit { min-height: 52px; }
.qsubmit-reassure { font-size: var(--fs-micro); color: var(--bone-faint); text-align: right; max-width: 34ch; }
@media (max-width: 500px) { .qsubmit-reassure { text-align: left; max-width: none; } }

.qresult { padding: var(--sp-6) 0; text-align: left; }
.qresult[hidden] { display: none; }
/* Post-submit is a conversion dead end without a next step — offer the
   faster channel (chat) and restate the phone number for anyone who wants
   both. Uses the same public window.Showcall.openChat hook the chat widget
   exposes (see initChatWidget), not a re-implementation of it. */
.qresult-next { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-4); margin-top: var(--sp-5); }
.qresult-phone { font-family: var(--f-mono); font-size: var(--fs-small); color: var(--amber); letter-spacing: 0.04em; }
.qresult-phone:hover, .qresult-phone:focus-visible { color: var(--amber-hi); }

/* "What happens next" — a compact rail between the form and the footer,
   reusing the Run-of-Show timeline's own amber-lit system (.how-step /
   .how-node / .how-line light up together via the same .cue.is-active
   ancestor rule cue-run uses — no new motion CSS needed) at cue-go's own
   scale. Gives the cue's dead middle a real, on-brand composition instead
   of empty space between the top-anchored form and the bottom-anchored
   footer. */
.cue-go-next { margin-top: var(--sp-8); }
.how-track-mini { padding-block: 0; gap: var(--sp-4); }
.how-track-mini .how-node { width: 8px; height: 8px; }
.how-track-mini .how-copy h3 { font-size: var(--fs-body-lg); margin-block: 0.35em 0; }
@media (max-width: 1000px) { .how-track-mini { gap: 0; } }

.cue-go-footer { margin-top: var(--sp-16); border-top: 1px solid var(--steel-dim); padding-top: var(--sp-6); }
.cue-go-footer-inner {
  display: flex; flex-wrap: wrap; gap: var(--sp-4) var(--sp-8); align-items: center;
  font-family: var(--f-mono); font-size: var(--fs-micro); color: var(--bone-faint); letter-spacing: 0.05em;
}
.cue-go-footer-inner a:hover { color: var(--amber); }
.footer-live { display: inline-flex; align-items: center; gap: var(--sp-2); color: var(--amber); letter-spacing: 0.15em; }
.noindex-note { display: none; }
/* Engine mode: pin the footer to the bottom of the cue instead of letting
   it trail right under the form — the quote group + footer were both
   bunching at the top of tall viewports, leaving the lower half of the
   cue empty. Anchoring the footer to the bottom gives the cue a real
   compositional floor and redistributes the dead space as a deliberate
   gap instead of a stray void beneath the content. */
@media (prefers-reduced-motion: no-preference) {
  html.js #cue-go .cue-go-footer { margin-top: auto; padding-top: var(--sp-6); }
}

/* ---------- 18. CUE NEXT / RESTART variant ---------- */
.cue-next-restart .cue-next-bar { background: linear-gradient(to right, transparent, var(--bone-faint)); }

/* ============================================================
   19. THE SHOW-CALLER DOCK — fixed console bar. JS + motion only.
   Visual Pass 3: rebuilt as a tactile hardware console — a recessed
   VFD-style readout, a real fader-track progress rail, and a GO button
   with an armed idle ring + a physical press. Every function/id the
   engine (showcall.js initCueEngine) depends on is unchanged; this is
   presentation only.
   ============================================================ */
.dock { display: none; }
@media (prefers-reduced-motion: no-preference) {
  html.js .dock {
    display: flex; position: fixed; inset: auto 0 0 0; z-index: 100;
    /* height grows by the safe-area inset, padding-bottom eats exactly
       that growth back — the dock's CONTENT band (dock-inner) always gets
       the original --dock-h of usable room; the extra is a pure clearance
       strip below it so the buttons never sit on top of the home
       indicator's gesture zone. */
    height: var(--dock-total-h); padding-bottom: var(--safe-b);
    background: rgba(8,8,10,0.92); backdrop-filter: blur(14px);
    border-top: 1px solid var(--steel-dim);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.03);
  }
}
.dock-inner {
  width: 100%; display: flex; align-items: center; gap: var(--sp-6);
  padding-inline: var(--edge);
}
.dock-brand { flex-shrink: 0; display: inline-flex; }
.dock-brand .brand-mark { width: 24px; height: 24px; }

/* ---- readout: a single recessed VFD-style window — "CUE n/7 — TITLE · STATUS" ---- */
.dock-readout {
  display: flex; align-items: center; gap: 0.55em; flex-shrink: 0; min-width: 0;
  font-family: var(--f-mono); font-size: var(--fs-micro); letter-spacing: 0.1em;
  font-variant-numeric: tabular-nums;
  padding: 0.5rem 0.85rem; border-radius: 3px;
  background: linear-gradient(180deg, rgba(0,0,0,0.55), rgba(0,0,0,0.3));
  border: 1px solid var(--steel-dim);
  box-shadow: inset 0 2px 5px rgba(0,0,0,0.55), inset 0 0 0 1px rgba(255,176,32,0.05);
  position: relative; overflow: hidden;
}
/* a hair of physical-display scanline grain — decorative texture only,
   never touches text contrast (sits behind it via normal paint order,
   opacity kept low). */
.dock-readout::before {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 0;
  background: repeating-linear-gradient(180deg, rgba(244,242,238,0.05) 0px, rgba(244,242,238,0.05) 1px, transparent 1px, transparent 3px);
  mix-blend-mode: overlay; opacity: 0.5;
}
.dock-readout > * { position: relative; z-index: 1; }
.dock-readout-cue { color: var(--bone-dim); white-space: nowrap; }
.dock-readout-cue b { color: var(--bone); font-weight: 700; }
.dock-readout-total { color: var(--bone-faint); }
.dock-readout-dash { color: var(--steel); }
.dock-readout-title {
  color: var(--bone); font-weight: 600; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; max-width: 12ch;
}
.dock-readout-dot {
  width: 6px; height: 6px; border-radius: 50%; background: var(--steel); flex-shrink: 0;
  transition: background-color 0.4s var(--ease-out), box-shadow 0.4s var(--ease-out);
}
.dock-readout-status {
  color: var(--bone-faint); font-weight: 700; white-space: nowrap;
  transition: color 0.4s var(--ease-out), text-shadow 0.4s var(--ease-out);
}
.dock-readout-status.is-go { color: var(--amber); text-shadow: 0 0 10px var(--amber-glow); }
.dock-readout-dot.is-go { background: var(--amber); box-shadow: 0 0 8px var(--amber-glow); }
@media (prefers-reduced-motion: no-preference) {
  .dock-readout-dot.is-go { animation: pulse-dot 2.4s var(--ease-inout) infinite; animation-delay: -0.8s; }
  /* a quick digital "refresh" flicker on the readout's own text every time
     the cue changes — showcall.js toggles .is-flicker in updateDock(). */
  html.js .dock-readout.is-flicker .dock-readout-cue,
  html.js .dock-readout.is-flicker .dock-readout-title,
  html.js .dock-readout.is-flicker .dock-readout-status {
    animation: readout-flicker 300ms steps(5) both;
  }
}
@keyframes readout-flicker {
  0% { opacity: 0.25; } 20% { opacity: 1; } 35% { opacity: 0.35; } 55% { opacity: 1; } 100% { opacity: 1; }
}

/* ---- rail: dots ride a real fader track that fills as the show advances ---- */
.dock-rail { display: flex; align-items: center; gap: var(--sp-3); flex: 1; justify-content: center; position: relative; }
.dock-rail::before {
  content: ""; position: absolute; left: 0; right: 0; top: 50%; height: 1px;
  background: var(--steel-dim); transform: translateY(-50%); z-index: 0; pointer-events: none;
}
.dock-rail::after {
  content: ""; position: absolute; left: 0; top: 50%; height: 1px;
  width: var(--rail-progress, 0%);
  background: linear-gradient(to right, transparent, var(--amber));
  box-shadow: 0 0 6px var(--amber-glow);
  transform: translateY(-50%); z-index: 0; pointer-events: none;
  transition: width 0.5s var(--ease-out);
}
@media (prefers-reduced-motion: reduce) { .dock-rail::after { transition: none; } }
.rail-dot {
  position: relative; z-index: 1;
  width: 9px; height: 9px; border-radius: 50%; background: var(--steel); flex-shrink: 0;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,0.4);
  transition: background-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.rail-dot:hover, .rail-dot:focus-visible { transform: scale(1.3); background: var(--amber-hi); box-shadow: 0 0 12px var(--amber-glow); }
.rail-dot[aria-current="true"] { background: var(--amber); box-shadow: 0 0 10px var(--amber-glow); }
.rail-dot[aria-current="true"]:hover, .rail-dot[aria-current="true"]:focus-visible { box-shadow: 0 0 18px var(--amber-glow); }
@media (prefers-reduced-motion: no-preference) {
  .rail-dot[aria-current="true"] { animation: pulse-dot 2.4s var(--ease-inout) infinite; animation-delay: -1.4s; }
}

.dock-controls { display: flex; align-items: center; gap: var(--sp-2); flex-shrink: 0; }
.dock-btn {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  font-family: var(--f-body); font-weight: 700; font-size: var(--fs-micro); letter-spacing: 0.08em; text-transform: uppercase;
  padding: 0.65rem 0.9rem; border: 1px solid var(--steel); border-radius: var(--radius); color: var(--bone-dim);
  background: rgba(255,255,255,0.015);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.04);
  transition: border-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.dock-btn svg { width: 14px; height: 14px; }
.dock-btn:hover, .dock-btn:focus-visible {
  border-color: var(--amber); color: var(--amber);
  transform: translateY(-2px);
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4), 0 0 0 1px var(--amber-line);
}
.dock-btn:active { transform: translateY(0) scale(0.96); }
.dock-btn:disabled { opacity: 0.35; cursor: not-allowed; }
.dock-btn:disabled:hover, .dock-btn:disabled:active { transform: none; box-shadow: none; }

/* ---- GO: the one control with real presence — a beveled physical key,
   an idle "armed" ring while it's live, and a flash on every press. The
   ring/flash live on ::before/::after (not the button's own box-shadow) so
   they never fight the :hover / :active states below for the same property. */
.dock-btn-go {
  /* position + z-index together establish a local stacking context, so the
     ::before/::after z-index:-1 layers below stay pinned just behind THIS
     button's own background — without z-index here they'd escape to the
     dock's stacking context (z-index:100) and paint behind the whole bar. */
  position: relative; z-index: 0;
  background: var(--amber); color: var(--black); border-color: var(--amber);
  padding: 0.75rem 1.4rem; font-size: var(--fs-small); gap: var(--sp-3);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.35), inset 0 -2px 0 rgba(0,0,0,0.18), 0 3px 10px rgba(0,0,0,0.35);
}
.dock-btn-go svg { width: 16px; height: 16px; }
.dock-btn-go::before, .dock-btn-go::after {
  content: ""; position: absolute; inset: -6px; border-radius: 5px; z-index: -1; pointer-events: none;
}
.dock-btn-go::before { box-shadow: 0 0 0 0 rgba(255,176,32,0.3); opacity: 0; }
.dock-btn-go::after { background: radial-gradient(circle, var(--amber-glow), transparent 70%); opacity: 0; transform: scale(0.6); }
@media (prefers-reduced-motion: no-preference) {
  html.js .dock-btn-go:not(:disabled)::before { animation: go-armed-ring 2.6s var(--ease-inout) infinite; }
  html.js .dock-btn-go.is-pressed::after { animation: go-press-flash 420ms var(--ease-out); }
}
@keyframes go-armed-ring {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255,176,32,0.24); opacity: 0.8; }
  50% { box-shadow: 0 0 0 7px rgba(255,176,32,0); opacity: 0; }
}
@keyframes go-press-flash {
  0% { opacity: 0.85; transform: scale(0.6); }
  100% { opacity: 0; transform: scale(1.7); }
}
.dock-btn-go:hover, .dock-btn-go:focus-visible {
  background: var(--amber-hi); color: var(--black);
  transform: translateY(-2px);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.4), inset 0 -2px 0 rgba(0,0,0,0.18), 0 8px 24px var(--amber-glow);
}
.dock-btn-go:active {
  transform: translateY(1px) scale(0.97);
  box-shadow: inset 0 2px 5px rgba(0,0,0,0.4);
}
.dock-btn-go:disabled {
  background: var(--steel-dim); border-color: var(--steel-dim); color: var(--bone-faint); box-shadow: none;
}
.dock-btn-go:disabled::before, .dock-btn-go:disabled::after { display: none; }

.dock-cta { display: flex; align-items: center; gap: var(--sp-3); flex-shrink: 0; }

@media (max-width: 1100px) {
  .dock-readout { display: none; }
}
@media (max-width: 900px) {
  .dock-cta .btn-ghost { display: none; }
  .dock-btn span { display: none; }
  /* min-width/height: icon-only Prev/Rundown were ~35px, Go ~45x38px —
     all under the 44px touch minimum. Padding stays as-is (it still
     drives the visual icon spacing); min-width/height just puts a floor
     under the box flex already centers the icon in. */
  .dock-btn { padding: 0.65rem; min-width: 44px; min-height: 44px; }
  .dock-btn-go { padding: 0.7rem 0.9rem; min-width: 44px; }
}
@media (max-width: 640px) {
  .dock-inner { gap: var(--sp-3); }
  .dock-cta .btn-primary { padding: 0.6rem 0.8rem; font-size: 0.68rem; min-height: 44px; }
  :root { --dock-h: 4.6rem; }

  /* Rail dots: 7 stops need to fit whatever's left of a ~360-390px dock
     after the (now 44px-tall) Prev/Rundown/Go cluster and the Get-a-Quote
     CTA — typically ~70-110px total. Seven isolated 44x44 targets don't
     fit in that (44*7=308px), so isolated squares aren't the honest fix
     here. Instead: stretch each dot's HIT ZONE to the full dock height
     (comfortably >44px — the one axis that WAS available) and let flexbox
     divide the available width evenly with zero dead space between dots,
     so every pixel of the rail does something and no tap is ambiguous.
     The painted dot itself stays a small centered mark (::after) so this
     still reads as a fader-track with position lights, not seven blown-up
     buttons. For a guaranteed-precise jump on a narrow phone, the Rundown
     panel's rows are real 44px+ targets — that's the intentional fallback
     the brief already calls out. */
  .dock-rail { gap: 0; align-self: stretch; min-width: 0; }
  .rail-dot {
    flex: 1 1 0; width: auto; height: 100%; min-width: 0;
    display: flex; align-items: center; justify-content: center;
    background: none; box-shadow: none;
  }
  .rail-dot::after {
    content: ""; width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0;
    background: var(--steel); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.4);
    transition: background-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
  }
  .rail-dot:hover, .rail-dot:focus-visible { transform: none; background: none; box-shadow: none; }
  .rail-dot:hover::after, .rail-dot:focus-visible::after { transform: scale(1.3); background: var(--amber-hi); box-shadow: 0 0 12px var(--amber-glow); }
  .rail-dot[aria-current="true"] { background: none; box-shadow: none; }
  .rail-dot[aria-current="true"]::after { background: var(--amber); box-shadow: 0 0 10px var(--amber-glow); }
  .rail-dot[aria-current="true"]:hover, .rail-dot[aria-current="true"]:focus-visible { box-shadow: none; }
  .rail-dot[aria-current="true"]:hover::after, .rail-dot[aria-current="true"]:focus-visible::after { box-shadow: 0 0 18px var(--amber-glow); }
}
/* Phone-width dock was overflowing (~411px of content in a 390px
   viewport, clipping the "Get a Quote" CTA). Drop the redundant brand
   mark (identity is already established by the hero/topnav) and tighten
   the outer gutter to claw back the room. */
@media (max-width: 430px) {
  .dock-inner { padding-inline: var(--sp-3); gap: var(--sp-2); }
  .dock-brand { display: none; }
  .dock-controls { gap: 3px; }
}

/* ---------- 20. RUNDOWN OVERLAY ---------- */
.rundown-panel { display: none; }
@media (prefers-reduced-motion: no-preference) {
  html.js .rundown-panel:not([hidden]) {
    display: block; position: fixed; inset: 0; z-index: 130;
  }
}
.rundown-scrim { position: absolute; inset: 0; background: rgba(8,8,10,0.82); backdrop-filter: blur(4px); }
.rundown-sheet {
  position: absolute; right: 0; top: 0; bottom: var(--dock-total-h); width: min(420px, 100vw);
  background: var(--surface); border-left: 1px solid var(--steel-dim);
  display: flex; flex-direction: column;
  transform: translateX(100%); transition: transform var(--dur-med) var(--ease-inout);
  overflow-y: auto;
}
html.js .rundown-panel.is-open .rundown-sheet { transform: translateX(0); }
@media (prefers-reduced-motion: reduce) { .rundown-sheet { transition: none; } }
.rundown-head { display: flex; align-items: center; justify-content: space-between; padding: calc(var(--sp-6) + var(--safe-t)) var(--sp-6) var(--sp-3); }
.rundown-head .section-num { margin: 0; }
.rundown-close { font-size: 1.6rem; line-height: 1; color: var(--bone-faint); padding: var(--sp-2); }
.rundown-close:hover { color: var(--amber); }
.rundown-list { padding: 0 var(--sp-4) var(--sp-6); }
.rundown-item {
  width: 100%; text-align: left; display: flex; flex-direction: column; gap: 2px;
  padding: var(--sp-4); border-radius: var(--radius); border: 1px solid transparent;
  transition: border-color var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out);
}
.rundown-item:hover, .rundown-item:focus-visible { border-color: var(--amber-line); background: var(--surface-2); }
.rundown-item[aria-current="true"] { border-color: var(--amber); background: var(--amber-dim); }
.rundown-item .ri-num { font-family: var(--f-mono); font-size: var(--fs-micro); color: var(--amber); letter-spacing: 0.1em; }
.rundown-item .ri-title { font-family: var(--f-display); text-transform: uppercase; font-size: 1.2rem; letter-spacing: 0.02em; }
.rundown-item .ri-desc { font-size: var(--fs-small); color: var(--bone-dim); }

/* ---------- 21. CHAT WIDGET ---------- */
.chat-launcher { position: fixed; right: var(--sp-6); z-index: 120; bottom: calc(var(--sp-6) + var(--safe-b)); }
html.js .chat-launcher { bottom: calc(var(--dock-total-h) + var(--sp-4)); }
@media (prefers-reduced-motion: reduce) { .chat-launcher { bottom: calc(var(--sp-6) + var(--safe-b)) !important; } }
.chat-toggle {
  display: flex; align-items: center; gap: var(--sp-3);
  background: var(--amber); color: var(--black); border-radius: 999px;
  padding: 0.9rem 1.3rem; font-weight: 700; font-size: var(--fs-small);
  box-shadow: 0 8px 30px rgba(0,0,0,0.5);
  transition: transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.chat-toggle:hover, .chat-toggle:focus-visible { transform: translateY(-2px); box-shadow: 0 10px 34px rgba(255,176,32,0.35); }
.chat-toggle-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--black); animation: pulse-dot 1.8s var(--ease-inout) infinite; animation-delay: -0.3s; }
@media (prefers-reduced-motion: reduce) { .chat-toggle-dot { animation: none; } }
/* Icon-only at ≤560 (label hidden) shrinks the launcher below the 44px
   touch minimum — it's reachable from every cue ("one tap from anywhere"
   per the brief), so it gets the same floor as the dock controls. */
@media (max-width: 560px) { .chat-toggle-label { display: none; } .chat-toggle { padding: 0.9rem; min-width: 44px; min-height: 44px; justify-content: center; } }

/* ---- "peek" invite: a one-time, contextual nudge (see initChatWidget's
   "showcall:cuechange" listener) — shown only once, only when the visitor
   reaches the Show Caller cue, dismissible, never reappears once seen or
   once chat opens. Two real <button>s (message + close), not a div with a
   click handler, so it's keyboard/AT reachable like everything else here. */
/* :not([hidden]) matters — see §17's note on .qnav for the full rationale:
   an author rule that sets `display` unconditionally beats the UA
   `[hidden]{display:none}` rule regardless of specificity, which would
   make this bubble render even while JS has it `hidden`. */
.chat-peek:not([hidden]) {
  position: absolute; right: 0; bottom: calc(100% + var(--sp-3));
  display: flex; align-items: flex-start; gap: var(--sp-2);
  width: max-content; max-width: min(260px, calc(100vw - 2 * var(--sp-6)));
  background: var(--surface); border: 1px solid var(--amber-line); border-radius: 8px;
  padding: var(--sp-3) var(--sp-3) var(--sp-3) var(--sp-4);
  box-shadow: 0 12px 34px rgba(0,0,0,0.5);
}
.chat-peek::after {
  content: ""; position: absolute; right: 1.1rem; top: 100%; width: 10px; height: 10px;
  background: var(--surface); border-right: 1px solid var(--amber-line); border-bottom: 1px solid var(--amber-line);
  transform: translateY(-50%) rotate(45deg);
}
.chat-peek-msg {
  flex: 1; text-align: left; font-size: var(--fs-small); color: var(--bone); line-height: 1.4;
}
.chat-peek-msg:hover, .chat-peek-msg:focus-visible { color: var(--amber-hi); }
/* min-width/height, not width/height — same pattern as .nav-toggle (§6):
   keep the small glyph centered while the actual hit target floors at the
   44px minimum every other real control on the site carries. */
.chat-peek-close {
  flex-shrink: 0; min-width: 44px; min-height: 44px; margin: -0.55rem -0.4rem -0.55rem 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 1.1rem; line-height: 1; color: var(--bone-faint); border-radius: 50%;
}
.chat-peek-close:hover, .chat-peek-close:focus-visible { color: var(--bone); background: var(--surface-2); }
@media (prefers-reduced-motion: no-preference) {
  .chat-peek { animation: peek-in 0.4s var(--ease-out) both; }
  @keyframes peek-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
}

.chat-panel {
  position: fixed; right: var(--sp-6); bottom: calc(var(--sp-6) + 4.2rem); z-index: 121;
  width: min(380px, calc(100vw - 2 * var(--sp-6))); max-height: min(600px, 70vh);
  background: var(--surface); border: 1px solid var(--steel-dim); border-radius: 6px;
  display: flex; flex-direction: column; overflow: hidden;
  box-shadow: 0 20px 60px rgba(0,0,0,0.6);
  opacity: 0; transform: translateY(16px) scale(0.98); pointer-events: none;
  transition: opacity var(--dur-med) var(--ease-out), transform var(--dur-med) var(--ease-out);
}
html.js .chat-panel { bottom: calc(var(--dock-total-h) + 4.2rem + var(--sp-4)); }
@media (prefers-reduced-motion: reduce) { html.js .chat-panel { bottom: calc(var(--sp-6) + 4.2rem + var(--safe-b)) !important; } }
.chat-panel.is-open { opacity: 1; transform: none; pointer-events: auto; }
@media (prefers-reduced-motion: reduce) { .chat-panel { transition: opacity var(--dur-fast) linear; transform: none; } }
@media (max-width: 560px) {
  .chat-panel { right: var(--sp-3); left: var(--sp-3); width: auto; bottom: calc(var(--sp-3) + 4.2rem + var(--safe-b)); }
  html.js .chat-panel { bottom: calc(var(--dock-total-h) + 4.2rem) !important; }
}

.chat-panel-head {
  position: relative;
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--sp-4); border-bottom: 1px solid var(--steel-dim); background: var(--surface-2);
}
/* A thin "on-air" edge — the same lighting-model language as the dock's
   readout/GO glow (--amber-glow) — so the header reads like a live comms
   channel rather than a plain panel title bar. */
.chat-panel-head::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 2px;
  background: linear-gradient(to right, var(--amber), transparent 70%);
}
.chat-caller-id { display: flex; align-items: center; gap: var(--sp-3); }
.chat-status-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--amber); box-shadow: 0 0 8px var(--amber-glow); animation: pulse-dot 2.4s var(--ease-inout) infinite; animation-delay: -0.6s; transition: background-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out); }
@media (prefers-reduced-motion: reduce) { .chat-status-dot { animation: none; } }
/* Degraded/off-headset state (showFallback) — same amber (one hue) but
   static and dimmer, so it never falsely reads as "live" the way the
   pulsing on-headset dot does. */
.chat-status-dot.is-away { background: var(--steel); box-shadow: none; animation: none; }
.chat-caller-id div { display: flex; flex-direction: column; line-height: 1.2; }
.chat-caller-id strong { font-family: var(--f-display); text-transform: uppercase; font-size: 0.95rem; letter-spacing: 0.03em; }
.chat-caller-id span { font-size: var(--fs-micro); color: var(--bone-faint); }
.chat-close { font-size: 1.4rem; line-height: 1; color: var(--bone-faint); padding: var(--sp-2); }
.chat-close:hover { color: var(--bone); }

.chat-messages { flex: 1; overflow-y: auto; padding: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-3); }
.chat-msg { max-width: 84%; font-size: var(--fs-small); padding: 0.65rem 0.9rem; border-radius: 4px; line-height: 1.45; }
.chat-msg.from-bot { align-self: flex-start; background: var(--surface-3); color: var(--bone); }
.chat-msg.from-user { align-self: flex-end; background: var(--amber-dim); color: var(--bone); border: 1px solid var(--amber-line); }

/* ---- quickstart chips: shown once, after the opening greeting, so a new
   conversation never starts from a blank box. Reuses the same pill/chip
   language as the quote form's discipline chips for one visual system. ---- */
.chat-quickstart:not([hidden]) { display: flex; flex-wrap: wrap; gap: var(--sp-2); padding: 0 var(--sp-4) var(--sp-3); }
.chat-chip {
  display: inline-flex; align-items: center; min-height: 44px; padding: 0.5rem 0.85rem;
  border: 1px solid var(--steel); border-radius: 999px;
  font-size: var(--fs-micro); color: var(--bone-dim); background: var(--surface-2);
  transition: border-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out);
}
.chat-chip:hover, .chat-chip:focus-visible { border-color: var(--amber); color: var(--amber-hi); background: var(--amber-dim); }

/* :not([hidden]) matters — same cascade rule as .qnav/.chat-quickstart above.
   Pre-existing bug found while restructuring this rule for the dots+label
   split: the old unconditional `.chat-typing{display:flex}` beat the UA
   `[hidden]{display:none}` rule regardless of JS toggling `typing.hidden`,
   so the bouncing dots rendered permanently from page load — exactly the
   opposite of the "reads human, not a generic bot" goal this pass is
   chasing. Fixed here alongside the restructure. */
.chat-typing:not([hidden]) { display: flex; align-items: center; gap: var(--sp-2); padding: 0 var(--sp-4) var(--sp-3); }
.chat-typing-dots { display: flex; gap: 4px; }
.chat-typing-dots span { width: 6px; height: 6px; border-radius: 50%; background: var(--bone-faint); animation: typing-bounce 1.1s var(--ease-inout) infinite; }
.chat-typing-dots span:nth-child(2) { animation-delay: 0.15s; }
.chat-typing-dots span:nth-child(3) { animation-delay: 0.3s; }
.chat-typing-label { font-family: var(--f-mono); font-size: var(--fs-micro); color: var(--bone-faint); letter-spacing: 0.04em; }
@keyframes typing-bounce { 0%, 60%, 100% { transform: translateY(0); opacity: 0.5; } 30% { transform: translateY(-4px); opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .chat-typing-dots span { animation: none; opacity: 0.7; } }

.chat-input-row:not([hidden]) { display: flex; gap: var(--sp-2); padding: var(--sp-4); border-top: 1px solid var(--steel-dim); }
.chat-input-row input {
  flex: 1; background: var(--surface-2); border: 1px solid var(--steel); border-radius: 4px;
  padding: 0.65rem 0.8rem; color: var(--bone); font-size: var(--fs-small);
}
.chat-input-row input:focus { border-color: var(--amber); }
.chat-input-row input:disabled { opacity: 0.55; }
/* the send button is a .btn — .btn:disabled (§5) already covers its
   disabled treatment; no override needed here. */

.chat-fallback:not([hidden]) { padding: var(--sp-4); border-top: 1px solid var(--steel-dim); display: flex; flex-direction: column; gap: var(--sp-3); }
.chat-fallback p { font-size: var(--fs-small); }
.chat-fallback .text-input { font-size: var(--fs-small); padding: 0.6rem 0.8rem; }

/* ---------- 22. MISC ---------- */
::selection { background: var(--amber); color: var(--black); }

/* ============================================================
   23. DEPTH, PARALLAX & LIGHTING (Visual Pass 2) + STAGE IGNITE (Pass 5)
   Three independent, transform/opacity/filter-only depth-and-light cues:

   (a) CUE-CHANGE SETTLE — on activation the backdrop (+ hero video)
       scale in from a touch further back, slower, than the foreground
       .cue-body — the far layer keeps drifting into focus a beat after
       the crossfade lands, so a cut reads as a real lit space settling
       rather than a flat cross-fade. Gated under html.js + no-preference,
       same as every other engine animation.

   (b) POINTER PARALLAX — desktop, fine pointer + hover only. JS
       (initPointerParallax in showcall.js) writes --px/--py (~-1..1) to
       <html> on pointermove, but ONLY when prefers-reduced-motion is
       "no-preference" AND matchMedia('(hover:hover) and (pointer:fine)')
       is true — it never even attaches a listener on touch/coarse-pointer
       devices or under reduced motion. The CSS side has no separate
       gate: translate always resolves through var(--px,0)/var(--py,0),
       so on every device where JS didn't wire it up, translate is just
       an inert 0,0 — no second media query needed, no risk of two rules
       fighting over the same element's `transition` list. The near
       "light source" glow layer moves further than the far backdrop
       (closer planes parallax more), and body copy never moves at all.

   A fourth, static piece rounds out the "lit room" atmosphere: a fixed
   foreground vignette (screen-wide, always on, no motion) and a per-cue
   amber glow that sits between backdrop and content (z-index 3) and
   screen-blends warm light into the SAME side each cue's scrim already
   leaves brightest, so it never eats into a scrim's legibility work.

   (c) STAGE IGNITE (Visual Pass 5) — the piece that was missing: the
       veil (§8) already nails the BLACKOUT half of "the call" (snap +
       bloom + follow-spot sweep + STANDBY/GO stamp), but the cue that
       arrives after it just cross-faded in on plain opacity — the stage
       itself never looked "lit." This closes that gap with two beats
       that fire the instant the curtain lifts (`.cue.is-active`, the
       same trigger §24's kinetic headlines already use, so it restarts
       cleanly every activation with zero JS):
         1. .cue-ignite-sweep — a follow-spot beam, same angle/direction
            as the veil's own .cue-veil-sweep, that swings across the
            newly-revealed backdrop. Reads as the SAME beam physically
            carrying through from the blackout onto the lit stage, not a
            second unrelated effect (mix-blend-mode:screen so it can only
            ever add light, never muddy the photo underneath).
         2. .cue-glow flare — the existing ambient corner light (already
            defined above) gets an --ignite-flare keyframe layered on top
            of its resting opacity/blur: it flares hot and slightly
            diffuse right as the beam lands, then settles back to its
            normal resting glow — the "haze catching the light and
            drifting down" beat, at zero extra DOM cost (same element,
            no new layer to paint).
       Both are markup-optional (`.cue-ignite-sweep` is only present on
       the six content cues — the hero keeps its own exclusive ignition
       per §11/§24's established exception) and both are no-ops with the
       resting CSS alone under reduced motion / no-JS, matching every
       other rule in this file.
   ============================================================ */

/* -- atmosphere: static, always on, not gated (no movement, just a
   subtle corner-darken frame + a warm accent light — see notes above
   on why glow position tracks each cue's existing bright/dark split). -- */
.stage-vignette {
  position: fixed; inset: 0; z-index: 50; pointer-events: none;
  background: radial-gradient(ellipse 85% 75% at 50% 45%, transparent 60%, rgba(4, 4, 5, 0.42) 100%);
}

.cue-glow {
  position: absolute; z-index: 3; pointer-events: none;
  width: 56vmax; height: 56vmax; border-radius: 50%;
  background: radial-gradient(circle, var(--amber-glow) 0%, rgba(255, 176, 32, 0.08) 45%, transparent 72%);
  mix-blend-mode: screen; opacity: 0.46; filter: blur(6px);
}
.cue-glow-l { top: 8%; left: -16%; }
.cue-glow-r { top: 8%; right: -18%; }
@media (max-width: 700px) { .cue-glow { width: 85vmax; height: 85vmax; opacity: 0.36; } }

/* -- (c) stage ignite: the beam, see markup note in the doc comment
   above. Sits above the backdrop/scrim (z-index 2, same tier as most
   .cue-scrim variants) but below .cue-glow/.cue-body, so it reads as
   light hitting the photo, never washing over copy. -- */
.cue-ignite-sweep {
  position: absolute; inset: -10% -20%; z-index: 2; pointer-events: none;
  background: linear-gradient(100deg,
    transparent 0%, transparent 42%,
    rgba(255, 176, 32, 0.55) 47%, var(--bone) 50%, rgba(255, 200, 92, 0.6) 53%,
    transparent 58%, transparent 100%);
  mix-blend-mode: screen; opacity: 0;
}

/* -- (a) + (b) + (c): backdrop/body settle, pointer parallax, stage ignite. -- */
@media (prefers-reduced-motion: no-preference) {
  html.js .cue-backdrop {
    scale: 1.06;
    translate: calc(var(--px, 0) * 6px) calc(var(--py, 0) * 4px);
    transition: scale 1.15s var(--ease-out), translate 0.6s var(--ease-out);
  }
  html.js .cue.is-active .cue-backdrop { scale: 1; }

  html.js .cue-glow {
    translate: calc(var(--px, 0) * 16px) calc(var(--py, 0) * 10px);
    transition: translate 0.55s var(--ease-out);
  }

  html.js .cue-body {
    scale: 1.012;
    transition: scale 0.6s var(--ease-out);
  }
  html.js .cue.is-active .cue-body { scale: 1; }

  /* fires once per activation — .is-active is a genuine off->on toggle
     each cue change (never re-added while already set), so `animation`
     restarts cleanly with no JS reflow trick needed. */
  html.js .cue.is-active .cue-ignite-sweep {
    animation: cue-ignite-sweep var(--ignite-sweep) var(--ease-out) both;
  }
  html.js .cue.is-active .cue-glow {
    animation: cue-glow-flare var(--ignite-flare) var(--ease-out) both;
  }
}
@keyframes cue-ignite-sweep {
  0%   { transform: translateX(-125%); opacity: 0.85; }
  60%  { opacity: 0.4; }
  100% { transform: translateX(125%); opacity: 0; }
}
/* end values match .cue-glow's own resting opacity/blur (0.46 / 6px)
   exactly, so the flare settles into the existing static look with no
   seam when the animation finishes. */
@keyframes cue-glow-flare {
  0%   { opacity: 0.08; filter: blur(2px); }
  40%  { opacity: 0.9; filter: blur(11px); }
  100% { opacity: 0.46; filter: blur(6px); }
}

@media (prefers-reduced-motion: reduce) {
  /* Belt + suspenders: no rule above applies without no-preference
     matching anyway, so these properties are never set — this just
     documents the inert baseline for anyone reading the fallback. */
  .cue-backdrop, .hero-video, .cue-body, .cue-glow { scale: 1; translate: 0 0; }
  .cue-ignite-sweep { display: none; }
}

/* ============================================================
   24. KINETIC HEADLINE TYPE SYSTEM (Visual Pass 3)
   A repeatable, three-beat title-sequence cascade shared by all six
   content-cue h2's (cue-model/board/run/proof/caller/go — the hero's h1
   keeps its own, more dramatic exclusive treatment in §11):

     1. the eyebrow ("Cue N / Title") wipe-stamps in first, like a slate
        being called — see .cue .section-num below.
     2. each headline .line mask-wipes up with a tracking "settle" (wide
        letter-spacing relaxing to rest), staggered top-to-bottom.
     3. the emphasized <em> word snaps — a small scale/glow kick, the
        beat that lands last, echoing the hero's bigger version.

   Entirely opt-in via .kinetic-h + .line/.line span markup (index.html);
   nothing here touches an element that isn't wearing that class. Hidden
   states are scoped to `html.js` + no-preference from the start (not
   "unconditional-then-overridden"), so no-JS and reduced-motion visitors
   simply never see a hidden state — the browser's own static rendering
   IS the fallback, no separate override block required.
   ============================================================ */

/* ---- 1. eyebrow stamp — a fast left-to-right wipe, leads the headline ---- */
@media (prefers-reduced-motion: no-preference) {
  html.js .cue .section-num {
    clip-path: inset(0 100% 0 0);
    transition: clip-path 0.5s var(--ease-out);
  }
  html.js .cue.is-active .section-num { clip-path: inset(0 0 0 0); }
}

/* ---- 2 + 3. headline line-wipe + tracking settle + word snap ---- */
.kinetic-h .line { display: block; overflow: hidden; }
.kinetic-h em { display: inline-block; }
@media (prefers-reduced-motion: no-preference) {
  html.js .kinetic-h .line span {
    display: block; transform: translateY(112%); opacity: 0; letter-spacing: 0.07em;
    transition: transform 0.75s var(--ease-out), opacity 0.6s var(--ease-out), letter-spacing 0.75s var(--ease-out);
  }
  html.js .cue.is-active .kinetic-h .line:nth-child(1) span { transition-delay: 0.1s; }
  html.js .cue.is-active .kinetic-h .line:nth-child(2) span { transition-delay: 0.21s; }
  html.js .cue.is-active .kinetic-h .line:nth-child(3) span { transition-delay: 0.32s; }
  html.js .cue.is-active .kinetic-h .line span { transform: translateY(0); opacity: 1; letter-spacing: 0.01em; }

  html.js .cue.is-active .kinetic-h em {
    animation: kinetic-word-snap 0.45s var(--ease-stamp) 0.6s both;
  }
}
@keyframes kinetic-word-snap {
  0% { transform: scale(1); text-shadow: 0 0 0 transparent; }
  55% { transform: scale(1.045); text-shadow: 0 0 12px var(--amber-glow); }
  100% { transform: scale(1); text-shadow: 0 0 0 transparent; }
}

/* ============================================================
   25. CONSOLE LABEL SYSTEM — number/label harmonization (Visual Pass 3)
   Every mono readout across the site (channel numbers, step tags, the
   rundown, the dock, the footer) now shares one tracking scale and
   tabular digit widths, so "CH.01", "STEP 02", "CUE 3/7" and "Step 1 of
   8" all read as one designed system instead of several hand-tuned ones.
   Pure additive/override — no selector here changes layout or markup.
   ============================================================ */
.section-num, .cue-tag, .status-line, .cue-card-label, .cue-card .who,
.how-copy .step-tag, .channel-num, .model-point-num, .mobile-nav .mnum,
.qprogress-label, .cue-go-footer-inner, .rundown-item .ri-num,
.cue-veil-readout, .cue-veil-sub, .dock-readout {
  font-variant-numeric: tabular-nums;
}
.channel-num { letter-spacing: 0.1em; }
.model-point-num { letter-spacing: 0.06em; }
.qprogress-label { letter-spacing: 0.12em; }
.rundown-item .ri-num { letter-spacing: 0.14em; }
.cue-go-footer-inner { letter-spacing: 0.08em; }

/* widow/orphan safety net for body copy — modern engines only, no-op
   elsewhere (progressive enhancement, never required for legibility). */
p { text-wrap: pretty; }

/* ============================================================
   26. FILMIC FINISH (Visual Pass 4) — a fixed, non-interactive grade +
   grain pair that makes the whole build read as one graded film instead
   of a web page. Two thin layers, not a filter on the page:

   (a) .film-grade — a soft-light color layer: a faint teal bias tucked
       into the two darkest corners (the "shadow" of the frame) and a
       faint warm-amber bias at center-frame (where the light already
       is — hero/cue-glow/GO). Soft-light is the gentlest blend mode for
       this job: on near-black pixels it barely moves them (blacks stay
       crushed, not lifted/muddied), on near-white text it barely moves
       either — verified below via computed contrast, not assumed.
   (b) .film-grain — a single tiled SVG feTurbulence noise tile, overlay-
       blended at ~5% opacity. That is the entire "texture": one static
       background-image, no per-frame regeneration. The only animated
       property anywhere in this section is grain opacity oscillating a
       couple percent (a compositor-only opacity keyframe — no layout,
       no repaint cost beyond what a single fixed layer already costs),
       which is what "static or barely-animated" grain calls for.

   Both sit ABOVE the cue content (z>4) but BELOW the dock/chat/rundown
   (z<100) — same rule §23's .stage-vignette already established: the
   "shot" gets graded, the console chrome stays clean instrument-panel
   glass, not tinted picture. Neither layer is gated behind no-preference:
   a static grade/grain is a finish, not motion, so reduced-motion and
   no-JS visitors get the same graded look — only the grain's opacity
   flicker (motion) is gated, with a static mid-opacity fallback.
   ============================================================ */
.film-grade {
  position: fixed; inset: 0; z-index: 51; pointer-events: none;
  mix-blend-mode: soft-light;
  background:
    radial-gradient(ellipse 62% 52% at 10% 6%, rgba(64,168,168,0.55), transparent 65%),
    radial-gradient(ellipse 62% 52% at 92% 98%, rgba(64,155,160,0.45), transparent 65%),
    radial-gradient(ellipse 55% 48% at 52% 40%, rgba(255,176,32,0.35), transparent 72%);
}
.film-grain {
  position: fixed; inset: -4px; z-index: 52; pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%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");
  background-size: 190px 190px;
  opacity: 0.05;
  mix-blend-mode: overlay;
}
@media (prefers-reduced-motion: no-preference) {
  html.js .film-grain { animation: grain-flicker 1.7s steps(3) infinite; }
}
@keyframes grain-flicker { 0%, 100% { opacity: 0.04; } 40% { opacity: 0.065; } 70% { opacity: 0.05; } }
@media (prefers-reduced-motion: reduce) { .film-grain { opacity: 0.05; } }
/* Print/no-CSS-background-image edge case: never let the grade/grain
   layers block interaction if something upstream fails to paint them. */
.film-grade, .film-grain { pointer-events: none !important; }

/* ============================================================
   27. SIGNATURE CURSOR (Visual Pass 4) — an amber aperture/reticle that
   replaces the native pointer, matching the brand mark's own aperture
   ring (see .brand-mark .aperture in §6). Desktop + fine-pointer +
   motion-allowed only (identical gate to pointer parallax in §"POINTER
   PARALLAX" of showcall.js): touch/coarse pointers and reduced-motion
   sessions never load it and keep the plain native cursor throughout.

   Markup (index.html, end of body): #fxCursor > .fx-cursor-ring +
   .fx-cursor-dot, positioned via one translate3d write per animation
   frame in showcall.js (compositor-only, no layout/repaint). The ring
   OPENS via transform:scale over interactive targets (dock buttons,
   CTAs, channels, chips, rail dots, cue-next) — an aperture blade
   opening, not a generic hover bump — and disappears entirely over text
   fields so the native I-beam is the only cursor visible there (see the
   cursor:auto restores below; never two cursors stacked at once).
   ============================================================ */
.fx-cursor {
  position: fixed; top: 0; left: 0; z-index: 200; pointer-events: none;
  width: 0; height: 0; display: none; opacity: 1;
  transition: opacity 0.18s var(--ease-out);
}
html.cursor-fx .fx-cursor { display: block; }
.fx-cursor.is-hidden { opacity: 0; }

/* native cursor: hidden site-wide once the reticle is live, restored on
   whatever explicitly needs its own (form fields keep text/auto; disabled
   controls keep the "not-allowed" affordance — losing that would read as
   a bug, not a feature). `button`/`.chip input` need explicit overrides
   because they already set their own literal `cursor` in §1/§17, which
   beats simple inheritance from html.cursor-fx. */
html.cursor-fx { cursor: none; }
html.cursor-fx button,
html.cursor-fx .chip input { cursor: none; }
html.cursor-fx input:not([type="checkbox"]):not([type="radio"]),
html.cursor-fx textarea,
html.cursor-fx select,
html.cursor-fx [contenteditable] {
  cursor: auto;
}
html.cursor-fx button:disabled,
html.cursor-fx .btn:disabled,
html.cursor-fx .dock-btn:disabled {
  cursor: not-allowed;
}

.fx-cursor-ring {
  position: absolute; top: -15px; left: -15px; width: 30px; height: 30px;
  border-radius: 50%; border: 1.4px solid var(--amber);
  opacity: 0.85; transform: scale(1);
  box-shadow: var(--bloom-sm);
  transition: transform 0.22s var(--ease-out), opacity 0.22s var(--ease-out),
              border-color 0.22s var(--ease-out), box-shadow 0.22s var(--ease-out);
}
/* four short crosshair ticks just outside the ring — the "aperture/
   reticle" read, echoing the brand mark's own tick (§6 .brand-mark line). */
.fx-cursor-ring::before, .fx-cursor-ring::after {
  content: ""; position: absolute; background: var(--amber);
}
.fx-cursor-ring::before { top: 50%; left: -7px; width: 5px; height: 1.4px; transform: translateY(-50%); }
.fx-cursor-ring::after { left: 50%; top: -7px; width: 1.4px; height: 5px; transform: translateX(-50%); }
.fx-cursor-dot {
  position: absolute; top: -1.5px; left: -1.5px; width: 3px; height: 3px;
  border-radius: 50%; background: var(--amber); box-shadow: var(--bloom-sm);
}
/* the "open" state — an aperture blade opening over anything callable */
.fx-cursor.is-interactive .fx-cursor-ring {
  transform: scale(1.55);
  border-color: var(--amber-hi);
  opacity: 1;
  box-shadow: var(--bloom-md);
}
/* the "shutter" state — a fast snap-closed on press (showcall.js onDown/
   onUp), reopening at the ring's normal 0.22s pace on release. Declared
   after .is-interactive so a press ON a hovered interactive target (the
   common case — clicking a button) wins the transform tug-of-war instead
   of the two rules fighting silently. Same amber, same ring — a gesture,
   not a second cursor state. */
.fx-cursor.is-clicking .fx-cursor-ring {
  transform: scale(0.72);
  transition: transform 0.1s var(--ease-out);
}
@media (prefers-reduced-motion: reduce) {
  /* belt + suspenders: initCustomCursor() already bails out under
     reduced motion and html.cursor-fx never gets added, so .fx-cursor
     stays display:none from the base rule above — this just documents
     the inert baseline the same way §23's reduce block does. */
  .fx-cursor { display: none !important; }
}

/* ============================================================
   28. WEBP PROGRESSIVE ENHANCEMENT (Pass 8 — perf)
   Every photo above is declared as a JPEG, which stays the guaranteed,
   always-correct baseline for every browser — no feature detection
   needed, nothing else in this file changes. Where the browser
   understands image-set() (in practice, every browser that does also
   decodes webp) this swaps in a same-crop, same-dimensions .webp sibling
   (dist/assets/*.webp, generated via `cwebp -q 82`, ~25-45% smaller —
   see the per-file totals in the pass notes) for the 9 backdrops/photos
   below the hero. @supports gates the whole block: a browser that can't
   parse image-set() never matches it and keeps the JPEG rule already
   declared above — this can only ever subtract bytes, never change which
   photo is in a slot or how it's cropped/framed.
   hero-stage.jpg is deliberately EXCLUDED here even though a .webp
   sibling exists on disk: it's the LCP element and is preloaded with
   fetchpriority="high" in index.html's <head> as a JPEG so the browser
   can start that fetch before it even sees this stylesheet. Swapping it
   here too would make webp-capable browsers fetch BOTH the preloaded
   JPEG and a different-URL webp for the same pixels — a double-download
   that would hurt the exact metric this section exists to help.
   (Pass 10: the hero now IS in the swap below — the preload in
   index.html became a type-gated webp+jpeg pair, so supporting
   browsers preload the webp the image-set resolves to, and the jpeg
   stays preloaded for the <video poster>, which uses it regardless.)
   ============================================================ */
@supports (background-image: image-set(url('/assets/venue-ballroom.webp') 1x)) {
  .cue-backdrop-standby { background-image: image-set(url('/assets/hero-stage.webp') 1x), linear-gradient(160deg, var(--surface-3), var(--black)); }
  .cue-backdrop-model { background-image: image-set(url('/assets/venue-ballroom.webp') 1x), linear-gradient(160deg, var(--surface-3) 0%, var(--surface) 55%, var(--black) 100%); }
  .cue-backdrop-board { background-image: image-set(url('/assets/disc-lighting.webp') 1x), linear-gradient(160deg, var(--surface-3) 0%, var(--black) 100%); }
  .channel[data-disc="audio"] .channel-shot { background-image: image-set(url('/assets/disc-audio.webp') 1x), linear-gradient(155deg, var(--surface-3), var(--black)); }
  .channel[data-disc="video"] .channel-shot { background-image: image-set(url('/assets/disc-video.webp') 1x), linear-gradient(155deg, var(--surface-3), var(--black)); }
  .channel[data-disc="led"] .channel-shot { background-image: image-set(url('/assets/disc-led.webp') 1x), linear-gradient(155deg, var(--surface-3), var(--black)); }
  .channel[data-disc="lighting"] .channel-shot { background-image: image-set(url('/assets/disc-lighting.webp') 1x), linear-gradient(155deg, var(--surface-3), var(--black)); }
  .channel[data-disc="staging"] .channel-shot { background-image: image-set(url('/assets/disc-staging.webp') 1x), linear-gradient(155deg, var(--surface-3), var(--black)); }
  .channel[data-disc="scenic"] .channel-shot { background-image: image-set(url('/assets/disc-scenic.webp') 1x), linear-gradient(155deg, var(--surface-3), var(--black)); }
  .cue-backdrop-run { background-image: image-set(url('/assets/crew-foh.webp') 1x), linear-gradient(160deg, var(--surface-3) 0%, var(--black) 100%); }
  .cue-backdrop-proof { background-image: image-set(url('/assets/disc-staging.webp') 1x), linear-gradient(160deg, var(--surface-3) 0%, var(--black) 100%); }
  .proof-shot-crew { background-image: image-set(url('/assets/crew-foh.webp') 1x), linear-gradient(155deg, var(--surface-3), var(--black)); }
  .proof-shot-venue { background-image: image-set(url('/assets/venue-ballroom.webp') 1x), linear-gradient(155deg, var(--surface-3), var(--black)); }
  .cue-backdrop-caller { background-image: image-set(url('/assets/caller-headset.webp') 1x), linear-gradient(160deg, var(--surface-3) 0%, var(--black) 100%); }
  .cue-backdrop-go { background-image: image-set(url('/assets/disc-lighting.webp') 1x), linear-gradient(160deg, var(--surface-3) 0%, var(--black) 100%); }
}

/* ============================================================
   29. CONTRAST HARDENING FOR REAL PHOTOGRAPHY (Pass 8 — a11y)
   §12/§15's .cue-scrim-side / .cue-scrim-side-r fade to their lightest
   point (was 0.15) on the side OPPOSITE each cue's readable copy — for
   model/proof that's the side the KINETIC HEADLINE + lede still sit on
   (narrow lead column, §12/§15 markup). Computed worst-case check (real
   sampled max-luminance pixel from the actual JPEGs, run through each
   cue's own `filter: saturate()/brightness()`, blended at each gradient
   stop): with the AI stand-in art this had headroom, but the real gig
   photography includes genuine hot highlights (stage lights, LED panels
   — several source photos sample at nearly pure white, see the pass
   notes) that pulled the worst-case contrast for bone-on-photo body copy
   to ~4.3:1 right at that faded edge — under the 4.5:1 AA floor for
   normal-weight text (the headline itself is large/bold and cleared 3:1
   fine; it was `.lede`/body-weight text at risk). Raising the floor from
   0.15 to 0.22 — a few percent darker at the edge that already reads as
   "the moody far side of the frame," not a visible design change — moves
   the same computed worst case to ~4.9:1, a real margin instead of a
   coin-flip. Every other cue's scrim was already comfortably clear of
   this (flat 0.78 full scrims on board/run, or the correctly-strong side
   of .cue-scrim-side on caller/go) and is untouched. */
.cue-scrim-side { background: linear-gradient(100deg, rgba(8,8,10,0.94) 0%, rgba(8,8,10,0.82) 42%, rgba(8,8,10,0.4) 75%, rgba(8,8,10,0.22) 100%); }
.cue-scrim-side-r { background: linear-gradient(260deg, rgba(8,8,10,0.94) 0%, rgba(8,8,10,0.82) 42%, rgba(8,8,10,0.4) 75%, rgba(8,8,10,0.22) 100%); }

/* ============================================================
   30. PREFERENCE MEDIA FEATURES (Pass 8 — a11y nice-to-haves)
   Cheap, additive support for two preference queries the site never
   checked before. Neither changes layout — both just swap already-
   defined colors/surfaces for higher-legibility ones. */
@media (prefers-reduced-transparency: reduce) {
  .topnav, .cue-card, .qform, .rundown-scrim, .chat-panel { backdrop-filter: none; }
  .topnav { background: var(--black); }
  .cue-card { background: var(--surface-2); }
  .qform { background: var(--surface); }
  .rundown-scrim { background: rgba(8,8,10,0.98); }
  .chat-panel { background: var(--surface); }
  html.js .dock { backdrop-filter: none; background: rgba(8,8,10,0.98); }
}
@media (prefers-contrast: more) {
  :root { --bone-dim: #c9c7c2; --bone-faint: #b3b1ab; }
  .cue-scrim-side, .cue-scrim-side-r { background-image: none; background-color: rgba(8,8,10,0.92); }
  .text-input, .select-input, .chip span { border-width: 2px; }
}

/* ---------- 30. BOARD CLEARANCE (Pass 10) ----------
   Pass 6 gave The Board chat-launcher clearance only under max-width:600px.
   This low-specificity floor covers desktop reduced-motion / no-JS too, so the
   Scenic card's copy never sits under the fixed launcher. The html.js engine-mode
   and mobile rules out-rank it wherever they apply. */
#cue-board .cue-body { padding-bottom: calc(var(--sp-16) + 2rem); }

/* ═══ SHORT VIEWPORTS (landscape phones) — the show still fits the frame ═══
   Same class of fix the Tway build needed: nothing here was height-gated.
   Discipline: compact chrome, reclaim the stage, never trap content. */
@media (max-height: 540px) {
  :root { --dock-h: 4rem; }
  html.js .cue-body {
    padding-top: calc(var(--topnav-total-h) + var(--sp-3));
    padding-bottom: calc(var(--dock-total-h) + var(--sp-4));
  }
  .cue-title, .cue h1, .cue-card h1 { font-size: clamp(1.6rem, 6.5vh, 2.6rem) !important; }
  .cue-lede, .cue-sub { font-size: 0.95rem; }
  /* the chat pill becomes a quiet dot — it was sitting on top of the show */
  .chat-toggle-label { display: none; }
  .chat-toggle { padding: 0.85rem; border-radius: 50%; }
  .chat-peek { display: none; }
  #cue-go .reassure { display: none; } /* the preamble yields so the first field shows above the fold */
  #cue-go .cue-lede { margin-bottom: var(--sp-3); }
  /* a breath of fade at the cue's bottom edge says "there's more" without saying it */
  html.js .cue.is-active .cue-body {
    -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 calc(100% - 18px), transparent 100%);
    mask-image: linear-gradient(180deg, #000 0%, #000 calc(100% - 18px), transparent 100%);
  }
}

/* ═══ JEWELRY — the details you feel (same discipline as the Tway v99 pass, showcall's language) ═══ */
html { -webkit-tap-highlight-color: transparent; }
input, select, textarea { caret-color: var(--amber, #FFB020); }
img, video { -webkit-user-drag: none; }
.cue-title, .cue h1, .cue h2, .cue-card h1 { text-wrap: balance; }
.dock-readout, .cue-tag, .dock [class*=count] { font-variant-numeric: tabular-nums; }
.chip:active, .board-ch:active, .qsubmit:active, .dock-go:active, .chat-toggle:active { transform: scale(0.98); }
.chat-body, .rundown-list { scrollbar-width: thin; scrollbar-color: #3a3a40 transparent; }
