/* ============================================================
   DRUG BOAT, STRIKE! — Stylesheet
   NES/Top Gun aesthetic + house brand colors
   ============================================================ */

/* --- CUSTOM PROPERTIES --- */
:root {
  /* Brand palette — PRIMARY: off-white #f2f1e8, black #1f1f1f, strong green #00ff85 */
  --pal-white: #ffffff;
  --pal-cream: #dad5c6;
  --pal-offwhite: #f2f1e8;      /* PRIMARY */
  --pal-black: #1f1f1f;         /* PRIMARY */
  --pal-teal: #80ffc1;
  --pal-green: #00ff85;         /* PRIMARY */
  --pal-dark-green: #1b7a3a;
  --pal-light-purple: #ccccff;
  --pal-dark-purple: #6a5acd;
  --pal-atomic-purple: #8f00ff;
  --pal-darkest-purple: #4333a8;
  --pal-yellow: #fdc919;
  --pal-orange: #ff580f;
  --pal-red: #f71824;
  --pal-red-text: #ff4550;      /* WCAG-AA red for SMALL text on the dark bg — #f71824 only clears AA at large sizes */
  --pal-coral: #FF6B73;

  /* Military/NES */
  --gold: #D4AF37;
  --gold-light: #F0D060;
  --mil-green: #4A5D23;
  --crt-black: #111111;

  /* Functional */
  --bg-primary: var(--pal-black);
  --bg-section: #1f1f1f;        /* OPI black — the whole site sits on one background */
  --bg-card: #1a1a1a;
  --text-primary: var(--pal-offwhite);
  --text-muted: var(--pal-cream);
  --border-gold: var(--gold);
  --accent: var(--pal-yellow);
  --danger: var(--pal-red);
  --success: var(--pal-green);

  /* Typography */
  --font-game: 'Press Start 2P', 'VT323', monospace;
  --font-heading: 'Fraunces', serif;
  --font-official: 'Instrument Serif', serif;
  --font-body: 'DotGothic16', sans-serif;
  --font-data: 'Geist Mono', 'JetBrains Mono', monospace;

  /* Spacing */
  --space-xs: 0.25rem;
  --space-sm: 0.5rem;
  --space-md: 1rem;
  --space-lg: 2rem;
  --space-xl: 4rem;
  --space-2xl: 6rem;

  /* Timing */
  --transition-fast: 150ms ease;
  --transition-med: 300ms ease;
  --scanline-speed: 8s;
}

/* --- RESET --- */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  scroll-behavior: smooth;
  scroll-padding-top: 3rem;
}

body {
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.6;
  color: var(--text-primary);
  background-color: var(--bg-primary);
  min-height: 100vh;
  overflow-x: hidden;
}

img {
  max-width: 100%;
  height: auto;
  image-rendering: pixelated;
}

button {
  cursor: pointer;
  font-family: inherit;
  border: none;
  background: none;
}

ul, ol {
  list-style: none;
}

a {
  color: var(--pal-teal);
  text-decoration: none;
  transition: color var(--transition-fast);
}

a:hover {
  color: var(--pal-green);
}

a:focus-visible,
button:focus-visible {
  outline: 3px solid var(--pal-atomic-purple);
  outline-offset: 2px;
}

/* --- SKIP LINK --- */
.skip-link {
  position: absolute;
  top: -100%;
  left: var(--space-md);
  padding: var(--space-sm) var(--space-md);
  background: var(--pal-atomic-purple);
  color: var(--pal-white);
  font-family: var(--font-game);
  font-size: 0.7rem;
  z-index: 9999;
  transition: top var(--transition-fast);
}

.skip-link:focus {
  top: var(--space-md);
}

/* --- CRT EFFECTS --- */
.crt-frame {
  position: relative;
  overflow: hidden;
}

.crt-frame::after {
  content: '';
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    0deg,
    transparent,
    transparent 2px,
    rgba(0, 0, 0, 0.12) 2px,
    rgba(0, 0, 0, 0.12) 4px
  );
  pointer-events: none;
  z-index: 2;
}

/* --- SECTIONS --- */
.section {
  min-height: 100vh;
  padding: calc(3rem + var(--space-lg)) var(--space-lg) var(--space-xl);
  display: flex;
  flex-direction: column;
  align-items: center;
}

.section__title {
  font-family: var(--font-heading);
  font-size: clamp(1.5rem, 4vw, 3rem);
  font-weight: 900;
  text-align: center;
  margin-bottom: var(--space-md);
  background: linear-gradient(180deg, var(--gold-light), var(--gold));
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

/* Dashboard section headers ("Mission Accomplished?", "Every strike on record")
   run larger than the in-CRT screen titles and scale fluidly from ~32px on a phone
   up to 56px on desktop. Scoped to the dashboard so the arcade titles, which are
   sized in CRT container units to fit the screen, keep their own sizing. */
.section--dashboard .section__title {
  font-size: clamp(2rem, 5.5vw, 3.5rem);
  line-height: 1.1;
  /* OPI strong green — override the gold text-gradient the base title uses in the arcade */
  background: none;
  -webkit-text-fill-color: var(--pal-green);
  color: var(--pal-green);
}

.section__subtitle {
  font-family: var(--font-body);
  font-size: 1rem;
  color: var(--text-muted);
  text-align: center;
  margin-bottom: var(--space-lg);
}

/* The dashboard's opening statement — a multi-paragraph, sober lead beneath the
   title. Bound to the grid's measure and set in a readable proportional face; the
   arcade/mono faces the rest of the page uses are illegible over a passage this long. */
.dashboard__lead {
  width: 100%;
  max-width: 640px;                 /* same measure as the grid + citation below */
  margin: 0 auto var(--space-lg);
  font-family: 'Geist', sans-serif; /* self-hosted; readable, not the pixel/mono UI faces */
  font-size: clamp(1rem, 1.6vw, 1.1rem);   /* stays >=16px on mobile; ~3:1 under the header */
  line-height: 1.7;
  color: var(--text-primary);
  text-align: left;
}
.dashboard__lead p { margin: 0 0 1em; color: inherit; }
.dashboard__lead p:last-child { margin-bottom: 0; }

/* ===== THE ARCADE MACHINE =====
   Fills the viewport from the ticker to the bottom edge — the set itself never
   scrolls; the rest of the page scrolls past it. */
.section.section--arcade {
  height: 100vh;
  height: 100dvh;
  min-height: 540px;
  /* No ticker anymore — let the TV breathe closer to the top/bottom edges */
  padding: clamp(16px, 3vh, 40px) clamp(28px, 3.2vw, 3rem);
  justify-content: center;
  background:
    radial-gradient(ellipse at 50% 28%, rgba(0, 60, 55, 0.22), transparent 70%),
    radial-gradient(ellipse at center, rgba(212, 175, 55, 0.05) 0%, transparent 70%),
    var(--bg-primary);
}

/* The machine wears an old-school TV shell: wood-grain rings + deep shadow.
   .hud.machine outranks the base .hud rules (whose clip-path would clip the
   shell's shadows entirely) — the TV bezel replaces the angular HUD corners. */
/* ===== THE CABINET — ported from the crt-tv.html component =====
   A black-plastic FRONT face with the screen recessed into it, a receding
   wood TOP panel, and a receding wood RIGHT panel with a carry handle. The
   two wood panels use the component's exact color-mix grain. Recolor via
   --wood; resize the wood depth via --topH / --sideW. */
/* Inside the arcade cabinet, EVERYTHING speaks in video-game type. Remapping
   the non-game font vars here (not globally) keeps the branded page sections below
   the machine — dashboard, WMD document, footer — on their proper brand fonts,
   because those sections live outside .machine. Serifs → Press Start 2P / VT323,
   the modern code mono (Geist) → VT323. DotGothic16 (--font-body) is already a
   pixel game font, so it stays. */
.hud.machine {
  --font-heading: 'Press Start 2P', 'VT323', monospace;
  --font-official: 'VT323', 'Press Start 2P', monospace;
  --font-data: 'VT323', 'Press Start 2P', monospace;
  --wood: #5c3a22;                  /* walnut · #a9814f oak · #6b2f22 mahogany */
  --topH: clamp(28px, 3.8vh, 50px);
  --sideW: clamp(40px, 4.8vw, 90px);
  position: relative;
  /* A TV sitting in the room, not edge-to-edge: capped width with breathing room
     on the sides. (max-width also undoes .hud's stray 980px cap.) */
  width: min(1100px, 90vw);
  max-width: 1100px;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  padding: var(--topH) var(--sideW) 0 0;   /* clear the two wood panels */
  border: none;
  border-radius: 3px;
  clip-path: none;
  background: none;
  box-shadow: none;
  /* The wood panels sit on the top + right, so the SCREEN is down-and-left of
     the cabinet's center. Nudge the whole TV right + up by half each panel so
     the game screen (not the cabinet) lands at the center of the window. */
  transform: translate(calc(var(--sideW) / 2), calc(var(--topH) / -2));
}

.hud.machine::before,
.hud.machine::after { display: none; }

/* Shared wood grain (the component's technique) */
.machine__top,
.machine__side { position: absolute; z-index: 3; }

/* TOP panel — recedes up-and-right, horizontal grain */
.machine__top {
  left: 0; top: 0;
  width: 100%; height: var(--topH);
  clip-path: polygon(0 100%, calc(100% - var(--sideW)) 100%, 100% 0, var(--sideW) 0);
  filter: brightness(1.07) drop-shadow(0 -3px 8px rgba(0, 0, 0, 0.28));
  background:
    repeating-linear-gradient(1deg, rgba(0,0,0,.15) 0px, rgba(0,0,0,0) 1px, rgba(0,0,0,0) 3px, rgba(0,0,0,.09) 4px, rgba(0,0,0,0) 5px, rgba(0,0,0,0) 8px, rgba(0,0,0,.12) 9px, rgba(0,0,0,0) 12px, rgba(0,0,0,0) 16px, rgba(0,0,0,.07) 17px, rgba(0,0,0,0) 21px),
    repeating-linear-gradient(-2deg, rgba(255,255,255,.05) 0px, rgba(255,255,255,0) 2px, rgba(255,255,255,0) 11px, rgba(255,255,255,.06) 12px, rgba(255,255,255,0) 14px, rgba(255,255,255,0) 25px),
    repeating-linear-gradient(0.5deg, color-mix(in srgb, var(--wood), black 26%) 0px, var(--wood) 3px, color-mix(in srgb, var(--wood), white 12%) 5px, var(--wood) 9px, color-mix(in srgb, var(--wood), black 16%) 13px, var(--wood) 18px, color-mix(in srgb, var(--wood), white 8%) 22px, var(--wood) 28px);
}

/* RIGHT panel — recedes, vertical grain, carries the handle */
.machine__side {
  right: 0; top: 0;
  width: var(--sideW); height: 100%;
  clip-path: polygon(0 var(--topH), 100% 0, 100% calc(100% - var(--topH)), 0 100%);
  filter: brightness(0.82) drop-shadow(7px 5px 12px rgba(0, 0, 0, 0.4));
  background:
    radial-gradient(45% 40% at 40% 50%, rgba(20,10,4,.16), rgba(20,10,4,0) 70%),
    repeating-linear-gradient(99deg, rgba(0,0,0,.16) 0px, rgba(0,0,0,0) 1px, rgba(0,0,0,0) 4px, rgba(0,0,0,.1) 5px, rgba(0,0,0,0) 6px, rgba(0,0,0,0) 9px, rgba(0,0,0,.13) 10px, rgba(0,0,0,0) 13px, rgba(0,0,0,0) 17px, rgba(0,0,0,.08) 18px, rgba(0,0,0,0) 22px),
    repeating-linear-gradient(95deg, rgba(255,255,255,.05) 0px, rgba(255,255,255,0) 2px, rgba(255,255,255,0) 12px, rgba(255,255,255,.07) 13px, rgba(255,255,255,0) 15px, rgba(255,255,255,0) 27px, rgba(255,255,255,.04) 28px, rgba(255,255,255,0) 33px),
    repeating-linear-gradient(100deg, color-mix(in srgb, var(--wood), black 24%) 0px, var(--wood) 3px, color-mix(in srgb, var(--wood), white 16%) 5px, var(--wood) 9px, color-mix(in srgb, var(--wood), black 16%) 12px, var(--wood) 17px, color-mix(in srgb, var(--wood), white 10%) 21px, var(--wood) 26px, color-mix(in srgb, var(--wood), black 10%) 31px, var(--wood) 37px);
}

.machine__handle {
  position: absolute;
  left: 18%; top: 42%;
  width: 64%; height: clamp(11px, 1.5vw, 20px);
  transform: skewY(-25deg);
  border-radius: 5px;
  background: color-mix(in srgb, var(--wood), black 38%);
  box-shadow: inset 0 3px 5px rgba(0,0,0,.55), inset 0 -2px 3px rgba(255,255,255,.08), 0 1px 0 rgba(255,255,255,.12);
}

.machine__screw {
  position: absolute;
  left: 34%; width: 5px; height: 5px; border-radius: 50%;
  background: color-mix(in srgb, var(--wood), black 45%);
  box-shadow: inset 0 1px 1px rgba(0,0,0,.6), 0 1px 0 rgba(255,255,255,.1);
}

/* Black-plastic front face — holds the recessed screen module */
.machine__front {
  position: relative;
  z-index: 2;
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: clamp(10px, 1.1vw, 18px);
  background: #141210;
  border-radius: 6px 0 0 6px;
  box-shadow:
    inset 0 0 0 2px rgba(255, 255, 255, 0.03),
    inset 0 0 26px rgba(0, 0, 0, 0.5),
    0 30px 50px -18px rgba(0, 0, 0, 0.7);
}

/* The telemetry strip reads as a slim inset label plate */
.machine__front > .hud__topbar {
  margin: 0;
  padding: 5px 12px;
  border: none;
  border-radius: 5px;
  background: linear-gradient(180deg, #0c130c, #060a06);
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.7), 0 1px 0 rgba(255, 255, 255, 0.06);
}

/* The screen glass, recessed into the black plastic. It's also a CONTAINER so
   the select screen can size its type in container units (cqh/cqw) — text binds
   to the CRT and scales with the screen instead of tiny fixed rem values. */
.machine__crt {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  container-type: size;
  border-radius: 7px;
  background: #050506;
  overflow: hidden;
  box-shadow:
    inset 0 0 0 5px #0c0c0d,
    inset 0 0 44px rgba(0, 0, 0, 0.85),
    0 0 0 1px rgba(0, 0, 0, 0.6);
}

.screen {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  overflow-y: auto;
}

.screen[hidden] { display: none; }

/* Cabinet-wide CRT: scanlines + RGB aperture + vignette + flicker over EVERY
   screen (and the hit modal). Sits above all
   content; pointer-events pass through. */
.machine__crt::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 60;
  pointer-events: none;
  background:
    repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.16) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(90deg,
      rgba(255, 0, 60, 0.025) 0 1px,
      rgba(0, 255, 120, 0.022) 1px 2px,
      rgba(0, 80, 255, 0.025) 2px 3px),
    radial-gradient(ellipse at 50% 50%, transparent 64%, rgba(0, 0, 0, 0.38) 100%);
  animation: crt-flicker 3.4s steps(12) infinite;
}

