/* ==========================================================================
   WASTELAND WRECKER 3D — apocalypse shell, full-viewport edition
   Adapted from the 2D game's shell (packages/site/public/game/style.css):
   same palette, hazard tape, CRT overlay and pressed-steel chips — but the
   stage fills the viewport for the chase camera instead of letterboxing a
   fixed canvas. Every selector is namespaced under body.wrecker3d.

   Palette:
     charcoal #1a1512 · rust #8a3324 · blood red #c1272d (Workhorse brand red)
     bone #e8dcc8 · hazard amber #f5a623 · chrome #b8bcc0
   ========================================================================== */

body.wrecker3d {
  /* -------- tokens -------- */
  --charcoal: #1a1512;
  --charcoal-2: #241d17;
  --charcoal-3: #0f0c0a;
  --rust: #8a3324;
  --blood: #c1272d;
  --bone: #e8dcc8;
  --bone-dim: rgba(232, 220, 200, 0.55);
  --amber: #f5a623;
  --chrome: #b8bcc0;
  --plate-edge: rgba(184, 188, 192, 0.28);

  --display: Impact, 'Arial Black', 'Franklin Gothic Bold', sans-serif;
  --body: Arial, 'Helvetica Neue', Helvetica, sans-serif;
  --mono: 'Courier New', ui-monospace, Menlo, monospace;

  --hazard: repeating-linear-gradient(
    -45deg,
    var(--amber) 0 12px,
    var(--charcoal-3) 12px 24px
  );
  --corner-cut: polygon(
    8px 0, calc(100% - 8px) 0, 100% 8px,
    100% calc(100% - 8px), calc(100% - 8px) 100%,
    8px 100%, 0 calc(100% - 8px), 0 8px
  );

  margin: 0;
  min-height: 100vh;   /* fallback first: iOS < 15.4 has no svh and drops that line */
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  background: var(--charcoal-3);
  color: var(--bone);
  font-family: var(--body);
  overscroll-behavior: none;
  -webkit-font-smoothing: antialiased;
}

body.wrecker3d :focus-visible {
  outline: 3px solid var(--amber);
  outline-offset: 2px;
}

body.wrecker3d .hidden { display: none !important; }

/* ==========================================================================
   Stage — the 3D viewport fills everything above the CTA
   ========================================================================== */

body.wrecker3d #stage3d {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  background: var(--charcoal);
  border-bottom: 2px solid var(--plate-edge);
}

body.wrecker3d #game3d-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none;
}

/* ==========================================================================
   Display type
   ========================================================================== */

body.wrecker3d h1,
body.wrecker3d h2,
body.wrecker3d .display {
  font-family: var(--display);
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  line-height: 0.92;
  margin: 0;
  color: var(--bone);
  text-shadow:
    0 0.06em 0 #000,
    0 0 22px rgba(193, 39, 45, 0.3);
}

/* THE TITLE FACE — "Astron Boy Video" by Ray Larabie (Larabie Fonts, © 2000, freeware —
   free for commercial use; larabiefonts.com / typodermic.com; donations encouraged).
   Shipped as a 6.8 KB SUBSET woff2 (A-Z 0-9 + minimal punctuation, the conservative
   reading of freeware terms — a title lockup, not a redistributed font). Visitors don't
   have system fonts, so embedding is the only way a named face reaches the title screen;
   the ?v= byte stamp is the same cache discipline as every asset. TITLE ONLY: body copy
   stays on the display stack — a chrome video-game face at paragraph size is noise. */
@font-face {
  font-family: 'Astron Boy Video';
  src: url('fonts/astron-boy-video.woff2?v=6848') format('woff2');
  font-display: swap;
}

body.wrecker3d #title3d h1 {
  font-family: 'Astron Boy Video', var(--display);
  font-weight: 400;            /* the face is single-weight; faux-bold smears its chrome */
  letter-spacing: 0.06em;
  line-height: 1.02;           /* its caps carry built-in speed-lines; give them air */
}

body.wrecker3d h1 em,
body.wrecker3d h1 .accent {
  font-style: normal;
  color: var(--blood);
  text-shadow:
    0 0.06em 0 #000,
    0 0 26px rgba(193, 39, 45, 0.55);
}

body.wrecker3d .hazard {
  height: 8px;
  border: 2px solid var(--charcoal-3);
  background: var(--hazard);
  width: min(60%, 340px);
  margin: 0.6rem auto;
}

/* ==========================================================================
   CRT overlay
   ========================================================================== */

body.wrecker3d .crt {
  position: absolute;
  inset: 0;
  z-index: 30;
  pointer-events: none;
  background:
    repeating-linear-gradient(
      to bottom,
      rgba(0, 0, 0, 0) 0 2px,
      rgba(0, 0, 0, 0.14) 2px 3px
    ),
    radial-gradient(
      ellipse 92% 84% at 50% 46%,
      rgba(0, 0, 0, 0) 60%,
      rgba(10, 6, 4, 0.55) 100%
    );
}

@media (prefers-reduced-motion: no-preference) {
  body.wrecker3d .crt {
    animation: wrecker3d-flicker 4s steps(2, jump-none) infinite;
  }
}

@keyframes wrecker3d-flicker {
  0%, 92% { opacity: 1; }
  93%, 95% { opacity: 0.88; }
  96%, 100% { opacity: 1; }
}

/* ==========================================================================
   HUD
   ========================================================================== */

body.wrecker3d #hud3d {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 20;
  pointer-events: none;
  font-family: var(--display);
  letter-spacing: 0.06em;
  background: linear-gradient(to bottom, rgba(15, 12, 10, 0.85), transparent);
  padding: 0.5rem 0.7rem 1.1rem;
}

body.wrecker3d .hud-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* STRUCTURE bar — the internals under the armour facings (the old single hp bar's slot). */
body.wrecker3d #struct-bar3d {
  position: relative;
  width: clamp(110px, 26vw, 250px);
  height: 18px;
  background: var(--charcoal-3);
  border: 2px solid #000;
  box-shadow: 0 0 0 1px var(--plate-edge), inset 0 2px 4px rgba(0, 0, 0, 0.8);
  clip-path: var(--corner-cut);
}

body.wrecker3d #struct-fill3d {
  position: absolute;
  inset: 2px;
  width: 100%;
  max-width: calc(100% - 4px);
  background: linear-gradient(to bottom, #de4a3a, var(--blood) 45%, var(--rust));
  transition: width 0.15s ease-out;
}

body.wrecker3d #struct-bar3d::after {
  content: '';
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    to right,
    transparent 0 calc(10% - 2px),
    rgba(15, 12, 10, 0.65) calc(10% - 2px) 10%
  );
  pointer-events: none;
}

body.wrecker3d #struct-bar3d.low #struct-fill3d {
  background: linear-gradient(to bottom, var(--amber), var(--blood));
}

@media (prefers-reduced-motion: no-preference) {
  body.wrecker3d #struct-bar3d.low #struct-fill3d {
    animation: wrecker3d-hp-pulse 0.7s ease-in-out infinite;
  }
}

/* BOSS BAR — top-center, below the hud-row. Two layers in the plate idiom: PLATE (amber,
   the armour on the arc your fire resolves to — the header's arc chip names which) over
   HULL (blood, structure). Hidden whenever hudSnapshot returns bossBar: null. */
body.wrecker3d #boss-bar3d {
  position: absolute;
  top: 52px;
  left: 50%;
  transform: translateX(-50%);
  width: clamp(180px, 42vw, 420px);
  pointer-events: none;
}

body.wrecker3d #boss-bar3d .bb-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 12px;
  letter-spacing: 0.14em;
  color: var(--bone, #d8cfc0);
  text-shadow: 0 1px 2px #000;
  margin-bottom: 3px;
}

body.wrecker3d #boss-bar3d #bb-arc3d { color: var(--amber, #e0a63c); }