/* CRT power-blip overlay between screens */
.machine__blip {
  position: absolute;
  inset: 0;
  z-index: 40;
  background: #000;
  transform: scaleY(0);
  pointer-events: none;
}

.machine__blip.is-on {
  animation: crt-blip 0.36s steps(6);
}

@keyframes crt-blip {
  0% { transform: scaleY(0); }
  40% { transform: scaleY(1); }
  55% { transform: scaleY(1); }
  100% { transform: scaleY(0.002); }
}

.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;
}

/* --- SCREEN 1: TITLE — a proper video-game start screen ---
   Vertical rhythm (never scrolls, holds on mobile): the big title fills the
   space ABOVE the water (top ~2/3, off the top edge), the water sits below it,
   PUSH START floats in the gap between the water and the copyright, and the
   copyright pins near the bottom. All sizes are container-relative (cqw/cqh). */
.screen--title {
  /* === TWEAKABLES ===  title size, and how PUSH START / copyright scale off it.
     Bump --title-font to grow everything; change the ratios independently.
     The 10cqh cap keeps the title from filling its whole half and jamming against
     the water — raise it toward the 8.6cqw for a bigger title / smaller gap. */
  --title-font: min(8.6cqw, 10cqh);
  --coin-ratio: 0.25;   /* PUSH START = 25% of the title */
  --copy-ratio: 0.20;   /* copyright  = 20% of the title */

  /* The title area (top) and the PUSH-START/copyright group (bottom) grow
     equally, so the WATER band always sits centered on the middle of the CRT. */
  justify-content: flex-start;
  text-align: center;
  padding: clamp(10px, 3.5cqh, 34px) clamp(10px, 3cqw, 30px);
  gap: 0;
  overflow: hidden;
  cursor: pointer;
  background:
    radial-gradient(ellipse at 50% 30%, rgba(0, 88, 248, 0.14), transparent 65%),
    var(--crt-black);
}

/* The title fills the space above the water (as big as the CRT width allows) */
.screen--title .hero__title {
  flex: 1 1 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  font-size: var(--title-font);
  line-height: 1.28;
  margin: 0;
}

.screen--title .hero__subtitle {
  font-size: clamp(0.72rem, 1.9vh, 1.05rem);
}

.screen--title:focus-visible {
  outline: 3px solid var(--pal-atomic-purple);
  outline-offset: -3px;
}

.title-tm {
  font-size: 0.22em;
  /* pin it up by the cap-line of STRIKE! so it reads as a true superscript ™ */
  vertical-align: top;
  position: relative;
  top: 0.2em;
  color: var(--pal-cream);
  margin-left: 5px;
}

/* Bottom group: mirrors the title area's height so the water stays centered.
   Inside it, PUSH START floats in the gap and the copyright pins to the bottom. */
.title-bottom {
  flex: 1 1 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.screen--title .hero__insert-coin {
  /* Floats between the water and the copyright */
  flex: 1 1 0;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  font-size: calc(var(--title-font) * var(--coin-ratio));
}

/* Animated water band with the fleet drifting through the attract screen */
.title-waves {
  position: relative;
  width: 100%;
  flex: 0 0 auto;
  height: clamp(38px, 11cqh, 92px);
  margin: 0;
  overflow: hidden;
  background:
    repeating-linear-gradient(90deg, rgba(244, 252, 255, 0.5) 0 10px, transparent 10px 46px) 0 30% / auto 3px no-repeat,
    repeating-linear-gradient(90deg, rgba(244, 252, 255, 0.4) 0 14px, transparent 14px 60px) 0 62% / auto 4px no-repeat,
    repeating-linear-gradient(90deg, rgba(244, 252, 255, 0.35) 0 18px, transparent 18px 74px) 0 92% / auto 5px no-repeat,
    linear-gradient(180deg, #0038a8 0 30%, #0048d0 30% 70%, #0058f8 70% 100%);
  animation: title-water 7s linear infinite;
}

@keyframes title-water {
  from { background-position: 0 30%, 0 62%, 0 92%, 0 0; }
  to { background-position: -92px 30%, 120px 62%, -148px 92%, 0 0; }
}

.title-boat {
  position: absolute;
  image-rendering: pixelated;
  animation: title-drift linear infinite;
}

.title-boat--1 { top: 12%; animation-duration: 26s; }
.title-boat--2 { top: 30%; animation-duration: 38s; animation-delay: -19s; }
.title-boat--3 { top: 55%; animation-duration: 17s; animation-delay: -6s; }

@keyframes title-drift {
  from { left: -90px; }
  to { left: 104%; }
}

/* A stray missile finds the water: a quick pixel burst (JS swaps the frames).
   The frames are square, so pin a 1:1 ratio — height:auto alone collapses to 0. */
.title-boom {
  position: absolute;
  transform: translate(-50%, -50%);
  width: clamp(24px, 4vw, 46px);
  aspect-ratio: 1;
  image-rendering: pixelated;
  z-index: 3;
  pointer-events: none;
}

.title-copyright {
  flex: 0 0 auto;              /* pins to the bottom of the bottom group */
  font-family: var(--font-game);
  font-size: calc(var(--title-font) * var(--copy-ratio));
  color: var(--pal-cream);
  opacity: 0.8;
  margin: 0;
  letter-spacing: 0.06em;
}

.hero__title {
  font-family: var(--font-game);
  font-size: clamp(1.2rem, 5vw, 2.5rem);
  line-height: 1.8;
  margin-bottom: var(--space-lg);
}

.hero__title-line {
  display: block;
  color: var(--pal-yellow);
  text-shadow: 3px 3px 0 var(--gold), -1px -1px 0 rgba(253, 201, 25, 0.3);
}

.hero__title-line--strike {
  color: var(--pal-red);
  font-size: 1.4em;
  text-shadow: 3px 3px 0 #8B0000, 0 0 20px rgba(247, 24, 36, 0.4);
}

.hero__subtitle {
  font-family: var(--font-official);
  font-size: clamp(1rem, 2.5vw, 1.5rem);
  font-style: italic;
  color: var(--pal-cream);
  margin-bottom: var(--space-lg);
}

.hero__mission {
  font-family: var(--font-body);
  font-size: 1.1rem;
  color: var(--text-muted);
  max-width: 600px;
  margin: 0 auto var(--space-xl);
  line-height: 1.8;
}

.hero__insert-coin {
  font-family: var(--font-game);
  font-size: 0.8rem;
  color: var(--pal-green);
}

.blink {
  animation: blink 1.2s steps(1) infinite;
}

@keyframes blink {
  0%, 50% { opacity: 1; }
  51%, 100% { opacity: 0; }
}

/* --- DASHBOARD --- */
.section--dashboard {
  background: var(--bg-section);
  /* LEARN MORE lands the title just below the fixed ticker, with a little air */
  scroll-margin-top: 3.25rem;
  min-height: auto;
  justify-content: flex-start;
  padding-top: calc(2.5rem + var(--space-lg));
  padding-bottom: var(--space-2xl);
}

.dashboard__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);   /* 2x2 */
  gap: var(--space-md);
  max-width: 640px;
  width: 100%;
}

/* All stat cards carry a red outline (red = the alarm this whole board is sounding). */
.stat-card {
  background: var(--bg-card);
  border: 2px solid var(--pal-red);
  padding: var(--space-lg) var(--space-md);
  text-align: center;
  position: relative;
  overflow: hidden;
  /* Center content vertically so a card carrying a footnote stays balanced next to
     the un-footnoted card sharing its row (the grid stretches them to equal height). */
  display: flex;
  flex-direction: column;
  justify-content: center;
}


.stat-card::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--pal-red), #ff5b63, var(--pal-red));
}

.stat-card__icon {
  font-size: 2rem;
  margin-bottom: var(--space-sm);
}

.stat-card__value {
  font-family: var(--font-game);
  font-size: clamp(1rem, 3vw, 1.8rem);
  color: var(--pal-red);
  margin-bottom: var(--space-sm);
  text-shadow: 0 0 10px rgba(247, 24, 36, 0.4);
}

.stat-card__label {
  font-family: var(--font-data);
  font-size: 0.75rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* Source note under the board. Neutral factual attribution — the numbers up top are
   real and dated, so they need to be cited. */
.dashboard__cite {
  max-width: 640px;
  width: 100%;
  margin: var(--space-md) auto 0;
  font-family: var(--font-data);
  font-size: 0.72rem;
  line-height: 1.6;
  color: var(--text-muted);
  text-align: center;
}

/* Content links outside the arcade carry the house OPI treatment: strong green,
   with a sweep-in green highlight + ink-black text on hover/focus — the standard
   opioidpolicy.org link behavior, adapted for this dark background. */
.section--dashboard a {
  color: var(--pal-green);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  background-image: linear-gradient(var(--pal-green), var(--pal-green));
  background-repeat: no-repeat;
  background-position: left center;
  background-size: 0% 100%;
  padding: 0.05em 0.18em;
  transition: background-size var(--transition-med), color var(--transition-fast);
}

.section--dashboard a:hover,
.section--dashboard a:focus-visible {
  background-size: 100% 100%;
  color: var(--pal-black);
  text-decoration: none;
  outline: none;
}

/* Footnote marker on a stat value (e.g. Fentanyl Stopped "0*"). */
.stat-card__star {
  font-size: 0.55em;
  vertical-align: super;
  line-height: 0;
  color: var(--pal-red-text);   /* small text — needs the AA-grade red (the big value beside it clears AA as large text) */
}

/* Footnote tied to a single stat — sits inside its card, at box width, explaining
   that card's asterisk (e.g. "*Alleged", the fentanyl caveat). */
.stat-card__note {
  margin-top: var(--space-sm);
  font-family: var(--font-data);
  font-size: 0.62rem;
  line-height: 1.4;
  color: var(--text-muted);
  opacity: 0.72;
}

/* --- STRIKE LOG — the real per-strike record. Sober, tabular, NOT a game element. --- */
.strike-log {
  max-width: 640px;
  width: 100%;
  /* Clear breathing room so "Every strike on record" reads as its own section */
  margin: var(--space-2xl) auto 0;
}

/* Full length on purpose — the sheer number of rows IS the point, so it runs down the
   page rather than hiding inside a scrollbox. */
.strike-log__scroll {
  border: 1px solid rgba(247, 24, 36, 0.5);
  background: var(--bg-card);
}

.strike-log__table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-data);
  font-size: 0.82rem;
  color: var(--text-muted);
}

.strike-log__table thead th {
  position: sticky;
  top: 0;
  background: #14100f;
  color: var(--pal-offwhite);
  text-align: left;
  font-weight: 400;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-size: 0.72rem;
  padding: 0.55rem 0.8rem;
  border-bottom: 1px solid var(--pal-red);
}

.strike-log__table td {
  padding: 0.45rem 0.8rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}

.strike-log__table tbody tr:nth-child(even) { background: rgba(255, 255, 255, 0.02); }

.strike-log__cas {
  text-align: right;
  color: var(--pal-red-text);   /* AA-grade red — this small casualties text failed 4.5:1 at brand #f71824 */
  font-variant-numeric: tabular-nums;
}

/* MOBILE: the 3-column table is too wide — reflow each row into a stacked card so nothing
   scrolls sideways. Date reads as the card heading; Location + Casualties are labeled rows
   (labels come from the td data-label attrs set in main.js). */