body.wrecker3d #boss-bar3d .bb-track {
  position: relative;
  height: 10px;
  background: var(--charcoal-3);
  border: 2px solid #000;
  box-shadow: 0 0 0 1px var(--plate-edge), inset 0 2px 4px rgba(0, 0, 0, 0.8);
  clip-path: var(--corner-cut);
}

body.wrecker3d #boss-bar3d .bb-hull { margin-top: 2px; height: 8px; }

body.wrecker3d #boss-bar3d .bb-track > div {
  position: absolute;
  inset: 1px;
  width: 100%;
  max-width: calc(100% - 2px);
  transition: width 0.15s ease-out;
}

body.wrecker3d #bb-plate-fill3d {
  background: linear-gradient(to bottom, #f0c869, var(--amber, #e0a63c) 45%, var(--rust));
}

body.wrecker3d #bb-hull-fill3d {
  background: linear-gradient(to bottom, #de4a3a, var(--blood) 45%, var(--rust));
}

/* ---- locational armour diamond -------------------------------------------
   Four kite-shaped wedges tiling a diamond: front = top, rear = bottom, matching the vehicle
   seen from above with the nose up. Each wedge's opacity is its facing's armour level
   (.lv1–.lv4, set by updateHud off the string-cached quantized state); a BREACHED facing
   flips to .dead — blood-red hazard hatching, a different HUE and TEXTURE, so at a glance it
   reads destroyed rather than merely low. The pulse on .dead is killed by the global
   prefers-reduced-motion override like every other animation in this sheet. */
body.wrecker3d #armor3d {
  position: relative;
  width: 34px;
  height: 34px;
  flex: 0 0 auto;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.8));
}

body.wrecker3d #armor3d::before {
  content: '';
  position: absolute;
  inset: 0;
  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
  background: var(--charcoal-3);
}

body.wrecker3d #armor3d .arc {
  position: absolute;
  inset: 0;
  background: var(--amber);
  opacity: 0.12;
  transition: opacity 0.15s ease-out;
}

body.wrecker3d #armor3d .arc.f { clip-path: polygon(50% 3%, 72% 25%, 50% 47%, 28% 25%); }
body.wrecker3d #armor3d .arc.l { clip-path: polygon(3% 50%, 25% 28%, 47% 50%, 25% 72%); }
body.wrecker3d #armor3d .arc.r { clip-path: polygon(97% 50%, 75% 72%, 53% 50%, 75% 28%); }
body.wrecker3d #armor3d .arc.b { clip-path: polygon(50% 97%, 28% 75%, 50% 53%, 72% 75%); }

body.wrecker3d #armor3d .arc.lv1 { opacity: 0.28; }
body.wrecker3d #armor3d .arc.lv2 { opacity: 0.5; }
body.wrecker3d #armor3d .arc.lv3 { opacity: 0.72; }
body.wrecker3d #armor3d .arc.lv4 { opacity: 0.95; }

body.wrecker3d #armor3d .arc.dead {
  opacity: 1;
  background: repeating-linear-gradient(-45deg, var(--blood) 0 3px, #2a0507 3px 6px);
}

@media (prefers-reduced-motion: no-preference) {
  body.wrecker3d #armor3d .arc.dead {
    animation: wrecker3d-hp-pulse 0.8s ease-in-out infinite;
  }
}

@keyframes wrecker3d-hp-pulse {
  50% { opacity: 0.55; }
}

body.wrecker3d #weapon-chip3d,
body.wrecker3d #wave-chip3d,
body.wrecker3d #scrap-chip3d,
body.wrecker3d #hook-chip3d,
body.wrecker3d #alt-chip3d,
body.wrecker3d #rampage-chip3d {
  background: linear-gradient(to bottom, var(--charcoal-2), var(--charcoal-3));
  border: 2px solid var(--plate-edge);
  clip-path: var(--corner-cut);
  padding: 0.22rem 0.65rem;
  font-size: clamp(0.72rem, 2.2vw, 0.95rem);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--bone);
  text-shadow: 0 2px 0 #000;
}

body.wrecker3d #weapon-chip3d { color: var(--amber); border-color: rgba(245, 166, 35, 0.5); }
body.wrecker3d #wave-chip3d   { color: var(--blood); border-color: rgba(193, 39, 45, 0.55); }
body.wrecker3d #scrap-chip3d  { color: var(--chrome); }
body.wrecker3d #hook-chip3d   { color: var(--bone-dim); }
body.wrecker3d #hook-chip3d.ready { color: var(--amber); }
/* SIDEARM (moto classes) — the hook chip's palette: dim while cooling, amber when it will
   actually do something on the next right-click. */
body.wrecker3d #sidearm-chip3d { color: var(--bone-dim); }
body.wrecker3d #sidearm-chip3d.ready { color: var(--amber); }

/* ALTITUDE — flying classes only (game3d.js hides it otherwise, the rack chip's rule). Mono,
   because it is a NUMBER that changes every frame and a proportional face makes it jitter
   sideways as the digits change width. `.low` is under 3 m: the band where the jet collects
   ground salvage AND the band where it is one dune from being scenery, so the same amber the
   structure bar uses for "this is about to matter". */
body.wrecker3d #alt-chip3d {
  font-family: var(--mono);
  font-weight: 700;
  color: var(--chrome);
}
body.wrecker3d #alt-chip3d.low { color: var(--amber); border-color: rgba(245, 166, 35, 0.55); }
@media (prefers-reduced-motion: no-preference) {
  body.wrecker3d #alt-chip3d.low { animation: wrecker3d-hp-pulse 0.9s ease-in-out infinite; }
}

body.wrecker3d #rampage-chip3d {
  margin-left: auto;
  font-family: var(--mono);
  font-weight: 700;
  color: var(--bone-dim);
  opacity: 0;
  transition: opacity 0.2s;
}
body.wrecker3d #rampage-chip3d.on { opacity: 1; color: var(--amber); }
body.wrecker3d #rampage-chip3d.max {
  color: var(--blood);
  border-color: var(--blood);
}
@media (prefers-reduced-motion: no-preference) {
  body.wrecker3d #rampage-chip3d.max { animation: wrecker3d-hp-pulse 0.5s ease-in-out infinite; }
}

/* weapon slot rack. WRAPS, because the rack is built from WEAPONS.forEach and the sim owns how
   many guns there are — a seventh (the CANNON) must fall onto a second row on a narrow phone
   rather than overflow the stage and become unclickable. */
body.wrecker3d #wslots3d {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 6px;
  pointer-events: auto;
}

body.wrecker3d .wslot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  min-width: 52px;
  padding: 3px 6px 4px;
  background: linear-gradient(to bottom, var(--charcoal-2), var(--charcoal-3));
  border: 2px solid var(--plate-edge);
  clip-path: var(--corner-cut);
  font-size: 0.6rem;
  color: var(--bone-dim);
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}

body.wrecker3d .wslot.active {
  color: var(--amber);
  border-color: rgba(245, 166, 35, 0.6);
}

/* A slot this rig does not carry: dimmed so the sim's silent refusal of a switch to an
 * unfitted weapon reads as "not on this rig" instead of "the weapons are broken". */
body.wrecker3d .wslot.unfitted {
  opacity: 0.32;
  cursor: default;
}

body.wrecker3d .wslot .wnum { font-family: var(--mono); font-weight: 700; }
body.wrecker3d .wslot .wname { letter-spacing: 0.05em; }
body.wrecker3d .wslot .wbar {
  width: 100%;
  height: 4px;
  background: var(--charcoal-3);
  border: 1px solid #000;
  overflow: hidden;
}
body.wrecker3d .wslot .wfill {
  display: block;
  height: 100%;
  width: 100%;
  background: var(--amber);
  transition: width 0.15s ease-out;
}
body.wrecker3d .wslot .wfill.hot { background: var(--blood); }

@media (max-width: 560px) {
  body.wrecker3d .wslot { min-width: 38px; }
  body.wrecker3d .wslot .wname { display: none; }
}