@media (max-width: 560px) {
  .strike-log__table thead {           /* hide the header row (labels move inline) — SR-safe */
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
  }
  .strike-log__table,
  .strike-log__table tbody,
  .strike-log__table tr,
  .strike-log__table td { display: block; }
  .strike-log__table tr {
    padding: 0.6rem 0.9rem;
    border-bottom: 1px solid rgba(247, 24, 36, 0.3);
  }
  .strike-log__table td { border: none; padding: 0; }
  .strike-log__table td[data-label="Date"] {   /* card heading */
    color: var(--pal-offwhite);
    font-size: 0.85rem;
    margin-bottom: 0.3rem;
  }
  .strike-log__table td[data-label="Location"],
  .strike-log__table td[data-label="Casualties"] {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    font-size: 0.8rem;
    padding: 0.12rem 0;
  }
  .strike-log__table td[data-label="Location"]::before,
  .strike-log__table td[data-label="Casualties"]::before {
    content: attr(data-label);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: 0.64rem;
    color: var(--pal-offwhite);
    opacity: 0.6;
    align-self: center;
  }
  .strike-log__cas { text-align: right; }
}

/* --- SCREEN 2: CHARACTER SELECT ---
   Sized in cqmin (the SMALLER of the CRT's width/height) so it NEVER blows up on
   an odd aspect ratio: a landscape CRT sizes off its height (unchanged), a
   snapped/portrait window sizes off its width, and neither overflows. The grid
   reshapes by CRT width — 4-across → 2x2 full cards → 2x2 faces (phone). No
   scrolling, ever. */
.screen--select {
  justify-content: space-evenly;
  padding: 2cqmin 2.5cqw;
  gap: 0;
  overflow: hidden;
}

.screen--select .section__title {
  font-size: 5.4cqmin;
  margin: 0;
  line-height: 1.3;
}

.character-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.2cqw;
  width: 100%;
  max-width: none;
  margin: 0;
  align-items: stretch;
}

.character-card {
  background: var(--bg-card);
  border: 3px solid var(--mil-green);
  padding: 1.8cqmin 0.7cqw 4.4cqmin;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.1cqmin;
  text-align: center;
  cursor: pointer;
  transition: all var(--transition-med);
  position: relative;
}

.character-card:hover {
  border-color: var(--pal-teal);
  transform: translateY(-4px);
  box-shadow: 0 8px 24px rgba(128, 255, 193, 0.15);
}

.character-card[aria-checked="true"] {
  border-color: var(--pal-green);
  box-shadow: 0 0 20px rgba(0, 255, 133, 0.35), inset 0 0 20px rgba(0, 255, 133, 0.12);
}

.character-card[aria-checked="true"]::after {
  content: '▶ SELECTED';
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  padding: 0.7cqmin 0;
  background: var(--pal-green);
  color: var(--pal-black);
  font-family: var(--font-game);
  font-size: 1.5cqmin;
}

.character-card__sprite {
  width: 18.5cqmin;
  height: 18.5cqmin;
  margin: 0;
  background: var(--crt-black);
  border: 2px solid var(--gold);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.character-card__sprite img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  image-rendering: pixelated;
}

.character-card__name {
  font-family: var(--font-game);
  font-size: 3.3cqmin;
  color: var(--pal-yellow);
  margin: 0;
}

.character-card__title {
  font-family: var(--font-data);
  font-size: 3.9cqmin;
  line-height: 1.15;
  color: var(--text-muted);
  margin: 0;
}

.character-card__stats {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.4cqmin;
  margin: 0;
}

.character-card__stat {
  font-family: var(--font-game);
  font-size: 1.6cqmin;
  padding: 0.45cqmin 0.4cqmin;
  background: rgba(0, 255, 133, 0.1);
  border: 1px solid var(--mil-green);
  color: var(--pal-teal);
  white-space: nowrap;
}

/* Selected warrior's quote — types out in the game font (VT323 via .machine's
   --font-official remap) with a blinking terminal cursor, like the ending.
   The box is a FIXED reserved area (~half the CRT wide, tall enough for the
   longest quote), so the text fills a locked blank space and NOTHING else on the
   screen shifts as it types. Text sits at the top and grows downward. */
.select__quote {
  font-family: var(--font-official);
  font-size: 4cqmin;
  line-height: 1.2;
  letter-spacing: 0.02em;
  color: var(--pal-teal);
  text-align: center;
  width: 56cqw;
  max-width: 56cqw;
  height: 16cqmin;
  margin: 0 auto;
  flex: 0 0 auto;            /* never grow or shrink — the reserve is constant */
  overflow: hidden;
  text-wrap: balance;
}

/* Type-on letters, shared by the warrior-select quote AND the ending "victory"
   captions/banners (vignettes.js say()): the full line is laid out in full
   (invisible) then revealed left-to-right, so the words stay STATIC — only the
   letters turn on. opacity (not visibility) keeps the full text in the layout +
   a11y tree, so centering never re-flows and a screen reader hears it once. */
.q-char { opacity: 0; }
.q-char.on { opacity: 1; }

/* The cursor rides just past the last revealed letter, absolutely positioned so
   it never adds width — width would re-center the line and shift the words. */
.q-char.cursor { position: relative; }
.q-char.cursor::after {
  content: '_';
  position: absolute;
  left: 100%;
  bottom: 0;
  color: currentColor;
  animation: blink 0.7s steps(1) infinite;
}

/* START MISSION is an even member of the space-evenly stack (no edge, no dead gap).
   Its flat NES look (no offset shadow / radius) comes from the shared `.machine .btn`
   rule; here we only set its size and kill the inherited text-shadow. */
.select__confirm {
  margin: 0;
}
.select__confirm .btn {
  text-shadow: none;
  font-size: 3.3cqmin;
  padding: 1.6cqmin 3.6cqw;
}

/* MEDIUM / narrow CRT (snapped window, tablet): 2x2 of COMPACT full cards. The
   sprite + type shrink, and the long roles CLAMP to 3 lines so no single card
   (Marco's endless title!) blows up the grid — so all four + quote + START fit
   any narrow window, tall OR short, without scrolling. */
@container (max-width: 700px) {
  .screen--select .section__title { font-size: 4.6cqmin; }
  .character-grid { grid-template-columns: repeat(2, 1fr); gap: 2cqmin; }
  .character-card { padding: 1.4cqmin 1cqmin 3.2cqmin; gap: 0.5cqmin; }
  .character-card__sprite { width: 11.5cqmin; height: 11.5cqmin; }
  .character-card__name { font-size: 3cqmin; }
  .character-card__title {
    font-size: 2.7cqmin;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .character-card__stat { font-size: 1.7cqmin; padding: 0.4cqmin 0.5cqmin; }
  .character-card[aria-checked="true"]::after { font-size: 1.7cqmin; }
  .select__quote { width: 82cqw; max-width: 82cqw; height: 13cqmin; font-size: 3.6cqmin; }
  .select__confirm .btn { font-size: 3cqmin; padding: 1.4cqmin 4cqw; }
}

/* PHONE / very narrow CRT: 2x2 of FACES; picking a warrior reveals its details
   (role clamped to 4 lines). Width-relative units keep it legible on a tiny CRT. */
@container (max-width: 440px) {
  .screen--select { gap: 2cqmin; padding: 3cqmin 3cqw; }
  .screen--select .section__title { font-size: 7cqw; }

  .character-grid { gap: 3cqw; align-items: start; }
  .character-card { padding: 3cqw 2cqw; gap: 2cqw; }
  .character-card__sprite { width: 32cqw; height: 32cqw; }

  /* faces only until chosen */
  .character-card__name,
  .character-card__title,
  .character-card__stats { display: none; }
  .character-card[aria-checked="true"] { padding-bottom: 8cqw; }
  .character-card[aria-checked="true"] .character-card__name { display: block; font-size: 5cqw; }
  .character-card[aria-checked="true"] .character-card__title {
    display: -webkit-box; -webkit-line-clamp: 4; line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden;
    font-size: 4cqw;
  }
  .character-card[aria-checked="true"] .character-card__stats { display: flex; gap: 1.4cqw; }
  .character-card[aria-checked="true"] .character-card__stat { font-size: 3.2cqw; padding: 1cqw 1.2cqw; }
  .character-card[aria-checked="true"]::after { font-size: 3.2cqw; padding: 1.4cqw 0; }

  .select__quote { width: 94cqw; max-width: 94cqw; height: auto; min-height: 14cqw; font-size: 5cqw; }
  .select__confirm .btn { font-size: 5cqw; padding: 2.4cqw 7cqw; }
}

/* --- BUTTONS --- */
.btn {
  font-family: var(--font-game);
  font-size: 0.7rem;
  padding: var(--space-sm) var(--space-lg);
  border: 2px solid;
  text-transform: uppercase;
  transition: all var(--transition-fast);
  letter-spacing: 0.05em;
}

.btn--primary {
  border-color: var(--pal-green);
  color: var(--pal-green);
  background: rgba(0, 255, 133, 0.1);
}

.btn--primary:hover:not(:disabled) {
  background: var(--pal-green);
  color: var(--pal-black);
  box-shadow: 0 0 16px rgba(0, 255, 133, 0.4);
}

.btn--primary:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.btn--secondary {
  border-color: var(--gold);
  color: var(--gold);
  background: rgba(212, 175, 55, 0.1);
}

.btn--secondary:hover {
  background: var(--gold);
  color: var(--pal-black);
}

/* NES, not neobrutalist. A previously-loaded global stylesheet gave every .btn a
   brand look — a hard 4px offset drop-shadow
   + 8px rounded corners. That was right for the page sections BELOW the cabinet,
   but wrong inside the arcade, where a button should read like a flat, square NES
   menu button. Strip both from EVERY button in the machine (select START, map
   CONFIRM, ending NEW MISSION / LEARN MORE, and the in-CRT hit modal).
   that stylesheet set these with !important, so we
   must use !important to win — plain specificity can't beat it. This also drops the
   variants' hover glow inside the machine, which is fine: the hover background swap
   is the feedback, and a flat NES button shouldn't glow-shift anyway. */
.machine .btn {
  box-shadow: none !important;
  border-radius: 0 !important;
}

/* The default focus ring is atomic-purple — brand-correct on the page, but wrong
   inside the arcade (same reason the neobrutalist shadow is killed above). Keep a
   visible ring for keyboard users, in the cabinet's own palette. */
.machine a:focus-visible,
.machine button:focus-visible {
  outline-color: var(--pal-offwhite);
}

/* Cockpit HUD shell with angled corners + bracket accents */
.hud {
  width: 100%;
  max-width: 980px;
  position: relative;
  padding: var(--space-md);
  background: linear-gradient(180deg, #06121c, #04090f);
  border: 2px solid var(--gold);
  box-shadow:
    inset 0 0 44px rgba(0, 40, 45, 0.55),
    0 0 30px rgba(0, 255, 133, 0.06);
  clip-path: polygon(
    0 14px, 14px 0, calc(100% - 14px) 0, 100% 14px,
    100% calc(100% - 14px), calc(100% - 14px) 100%, 14px 100%, 0 calc(100% - 14px)
  );
}

.hud::before,
.hud::after {
  content: '';
  position: absolute;
  width: 22px;
  height: 22px;
  border: 2px solid var(--pal-teal);
  pointer-events: none;
}

.hud::before { top: 7px; left: 7px; border-right: 0; border-bottom: 0; }
.hud::after { bottom: 7px; right: 7px; border-left: 0; border-top: 0; }

/* Telemetry topbar is cockpit chrome — it only exists during the SORTIE */
.hud__topbar {
  display: none;
  flex-wrap: wrap;
  gap: var(--space-sm);
  justify-content: space-between;
  align-items: center;
  padding-bottom: var(--space-sm);
  margin-bottom: var(--space-md);
  border-bottom: 1px solid rgba(128, 255, 193, 0.3);
}

/* The "RADAR LIVE · … · WEAPONS HOT" strip is gone — the windshield runs to the
   top of the screen. (#hud-lock / #hud-sector still update harmlessly, unseen.) */
.machine[data-screen="sortie"] .hud__topbar {
  display: none;
}

.hud__tag {
  font-family: var(--font-game);
  font-size: 0.5rem;
  letter-spacing: 0.05em;
  color: var(--pal-teal);
}

.hud__tag--rec::before {
  content: '\25CF ';
  color: var(--pal-red);
  animation: blink 1.2s steps(1) infinite;
}

.hud__tag--hot { color: var(--pal-yellow); }
#hud-lock { color: var(--gold-light); }

/* ===== WINDSHIELD — Konami Top Gun canopy view =====
   Inside the machine the windshield flex-fills the SORTIE screen above the
   cockpit dashboard; no fixed aspect ratio. */
.screen--sortie {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  overflow: hidden;
}

.windshield {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  overflow: hidden;
  background: #3cbcfc;
  border-bottom: 3px solid #16222b;
  image-rendering: pixelated;
  cursor: none;
}

.windshield:focus-visible {
  outline: 3px solid var(--pal-atomic-purple);
  outline-offset: 2px;
}

/* NES banded sky */
.ws-sky {
  position: absolute;
  inset: 0 0 auto 0;
  height: 36%;
  background: linear-gradient(180deg,
    #0058f8 0 28%, #3cbcfc 28% 68%, #a4e4fc 68% 100%);
}

/* Blocky pixel clouds drifting */
/* Clouds stream right-to-left continuously so it feels like the jet is moving */
.ws-cloud {
  position: absolute;
  width: 10px;
  height: 10px;
  background: #f8f8f8;
  box-shadow:
    10px 0 #f8f8f8, 20px 0 #f8f8f8, 30px 0 #f8f8f8, 40px 0 #f8f8f8,
    10px -10px #f8f8f8, 20px -10px #f8f8f8, 30px -10px #f8f8f8,
    -10px 0 #e8e8e8, 50px 0 #e8e8e8;
  opacity: 0.9;
  animation: ws-fly var(--cdur, 40s) linear infinite;
}

.ws-cloud--1 { top: 42%; --cdur: 30s; animation-delay: -7s; }
.ws-cloud--2 { top: 12%; --cdur: 48s; animation-delay: -26s; transform: scale(1.5); }
.ws-cloud--3 { top: 58%; --cdur: 22s; animation-delay: -14s; transform: scale(0.8); }

@keyframes ws-fly {
  from { left: 104%; }
  to { left: -20%; }
}

.ws-horizon {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(36% - 2px);
  height: 3px;
  background: #a4e4fc;
  opacity: 0.9;
}

/* Land rising over the horizon as the mission wears on (height set by JS) */
.ws-land {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 64%;
  height: 0%;
  z-index: 1;
  background:
    repeating-linear-gradient(90deg,
      #1b7a3a 0 26px, #23904a 26px 44px, #16632f 44px 70px);
  border-top: 2px solid #2fae59;
  transition: height 1.1s steps(5);
}

/* NES banded ocean */
.ws-ocean {
  position: absolute;
  inset: 36% 0 0 0;
  background: linear-gradient(180deg,
    #0038a8 0 16%, #0048d0 16% 45%, #0058f8 45% 100%);
}

/* Parallax wave rows scrolling toward the player */
.ws-wave {
  position: absolute;
  left: 0;
  right: 0;
  background: repeating-linear-gradient(90deg,
    rgba(244, 252, 255, 0.9) 0 var(--dash, 8px),
    transparent var(--dash, 8px) var(--gap, 26px));
  animation: ws-wave var(--dur, 4s) linear infinite;
}

.ws-wave:nth-child(1) { top: 10%; height: 2px; opacity: 0.25; --dash: 5px; --gap: 18px; --fall: 7px; --dur: 6.5s; }
.ws-wave:nth-child(2) { top: 26%; height: 2px; opacity: 0.32; --dash: 7px; --gap: 24px; --fall: 11px; --dur: 5.4s; }
.ws-wave:nth-child(3) { top: 44%; height: 3px; opacity: 0.4; --dash: 9px; --gap: 32px; --fall: 15px; --dur: 4.4s; }
.ws-wave:nth-child(4) { top: 64%; height: 4px; opacity: 0.48; --dash: 13px; --gap: 44px; --fall: 20px; --dur: 3.6s; }
.ws-wave:nth-child(5) { top: 85%; height: 5px; opacity: 0.55; --dash: 17px; --gap: 58px; --fall: 26px; --dur: 2.9s; }

@keyframes ws-wave {
  from { transform: translateY(0); }
  to { transform: translateY(var(--fall, 12px)); }
}

/* Sprite layers */
.ws-boats,
.ws-fx {
  position: absolute;
  inset: 0;
}

.ws-boats { z-index: 3; }
.ws-fx { z-index: 4; }

.ws-boat {
  position: absolute;
  left: 0;
  top: 0;
  transform-origin: 50% 100%;
  will-change: transform;
  pointer-events: none;
}

.ws-boat img {
  display: block;
  height: auto;
  image-rendering: pixelated;
  animation: ws-bob 1.7s steps(2) infinite;
}

.ws-boat--locked img {
  filter: drop-shadow(0 0 4px rgba(247, 24, 36, 0.95)) drop-shadow(0 0 1px #fff);
}

@keyframes ws-bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-2px); }
}

/* Free-moving crosshair; brackets use currentColor so lock can recolor them */
.ws-reticle {
  position: absolute;
  left: 50%;
  top: 58%;
  width: 40px;
  height: 40px;
  margin: -20px 0 0 -20px;
  z-index: 6;
  pointer-events: none;
  color: var(--pal-teal);
  filter: drop-shadow(0 0 3px currentColor);
  background:
    linear-gradient(currentColor, currentColor) left top / 10px 2px no-repeat,
    linear-gradient(currentColor, currentColor) left top / 2px 10px no-repeat,
    linear-gradient(currentColor, currentColor) right top / 10px 2px no-repeat,
    linear-gradient(currentColor, currentColor) right top / 2px 10px no-repeat,
    linear-gradient(currentColor, currentColor) left bottom / 10px 2px no-repeat,
    linear-gradient(currentColor, currentColor) left bottom / 2px 10px no-repeat,
    linear-gradient(currentColor, currentColor) right bottom / 10px 2px no-repeat,
    linear-gradient(currentColor, currentColor) right bottom / 2px 10px no-repeat;
}

.ws-reticle::before,
.ws-reticle::after {
  content: '';
  position: absolute;
  background: currentColor;
  opacity: 0.8;
}

.ws-reticle::before { left: 50%; top: 25%; bottom: 25%; width: 2px; margin-left: -1px; }
.ws-reticle::after { top: 50%; left: 25%; right: 25%; height: 2px; margin-top: -1px; }

.ws-reticle.is-locked { color: var(--pal-red); }

.ws-reticle__lock {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 5px);
  transform: translateX(-50%);
  font-family: var(--font-game);
  font-size: 0.5rem;
  color: var(--pal-red);
  background: rgba(0, 0, 0, 0.6);
  padding: 2px 5px;
  white-space: nowrap;
  display: none;
}

.ws-reticle.is-locked .ws-reticle__lock {
  display: block;
  animation: blink 0.4s steps(1) infinite;
}

/* Canopy glass: glare streak only — scanlines/flicker come from the
   cabinet-wide .machine__crt::after overlay now */
/* SEA SHELL INCIDENT — shells tumble down the WINDSHIELD (the game view), not the HUD.
   z-index 6 puts them over the water/boats but under the canopy, and pointer-events are
   off so they can't block targeting. Everything is removed after the burst. */
.ws-shell {
  position: absolute;
  z-index: 6;
  pointer-events: none;
  height: auto;
  image-rendering: auto;
  filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.45));
  animation: ws-shell-incoming cubic-bezier(0.45, 0.05, 0.75, 0.6) forwards;
  transform-origin: 50% 50%;
  will-change: transform, opacity;
}