/* ==========================================================================
   ENLISTMENT PICKERS — portrait / race / class
   Three rows of buttons in the DRIVER panel. Deliberately built from the same
   plate-and-cut-corner vocabulary as the weapon slots and shop cards rather
   than a new look, so the panel reads as one screen.
   ========================================================================== */

body.wrecker3d .g-faces {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

body.wrecker3d .g-face {
  position: relative;
  width: 46px;
  height: 46px;
  padding: 0;
  overflow: hidden;
  background: linear-gradient(to bottom, var(--charcoal-2), var(--charcoal-3));
  border: 2px solid var(--plate-edge);
  clip-path: var(--corner-cut);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

body.wrecker3d .g-face-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The numeral sits UNDER the portrait, not instead of it: if the image fails to decode the
   img element is removed and this is already there, so a face never collapses to an empty box. */
body.wrecker3d .g-face-n {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--display);
  font-size: 1.1rem;
  color: var(--bone-dim);
}

body.wrecker3d .g-face.picked {
  border-color: var(--amber);
  box-shadow: 0 0 0 2px rgba(245, 166, 35, 0.35);
}

body.wrecker3d .g-picks {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}

body.wrecker3d .g-pick {
  padding: 0.28rem 0.6rem;
  background: linear-gradient(to bottom, var(--charcoal-2), var(--charcoal-3));
  border: 2px solid var(--plate-edge);
  clip-path: var(--corner-cut);
  font-family: var(--display);
  font-size: 0.72rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--bone-dim);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

body.wrecker3d .g-pick.picked {
  color: var(--amber);
  border-color: rgba(245, 166, 35, 0.6);
}

/* Blurb + the real numbers. min-height reserves the two lines so selecting an entry does not
   reflow the panel under the player's thumb — the picker rows would jump as they read. */
body.wrecker3d .g-picknote {
  display: block;
  margin-top: 5px;
  min-height: 2.1em;
  line-height: 1.25;
}

body.wrecker3d .g-pickblurb { display: block; font-size: 0.76rem; color: var(--bone-dim); }
body.wrecker3d .g-pickblurb.dim { opacity: 0.6; font-style: italic; }
body.wrecker3d .g-pickfx {
  display: block;
  font-family: var(--mono);
  font-size: 0.68rem;
  color: var(--amber);
}

/* The vehicle-card showcase turntable (task #43). */
body.wrecker3d .g-show {
  display: block;
  width: 96px;
  height: 96px;
  margin: 0 auto 0.3rem;
  background: radial-gradient(60% 60% at 50% 42%, rgba(58, 44, 34, 0.9), rgba(15, 12, 10, 0.95));
  border: 1px solid var(--plate-edge);
  clip-path: var(--corner-cut);
}
@media (pointer: coarse), (max-height: 560px) {
  body.wrecker3d .g-show { width: 72px; height: 72px; }
}

/* Kin + trade on a roster card, under the name. */
body.wrecker3d .si-kin {
  font-family: var(--display);
  font-size: 0.66rem;
  letter-spacing: 0.06em;
  color: var(--amber);
  opacity: 0.85;
}

/* Same compaction axis as the HUD below — three new rows in this panel is exactly the crowding
   that was just fixed, so they shrink on the same devices rather than re-creating it. */
@media (pointer: coarse), (max-height: 560px) {
  /* 32px, not 38: the roster grew to 30 faces (6 originals + the race x class 24), and at
     38px that is seven rows of thumbing before the RACE picker. 32px keeps it to ~4 rows
     and stays above the 24px floor where a face stops reading as a face. */
  body.wrecker3d .g-face { width: 32px; height: 32px; }
  body.wrecker3d .g-faces { gap: 4px; }
  body.wrecker3d .g-pick { padding: 0.2rem 0.44rem; font-size: 0.62rem; border-width: 1px; }
  body.wrecker3d .g-picks { gap: 4px; }
  body.wrecker3d .g-pickblurb { font-size: 0.66rem; }
  body.wrecker3d .g-pickfx { font-size: 0.6rem; }
  body.wrecker3d .g-picknote { min-height: 1.9em; margin-top: 3px; }
}

/* ==========================================================================
   COMPACT TITLE SCREEN — same coarse/short axis as the HUD compaction below.
   The device lab photographed the real failure: on an 844x390 phone the title
   panel ran ~3 screens tall — h1 decapitated, a horizontal overflow scrollbar,
   five lines of keyboard help. Height is the scarce axis, so everything here
   trades vertical size; the board plate caps its rows instead of scrolling.
   ========================================================================== */
@media (pointer: coarse), (max-height: 560px) {
  body.wrecker3d #title3d h1 {
    /* svh-driven so the TITLE fits the screen that is actually there — 10vw on a
       landscape phone is 84px x 2 lines, half the viewport gone before the buttons. */
    font-size: clamp(1.6rem, 11svh, 3.4rem);
  }
  body.wrecker3d #title3d .kicker { font-size: 0.62rem; }
  body.wrecker3d #title3d .hazard { margin: 0.3rem auto; height: 5px; }
  body.wrecker3d #title3d .stats { font-size: 0.68rem; line-height: 1.3; margin: 0.3rem 0; }
  body.wrecker3d #title3d .title-actions { margin: 0.4rem 0; gap: 0.5rem; }
  body.wrecker3d #title3d .btn { padding: 0.45rem 1rem; font-size: 0.9rem; }
  /* The world board earns its place on a phone at five rows, not ten. The rows keep
     rendering (one DOM, one code path) — the tail is display-capped. */
  body.wrecker3d .acct-plate .board-row:nth-child(n+6) { display: none; }
  body.wrecker3d .acct-plate { padding: 0.35rem 0.5rem; }
}

/* ==========================================================================
   MOBILE HUD COMPACTION — reported from play: "the header and footer are
   crowding screen real estate" on a phone.

   THE AXIS WAS WRONG, which is why the shrink above never fired on the device
   that needed it. This game NUDGES PHONES TO LANDSCAPE (see the rotate-nudge
   at `orientation: portrait and pointer: coarse`), and a landscape phone is
   844x390 — WIDE and SHORT. `max-width: 560px` does not match it. Width is the
   wrong axis for a game that owns the whole viewport in landscape; the binding
   constraint is HEIGHT, and the device signal is a COARSE POINTER.

   Deliberately matching the SAME population that gets the touch controls
   (#touch3d is hidden only for `hover: hover` + `pointer: fine`) rather than
   inventing a third definition of "mobile" — two disagreeing detectors is
   exactly the bug that hid the tablet controls entirely once already.

   The HUD is an absolute overlay, so this does not reclaim layout height the
   way deleting the footer did; it reclaims SIGHT LINES, which on a 390px-tall
   viewport is the same complaint.
   ========================================================================== */
@media (pointer: coarse), (max-height: 560px) {
  body.wrecker3d #hud3d {
    padding: 0.3rem 0.45rem 0.55rem;
    letter-spacing: 0.03em;
  }

  body.wrecker3d .hud-row { gap: 0.3rem; }

  /* HP status: the armour diamond and the structure bar it protects. Both shrink
     together — the diamond is read as a SHAPE (which wedge is dark), not as text,
     so it survives scaling better than a chip does. */
  body.wrecker3d #armor3d { width: 24px; height: 24px; }
  body.wrecker3d #struct-bar3d {
    width: clamp(72px, 17vw, 132px);
    height: 11px;
  }
  /* The 10% tick hatching turns to mush under 12px; drop it rather than ship noise. */
  body.wrecker3d #struct-bar3d::after { display: none; }

  /* Boss bar rides the same compaction: thinner tracks, smaller head, tucked closer
     to the shrunken hud-row. */
  body.wrecker3d #boss-bar3d { top: 36px; width: clamp(140px, 46vw, 260px); }
  body.wrecker3d #boss-bar3d .bb-head { font-size: 9px; margin-bottom: 2px; }
  body.wrecker3d #boss-bar3d .bb-track { height: 7px; }
  body.wrecker3d #boss-bar3d .bb-hull { height: 6px; }

  body.wrecker3d #weapon-chip3d,
  body.wrecker3d #wave-chip3d,
  body.wrecker3d #scrap-chip3d,
  body.wrecker3d #hook-chip3d,
  body.wrecker3d #alt-chip3d,
  body.wrecker3d #rampage-chip3d {
    padding: 0.12rem 0.4rem;
    font-size: 0.62rem;
    border-width: 1px;
  }

  /* Weapon selection: the SLOT STRIP is the interactive control and it now carries a
     sprite icon per weapon, so the word is redundant at thumb size — the icon plus the
     active highlight identifies the gun. The ammo/heat READOUT is not redundant and
     stays (see .wc-name below: the chip keeps its number and drops its name). */
  body.wrecker3d #wslots3d { gap: 3px; margin-top: 3px; }
  body.wrecker3d .wslot {
    min-width: 0;
    padding: 2px 4px 3px;
    font-size: 0.5rem;
    border-width: 1px;
  }
  body.wrecker3d .wslot .wname { display: none; }
  body.wrecker3d .wslot .wbar { height: 3px; }
  body.wrecker3d .wslot .wicon { width: 15px !important; height: 15px !important; margin-right: 0 !important; }

  /* The active weapon's NAME is already shown by the highlighted slot's icon, so the
     chip keeps only the part the strip cannot show: rounds left / heat / fuel. */
  body.wrecker3d .wc-name { display: none; }
}

/* ==========================================================================
   Banner + damage vignette
   ========================================================================== */

body.wrecker3d #banner3d {
  position: absolute;
  top: 22%;
  left: 0;
  right: 0;
  z-index: 25;
  text-align: center;
  pointer-events: none;
  font-family: var(--display);
  font-size: clamp(1.6rem, 6vw, 3.4rem);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--bone);
  text-shadow: 0 3px 0 #000, 0 0 30px rgba(193, 39, 45, 0.5);
  opacity: 0;
  transform: skewX(-4deg) scale(0.92);
  transition: opacity 0.18s ease-out, transform 0.18s ease-out;
}
body.wrecker3d #banner3d.show { opacity: 1; transform: skewX(-4deg) scale(1); }
body.wrecker3d #banner3d.gold { color: var(--amber); }
body.wrecker3d #banner3d.blood { color: var(--blood); }
body.wrecker3d #banner3d.warn { color: var(--amber); font-size: clamp(1.1rem, 4vw, 2rem); }

/* THE COLLECT BEAT. Sits UNDER the WAVE CLEAR banner (which owns 22%) rather than replacing it:
   the two are one message — the wave is over, here is what the pause is for. Deliberately
   quieter than a banner (smaller, dimmer, no skew) because it is an instruction, not an event,
   and it is on screen for seconds rather than for a beat. */
body.wrecker3d #collect3d {
  position: absolute;
  top: 33%;
  left: 0;
  right: 0;
  z-index: 25;
  text-align: center;
  pointer-events: none;
  font-family: var(--display);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: clamp(0.85rem, 2.6vw, 1.35rem);
  color: var(--amber);
  text-shadow: 0 2px 0 #000, 0 0 22px rgba(245, 166, 35, 0.45);
}
body.wrecker3d #collect3d.hidden { display: none; }
body.wrecker3d #collect3d .cl-lbl { color: var(--bone); }
body.wrecker3d #collect3d .cl-t {
  font-family: var(--mono);
  margin-left: 0.5em;
  color: var(--amber);
}
/* The count of salvage still in reach — the REASON the bay is waiting. Dimmer than the label
   and on its own line on a narrow screen, so it never competes with the countdown. */
body.wrecker3d #collect3d .cl-n {
  display: inline-block;
  margin-left: 0.7em;
  font-size: 0.72em;
  letter-spacing: 0.1em;
  color: var(--chrome);
}
body.wrecker3d #collect3d .cl-n:empty { display: none; }
/* HELD: the countdown has run out and the bay is waiting on a player who is still hoovering.
   No number is shown in that state (see stepCollectBeat), so the pulse is what says the game is
   waiting ON PURPOSE rather than stuck. Motion-sensitive players get the colour shift only. */
body.wrecker3d #collect3d.held .cl-lbl { color: var(--amber); }
@media (prefers-reduced-motion: no-preference) {
  body.wrecker3d #collect3d.held { animation: wrecker3d-hp-pulse 0.75s ease-in-out infinite; }
}
@media (max-width: 30rem) {
  body.wrecker3d #collect3d .cl-n:not(:empty) { display: block; margin: 0.15em 0 0; }
}

body.wrecker3d #vignette3d {
  position: absolute;
  inset: 0;
  z-index: 24;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(
    ellipse 75% 70% at 50% 50%,
    rgba(0, 0, 0, 0) 55%,
    rgba(193, 39, 45, 0.55) 100%
  );
}
body.wrecker3d #vignette3d.flash { animation: wrecker3d-dmg 0.45s ease-out; }
@keyframes wrecker3d-dmg {
  0% { opacity: 1; }
  100% { opacity: 0; }
}

/* ==========================================================================
   Title + game over panels
   ========================================================================== */

body.wrecker3d #title3d,
body.wrecker3d #gameover3d {
  position: absolute;
  inset: 0;
  z-index: 40;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.9rem;
  text-align: center;
  padding: 1rem;
}

body.wrecker3d #title3d {
  background:
    repeating-linear-gradient(
      -45deg,
      rgba(138, 51, 36, 0.05) 0 18px,
      transparent 18px 36px
    ),
    radial-gradient(90% 80% at 50% 40%, rgba(36, 29, 23, 0.88), rgba(15, 12, 10, 0.95));
}

body.wrecker3d #gameover3d {
  background: radial-gradient(
    90% 80% at 50% 45%,
    rgba(26, 21, 18, 0.88),
    rgba(15, 12, 10, 0.96)
  );
}

body.wrecker3d .kicker {
  font-family: var(--display);
  font-size: clamp(0.68rem, 2.2vw, 1rem);
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--amber);
  text-shadow: 0 2px 0 #000;
  display: flex;
  align-items: center;
  gap: 0.7rem;
}

body.wrecker3d .kicker::before,
body.wrecker3d .kicker::after {
  content: '';
  width: clamp(18px, 6vw, 48px);
  height: 6px;
  background: var(--hazard);
  border: 1px solid var(--charcoal-3);
}

body.wrecker3d #title3d h1 {
  font-size: clamp(2.4rem, 10vw, 5.6rem);
  transform: skewX(-4deg);
}

body.wrecker3d #gameover3d h1 {
  font-size: clamp(2.2rem, 9vw, 4.6rem);
  color: var(--blood);
  transform: skewX(-4deg);
  text-shadow:
    0 0.06em 0 #000,
    0 0 30px rgba(193, 39, 45, 0.6);
}

body.wrecker3d .stats {
  margin: 0;
  font-family: var(--mono);
  font-weight: 700;
  font-size: clamp(0.8rem, 2.4vw, 1.05rem);
  letter-spacing: 0.08em;
  line-height: 1.5;
  color: var(--bone);
}

body.wrecker3d .stats b { color: var(--amber); }
body.wrecker3d .stats.dim { color: var(--bone-dim); font-size: clamp(0.7rem, 2vw, 0.85rem); }

/* ==========================================================================
   Buttons
   ========================================================================== */