/* Shells are FIRED AT the cockpit: each one starts tiny near the horizon and rushes
   outward past the viewer, growing as it comes. --dx/--dy (px, set in JS) aim it at its
   own patch of glass so the whole sky, ocean and land get covered. All distances are px
   because translate percentages resolve against the ELEMENT, not the windshield. */
/* Each shell is parked on its own grid cell and RUSHES IN to fill it: it starts tiny,
   offset back toward the centre of the glass (--fromX/--fromY), then slams out to full
   size in place. Because every cell is claimed, the finished swarm covers the entire
   windshield — sky, ocean and land — rather than clumping in the middle. They hold at
   full size together, then fade as one. */
@keyframes ws-shell-incoming {
  0%   {
    transform: translate(var(--fromX, 0px), var(--fromY, 0px))
               scale(0.06) rotate(calc(var(--spin, 180deg) * -1));
    opacity: 0;
  }
  6%   { opacity: 1; }
  12%  {   /* rushed in fast (~0.6s at the 5s duration), then HELD for the rest */
    transform: translate(0, 0) scale(var(--scale, 1.6)) rotate(0deg);
    opacity: 1;
  }
  92%  {
    transform: translate(0, 0) scale(var(--scale, 1.6)) rotate(0deg);
    opacity: 1;
  }
  100% {
    transform: translate(0, 0) scale(calc(var(--scale, 1.6) * 1.08)) rotate(0deg);
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ws-shell { animation-duration: 0.01s !important; }
}

.ws-canopy {
  position: absolute;
  inset: 0;
  z-index: 7;
  pointer-events: none;
  background:
    linear-gradient(115deg, rgba(255, 255, 255, 0.09) 0 10%, transparent 10.5%);
}

@keyframes crt-flicker {
  0%, 100% { opacity: 1; }
  7% { opacity: 0.95; }
  8% { opacity: 1; }
  44% { opacity: 0.97; }
  45% { opacity: 1; }
  79% { opacity: 0.94; }
  80% { opacity: 1; }
}

/* Kash's kamikaze jet streaking across the glass */
.ws-jet {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 6;
  pointer-events: none;
  will-change: transform;
  image-rendering: pixelated;
  filter: drop-shadow(0 0 4px rgba(0, 0, 0, 0.5));
}

.ws-jet img { display: block; image-rendering: pixelated; }

/* Dashboard radar panel — big center RADAR:1 grid, Konami style */
.dash-radar {
  position: relative;
  flex: 1 1 auto;
  min-height: 56px;
  border: 2px solid rgba(0, 255, 133, 0.75);
  background:
    repeating-linear-gradient(0deg, transparent 0 9px, rgba(0, 255, 133, 0.16) 9px 10px),
    repeating-linear-gradient(90deg, transparent 0 9px, rgba(0, 255, 133, 0.16) 9px 10px),
    #06120a;
  overflow: hidden;
}

.dash-radar::before {
  content: 'RADAR:1';
  position: absolute;
  top: 2px;
  left: 4px;
  z-index: 2;
  font-family: var(--font-game);
  font-size: 0.42rem;
  color: rgba(0, 255, 133, 0.9);
  text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.8);
}

.ws-hud__self {
  position: absolute;
  bottom: 4px;
  left: 50%;
  width: 6px;
  height: 6px;
  margin-left: -3px;
  background: var(--pal-teal);
}

.ws-blip {
  position: absolute;
  width: 5px;
  height: 5px;
  background: var(--pal-green);
  box-shadow: 0 0 4px rgba(0, 255, 133, 0.7);
}

.ws-blip--civ {
  background: #f8f8f8;
  opacity: 0.85;
  box-shadow: none;
}

/* Floating score popups (+100 / -500) */
.ws-pop {
  position: absolute;
  z-index: 8;
  font-family: var(--font-game);
  font-size: 0.7rem;
  pointer-events: none;
  text-shadow: 1px 1px 0 #000;
  animation: ws-pop 1s steps(6) forwards;
}

.ws-pop--good { color: var(--pal-yellow); }
.ws-pop--bad { color: var(--pal-red); font-size: 0.85rem; }

@keyframes ws-pop {
  from { transform: translateY(0); opacity: 1; }
  to { transform: translateY(-36px); opacity: 0; }
}

/* Carrier-landing cutscene — every warrior's final approach onto the deck */
/* Carrier-landing cutscene: a flipbook of pre-composed frames laid over the whole
   windshield (game.js carrierLanding swaps .ws-land-frame's src on a timeline). It sits
   above the game view + canopy so nothing shows through. The APPROACH frames are
   transparent → the game's sky/ocean reads behind the carrier + descending jet; the
   is-deck close-up switches on a sunset backdrop. */
.ws-land-scene {
  position: absolute;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.ws-land-scene.is-deck {
  background:
    linear-gradient(180deg, #ffe6a0 0%, #ffbf63 34%, #f3853a 52%, #b6461a 66%, #4a2110 82%, #241009 100%);
}
.ws-land-frame {
  width: 100%;
  height: 100%;
  object-fit: contain;
  image-rendering: auto;   /* smooth downscale of the 1600×1200 art */
}

.ws-carrier {
  position: absolute;
  left: 50%;
  top: 36%;
  z-index: 3;
  transform: translate(-50%, -14%) scale(0.12);
  transition: transform 4.2s ease-in, top 4.2s ease-in;
  image-rendering: pixelated;
}

.ws-carrier.is-approach {
  top: 50%;
  transform: translate(-50%, -14%) scale(1.45);
}

/* Blinking red guidance bracket hovering over the touchdown point */
.ws-landing-guide {
  position: absolute;
  left: 50%;
  top: 39%;
  width: 38px;
  height: 30px;
  margin: -15px 0 0 -19px;
  z-index: 8;
  pointer-events: none;
  color: var(--pal-red);
  filter: drop-shadow(0 0 3px rgba(247, 24, 36, 0.7));
  background:
    linear-gradient(currentColor, currentColor) left top / 10px 3px no-repeat,
    linear-gradient(currentColor, currentColor) left top / 3px 10px no-repeat,
    linear-gradient(currentColor, currentColor) right top / 10px 3px no-repeat,
    linear-gradient(currentColor, currentColor) right top / 3px 10px no-repeat,
    linear-gradient(currentColor, currentColor) left bottom / 10px 3px no-repeat,
    linear-gradient(currentColor, currentColor) left bottom / 3px 10px no-repeat,
    linear-gradient(currentColor, currentColor) right bottom / 10px 3px no-repeat,
    linear-gradient(currentColor, currentColor) right bottom / 3px 10px no-repeat;
  transition: top 4.2s ease-in;
  animation: blink 0.5s steps(1) infinite;
}

.ws-landing-guide.is-approach {
  top: 56%;
}

.ws-landing-guide::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 14px;
  height: 14px;
  transform: translate(-50%, -50%);
  background: repeating-linear-gradient(0deg, currentColor 0 2px, transparent 2px 6px);
}

/* Touchdown jolt when the hook catches the wire */
.windshield.ws-jolt {
  animation: ws-jolt 0.5s steps(4);
}

@keyframes ws-jolt {
  0% { transform: translateY(0); }
  25% { transform: translateY(5px); }
  50% { transform: translateY(-2px); }
  75% { transform: translateY(3px); }
  100% { transform: translateY(0); }
}

.ws-landing-label {
  position: absolute;
  left: 50%;
  top: 34%;
  transform: translate(-50%, -50%);
  z-index: 9;
  font-family: var(--font-game);
  font-size: clamp(0.8rem, 2.5vw, 1.1rem);
  color: var(--pal-yellow);
  text-shadow: 2px 2px 0 #000;
  animation: blink 0.8s steps(1) infinite;
}

/* In-flight shots, kash dollar, explosions, splashes (spawned by JS) */
.ws-sprite {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 5;
  pointer-events: none;
  will-change: transform;
  image-rendering: pixelated;
}

.ws-sprite img {
  display: block;
  image-rendering: pixelated;
}

.ws-sprite--spin img {
  animation: ws-spin 0.45s linear infinite;
}

@keyframes ws-spin {
  to { transform: rotate(360deg); }
}

.ws-puff {
  position: absolute;
  width: 5px;
  height: 5px;
  background: #f0f0f0;
  z-index: 4;
  pointer-events: none;
  animation: ws-puff 0.45s steps(3) forwards;
}

@keyframes ws-puff {
  0% { opacity: 0.85; transform: scale(1); }
  100% { opacity: 0; transform: scale(2.2); }
}

/* NES flower-burst explosion: a 3-frame sprite img swapped by boomFx() */
.ws-boom {
  position: absolute;
  width: 52px;
  height: 52px;
  z-index: 6;
  pointer-events: none;
  image-rendering: pixelated;
}

/* Star Fox-style comms portrait, set in the dashboard chrome */
.comms {
  display: flex;
  gap: 6px;
  align-items: center;
  margin: 0;
  padding: 3px 7px;
  min-height: 0;
  flex: 0 1 auto;
  overflow: hidden;
  background: linear-gradient(180deg, #1c0d05, #0d0503);
  border: 2px solid var(--pal-orange);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 8px), calc(100% - 8px) 100%, 0 100%);
  transition: border-color 0.2s;
}