body.wrecker3d .btn {
  font-family: var(--display);
  font-size: clamp(1rem, 3vw, 1.3rem);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--bone);
  background: linear-gradient(to bottom, #a03c2a, var(--rust) 55%, #63241a);
  border: 3px solid #000;
  box-shadow: 0 5px 0 #000, inset 0 2px 0 rgba(232, 220, 200, 0.25);
  padding: 0.6rem 1.6rem;
  cursor: pointer;
  text-shadow: 0 2px 0 #000;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

body.wrecker3d .btn:hover { filter: brightness(1.12); }

body.wrecker3d .btn:active {
  transform: translateY(4px);
  box-shadow: 0 1px 0 #000, inset 0 2px 0 rgba(232, 220, 200, 0.25);
}

body.wrecker3d .btn.primary {
  background: linear-gradient(to bottom, #e04a43, var(--blood) 55%, #7d191d);
}

body.wrecker3d .btn:disabled {
  filter: grayscale(0.7) brightness(0.7);
  cursor: not-allowed;
}

/* THE BATTLEGROUND PICKER (title screen). Small pressed-steel plates in a row — the .btn
   idiom at chip scale, amber when selected so the active map reads at a glance. The buttons
   themselves are built by game3d.js from the sim's MAPS registry. */
body.wrecker3d .map-row {
  display: flex;
  gap: 0.4rem;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  margin: 0.35rem 0 0.1rem;
}

body.wrecker3d .map-row .map-plate {
  font-size: clamp(0.62rem, 1.9vw, 0.8rem);
  padding: 0.3rem 0.7rem;
  border-width: 2px;
  box-shadow: 0 3px 0 #000, inset 0 1px 0 rgba(232, 220, 200, 0.2);
  background: linear-gradient(to bottom, #3a2c22, #241d17 55%, #17110d);
  color: var(--chrome);
}

body.wrecker3d .map-row .map-plate.selected {
  background: linear-gradient(to bottom, #a03c2a, var(--rust) 55%, #63241a);
  color: var(--bone);
  border-color: var(--amber);
}

body.wrecker3d .map-row .map-plate:active {
  transform: translateY(2px);
  box-shadow: 0 1px 0 #000, inset 0 1px 0 rgba(232, 220, 200, 0.2);
}

/* ==========================================================================
   Touch controls — dual sticks + thumb buttons, phone-first
   ========================================================================== */

/* The sticks are PLAY controls; on the title, gameover, salvage-bay and garage screens they
   are dead weight peeking around the panel — photographed cluttering a 390px-tall phone by
   the device lab. #touch3d follows every panel in the DOM (order verified 67/96/120/155/181),
   so a sibling selector does this with zero JS; the pause overlay is a banner, not a panel,
   so mid-run pause keeps its controls (releasing them there would drop your thumbs mid-fight). */
body.wrecker3d #title3d:not(.hidden) ~ #touch3d,
body.wrecker3d #gameover3d:not(.hidden) ~ #touch3d,
body.wrecker3d #shop3d:not(.hidden) ~ #touch3d,
body.wrecker3d #garage3d:not(.hidden) ~ #touch3d { display: none; }

body.wrecker3d #touch3d {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 26;
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  padding: 0 14px calc(12px + env(safe-area-inset-bottom, 0px));
  pointer-events: none;
}

body.wrecker3d #touch3d .tgroup {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  pointer-events: none;
}

body.wrecker3d #touch3d .tcol {
  display: flex;
  flex-direction: column;
  gap: 10px;
  align-items: center;
}

body.wrecker3d .stick {
  pointer-events: auto;
  position: relative;
  width: 112px;
  height: 112px;
  border-radius: 50%;
  background: rgba(232, 220, 200, 0.07);
  border: 2px solid rgba(232, 220, 200, 0.3);
  box-shadow: inset 0 0 0 5px rgba(15, 12, 10, 0.3);
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}

body.wrecker3d .stick.aim { border-color: rgba(245, 166, 35, 0.4); }

body.wrecker3d .stick .nub {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 46px;
  height: 46px;
  margin: -23px 0 0 -23px;
  border-radius: 50%;
  background: rgba(232, 220, 200, 0.22);
  border: 2px solid rgba(232, 220, 200, 0.5);
  pointer-events: none;
}

body.wrecker3d .stick.aim .nub {
  background: rgba(245, 166, 35, 0.25);
  border-color: rgba(245, 166, 35, 0.6);
}

body.wrecker3d .tbtn {
  pointer-events: auto;
  width: 66px;
  height: 66px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--display);
  font-size: 1.4rem;
  color: rgba(232, 220, 200, 0.85);
  background: rgba(232, 220, 200, 0.09);
  border: 2px solid rgba(232, 220, 200, 0.35);
  box-shadow: inset 0 0 0 4px rgba(15, 12, 10, 0.25);
  text-shadow: 0 2px 0 #000;
  user-select: none;
  -webkit-user-select: none;
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
}

body.wrecker3d .tbtn.small { width: 50px; height: 50px; font-size: 1.1rem; }

body.wrecker3d .tbtn:active,
body.wrecker3d .tbtn.active {
  background: rgba(245, 166, 35, 0.28);
  border-color: var(--amber);
  color: var(--amber);
}

body.wrecker3d .tbtn.fire { border-color: rgba(193, 39, 45, 0.6); }

body.wrecker3d .tbtn.fire:active,
body.wrecker3d .tbtn.fire.active {
  background: rgba(193, 39, 45, 0.32);
  border-color: var(--blood);
  color: var(--bone);
}

/* keyboard-and-mouse rigs don't need thumbs */
/* One media split decides keyboard vs touch for BOTH the controls and the title help —
   a second detector that could disagree is exactly the bug that once hid the tablet
   controls entirely. Default = touch (help-touch shows); hover+fine = keyboard. */
body.wrecker3d #title-help3d { display: none; }
@media (hover: hover) and (pointer: fine) {
  body.wrecker3d #touch3d { display: none; }
  body.wrecker3d #title-help3d { display: block; }
  body.wrecker3d #title-help-touch3d { display: none; }
}

@media (max-height: 480px) {
  body.wrecker3d .stick { width: 88px; height: 88px; }
  body.wrecker3d .tbtn { width: 54px; height: 54px; font-size: 1.15rem; }
  /* 44px floor, not 42. This query is exactly the landscape-phone case where the shrink applies,
     which is also the only case where these are the ONLY controls — and 44px is the documented
     minimum comfortable touch target. The bomb rack and weapon-cycle buttons are both .small,
     so 42 made the two newest controls the hardest to hit. */
  body.wrecker3d .tbtn.small { width: 44px; height: 44px; }
}

/* ==========================================================================
   Footer CTA — the real business under the wasteland
   Phone mirrors BUSINESS_PHONE in packages/shared/src/nap.ts (NAP SSOT,
   UNCONFIRMED pending Twilio). Never invent digits here.
   ========================================================================== */

/* The .cta footer's rules were REMOVED with the element itself (2026-08-13). It was
   `flex: 0 0 auto` in the body's flex column, so unlike the HUD — which is an absolute
   overlay — it took real height away from the canvas on every device. */

/* ==========================================================================
   Motion off = readable, nothing pulses
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  body.wrecker3d *,
  body.wrecker3d *::before,
  body.wrecker3d *::after {
    animation: none !important;
    transition: none !important;
  }
}

/* Cross-link between the two games (the 3D arena and the 2D original). */
body.wrecker3d .xgame{color:var(--amber,#f5a623);text-decoration:none;border-bottom:1px dashed currentColor;
  letter-spacing:.04em}
body.wrecker3d .xgame:hover{color:var(--blood,#c1272d)}

/* ============================================================ SALVAGE BAY
   The between-waves shop. Real DOM, real buttons — see the note in index.html.
   Everything here is scoped under body.wrecker3d like the rest of this sheet. */

/* The SCRIM. Same shape as #title3d / #gameover3d — absolute, inset 0, flex-centred — but at
   z-index 45 so it sits above the CRT overlay (30) as well as the HUD. Without this the panel
   had no positioning at all and drew as unstyled text in normal flow over the canvas: visible
   but "very faint", with no backdrop, which is exactly how it was reported. */
body.wrecker3d #shop3d {
  position: absolute;
  inset: 0;
  z-index: 45;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  /* Dim the arena so 23 rows of small text are readable over a lit desert, and so it is
     obvious the game is deliberately parked rather than hung. */
  background: radial-gradient(90% 80% at 50% 45%, rgba(26, 21, 18, 0.9), rgba(15, 12, 10, 0.96));
}
body.wrecker3d #shop3d.hidden { display: none; }

/* The PANEL itself. Wider and taller than the title/gameover boxes: 23 parts across 5
   branches will not read in a 30rem box, and a shop you have to scroll blind is one you skip. */
body.wrecker3d .shop-box {
  width: min(58rem, 94vw);
  max-height: 86svh;
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  text-align: left;
  padding: clamp(0.9rem, 2.5vw, 1.5rem);
  background: linear-gradient(180deg, rgba(36, 29, 23, 0.96), rgba(20, 16, 13, 0.98));
  border: 1px solid #3a322c;
  border-radius: 10px;
  box-shadow: 0 18px 60px rgba(0, 0, 0, 0.6);
  overflow: hidden;   /* the inner .shop-grid owns the scrolling */
}

body.wrecker3d .shop-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem;
}
body.wrecker3d .shop-head h1 { margin: 0; font-size: clamp(1.5rem, 4vw, 2.3rem); }
body.wrecker3d .shop-purse { text-align: right; white-space: nowrap; }
body.wrecker3d .purse-num {
  display: block; font-family: Impact, 'Arial Black', sans-serif;
  font-size: clamp(1.6rem, 5vw, 2.6rem); line-height: 1; color: var(--amber);
}
body.wrecker3d .purse-lbl {
  font-size: 0.68rem; letter-spacing: 0.22em; text-transform: uppercase; color: var(--bone-dim);
}

body.wrecker3d .shop-tabs { display: flex; flex-wrap: wrap; gap: 0.35rem; }
body.wrecker3d .shop-tab {
  font: inherit; cursor: pointer; background: rgba(0, 0, 0, 0.35);
  color: var(--bone-dim); border: 1px solid #3a322c; border-radius: 5px;
  padding: 0.34rem 0.7rem; font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase;
}
body.wrecker3d .shop-tab[aria-selected="true"] {
  color: var(--amber); border-color: var(--amber); background: rgba(245, 166, 35, 0.1);
}

body.wrecker3d .shop-grid {
  display: grid; gap: 0.45rem; overflow-y: auto; padding-right: 0.2rem;
  grid-template-columns: 1fr;
}
@media (min-width: 44rem) {
  body.wrecker3d .shop-grid { grid-template-columns: 1fr 1fr; }
}

body.wrecker3d .shop-item {
  font: inherit; text-align: left; cursor: pointer; color: var(--bone);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.045), rgba(0, 0, 0, 0.3));
  border: 1px solid #3a322c; border-radius: 8px; padding: 0.6rem 0.7rem;
  display: grid; gap: 0.2rem;
}
body.wrecker3d .shop-item:hover:not(:disabled),
body.wrecker3d .shop-item.sel { border-color: var(--blood); }
/* A separate, unmissable ring for the keyboard cursor. The .sel border alone is too close to
   the hover treatment to serve as the only "you are here" signal. */
body.wrecker3d .shop-item.sel { outline: 2px solid var(--amber); outline-offset: 1px; }
body.wrecker3d .shop-item:disabled { cursor: default; opacity: 0.55; }
body.wrecker3d .shop-item.owned { border-color: rgba(245, 166, 35, 0.45); }

body.wrecker3d .si-top { display: flex; align-items: baseline; justify-content: space-between; gap: 0.5rem; }
body.wrecker3d .si-name {
  font-family: Impact, 'Arial Black', sans-serif; font-size: 0.98rem;
  letter-spacing: 0.03em; color: var(--amber);
}
body.wrecker3d .si-cost { font-size: 0.8rem; color: var(--bone); white-space: nowrap; }
body.wrecker3d .si-cost.cant { color: var(--blood); }
body.wrecker3d .si-cost.max { color: var(--bone-dim); }
body.wrecker3d .si-desc { font-size: 0.79rem; line-height: 1.35; color: var(--bone-dim); }
/* Rank pips: filled = fitted. Text too ("2/3") — pips alone would carry the information in
   shape only, which is unreadable to a screen reader and to anyone who cannot see the fill. */
body.wrecker3d .si-pips { letter-spacing: 0.18em; font-size: 0.8rem; color: var(--amber); }

body.wrecker3d .shop-msg {
  margin: 0; min-height: 1.2em; font-size: 0.76rem; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--blood);
}
body.wrecker3d .shop-msg.ok { color: var(--amber); }
body.wrecker3d .shop-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap;
}
body.wrecker3d .shop-hint { font-size: 0.68rem; color: var(--bone-dim); }

/* ===================================================== portrait nudge (mobile)
   Shown ONLY on a coarse pointer in portrait. The `pointer: coarse` half matters: without it a
   narrow desktop window would get a "turn your phone sideways" overlay, which is nonsense.
   `hidden` in the markup is the default, so a browser that supports neither the media query nor
   the script simply never shows it — failing closed, never blocking play. */
body.wrecker3d #rotate-nudge { display: none; }

@media (orientation: portrait) and (pointer: coarse) {
  body.wrecker3d #rotate-nudge:not([hidden]) {
    display: flex;
    position: fixed;
    inset: 0;
    z-index: 60;           /* above every panel (45) and the CRT (30) */
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    background: rgba(12, 9, 7, 0.97);
    text-align: center;
  }
}
body.wrecker3d.rotate-ok #rotate-nudge { display: none !important; }

body.wrecker3d .rn-box { display: grid; gap: 0.7rem; justify-items: center; max-width: 22rem; }
body.wrecker3d .rn-icon {
  font-size: 3.4rem; line-height: 1; color: var(--amber);
  /* The turn hint animates, because a static arrow reads as decoration. Honours the motion
     preference — someone who asked for less motion still gets the words. */
  animation: rn-turn 2.4s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  body.wrecker3d .rn-icon { animation: none; }
}
@keyframes rn-turn {
  0%, 40% { transform: rotate(0deg); }
  60%, 100% { transform: rotate(90deg); }
}
body.wrecker3d .rn-h {
  margin: 0; font-family: Impact, 'Arial Black', sans-serif; font-size: 1.5rem;
  letter-spacing: 0.03em; text-transform: uppercase; color: var(--bone);
}
body.wrecker3d .rn-p { margin: 0; font-size: 0.9rem; color: var(--bone-dim); }
body.wrecker3d #rn-anyway { margin-top: 0.4rem; font-size: 0.8rem; }

/* The salvage bay on a landscape phone. 86svh of a ~390 px-tall viewport is ~335 px for a head,
   five tabs, a scrolling grid, a status line and a footer — it fits, but only if it stops
   spending that height on padding and display type. Tap targets inside the grid are untouched;
   only the chrome shrinks. */
@media (max-height: 520px) {
  body.wrecker3d .shop-box {
    max-height: 94svh;
    padding: 0.6rem 0.7rem;
    gap: 0.45rem;
  }
  body.wrecker3d .shop-head h1 { font-size: 1.25rem; }
  body.wrecker3d .purse-num { font-size: 1.5rem; }
  body.wrecker3d .shop-tabs { gap: 0.25rem; }
  body.wrecker3d .shop-tab { padding: 0.28rem 0.5rem; font-size: 0.66rem; }
  body.wrecker3d .si-desc { font-size: 0.74rem; }
  /* The hint is the first thing to go — the ROLL OUT button next to it says what to do. */
  body.wrecker3d .shop-hint { display: none; }
}

/* ================================================================ THE GARAGE
   The title-screen driver/vehicle/paint flow. It REUSES the salvage bay's chrome by wearing the
   same classes on the same structure (.shop-box, .shop-tabs/.shop-tab, .shop-head, .shop-msg,
   .shop-foot, .si-top/.si-name/.si-cost/.si-desc), so everything below is only the delta. The
   one thing it cannot inherit is the SCRIM: #shop3d's rule is per-ID (there is no .panel rule in
   this sheet — see the note in index.html), so the garage needs its own copy or it would draw as
   unstyled text in normal flow over the canvas. */