.comms.is-pop { animation: comms-pop 0.3s ease; }
.comms.is-hit { border-color: var(--pal-red); }
.comms.is-alert { border-color: var(--pal-yellow); }

.comms__portrait {
  position: relative;
  width: 30px;
  height: 30px;
  flex: none;
  background: var(--crt-black);
  border: 1px solid var(--gold);
  overflow: hidden;
}

.comms__portrait img {
  width: 100%;
  height: 100%;
  display: block;
  image-rendering: pixelated;
}

.comms__portrait::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(0deg, transparent 0 2px, rgba(0, 0, 0, 0.28) 2px 3px);
}

.comms__name {
  font-family: var(--font-game);
  font-size: 0.5rem;
  color: var(--pal-yellow);
  min-width: 0;
  overflow-wrap: anywhere;
}

.comms.is-hit .comms__name { color: var(--pal-red); }

@keyframes comms-pop {
  0% { transform: scale(1); }
  40% { transform: scale(1.04); }
  100% { transform: scale(1); }
}

/* ===== COCKPIT DASHBOARD — full-width bottom strip: LEDs left, RADAR center,
   MISSILE REMAIN + mission stats right. Army-issue grey/green metal. */
.cockpit-dash {
  position: relative;   /* the co-pilot call-in floats ON TOP of this HUD */
  flex: 1 1 30%;
  min-height: 150px;
  max-height: 240px;
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr) minmax(0, 1.05fr);
  gap: 1cqw;
  padding: 1.2cqh 1cqw;
  background: linear-gradient(180deg, #5c6552, #454d3e 55%, #2c3128);
  border-top: 3px solid #1c211a;
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.18),
    inset 0 -3px 0 rgba(0, 0, 0, 0.4);
  /* A query container so every instrument sizes to the DASH itself (cqh/cqw),
     which is what makes the type legible AND responsive — the dash is a fixed-ish
     strip, so sizing off the whole CRT (huge) overflowed it or left text tiny. */
  container-type: size;
}

.dash-left,
.dash-center,
.dash-right {
  display: flex;
  flex-direction: column;
  gap: 1.4cqh;
  min-width: 0;
  min-height: 0;
  overflow: hidden; /* instruments never spill out of the chrome */
}

/* CENTER: a single speedometer dial with two needles (ALT + SPD), like a car
   gauge. The SVG scales to fill the center column; needles rotate from game.js. */
.dash-dial {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.6cqh;
  background: #0a0f0a;
  border: 1px solid rgba(0, 0, 0, 0.75);
  padding: 1cqh 1cqw;
}

.dash-dial__face {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  height: 100%;
  display: block;
}

.dash-dial__arc {
  fill: none;
  stroke: rgba(0, 255, 133, 0.5);
  stroke-width: 2.5;
}

.dash-dial__ticks line {
  stroke: rgba(0, 255, 133, 0.45);
  stroke-width: 1.6;
}

/* Needles pivot at the dial centre (60,60 in the viewBox); game.js sets rotate(). */
.dash-dial__needle {
  transform-box: view-box;
  transform-origin: 60px 60px;
  transition: transform 0.28s ease-out;
  stroke-linecap: round;
}
.dash-dial__needle--alt { stroke: var(--pal-teal); stroke-width: 2.4; }
.dash-dial__needle--spd { stroke: var(--pal-green); stroke-width: 3.4; }
.dash-dial__hub { fill: #d8d3c6; }

.dash-dial__legend {
  flex: 0 0 auto;
  display: flex;
  gap: 3cqw;
  font-family: var(--font-game);
  font-size: 5.5cqh;
}

.dash-dial__key { display: inline-flex; align-items: center; gap: 0.8cqw; }
.dash-dial__key::before {
  content: '';
  width: 2cqh;
  height: 2cqh;
  display: inline-block;
}
.dash-dial__key--alt { color: var(--pal-teal); }
.dash-dial__key--alt::before { background: var(--pal-teal); }
.dash-dial__key--spd { color: var(--pal-green); }
.dash-dial__key--spd::before { background: var(--pal-green); }

/* Missile status readout at the top of the speedometer housing (set from game.js).
   Tells the player when they can fire: LOADING (yellow) while the missile arms, ARMED
   (green, pulsing) when the trigger's live, JAMMED (red) for 1.5s after a target slips
   past. A blocked-fire attempt slams it red-on-black for a beat. */
.dash-missile {
  flex: 0 0 auto;
  align-self: stretch;          /* span the full width of the speedometer column */
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-family: var(--font-game);
  font-size: 4.2cqh;
  letter-spacing: 0.05em;
  line-height: 1;
  padding: 0.8cqh 1cqw;         /* a little taller than the text so it reads as a bar */
  border: 1px solid rgba(0, 0, 0, 0.75);
  background: #0a0f0a;
  white-space: nowrap;
  overflow: hidden;
}
.dash-missile.is-loading {
  background: var(--pal-yellow);
  color: #000;
  text-shadow: none;
}
.dash-missile.is-armed {
  color: var(--pal-green);
  text-shadow: 0 0 8px rgba(0, 255, 133, 0.5);
  animation: missile-pulse 0.9s ease-in-out infinite;
}
.dash-missile.is-jammed {
  color: var(--pal-red);
  text-shadow: 0 0 8px rgba(247, 24, 36, 0.55);
}
/* Blocked fire: red background, black text, a hard blink for a beat (JS strips it) */
.dash-missile.is-blocked {
  background: var(--pal-red);
  color: #000;
  text-shadow: none;
  animation: missile-blink 0.16s steps(1) 3;
}

@keyframes missile-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.4; }
}
@keyframes missile-blink {
  0%, 100% { background: var(--pal-red); color: #000; }
  50%      { background: #0a0f0a; color: var(--pal-yellow); }
}

/* Replaces the speedometer dial during the carrier-landing approach (set in
   carrierLanding). Green pixel readout on the dial's dark panel — sized in cqh so it
   stays legible + fills the centre column. */
.dash-landing {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  background: #0a0f0a;
  border: 1px solid rgba(0, 0, 0, 0.75);
  font-family: var(--font-game);
  font-size: 7cqh;
  line-height: 1.6;
  letter-spacing: 0.04em;
  color: var(--pal-green);
  text-shadow: 0 0 8px rgba(0, 255, 133, 0.45);
  padding: 1cqh 1cqw;
}

.dash-gauges {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  gap: 4px;
}

/* Warrior comms panel: portrait on the left, name + spoken reply beside it —
   the pilot's voice reads RIGHT HERE, Star Fox style. */
.dash-pilot {
  flex: 1 1 auto;
  min-height: 0;
  position: relative;
  flex-direction: row;
  align-items: stretch;
  gap: 5px;
  padding: 4px;
}

.dash-pilot .comms__portrait {
  width: auto;
  height: auto;
  flex: 0 0 auto;
  min-height: 0;
  aspect-ratio: 1;
}

.dash-pilot .comms__portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.comms__side {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.dash-pilot .comms__name {
  font-size: 6cqh;
  line-height: 1.3;
  color: var(--pal-yellow);
}

/* The pilot's spoken line — the primary commentary, now LARGE and legible. */
.comms__line {
  flex: 1 1 auto;
  min-height: 0;
  font-family: var(--font-data);
  font-size: 10.5cqh;
  line-height: 1.25;
  color: var(--pal-teal);
  background: #0a0f0a;
  border: 1px solid rgba(0, 0, 0, 0.7);
  padding: 0.8cqh 1cqw;
  /* NO fixed line-clamp. It used to clamp to 3 lines regardless of the box's real
     height, so on a narrow phone (~8 chars a line) dialogue truncated with an ellipsis
     while two-thirds of the box sat empty. The line now fills whatever height it has,
     and game.js pages through anything that STILL doesn't fit (setLineText) so text is
     never clipped and the arcade never scrolls. */
  overflow: hidden;
}

.dash-pilot.is-hit .comms__line { color: var(--pal-green); }
.dash-pilot.is-alert .comms__line { color: var(--pal-coral); }

/* Co-pilot call-in (Kash / Miller) — an EXACT twin of .comms.dash-pilot (it reuses
   those classes for all styling). This rule only floats it flush ON TOP of the HUD
   in the bottom-left of the game view, and slides it in from the left. Its exact
   size + left are copied from the live pilot box in JS (syncCopilotSize); the cq
   values below are only first-frame fallbacks. */
.dash-copilot {
  position: absolute;
  left: 1cqw;                  /* JS overrides to the pilot box's exact left */
  bottom: calc(100% + 3px);    /* bottom sits flush on the dash's top edge (clears its 3px border) */
  width: 37cqw;                /* JS overrides; ≈ the dash-left column */
  height: 79cqh;               /* JS overrides; ≈ the flex-stretched pilot box */
  margin: 0;
  z-index: 6;
  pointer-events: none;        /* never intercept targeting over the game view */
  transform: translateX(-110%);
  transition: transform 0.3s steps(5), border-color 0.2s;
}

.dash-copilot.is-in { transform: translateX(0); }
.dash-copilot[hidden] { display: none; }

/* Fuel bar: F at the top, E at the bottom — fuel is your ammo fraction */
/* Fuel gauge: F/E overlay the track ends, the green bar rises from the bottom by
   the ammo fraction (game.js sets its % height). overflow:hidden keeps the fill
   inside the track so it can't push the instrument cluster taller. */
.dash-fuel {
  flex: 0 0 7cqw;
  max-width: 30px;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  background: #0a0f0a;
  border: 1px solid rgba(0, 0, 0, 0.75);
  overflow: hidden;
}

.dash-fuel span {
  position: absolute;
  left: 0;
  right: 0;
  text-align: center;
  z-index: 2;
  font-family: var(--font-game);
  font-size: 5.5cqh;
  color: var(--pal-cream);
  text-shadow: 0 0 2px #000, 0 1px 1px #000;
}

.dash-fuel span:first-of-type { top: 0.6cqh; }
.dash-fuel span:last-of-type { bottom: 0.6cqh; }

/* Full-height track fill, scaled from the bottom by the fuel fraction. Using
   transform: scaleY (set by game.js) instead of a % height — a % height wouldn't
   resolve here (the dash is a size-containment container). */
.dash-fuel i {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 21%;
  right: 21%;
  transform-origin: bottom;
  transform: scaleY(1);
  background: var(--pal-green);
  box-shadow: 0 0 5px rgba(0, 255, 133, 0.5);
  transition: transform 0.3s steps(4);
}

.dash-center {
  position: relative;
  flex-direction: column;   /* status bar on TOP, the gauges (dial + fuel) row BELOW */
  gap: 1cqh;
  align-items: stretch;
}

/* The speedometer dial + fuel gauge, side by side, filling the space under the bar. */
.dash-gauges {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  gap: 1cqw;
  align-items: stretch;
}

/* Mission stats stacked in the dash-right deadspace under the ammo pips */
.dash-stats {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  overflow: hidden;
}

.dash-stat {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1cqw;
  padding: 0.4cqh 1cqw;
  background: #0a0f0a;
  border: 1px solid rgba(0, 0, 0, 0.75);
  font-family: var(--font-game);
  font-size: 6.5cqh;
  color: var(--pal-cream);
  white-space: nowrap;
  min-height: 0;
}

.dash-stat b {
  font-weight: normal;
  font-size: 8cqh;
  color: var(--pal-green);
  text-shadow: 0 0 0.8cqh rgba(0, 255, 133, 0.55);
}

.dash-stat--lives b {
  color: var(--pal-red);
  text-shadow: 0 0 5px rgba(247, 24, 36, 0.55);
}

/* MISSILE REMAIN readout — big green digits, NES Top Gun style */
.dash__remain {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas:
    'label count'
    'type count';
  gap: 0 var(--space-sm);
  align-items: center;
  background: #0a0f0a;
  border: 1px solid rgba(0, 0, 0, 0.75);
  padding: 4px 9px;
  margin-top: 0;
}

.dash__remain-label {
  grid-area: label;
  font-family: var(--font-game);
  font-size: 6.5cqh;
  color: var(--pal-coral);
  text-shadow: 0 0 4px rgba(247, 24, 36, 0.4);
}

.dash__remain-type {
  grid-area: type;
  font-family: var(--font-game);
  font-size: 6.5cqh;
  color: var(--pal-cream);
}

.dash__remain-type b {
  color: var(--pal-green);
  font-weight: normal;
}

.dash__remain-count {
  grid-area: count;
  font-family: var(--font-game);
  font-size: 26cqh;
  color: var(--pal-green);
  text-shadow: 0 0 1.4cqh rgba(0, 255, 133, 0.6);
}

/* Ammo pips: one pixel missile per round */
.dash__ammo {
  display: flex;
  flex-wrap: wrap;
  gap: 3px;
  margin-top: 0;
  flex: none;
}

.dash__ammo span {
  width: 5px;
  height: 14px;
  background: var(--pal-green);
  box-shadow: inset 0 -4px 0 rgba(0, 0, 0, 0.45), inset 0 2px 0 rgba(255, 255, 255, 0.35);
}

.dash__ammo span.is-spent {
  background: #2c342c;
  box-shadow: none;
}

.panel__section {
  margin-bottom: var(--space-lg);
}

.panel__title {
  font-family: var(--font-game);
  font-size: 0.55rem;
  color: var(--pal-yellow);
  margin-bottom: var(--space-sm);
  padding-bottom: var(--space-xs);
  border-bottom: 1px solid var(--gold);
}

.panel__stat {
  display: flex;
  justify-content: space-between;
  padding: var(--space-xs) 0;
  font-family: var(--font-data);
  font-size: 0.8rem;
}

.panel__stat-label {
  color: var(--text-muted);
}

.panel__stat-value {
  color: var(--pal-teal);
  font-weight: 700;
}

.panel__stat--highlight .panel__stat-value {
  color: var(--pal-red);
}

.panel__feed {
  max-height: 200px;
  overflow-y: auto;
  font-family: var(--font-data);
  font-size: 0.75rem;
  color: var(--text-muted);
}

.panel__feed-item {
  padding: var(--space-xs) 0;
  border-bottom: 1px solid rgba(75, 93, 35, 0.3);
}

.panel__feed-item--hit {
  color: var(--pal-red);
}

.panel__feed-item--miss {
  color: rgba(150, 150, 150, 0.6);
}

/* --- HIT MODAL: overlays the CRT rectangle itself, arcade-style --- */
.modal {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.88);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 30;
  padding: var(--space-sm) var(--space-md);
}

.modal[hidden] {
  display: none;
}

.modal__content {
  border: 3px solid var(--pal-red);
  background: var(--bg-card);
  max-width: 560px;
  max-height: 100%;
  width: 100%;
  padding: var(--space-md);
  text-align: center;
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-sm);
  animation: modal-in 0.3s ease;
  overflow: hidden;
  /* A query container so modal text can size to THIS box (cqw), not the whole CRT
     — keeps big type proportional to the 560px panel and never overflows it. */
  container-type: inline-size;
}

@keyframes modal-in {
  from { transform: scale(0.9); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

/* The Lolita Express strike: a photo flashes over the CRT (sized like the hit modal)
   BEFORE the modal opens — fade in 1.5s, hold ~3s, fade out 1.5s (6s total; the timer in
   lolitaReaction() must match). Sits just under the modal's z-index. */
.lolita-flash {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 29;
  padding: var(--space-sm) var(--space-md);
  background: rgba(0, 0, 0, 0.88);
  pointer-events: none;
  animation: lolita-flash 6s ease-in-out forwards;
}
.lolita-flash img {
  width: 100%;
  max-width: 560px;
  max-height: 100%;
  object-fit: contain;
}
@keyframes lolita-flash {
  0%   { opacity: 0; }
  25%  { opacity: 1; }   /* faded in by ~1.5s */
  75%  { opacity: 1; }   /* held through ~4.5s */
  100% { opacity: 0; }   /* faded out by 6s */
}

/* Modal type scales with the box (cqw of .modal__content), with clamp floors so
   it stays readable on a small CRT and doesn't balloon on a big one. */
.modal__stars {
  font-family: var(--font-game);
  font-size: clamp(0.5rem, 2.65cqw, 1.15rem);
  color: var(--gold);
  letter-spacing: 0.1em;
}

.modal__headline {
  font-family: var(--font-game);
  font-size: clamp(0.7rem, 3.3cqw, 1.5rem);
  line-height: 1.45;
  color: var(--pal-red);
}

/* Both hit modals show the subtext punchline (there's room now that the "TARGET
   NEUTRALIZED" header and the fentanyl row are gone). */
.modal__subtext {
  display: block;
  font-size: clamp(0.62rem, 2.5cqw, 1.05rem);
  line-height: 1.4;
  color: var(--text-muted);
  margin: 0 0 var(--space-sm);
}

/* The strike-result modal fills ~75% of the CRT so its type can be large and readable.
   Narrower panel so the label/value rows aren't stretched apart. The type multipliers
   below are raised to compensate: everything here is cqw OF THIS BOX, so shrinking the
   box would otherwise shrink the text with it. */
#hit-modal .modal__content {
  width: 70cqw;
  max-width: 860px;
}

.modal__math {
  background: var(--crt-black);
  border: 1px solid var(--mil-green);
  padding: 1.2cqw 1.8cqw;
  text-align: left;
  font-family: var(--font-data);
  font-size: clamp(0.72rem, 3.55cqw, 1.6rem);
  color: var(--pal-teal);
  overflow-y: auto;
  min-height: 0;
}

.modal__math-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: 3px 0;
  border-bottom: 1px solid rgba(75, 93, 35, 0.3);
}

.modal__math-label {
  color: var(--text-muted);
}

.modal__math-value {
  color: var(--pal-green);
  text-align: right;
}

.modal__math-value--bad {
  color: var(--pal-red);
}

/* Same treatment as the ending screen's NEW MISSION / LEARN MORE: inherit .btn's font
   and padding untouched, so every button in the cabinet reads at one size. It used to
   set its own cqw font + padding, which blew it up far larger than the rest. */
.modal__close {
  align-self: center;
}

/* --- GAME FX: banners, flying projectiles, Kash Crash --- */
.fx-banner {
  position: fixed;
  inset: 0;
  z-index: 2500;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
  text-align: center;
  pointer-events: none;
  animation: fx-fade 1.9s forwards;
}

.fx-banner--victory {
  animation-duration: 2.6s;
}

.fx-banner__big {
  font-family: var(--font-game);
  font-size: clamp(2rem, 11vw, 5.5rem);
  line-height: 1.2;
  color: var(--pal-yellow);
  background: rgba(0, 0, 0, 0.55);
  border: 5px solid var(--pal-yellow);
  padding: 0.2em 0.35em;
  text-shadow: 6px 6px 0 var(--pal-red), 0 0 30px rgba(253, 201, 25, 0.6);
  animation: fx-slam 0.5s cubic-bezier(0.2, 1.5, 0.3, 1) both;
}

.fx-banner--victory .fx-banner__big {
  color: var(--gold-light);
  border-color: var(--gold);
  text-shadow: 4px 4px 0 var(--gold), 0 0 30px rgba(253, 201, 25, 0.85);
}

.fx-banner__sub {
  font-family: var(--font-game);
  font-size: clamp(0.55rem, 2.2vw, 0.95rem);
  line-height: 1.9;
  color: var(--pal-white);
  background: rgba(0, 0, 0, 0.78);
  border: 1px solid var(--gold);
  padding: 0.7em 1em;
  max-width: min(90%, 640px);
  animation: fx-pop 0.4s 0.25s both;
}

/* Portrait dropped into a banner (Kash appears with his crash) */
.fx-banner__img {
  width: clamp(90px, 16vw, 150px);
  height: auto;
  filter: drop-shadow(0 4px 14px rgba(0, 0, 0, 0.65));
  animation: fx-slam 0.5s cubic-bezier(0.2, 1.5, 0.3, 1) both;
}

@keyframes fx-slam {
  0%   { transform: scale(3) rotate(-7deg); opacity: 0; }
  60%  { transform: scale(1) rotate(2deg); opacity: 1; }
  100% { transform: scale(1) rotate(0); opacity: 1; }
}

@keyframes fx-pop {
  from { transform: translateY(14px); opacity: 0; }
  to   { transform: translateY(0); opacity: 1; }
}

@keyframes fx-fade {
  0%, 82% { opacity: 1; }
  100%    { opacity: 0; }
}

/* ===== SCREEN 3: STRIKE ZONE MAP ===== */
.screen--map {
  justify-content: center;
  padding: var(--space-sm) var(--space-md);
  gap: var(--space-sm);
}

.screen--map__title {
  /* cqmin (smaller of the CRT's w/h) so it scales with the cabinet on any screen,
     and stays a step larger than the selected mission name below. */
  font-size: 5cqmin;
  margin-bottom: 0;
}

.map__frame {
  position: relative;
  flex: 0 0 auto;
  /* A SQUARE sized to fit both the CRT width and height, so the square map image
     fills it exactly (no letterbox) and the percentage pins line up with it.
     Width AND height are set to the same value — relying on aspect-ratio alone
     let the flex column stretch the height a few px, which re-introduced the
     letterbox/pin drift this screen exists to avoid. */
  width: min(94cqw, 57cqh);
  height: min(94cqw, 57cqh);
  margin: 0 auto;
}

.map__img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  image-rendering: auto;
}

.map__pins {
  position: absolute;
  inset: 0;
}

/* Blinking bracket cursor pins — same corner-bracket trick as the reticle */
.map-pin {
  position: absolute;
  width: 30px;
  height: 30px;
  margin: -15px 0 0 -15px;
  background:
    linear-gradient(currentColor, currentColor) left top / 9px 2px no-repeat,
    linear-gradient(currentColor, currentColor) left top / 2px 9px no-repeat,
    linear-gradient(currentColor, currentColor) right top / 9px 2px no-repeat,
    linear-gradient(currentColor, currentColor) right top / 2px 9px no-repeat,
    linear-gradient(currentColor, currentColor) left bottom / 9px 2px no-repeat,
    linear-gradient(currentColor, currentColor) left bottom / 2px 9px no-repeat,
    linear-gradient(currentColor, currentColor) right bottom / 9px 2px no-repeat,
    linear-gradient(currentColor, currentColor) right bottom / 2px 9px no-repeat;
  border: 0;
  color: var(--pal-yellow);
  cursor: pointer;
  filter: drop-shadow(0 0 3px currentColor);
}

.map-pin::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 6px;
  height: 6px;
  margin: -3px 0 0 -3px;
  background: currentColor;
}

.map-pin[aria-checked="true"] {
  color: var(--pal-red);
  animation: blink 0.6s steps(1) infinite;
}

.map-pin:focus-visible {
  outline: 2px solid var(--pal-atomic-purple);
  outline-offset: 3px;
}

.map__readout {
  flex: 0 0 auto;
  /* Matches the map frame's width (same min() as .map__frame) so the panel lines
     up exactly under the map at every aspect ratio. */
  width: min(94cqw, 57cqh);
  text-align: center;
  background: #0a0f0a;
  /* The selected-zone panel wears the SAME bright-green highlight as a selected
     warrior card — border + inner/outer glow — so picking a strike zone reads the
     same as picking a warrior. */
  border: 2px solid var(--pal-green);
  box-shadow: 0 0 20px rgba(0, 255, 133, 0.35), inset 0 0 20px rgba(0, 255, 133, 0.12);
  /* Column that HUGS its content (name + optional briefing + button) and scales
     with the CRT — no fixed slab with a dead gap in the middle. */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.4cqmin;
  padding: 2.4cqmin 2.6cqmin;
}

/* The selected strike-zone name — the readout's headline. Prominent once a zone
   is chosen, sized in cqmin so it scales with the cabinet on any screen. */
.map__readout-name {
  font-family: var(--font-game);
  font-size: 4.2cqmin;
  line-height: 1.2;
  color: var(--pal-yellow);
}

.map__readout-blurb {
  font-family: var(--font-data);
  font-size: 2.7cqmin;
  line-height: 1.35;
  color: var(--pal-cream);
  max-width: 46ch;
  margin: 0;
}
/* No briefing for this zone → collapse entirely so the box stays proportional
   (no reserved empty gap between the name and the CONFIRM button). */
.map__readout-blurb:empty { display: none; }

.map__readout .btn {
  /* Sized to fit on one line inside the map-width box at any aspect ratio. */
  font-size: 2.8cqmin;
  padding: 1.4cqmin 3cqmin;
  white-space: nowrap;
}

/* ===== SCREEN 5: ENDING — vignette stage + results ===== */
.screen--ending {
  justify-content: center;   /* scene + caption + buttons ride as one centred group */
  padding: var(--space-md);
  gap: var(--space-sm);
  text-align: center;
}

/* In-flow, never an overlay: the vignette shrinks (and clips) to make room
   for the results below it once they're released. */