body.wrecker3d #garage3d {
  position: absolute;
  inset: 0;
  z-index: 46;              /* above the bay (45), which can never be open at the same time */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  background: radial-gradient(90% 80% at 50% 45%, rgba(26, 21, 18, 0.9), rgba(15, 12, 10, 0.96));
}
body.wrecker3d #garage3d.hidden { display: none; }

body.wrecker3d .garage-back {
  min-width: 44px;
  min-height: 44px;
  padding: 0.35rem 0.8rem;
  font-size: 0.85rem;
  flex: 0 0 auto;
  align-self: flex-start;
}
@media (pointer: coarse), (max-height: 560px) {
  body.wrecker3d .garage-back { padding: 0.3rem 0.6rem; font-size: 0.78rem; }
}

/* Wider than the bay: a vehicle card carries twelve stats read live out of the sim. */
body.wrecker3d .garage-box { width: min(64rem, 96vw); }

/* The panel body scrolls; the head, tabs, message and footer stay put. .shop-grid already owns
   overflow-y — this only stops the grid template from splitting the blocks into columns. */
body.wrecker3d .garage-grid { display: block; }

body.wrecker3d .g-block { margin: 0 0 0.7rem; }
body.wrecker3d .g-label {
  font-family: var(--display);
  font-size: 0.74rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--amber);
  margin-bottom: 0.35rem;
}
body.wrecker3d .g-sub {
  font-size: 0.74rem;
  line-height: 1.35;
  color: var(--bone-dim);
  margin-top: 0.2rem;
}

/* A row: same skin as a .shop-item, but 44px minimum so it is a real touch target on a phone
   even when its text is one short line. */
body.wrecker3d .gcard {
  font: inherit;
  text-align: left;
  cursor: pointer;
  color: var(--bone);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.045), rgba(0, 0, 0, 0.3));
  border: 1px solid #3a322c;
  border-radius: 8px;
  padding: 0.55rem 0.7rem;
  min-height: 44px;
  display: grid;
  gap: 0.2rem;
  align-content: center;
  width: 100%;
}
body.wrecker3d .gcard:hover { border-color: var(--blood); }
/* The keyboard cursor gets its own unmissable ring — the hover border alone is too close to it
   to serve as the only "you are here" signal (the bay learned this). */
body.wrecker3d .gcard.sel { outline: 2px solid var(--amber); outline-offset: 1px; }
body.wrecker3d .gcard.current { border-color: rgba(245, 166, 35, 0.75); }
/* REFUSED, not disabled: a locked rig and an unaffordable perk both have a reason worth
   hearing, and a `disabled` button cannot take focus, so nobody on a keyboard or a screen
   reader could ever reach it to be told why. It stays focusable and clickable; the click
   prints the refusal. */
body.wrecker3d .gcard[aria-disabled="true"] { opacity: 0.55; }
body.wrecker3d .g-lock {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  color: var(--blood);
}

body.wrecker3d .g-list,
body.wrecker3d .g-cards { display: grid; gap: 0.45rem; grid-template-columns: 1fr; }
@media (min-width: 44rem) {
  body.wrecker3d .g-list { grid-template-columns: 1fr 1fr 1fr; }
  body.wrecker3d .g-cards { grid-template-columns: 1fr 1fr; }
}
@media (min-width: 62rem) {
  body.wrecker3d .g-cards { grid-template-columns: 1fr 1fr 1fr; }
}

/* Enlist row: a real <label for>, a real text input, a real submit button. */
body.wrecker3d .g-enlist { display: flex; gap: 0.4rem; flex-wrap: wrap; }
body.wrecker3d .g-input {
  font-family: var(--mono);
  font-weight: 700;
  font-size: 1rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bone);
  background: rgba(0, 0, 0, 0.45);
  border: 1px solid #3a322c;
  border-radius: 6px;
  padding: 0 0.7rem;
  min-height: 44px;
  flex: 1 1 12rem;
  min-width: 0;
}
body.wrecker3d .g-input::placeholder { color: rgba(232, 220, 200, 0.32); }
body.wrecker3d .g-btn {
  font-family: var(--display);
  font-size: 0.9rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bone);
  background: linear-gradient(to bottom, #a03c2a, var(--rust) 55%, #63241a);
  border: 2px solid #000;
  border-radius: 6px;
  padding: 0 1.1rem;
  min-height: 44px;
  cursor: pointer;
  text-shadow: 0 2px 0 #000;
  touch-action: manipulation;
}

/* XP bar. The fill animates only where motion is welcome. */
body.wrecker3d .xp-bar {
  height: 14px;
  border: 1px solid var(--plate-edge);
  background: rgba(0, 0, 0, 0.5);
  overflow: hidden;
}
body.wrecker3d .xp-fill {
  height: 100%;
  background: linear-gradient(to bottom, #ffd07a, var(--amber) 60%, #a86a10);
}
@media (prefers-reduced-motion: no-preference) {
  body.wrecker3d .xp-fill { transition: width 0.35s ease-out; }
}

/* Perk tree: three branch columns, four tiers each, top tier first. */
body.wrecker3d .g-perks { display: grid; gap: 0.5rem; grid-template-columns: 1fr; }
@media (min-width: 44rem) {
  body.wrecker3d .g-perks { grid-template-columns: 1fr 1fr 1fr; }
}
body.wrecker3d .g-branch { display: grid; gap: 0.35rem; align-content: start; }
body.wrecker3d .g-branch-h {
  font-family: Impact, 'Arial Black', sans-serif;
  font-size: 0.95rem;
  letter-spacing: 0.08em;
  color: var(--chrome);
}

/* The twelve live stats on a vehicle card. Two columns of key/value, monospaced so the numbers
   line up and a mismatch with the sim would be obvious at a glance. */
body.wrecker3d .g-stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.05rem 0.6rem;
  margin-top: 0.25rem;
}
body.wrecker3d .g-stat { display: flex; justify-content: space-between; gap: 0.4rem; }
body.wrecker3d .g-stat-k {
  font-size: 0.62rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--bone-dim);
}
body.wrecker3d .g-stat-v { font-family: var(--mono); font-size: 0.7rem; color: var(--bone); }

/* Paint chips: swatch + name, wrapped. FACTORY has no hex, so it wears the hazard tape. */
body.wrecker3d .g-swatches { display: flex; flex-wrap: wrap; gap: 0.4rem; }
body.wrecker3d .gpaint {
  width: auto;
  grid-auto-flow: column;
  align-items: center;
  justify-content: start;
  gap: 0.5rem;
  padding: 0.4rem 0.7rem;
}
body.wrecker3d .swatch {
  width: 26px;
  height: 26px;
  border: 1px solid var(--plate-edge);
  border-radius: 4px;
  flex: none;
}
body.wrecker3d .swatch.factory { background: var(--hazard); background-size: 8px 8px; }
body.wrecker3d .g-paint-n {
  font-family: var(--mono);
  font-size: 0.74rem;
  letter-spacing: 0.1em;
}

/* Title screen: ROLL OUT and GARAGE side by side, stacking on a narrow phone. */
body.wrecker3d .title-actions {
  display: flex;
  gap: 0.6rem;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
}

/* Who is driving, on the HUD. Same pressed-steel chip as the others (see the #weapon-chip3d
   group), amber-on-charcoal so it reads as status rather than as a warning. */
body.wrecker3d #driver-chip3d {
  padding: 0.28rem 0.55rem;
  border: 1px solid var(--plate-edge);
  background: rgba(15, 12, 10, 0.72);
  font-size: clamp(0.6rem, 1.8vw, 0.78rem);
  letter-spacing: 0.1em;
  color: var(--chrome);
  white-space: nowrap;
}

/* The garage on a landscape phone — same trim as the bay's, plus the stat grid collapsing to one
   column so a card's twelve numbers stay legible instead of shrinking. */
@media (max-height: 520px) {
  body.wrecker3d .garage-box { max-height: 94svh; padding: 0.6rem 0.7rem; gap: 0.45rem; }
  body.wrecker3d .g-label { font-size: 0.66rem; margin-bottom: 0.25rem; }
  body.wrecker3d .g-block { margin-bottom: 0.5rem; }
}
@media (max-width: 30rem) {
  body.wrecker3d .g-stats { grid-template-columns: 1fr; }
}

/* Garage tabs get a real touch target. The bay's .shop-tab is ~26 px tall, which is fine for a
   panel you open mid-fight with the keyboard and not for a title screen a thumb navigates. Scoped
   to #garage3d so the bay is untouched. */
body.wrecker3d #garage3d .shop-tab {
  min-height: 44px;
  padding: 0 0.9rem;
  display: inline-flex;
  align-items: center;
}

/* ================================================= dev console (` / ~)
   A test tool, styled to sit apart from the game's own panels: mono, terminal-green, and pinned
   to the top so it never covers the HUD you are trying to read while testing. Above everything
   (the garage scrim is 46) because it is modal over all of them. */
body.wrecker3d #devcon3d {
  position: absolute;
  inset: 0 0 auto 0;
  z-index: 70;
  display: flex;
  justify-content: center;
  padding: 0.6rem;
  pointer-events: none;   /* only the box takes input; the arena stays visible below */
}
body.wrecker3d #devcon3d.hidden { display: none; }
body.wrecker3d .dc-box {
  pointer-events: auto;
  width: min(52rem, 96vw);
  background: rgba(8, 10, 8, 0.94);
  border: 1px solid #2f4a32;
  border-radius: 6px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.6);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 0.5rem 0.6rem;
}
body.wrecker3d .dc-log {
  max-height: 40svh;
  overflow-y: auto;
  font-size: 0.78rem;
  line-height: 1.45;
  color: #8fe39a;
  white-space: pre-wrap;
}
body.wrecker3d .dc-line { margin: 0; }
body.wrecker3d .dc-row { display: flex; align-items: center; gap: 0.4rem; }
body.wrecker3d .dc-prompt { color: #8fe39a; font-size: 0.9rem; }
body.wrecker3d .dc-input {
  flex: 1;
  min-height: 44px;              /* the touch floor: reachable on a tablet too */
  background: transparent;
  border: 0;
  outline: none;
  color: #cfeed3;
  font: inherit;
  font-size: 0.9rem;
}
body.wrecker3d .dc-input::placeholder { color: #4d6b50; }

/* ==========================================================================
   ACCOUNTS — the world-high-scores plate, the cloud-saves strip, the
   sign-in/register modal. Everything here dresses DOM that accountui.js only
   unhides when the API health probe answers, so none of it exists visually
   under a plain python serve (the supported anonymous experience). Built from
   the established vocabulary — plate gradient, 2px plate-edge border,
   clip-path corner cut — rather than a third look.
   ========================================================================== */

body.wrecker3d .acct-plate {
  width: min(24rem, 92vw);
  padding: 0.5rem 0.8rem 0.6rem;
  background: linear-gradient(to bottom, var(--charcoal-2), var(--charcoal-3));
  border: 2px solid var(--plate-edge);
  clip-path: var(--corner-cut);
  text-align: left;
}

body.wrecker3d .board-head {
  font-family: var(--display);
  font-size: clamp(0.72rem, 2.2vw, 0.9rem);
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--amber);
  text-shadow: 0 2px 0 #000;
  margin-bottom: 0.3rem;
}

/* The board itself: an <ol> so ranking is structure, not decoration. */
body.wrecker3d .board-rows {
  margin: 0;
  padding: 0 0 0 1.6em;
  font-family: var(--mono);
  font-weight: 700;
  font-size: clamp(0.7rem, 2vw, 0.85rem);
  letter-spacing: 0.06em;
  color: var(--bone);
}
body.wrecker3d .board-row {
  display: flex;
  gap: 0.5em;
  align-items: baseline;
  line-height: 1.55;
}
body.wrecker3d .board-row::marker { color: var(--bone-dim); }
body.wrecker3d .board-handle {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
body.wrecker3d .board-rig { color: var(--bone-dim); font-size: 0.85em; }
body.wrecker3d .board-score { color: var(--amber); }

/* Account bar + strip buttons: the .btn steel in a size that sits inside a plate.
   min-height 32px on fine pointers, the 44px touch floor on coarse (below). */
body.wrecker3d .acct-small {
  font-size: 0.72rem;
  padding: 0.3rem 0.8rem;
  border-width: 2px;
  box-shadow: 0 3px 0 #000, inset 0 1px 0 rgba(232, 220, 200, 0.25);
}
body.wrecker3d .acct-small:active { transform: translateY(2px); box-shadow: 0 1px 0 #000; }
/* The armed ARE-YOU-SURE state (two-tap confirm on LOAD/CLEAR/overwrite-SAVE). */
body.wrecker3d .acct-small.armed {
  background: linear-gradient(to bottom, #e04a43, var(--blood) 55%, #7d191d);
  color: #fff;
}
body.wrecker3d .acct-who { display: inline-flex; align-items: center; gap: 0.3em; }

body.wrecker3d .cloud-slot {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.22rem 0;
}
body.wrecker3d .cloud-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--mono);
  font-weight: 700;
  font-size: clamp(0.68rem, 2vw, 0.8rem);
  letter-spacing: 0.06em;
}
body.wrecker3d .cloud-msg { min-height: 1.2em; margin: 0.15rem 0 0; }

/* The modal: same scrim-and-box construction as #shop3d / #garage3d. */
body.wrecker3d #account3d {
  position: absolute;
  inset: 0;
  z-index: 46;             /* above the title (40) and the CRT (30) */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  background: radial-gradient(90% 80% at 50% 45%, rgba(26, 21, 18, 0.9), rgba(15, 12, 10, 0.96));
}
body.wrecker3d .account-box { width: min(26rem, 94vw); }
body.wrecker3d .account-box form { display: flex; flex-direction: column; gap: 0.45rem; }
body.wrecker3d .account-box .g-input { flex: none; width: 100%; box-sizing: border-box; }
body.wrecker3d .account-box .g-input#acct-email3d,
body.wrecker3d .account-box .g-input#acct-pass3d { text-transform: none; }
body.wrecker3d .acct-hint { color: var(--bone-dim); letter-spacing: 0.04em; }
body.wrecker3d .acct-swap {
  font: inherit;
  background: none;
  border: 0;
  padding: 0;
  color: var(--amber);
  cursor: pointer;
  text-decoration: underline;
  letter-spacing: 0.06em;
}

/* Same compaction axis as the HUD and the enlistment pickers — never a third one. On a
   844x390 landscape phone the title column is already tight; the plates give up padding
   and the board caps its height rather than pushing ROLL OUT off screen. */
@media (pointer: coarse), (max-height: 560px) {
  body.wrecker3d .acct-plate { padding: 0.35rem 0.6rem 0.45rem; }
  body.wrecker3d .board-rows { max-height: 17svh; overflow-y: auto; }
  body.wrecker3d .acct-small { min-height: 40px; }
  body.wrecker3d .cloud-slot { padding: 0.12rem 0; }
}

/* ==========================================================================
   COMPACT PANELS SCROLL FROM THE TOP — deliberately the LAST rules in this
   sheet. A CENTERED flex column that overflows clips at BOTH ends and no
   scroll reaches the start (the device lab photographed the h1 decapitated
   on 844x390 — twice: the first fix for this sat at line ~516, BEFORE the
   base panel rule at ~692, and at equal specificity the later `center` won;
   the computed style showed overflow-y applied and justify-content ignored,
   which is how a half-winning rule looks). Position in the file is the
   mechanism here, so do not "tidy" these upward.
   ========================================================================== */
@media (pointer: coarse), (max-height: 560px) {
  body.wrecker3d #title3d,
  body.wrecker3d #gameover3d {
    justify-content: flex-start;
    overflow-y: auto;
    gap: 0.5rem;
  }
}