.vignette {
  width: 100%;
  max-width: 640px;
  margin: 0 auto;
  flex: 0 1 auto;             /* hug the scene — don't stretch to fill the CRT */
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* The bordered box IS the 4:3 scene. It used to stretch to fill the CRT with the
   scene floating in the middle, which left big dead bands inside the gold border
   (worst on a tall phone). max-height is in cqh of the CRT so a short/wide screen
   caps the height and the width follows the ratio down. */
.vignette__stage {
  position: relative;
  flex: 0 1 auto;
  align-self: center;
  /* Both caps in the WIDTH, so the ratio always holds: 100% of the column, or the
     widest a 4:3 box can be within the 72cqh height budget (72 * 4/3 = 96cqh).
     Capping via max-height alone left width:100% intact and squashed it to 1.7:1. */
  width: min(100%, 96cqh);
  aspect-ratio: 4 / 3;
  background:
    linear-gradient(180deg, #10202c 0 62%, #0c1418 62% 100%);
  border: 2px solid var(--gold);
  overflow: hidden;
  display: flex;
}

/* EVERY actor/prop/effect lives in this frame, not the stage. The stage's shape
   swings wildly (~1:1 on a laptop CRT, ~1:2 on a phone), and because the banner is
   anchored to the top while the character and podium are anchored to the bottom,
   a % layout drifted apart as that ratio changed — the podium swallowed the
   character on a laptop and the whole scene scattered on a phone. Locking the frame
   to ONE aspect means every % resolves the same everywhere: tune it once. The stage
   keeps its own background, so the leftover space reads as sky/room, not letterbox. */
.vg-frame {
  position: relative;
  width: 100%;
  height: 100%;               /* the stage is already 4:3, so just fill it */
  flex: 1 1 auto;
  /* Query container for everything drawn INSIDE the scene, so text sizes in cqw and
     scales with the art itself. Text painted into a bubble/banner must stay
     PROPORTIONAL to it — a rem or vh size would keep the box scaling while the text
     didn't, and overflow on small screens. inline-size (not size) is enough: the
     frame's ratio is fixed, so width determines height anyway. */
  container-type: inline-size;
  /* One knob for all text drawn INTO the scene (executive order + question bubbles),
     so the two stay the same size as each other. cqw = scales with the art. */
  --vg-scene-text: 4.4cqw;
}

.vignette__text {
  flex: 0 0 auto;
}

/* The action buttons sit there from the moment the ending opens — they used to be
   hidden until the vignette finished, but popping them in at the end resized the
   column and shifted the scene (same class of jump as the caption growing). */

/* Same green as the warrior-select quote (and the cockpit pilot line) so the ending
   caption reads as the same voice. The type-on cursor inherits it via currentColor. */
.vignette__text {
  font-family: var(--font-game);
  font-size: 0.62rem;
  line-height: 1.9;
  color: var(--pal-teal);
  min-height: 2.5em;
  padding: var(--space-sm) var(--space-xs) 0;
}

/* The ending caption types on with per-letter .q-char spans (a shared cursor rides
   the last revealed letter) — same effect as the warrior-select quote. */

/* Action row sits at the very bottom of the CRT, out of the vignette's way */
.ending__actions {
  margin: 0 0 var(--space-sm);   /* sits with the group, not pinned to the CRT floor */
  display: flex;
  gap: var(--space-md);
  flex-wrap: wrap;
  justify-content: center;
}

/* --- Vignette actors, props and particles --- */
.vg { position: absolute; }

img.vg {
  height: auto;
  max-width: 92%;
  max-height: 92%;
  object-fit: contain;
  image-rendering: auto;
}

/* A pre-composed ending scene (banner + podium + character already arranged in the
   art, authored 4:3 to match .vg-frame). It fills the frame exactly, so there is NO
   per-element positioning left to drift between a laptop and a phone — the whole
   composition just scales. Effects (makeup, spill, ketchup) still overlay on top,
   anchored by explicit x/y percentages in data.js. */
img.vg-scene {
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  object-fit: contain;
  opacity: 0;
  transition: opacity 0.45s ease-out;
  z-index: 1;
}

img.vg-scene.is-in { opacity: 1; }
img.vg-scene.is-out { opacity: 0; }

/* Banner pinned across the TOP of the scene, BEHIND the characters (like the
   real carrier "Mission Accomplished" photo op — the figure stands in front of
   it). z-index sits under the actor (2) and podium (3). */
/* Sits high in the frame. The lift is a transform of the banner's OWN height
   (translateY %), NOT a % of the stage height — so the "MISSION ACCOMPLISHED"
   text lands high on both a wide laptop stage and a tall/narrow phone stage
   (a fixed top:% would shove it off the top on a short-banner phone). */

/* Banner slides back up out of frame (JD, once he leaves the podium) */

/* JD's celebratory re-unfurl: a quick horizontal wipe (unused now, kept for scenes) */

/* Two constraints at once, tuned against the fixed .vg-frame:
   WIDTH — the podium GRAPHIC is only ~53% of its padded PNG, so 44% here renders it
   about as wide as the character (sprites fill ~79–100% of theirs).
   HEIGHT — the PNG is square, so width also sets height, and the art is a full-height
   lectern while the sprites are head-and-shoulders busts. Too wide and the lectern
   swallows the character to the chin.
   BOTTOM — dropped so the lectern top sits just under the character's torso, hiding only
   the sprite's hard bottom crop instead of a fifth of his body, so he reads as standing
   BEHIND it rather than buried in it. -16% is effectively the FLOOR, for two reasons:
   (1) the sprites are cut off square at the bottom (alpha runs to the PNG edge), so the
   lectern top must stay above the character's bottom edge or that flat cut line floats
   in mid-air; at -16% the overlap is down to ~0.4% of the frame.
   (2) the presidential seal occupies 48–71.5% of the podium PNG's height and must stay
   visible — here its bottom lands ~99%. Don't lower this further without also raising
   the character or re-cropping the sprites. */

/* Podium slides back down out of frame when the presser's over */

/* The sprites are head-and-shoulders busts that end in a HARD horizontal cut (their
   alpha runs to the PNG's bottom edge), so that cut line has to be hidden by the podium
   in BOTH axes or it shows as a flat edge floating in the scene:
   WIDTH — sized so the WIDEST sprite (Marco fills 100% of his PNG) stays just inside the
   podium graphic's width, so the cut edge can't poke out either side. The narrower
   sprites (Pete .887, JD .869, DJT .791 of their PNG) sit well inside it.
   BOTTOM — puts the cut edge just BELOW the podium's top edge so the lectern covers it,
   with enough margin that it survives the smaller frame on a phone.
   NOTE these two track .vg-podium's width: how much of the character shows ABOVE the
   lectern is just his height, his height is capped by his width, and his width is capped
   by the podium's. So he can only be made taller/higher by widening the podium too —
   moving him up alone just exposes the cut edge. */
.vg-actor {
  left: 50%;
  bottom: 32.5%;
  width: 27.5%;
  transform: translate(-50%, 150%);
  transition: transform 0.6s ease-out;
  z-index: 2;
}

.vg-actor.is-in { transform: translate(-50%, 0); }

/* Pete's post-makeup reveal gets a little star-power flash */
.vg-actor.is-glam {
  animation: vg-glam 0.6s steps(3);
}

@keyframes vg-glam {
  0% { filter: brightness(2.2) saturate(0.4); }
  100% { filter: brightness(1) saturate(1); }
}

/* Pete: powder puff dabs + blush + sparkles */
.vg-puff {
  left: 34%;
  top: 30%;
  width: 26px;
  height: 18px;
  background: #f8f8f8;
  border: 2px solid #d8d0c0;
  border-radius: 2px;
  box-shadow: 8px 14px 0 -4px #c8b090; /* little handle */
  opacity: 0;
  z-index: 4;
  transition: transform 0.25s steps(3), opacity 0.2s;
}

.vg-puff.is-dab1 { opacity: 1; transform: translate(26px, 12px); }
.vg-puff.is-dab2 { transform: translate(58px, 6px); }
.vg-puff.is-dab3 { transform: translate(40px, 22px); }
.vg-puff.is-done { opacity: 0; }

.vg-powder {
  width: 4px;
  height: 4px;
  background: #f8f0e0;
  z-index: 4;
  animation: vg-powder 0.7s steps(4) forwards;
}

@keyframes vg-powder {
  from { opacity: 0.95; transform: translateY(0); }
  to { opacity: 0; transform: translateY(14px) scale(1.6); }
}

.vg-blush {
  width: 12px;
  height: 8px;
  background: var(--pal-coral);
  opacity: 0.85;
  z-index: 4;
  animation: vg-pop-in 0.3s steps(2) both;
}

.vg-sparkle {
  width: 10px;
  height: 10px;
  z-index: 5;
  background:
    linear-gradient(#fff, #fff) 4px 0 / 2px 10px no-repeat,
    linear-gradient(#fff, #fff) 0 4px / 10px 2px no-repeat;
  animation: blink 0.5s steps(1) 3;
}

@keyframes vg-pop-in {
  from { transform: scale(0); }
  to { transform: scale(1); }
}

/* DJT press conference: the Nobel EO scrolls up behind his head like the
   MISSION ACCOMPLISHED banner, press-pool mics below, camera flashes, the
   two question bubbles, the ketchup arc, the slide. */
/* z-index 0 puts the order BEHIND the scene art (which is z-index 1). The scene is
   transparent around Trump, so the banner reads on the wall behind him instead of
   hanging in front of his face. */
.vg-eo-banner {
  left: 50%;
  top: 2%;
  width: 88%;
  transform: translate(-50%, 260%);
  background: var(--pal-offwhite);
  border: 4px solid var(--gold);
  padding: 1.1cqw 1.6cqw;
  text-align: center;
  z-index: 0;
  transition: transform 0.9s steps(8);
}

.vg-eo-banner.is-up { transform: translate(-50%, 0); }

/* The order text types itself in, cursor and all */
/* cqw of .vg-frame, so the order's type scales with the banner it sits in.
   min-height is in em, so the plate grows with the text automatically. */
.vg-eo-banner__line {
  display: block;
  min-height: 2.4em;
  font-family: var(--font-official);
  font-size: var(--vg-scene-text);
  font-weight: 700;
  line-height: 1.4;
  color: #1f1f1f;
}

/* Cursor comes from the shared .q-char.cursor span (see typing rules above). */

/* Press pool: pole mics rising from the bottom edge */
.vg-mic {
  bottom: 0;
  width: 4px;
  z-index: 5;
  background: #101820;
  animation: vg-pop-in 0.3s steps(3) backwards;
}

.vg-mic::after {
  content: '';
  position: absolute;
  top: -10px;
  left: 50%;
  transform: translateX(-50%);
  width: 12px;
  height: 12px;
  background: #2c3138;
  border: 2px solid #101820;
}

/* Camera flash pops in the press area */
.vg-flash {
  width: 14px;
  height: 14px;
  z-index: 6;
  background:
    linear-gradient(#fff, #fff) 6px 0 / 2px 14px no-repeat,
    linear-gradient(#fff, #fff) 0 6px / 14px 2px no-repeat,
    radial-gradient(circle, #fff 0 4px, rgba(255, 255, 255, 0.5) 4px 6px, transparent 6px);
  animation: vg-flash 0.35s steps(2) forwards;
}

@keyframes vg-flash {
  from { opacity: 1; transform: scale(1.3); }
  to { opacity: 0; transform: scale(0.6); }
}

/* Reporter question bubbles (user art + user copy) — big + legible */
.vg-qbox {
  top: 15%;
  width: 54%;
  z-index: 7;
  transform: scale(0);
  transition: transform 0.3s cubic-bezier(0.2, 1.4, 0.4, 1);
}

.vg-qbox--right { right: -3%; }
.vg-qbox--left { left: -3%; }

/* Mirror the left bubble's art so its tail points in toward him. Only the <img> is
   flipped — the text sits in a sibling element, so it stays readable. */
.vg-qbox--left img { transform: scaleX(-1); }

.vg-qbox.is-pop { transform: scale(1); }

.vg-qbox img {
  width: 100%;
  height: auto;
  image-rendering: auto;
}

/* Inset matches the ART's white interior (measured: it spans x 20–80%, y 30–66% of the
   PNG — the rounded shape is narrower than the image). The old 11% sides let the text
   run straight out over the bubble's right edge. */
.vg-qbox__text {
  position: absolute;
  inset: 30% 20% 34% 20%;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-family: var(--font-data);
  font-size: var(--vg-scene-text);   /* same size as the executive order (see .vg-frame) */
  font-weight: 700;
  line-height: 1.25;
  color: #1f1f1f;
}

/* The throw: ketchup arcs from Trump to the EO banner, splats ON it */
.vg-splat {
  left: 46%;
  top: 46%;
  width: 14%;
  opacity: 0;
  z-index: 5;
  transition: left 0.45s ease-in, top 0.45s ease-in, transform 0.45s linear, opacity 0.05s;
}

.vg-splat.is-throw {
  opacity: 1;
  left: 44%;
  top: 4%;
  transform: rotate(-540deg) scale(0.7);
}

.vg-splat.is-splat {
  opacity: 1;
  left: 50%;
  top: 0%;
  width: 34%;
  transform: translate(-50%, 0) rotate(0);
  transition: width 0.12s steps(2), transform 0.12s steps(2), left 0.12s steps(2), top 0.12s steps(2);
  z-index: 6;
}

/* The dismount: straight down behind the podium */
.vg-actor.is-slide {
  transition: transform 0.9s steps(7);
  transform: translate(-50%, 130%) !important;
}

.vg--jolt { animation: ws-jolt 0.45s steps(4); }

/* JD: the couch slides in from the RIGHT, then the walk, flop, wiggle */
/* left + width must stay under 100% or the couch hangs off the frame's right edge
   (it used to get clipped by the stage's overflow:hidden). */
.vg-couch {
  left: 54%;
  bottom: 6%;
  width: 42%;
  z-index: 1;
}

.vg-couch--slide {
  transform: translateX(160%);
  transition: transform 0.7s cubic-bezier(0.25, 1, 0.4, 1);
}

.vg-couch--slide.is-in { transform: translateX(0); }

/* Side-profile JD strolls in toward the couch on the right */
.vg-walker {
  left: 30%;
  bottom: 4%;
  width: 26%;
  z-index: 2;
  opacity: 0;
  transition: left 2.2s steps(11), opacity 0.2s;
}

.vg-walker.is-in { opacity: 1; }

.vg-walker.is-walking {
  animation: vg-walk-bob 0.4s steps(2) infinite;
}

@keyframes vg-walk-bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-2.5%); }
}

/* Then he's ON it: sprawled across the cushions, wiggling. Centred on the couch,
   which now spans 54–96% — so 55% + 40% wide lands him on the cushions, in frame. */
.vg-lying {
  left: 55%;
  bottom: 24%;
  width: 40%;
  z-index: 2;
  opacity: 0;
  transform: rotate(-8deg);
  transition: opacity 0.25s steps(2);
}

.vg-lying.is-on { opacity: 1; }

.vg-lying.is-wiggling {
  animation: vg-wiggle 0.5s steps(2) infinite;
}

@keyframes vg-wiggle {
  0%, 100% { transform: rotate(-8deg) translateY(0); }
  50% { transform: rotate(-11deg) translateY(-2%); }
}

@keyframes vg-tremble {
  0%, 100% { margin-left: 0; }
  50% { margin-left: 2px; }
}

.vg-actor.is-shudder { animation: vg-tremble 0.18s steps(2) 6; }

.vg-drop {
  width: 5px;
  height: 7px;
  background: #3cbcfc;
  z-index: 3;
  animation: vg-drop 0.8s steps(5) forwards;
}

@keyframes vg-drop {
  from { opacity: 1; transform: translateY(0); }
  to { opacity: 0; transform: translateY(26px); }
}

/* Instant mode (reduced-motion / tests): jump to the final tableau */
.vg--instant .vg,
.vg--instant .vg * {
  animation: none !important;
  transition: none !important;
}

/* The player-provided art is hi-res "fake pixel" style — let the browser
   smooth-scale it instead of nearest-neighbor chunking it */
.character-card__sprite img,
.comms__portrait img,
.fx-banner__img,
img.vg {
  image-rendering: auto;
}

/* --- PIXEL BACK-TO-TOP ARROW (appears after scrolling past the arcade) ---
   display:block !important beats canonical global.css's per-page-arrow hide,
   same fix as unallowablespending/settlementsifter. */
.pixel-top {
  display: none;
  position: fixed;
  right: max(1.1rem, env(safe-area-inset-right));
  /* Sits well above the footer's bottom text rows (motto / © / CLI) so it never covers them */
  bottom: calc(5.5rem + env(safe-area-inset-bottom, 0px));
  z-index: 1200;
  width: 48px;
  height: 48px;
  background: #0a0f0a;
  border: 3px solid var(--pal-green);
  color: var(--pal-green);
  cursor: pointer;
  box-shadow: 5px 5px 0 rgba(0, 0, 0, 0.65);
  image-rendering: pixelated;
}

.pixel-top.is-visible { display: block !important; }

.pixel-top:hover,
.pixel-top:focus-visible {
  background: var(--pal-green);
  color: var(--pal-black);
}

/* The arrow itself: hand-placed 5px "pixels" via box-shadow */
.pixel-top i {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 5px;
  height: 5px;
  margin: -13px 0 0 -2.5px;
  background: currentColor;
  box-shadow:
    -5px 5px 0 currentColor, 0 5px 0 currentColor, 5px 5px 0 currentColor,
    -10px 10px 0 currentColor, 0 10px 0 currentColor, 10px 10px 0 currentColor,
    0 15px 0 currentColor, 0 20px 0 currentColor;
}

/* Contact-support otter — stacks just above the back-to-top arrow and shows with it.
   Squared + green-bordered to match this page's NES look; the shared global.css styles
   it as a circle, so these local rules (style.css loads after global.css) win. */
.custy-support {
  display: none;
  position: fixed;
  right: max(1.1rem, env(safe-area-inset-right));
  bottom: calc(5.5rem + 48px + 0.6rem + env(safe-area-inset-bottom, 0px));
  z-index: 1200;
  width: 48px;
  height: 48px;
  background: #0a0f0a;
  border: 3px solid var(--pal-green);
  border-radius: 0;               /* square — overrides global.css's 50% circle */
  overflow: hidden;
  box-shadow: 5px 5px 0 rgba(0, 0, 0, 0.65);
  align-items: center;
  justify-content: center;
  text-decoration: none;
  image-rendering: pixelated;
}

.custy-support.is-visible { display: flex; }

.custy-support img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.custy-support:hover {
  transform: translate(2px, 2px);
  box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.65);
}

.custy-support:focus-visible {
  outline: 3px solid var(--pal-offwhite);
  outline-offset: 2px;
}

/* --- SITE FOOTER --- Minimal and self-contained. This replaced a shared footer that
   was injected cross-origin; the site now loads nothing from a third party. */
.site-footer {
  padding: 2rem 1.5rem 0.5rem;
  background: var(--pal-black);
  text-align: center;
}

.site-footer__line {
  max-width: 1280px;
  margin: 0 auto;
  padding-top: 1.25rem;
  border-top: 1px solid rgba(242, 241, 232, 0.15);
  font-family: 'DotGothic16', sans-serif;
  font-size: 0.8rem;
  color: rgba(218, 213, 198, 0.75);
}

/* --- RESPONSIVE --- */
@media (max-width: 767px) {
  .section {
    padding: var(--space-xl) var(--space-md) var(--space-lg);
  }

  .section--arcade {
    padding-left: var(--space-xs);
    padding-right: var(--space-xs);
  }

  /* The CRT flex-fills the cabinet at every size now (it's a query container),
     and the warrior-select reshapes itself via @container — so no fixed CRT
     height or viewport-based grid override here (those fought the container
     queries during a resize). */

  .dashboard__grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* Short screens: keep the TV close to the top/bottom edges. The CRT still
   flex-fills (no fixed height), and every screen sizes itself in container units
   off the CRT, so they scale with it automatically. */
@media (max-height: 800px) {
  .section--arcade {
    padding-top: var(--space-sm);
    padding-bottom: var(--space-sm);
  }

  .cockpit-dash { flex: 1 1 27%; min-height: 120px; max-height: 180px; }
}

/* Non-laptop screens: the speedometer DIAL + fuel gauge aren't important on a smaller
   screen, so drop those — but KEEP the missile status readout (it's functional: it tells
   the player when they can fire), moved to a full-width strip across the top of the dash.
   comms + weapons take the two columns below. CONTAINER query on the CRT, so a snapped
   desktop window (a smaller-than-laptop CRT) collapses to this too — not just phones.
   Placed after the short-screen cap so its height wins. */
@container (max-width: 820px) {
  .dash-gauges { display: none; }
  .cockpit-dash {
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
    grid-template-rows: auto 1fr;
    grid-template-areas:
      'missile missile'
      'left right';
    flex: 1 1 30%;
    min-height: 175px;
    max-height: 250px;
  }
  /* Center the status as a compact bar (speedometer-width), NOT the full HUD width. */
  .dash-center { grid-area: missile; gap: 0; overflow: visible; align-items: center; justify-content: center; }
  .dash-missile { font-size: 0.72rem; padding: 4px 12px; align-self: center; width: 52%; min-width: 160px; max-width: 320px; }
  .dash-left  { grid-area: left; }
  .dash-right { grid-area: right; }
  /* Mid-size fix: the comms line + name were sized off the dash HEIGHT (10.5cqh ≈ 24px
     here), far too big for the narrow 2-column dash. Cap them to a sane, width-based size. */
  .comms__line { font-size: clamp(0.82rem, 2.2cqw, 0.98rem); }
  .dash-pilot .comms__name { font-size: clamp(0.66rem, 1.7cqw, 0.82rem); }
  .dash__remain-count { font-size: clamp(1.4rem, 7cqw, 2.6rem); }
}

/* Phone: same 2 columns (comms | weapons), but bump the dash height for the weapons
   stack and fix the type sizes (px, stable at any phone height). */
@container (max-width: 440px) {
  .cockpit-dash {
    flex: 1 1 40%;
    min-height: 210px;
    max-height: 285px;
    /* give the weapons column more room so long stat labels (MISSILES FIRED /
       LIVES SAVED) don't clip on a phone-narrow CRT. */
    grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.12fr);
  }

  .dash__remain-count { font-size: 1.5rem; }
  .dash__remain-label,
  .dash__remain-type { font-size: 0.58rem; }
  .dash-pilot .comms__name { font-size: 0.6rem; }
  .comms__line { font-size: 0.82rem; }   /* no clamp — see base rule */
  .dash-stat { font-size: 0.54rem; line-height: 1.2; }
  .dash-stat b { font-size: 0.66rem; }
  /* The status bar font was oversized on a phone — shrink it and let it hug its text. */
  .dash-missile { font-size: 0.56rem; padding: 3px 9px; width: auto; min-width: 0; max-width: 86%; }

  /* In the narrow phone column the portrait would stretch to the full comms
     height and crowd out the pilot line — pin it small so the commentary shows. */
  .dash-pilot { align-items: flex-start; }
  .dash-pilot .comms__portrait { width: 3rem; height: 3rem; aspect-ratio: auto; }
}

/* PHONES — VIEWPORT media query (NOT the CRT container query above). The dash fonts are
   sized in cqh (% of the CRT height), which balloons on a tall phone; the container query
   is supposed to rein them in but doesn't fire reliably everywhere (e.g. Firefox mobile),
   so pin them here off the viewport instead. Also STACKS the comms box — portrait full
   width on top, then the name, then the spoken line — so the dialogue (where the jokes
   live) reads clearly instead of getting squeezed into a sliver beside the portrait. */
@media (max-width: 500px) {
  /* Comms box: stack it vertically */
  .dash-pilot {
    flex-direction: column;
    align-items: stretch;
    gap: 3px;
    padding: 4px;
  }
  .dash-pilot .comms__portrait {
    width: 100%;
    height: auto;
    aspect-ratio: 4 / 3;              /* full width, short enough to leave room for the text */
  }
  .comms__side { flex: 1 1 auto; min-height: 0; gap: 2px; }
  .dash-pilot .comms__name { font-size: 0.7rem; line-height: 1.2; }
  .comms__line { font-size: 0.82rem; line-height: 1.28; flex: 1 1 auto; }

  /* Weapons / stats column: rein in the giant cqh digits so nothing clips or jams */
  .dash__remain-count { font-size: 1.5rem; }
  .dash__remain-label,
  .dash__remain-type { font-size: 0.56rem; }
  .dash-stat { font-size: 0.52rem; line-height: 1.2; }
  .dash-stat b { font-size: 0.64rem; }

  /* Missile status bar */
  .dash-missile { font-size: 0.56rem; padding: 3px 9px; width: auto; min-width: 0; max-width: 90%; }
}

@media (max-width: 479px) {
  .dashboard__grid {
    grid-template-columns: 1fr;
  }

  .stat-card__value {
    font-size: 1rem;
  }
  /* .hero__title and .character-card__sprite size themselves in container units
     now — no fixed-px overrides that would fight the container queries. */
}

/* --- REDUCED MOTION --- */
@media (prefers-reduced-motion: reduce) {
  .blink {
    animation: none;
  }

  .dash-copilot {
    transition: none;
  }

  .dash-missile.is-armed,
  .dash-missile.is-blocked {
    animation: none;
  }

  /* No fade — the photo just shows for its window, then the modal opens. */
  .lolita-flash { animation: none; }

  .q-char.cursor::after {
    animation: none;
  }

  .ws-land {
    transition: none;
  }

  .vg-mic,
  .vg-flash {
    animation: none;
  }

  .vg-qbox,
  .vg-eo-banner,
  .vg-splat,
  .vg-actor.is-slide {
    transition: none;
  }

  .modal__content {
    animation: none;
  }

  .fx-banner__big,
  .fx-banner__sub {
    animation: none;
  }

  .ws-wave,
  .ws-cloud,
  .ws-boat img,
  .ws-canopy,
  .ws-landing-label {
    animation: none;
  }

  .machine__blip.is-on,
  .kash-callin::after,
  .map-pin[aria-checked="true"],
  .fx-banner__img,
  .machine__crt::after,
  .title-waves,
  .title-boat {
    animation: none;
  }

  .ws-reticle.is-locked .ws-reticle__lock {
    animation: none;
  }

  .ws-pop {
    animation-duration: 0.3s;
  }

  .ws-carrier,
  .ws-landing-guide {
    transition-duration: 0.5s;
  }

  .ws-landing-guide {
    animation: none;
  }

  .windshield.ws-jolt {
    animation: none;
  }

  html {
    scroll-behavior: auto;
  }
}
