/* =========================================================================
   BLENDISH — UI layer.  Owned by the ui-social lane.
   Dark, high-contrast, mobile-first. Pure CSS, no frameworks, no external
   fonts or assets — self-contained for an offline Android WebView build.

   Layout rules that are load-bearing, do not casually break them:
   · every tappable thing is >= 44px in BOTH axes
   · nothing is hover-only; :hover is decoration on top of a visible state
   · the hide toolbar owns ONE 56px row by default so the player's feet stay
     visible and paintable at 375x812 (CRITIQUE #10)
   · the gaze read lives in the screen EDGE, never over the player's body
   ========================================================================= */

:root {
  /* DIP TANK. A hazard-orange paint shop at night: the ground is cold and
     lavender-grey, and the one wet dangerous thing on the screen is the thing you
     are about to press.

     WHY ORANGE, in this game specifically. Orange is the one hue none of the seven
     rooms owns — not the teal brick, the gilt frame, the sepia rafters, the neon
     violet, the awning red, the cast iron or the cream tile. The chrome must never
     wear a colour a player is trying to match, so it takes the hue no room has. It
     is also the complement of the arcade backdrop, which is why the primary button
     belongs to the picture instead of floating in front of it.

     --signal and --accent are DIFFERENT COLOURS ON PURPOSE. --accent is gameplay
     (the gaze read, the score, "you are earning" — see the note at .gaze-frame) and
     must not move. --signal is the menu's primary action. They used to be one token
     doing seven jobs, which is the same as doing none. */
  --void: #05060a;
  --bg-1: #0a0b10;
  --bg-2: #10121a;
  --panel: rgba(23, 25, 34, 0.78);
  --panel-solid: #171922;
  --panel-2: #21242f;
  --panel-edge: rgba(255, 255, 255, 0.13);
  --panel-edge-hi: rgba(255, 255, 255, 0.26);
  --surface: #171922;
  --surface-raised: #21242f;
  --surface-sunk: #0b0c11;
  --plate: rgba(12, 13, 19, 0.82);
  --highlight: rgba(255, 255, 255, 0.22);
  --ink: #f5f1ea;             /* warm white — never pure #fff on a warm UI */
  --ink-dim: #a8adbd;
  --ink-faint: #7e8496;       /* was #6b7186 = 3.99:1, below AA. This is 4.7:1. */

  /* the primary action */
  --signal: #ff6a1f;
  --signal-hi: #ff9147;
  --signal-lo: #e14e08;
  --signal-wall: #8f3005;     /* the SIDE WALL. Never black: a black wall under a
                                 coloured face is a drop shadow, and reads as a card. */
  --signal-ink: #1a0a02;      /* 6.7:1 on the face. White would be 2.9:1 — do not. */
  --signal-glow: rgba(255, 106, 31, 0.34);

  --accent: #c6ff3d;          /* toxic chartreuse — GAMEPLAY ONLY: paint / go / score */
  --accent-ink: #0a1400;

  /* Radius encodes SIZE, not type: r = clamp(6px, height / 4, 24px). --r-pill is
     reserved for one job, a state readout you cannot press. If it takes a tap it is
     a rectangle. */
  --r-1: 6px; --r-2: 10px; --r-3: 14px; --r-4: 18px; --r-5: 24px;

  /* Four sizes and a floor, with brutal jumps between them. Fourteen sizes between
     0.6rem and 0.92rem is a documentation site; the differences were invisible and
     the result was mush. */
  --t-word: clamp(3.2rem, 16vw, 5.6rem);
  --t-shout: 1.75rem;
  --t-call: 1.05rem;
  --t-body: 0.875rem;
  --t-whisper: 0.6875rem;

  /* Three legal vertical gaps. Three gaps is what produces GROUPS; a uniform 10px
     between everything is what produces a list, and lists look like documents. */
  --gap-in: 8px; --gap-group: 20px; --gap-act: 32px; --pad-plate: 16px;

  /* The rooms' real colours, mirrored from js/ui.js MAP_ACCENT. Kept here too so a
     stylesheet-only change can reach them. */
  --map-laundry-a: #2fa3a0;  --map-laundry-b: #10393c;
  --map-gallery-a: #c9a227;  --map-gallery-b: #3b2a0e;
  --map-attic-a: #a9713f;    --map-attic-b: #2f1f10;
  --map-arcade-a: #ff4fa3;   --map-arcade-b: #2a1140;
  --map-market-a: #e2553c;   --map-market-b: #163a2e;
  --map-workshop-a: #5b8fb9; --map-workshop-b: #241e19;
  --map-subway-a: #e9d9b8;   --map-subway-b: #4a1f22;
  --danger: #ff3b5c;          /* seen / caught */
  --warn: #ffb23f;
  --cool: #5fc8ff;            /* found / hunter / info */
  --like: #ff5c8a;
  /* live gaze colour, written by js/ui.js: lime while a look is PAYING, red once suspicion
     is about to turn that look into a recognition. Default = the earning colour. */
  --gaze-rgb: 198, 255, 61;
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left: env(safe-area-inset-left, 0px);
  --safe-right: env(safe-area-inset-right, 0px);
  --tap: 44px;
  --quick-h: 56px;

  /* THE TOP-LEFT CORNER IS SHARED, SO ITS GEOMETRY IS PUBLISHED HERE.
     Three lanes anchor to this corner and all three used to lay themselves out from
     private constants: our .chrome-bar chip row, js/game.js's END MATCH chip, and
     js/game.js's WALL / LIE / CROUCH rail. Measured at 1920x950 and 1280x720, HIDE
     phase, live: END MATCH sat at x=14..99 y=10..40 and the mute and options chips at
     x=12..56 and x=64..108 y=12..56 — a 42x28 px and a 34x28 px overlap, i.e. two real
     buttons underneath a third one. These three numbers are the contract instead:
       --corner-btn-x / --corner-btn-y  where END MATCH goes (js/game.js reads them)
       --corner-bottom                  the first y anything else may start at
     js/game.js's layoutRails() reads --corner-bottom, so moving the chips here moves
     the rail with them and neither lane has to know the other's sizes. */
  /* 3 chips x 44px + 2 x 8px gap ends at x=160, so END MATCH starts at 168 and is
     centred on the 44px row (12 + (44 - 30) / 2 = 19). */
  --corner-btn-x: 168px;
  --corner-btn-y: 19px;
  --corner-bottom: 62px;
  /* ...and the first x free of that whole cluster on its own line. END MATCH is
     168 + 85 wide, so 262. The SEEK bar starts here; the HIDE bar drops below the
     cluster instead and only has to clear the rail. */
  --corner-right: 262px;
  font-size: 16px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0; padding: 0; width: 100%; height: 100%;
  background: var(--void);
  overflow: hidden;
  overscroll-behavior: none;
  touch-action: none;
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}

body {
  font-family: -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  color: var(--ink);
  cursor: default;
}

button, input, select { font-family: inherit; -webkit-user-select: none; user-select: none; }
input { -webkit-user-select: text; user-select: text; }
img, svg { -webkit-user-drag: none; pointer-events: none; }

button {
  border: none; background: none; color: inherit;
  cursor: pointer; touch-action: manipulation;
}
button:active { transform: translateY(1px) scale(0.985); }
button:disabled { opacity: 0.4; cursor: default; }
/* A 3px offset outline is the single most recognisable web artefact there is, and
   this one was in the gameplay lime, which means the keyboard focus ring and the
   "you are earning" colour were the same colour. A ring plus a glow, in --signal,
   reads as a selected object in a console UI and still passes 3:1 non-text contrast. */
button:focus-visible, input:focus-visible {
  outline: 2px solid var(--signal-hi); outline-offset: 3px;
  box-shadow: 0 0 0 4px rgba(255, 106, 31, 0.28);
}

#game {
  position: fixed; inset: 0; width: 100vw; height: 100vh;
  display: block; touch-action: none; background: var(--void);
}

#ui-root {
  position: fixed; inset: 0; z-index: 10;
  pointer-events: none;
}

/* HUD text over bright 3D — a real stroke, not a blurred shadow. */
.hud-text {
  -webkit-text-stroke: 3px rgba(0, 0, 0, 0.9);
  paint-order: stroke fill;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
  letter-spacing: 0.02em;
}

/* =========================================================================
   Persistent chrome (mute / options / quit)
   ========================================================================= */
.chrome-bar {
  position: fixed; z-index: 80;
  top: calc(12px + var(--safe-top));
  left: calc(12px + var(--safe-left));
  display: flex; gap: 8px;
  pointer-events: none;
}
.chip-btn {
  width: var(--tap); height: var(--tap);
  border-radius: 50%;
  background: var(--panel-solid);
  border: 1px solid var(--panel-edge);
  display: flex; align-items: center; justify-content: center;
  font-size: 1.05rem;
  pointer-events: auto;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.4);
}
.chip-btn.muted { opacity: 0.55; }
.chip-btn.quit { display: none; color: var(--ink-dim); }
.chip-btn.quit.on { display: flex; }

/* =========================================================================
   Options popover
   ========================================================================= */
.options-pop {
  position: fixed; z-index: 95;
  top: calc(66px + var(--safe-top));
  left: calc(12px + var(--safe-left));
  right: calc(12px + var(--safe-right));
  max-width: 420px;
  padding: 14px;
  border-radius: 18px;
  background: #0f0f17;
  border: 1px solid var(--panel-edge);
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.7);
  display: none; flex-direction: column; gap: 8px;
  pointer-events: auto;
}
.options-pop.open { display: flex; }
.options-title {
  font-size: 0.68rem; font-weight: 900; letter-spacing: 0.22em;
  color: var(--ink-faint);
}
.opt-row {
  display: flex; align-items: center; gap: 12px;
  width: 100%; min-height: var(--tap);
  padding: 10px 12px;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid transparent;
  text-align: left;
}
.opt-row.static { cursor: default; }
.opt-text { flex: 1 1 auto; min-width: 0; }
.opt-label { font-size: 0.86rem; font-weight: 700; color: var(--ink); }
.opt-blurb { font-size: 0.7rem; line-height: 1.35; color: var(--ink-faint); margin-top: 3px; }
.opt-input {
  flex: 0 0 auto; width: 120px; height: var(--tap);
  border-radius: 10px; padding: 0 10px;
  background: #05050a; color: var(--ink);
  border: 1px solid var(--panel-edge);
  font-size: 0.9rem; font-weight: 700;
}
.opt-switch {
  flex: 0 0 auto;
  width: 52px; height: 30px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid var(--panel-edge);
  position: relative;
  transition: background 160ms ease;
}
.opt-switch.as-btn { width: 56px; height: var(--tap); border-radius: 999px; }
.opt-knob {
  position: absolute; top: 50%; left: 3px;
  width: 24px; height: 24px; margin-top: -12px;
  border-radius: 50%;
  background: var(--ink-dim);
  transition: transform 160ms ease, background 160ms ease;
}
.opt-switch.as-btn .opt-knob { width: 34px; height: 34px; margin-top: -17px; left: 4px; }
.opt-row.on .opt-switch, .opt-switch.on { background: rgba(198, 255, 61, 0.28); }
.opt-row.on .opt-knob, .opt-switch.on .opt-knob { background: var(--accent); transform: translateX(22px); }
.opt-switch.as-btn.on .opt-knob { transform: translateX(14px); }
.mini-btn {
  flex: 0 0 auto;
  min-width: var(--tap); height: var(--tap); padding: 0 14px;
  border-radius: 10px;
  background: var(--panel-2); border: 1px solid var(--panel-edge);
  font-size: 0.72rem; font-weight: 800; letter-spacing: 0.1em; color: var(--ink-dim);
}

/* =========================================================================
   Screens
   ========================================================================= */
.screen {
  position: absolute; inset: 0;
  display: none; flex-direction: column;
  pointer-events: none;
  opacity: 0; transform: scale(1.012);
  transition: opacity 200ms ease, transform 200ms ease;
}
.screen.active { display: flex; opacity: 1; transform: scale(1); }
.screen button, .screen input, .screen .interactive { pointer-events: auto; }

.menu-scroll {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  pointer-events: auto;
  touch-action: pan-y;
  padding: calc(68px + var(--safe-top)) calc(18px + var(--safe-right))
           calc(28px + var(--safe-bottom)) calc(18px + var(--safe-left));
  display: flex; justify-content: center;
  /* flex-start, NOT stretch: a stretched column child gets its height clamped to the
     viewport and then every row inside it flex-SHRINKS. Measured at 375x812: settings
     rows collapsed from 44px to 20px and their controls drew over the next row. */
  align-items: flex-start;
}
/* Nothing inside a scrolling column may shrink — it scrolls instead. */
.menu-inner > *, .lobby-inner > *, .reveal-inner > *,
.settings-panel > *, .roster > *, .map-list > *,
.hunters-list > *, .players-list > *, .near-miss-list > *, .breakdown > * {
  flex: 0 0 auto;
}

/* =========================================================================
   MENU
   ========================================================================= */
#screen-menu, #screen-lobby, #screen-result {
  background:
    radial-gradient(120% 80% at 50% 0%, rgba(198, 255, 61, 0.07), transparent 58%),
    linear-gradient(180deg, #0b0b12 0%, #08080c 60%, #050508 100%);
}
#screen-result {
  background:
    radial-gradient(120% 80% at 50% 0%, rgba(95, 200, 255, 0.08), transparent 58%),
    linear-gradient(180deg, #0b0b12 0%, #08080c 60%, #050508 100%);
}

.menu-inner, .lobby-inner, .reveal-inner {
  width: 100%; max-width: 520px;
  display: flex; flex-direction: column; align-items: stretch;
  gap: 12px;
}
.menu-inner { align-items: center; text-align: center; }

.wordmark {
  font-size: clamp(2.6rem, 13vw, 4.2rem);
  font-weight: 900; letter-spacing: -0.03em; line-height: 1;
  margin: 0;
}
.wordmark .glyph-hide { color: var(--accent); text-shadow: 0 0 28px rgba(198, 255, 61, 0.5); }

.tagline {
  color: var(--ink-dim); font-size: 0.9rem; font-weight: 600;
  letter-spacing: 0.14em; margin: 0 0 4px;
}

.profile-chip {
  display: inline-flex; align-items: center; gap: 8px;
  height: var(--tap); padding: 0 14px 0 6px;
  border-radius: 999px;
  background: var(--panel); border: 1px solid var(--panel-edge);
  margin-bottom: 6px;
}
.profile-dot {
  width: 32px; height: 32px; border-radius: 50%;
  background: linear-gradient(135deg, var(--accent), #6bb32a);
  color: var(--accent-ink); font-weight: 900; font-size: 0.9rem;
  display: flex; align-items: center; justify-content: center;
}
.profile-name { font-size: 0.85rem; font-weight: 800; }
.profile-likes { font-size: 0.8rem; font-weight: 800; color: var(--like); display: inline-flex; gap: 3px; }
.heart { color: var(--like); }

.big-btn {
  width: 100%; min-height: 64px;
  padding: 12px 18px;
  border-radius: 18px;
  background: var(--panel-2);
  border: 1px solid var(--panel-edge);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
}
.big-btn.primary {
  background: linear-gradient(180deg, #d4ff54, #a8e02b);
  border-color: #d4ff54;
  color: var(--accent-ink);
  box-shadow: 0 8px 26px rgba(198, 255, 61, 0.28);
}
.big-btn.ghost { background: rgba(255, 255, 255, 0.04); }
.big-btn-main { font-size: 1.05rem; font-weight: 900; letter-spacing: 0.06em; }
.big-btn-sub { font-size: 0.7rem; font-weight: 600; letter-spacing: 0.04em; opacity: 0.72; }

.join-row { display: flex; gap: 10px; width: 100%; }
.join-input {
  flex: 1 1 auto; height: var(--tap);
  min-width: 0;
  border-radius: 14px; padding: 0 16px;
  background: #05050a; color: var(--ink);
  border: 1px solid var(--panel-edge);
  font-size: 1.2rem; font-weight: 900; letter-spacing: 0.4em; text-align: center;
}
.join-input::placeholder { color: var(--ink-faint); letter-spacing: 0.3em; font-weight: 700; }
.join-row .text-btn { flex: 0 0 auto; min-width: 92px; }
.text-btn.pulse { animation: btn-pulse 1.4s ease-in-out infinite; }
@keyframes btn-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(198, 255, 61, 0.5); }
  50% { box-shadow: 0 0 0 10px rgba(198, 255, 61, 0); }
}

.section-head {
  width: 100%;
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  margin-top: 12px; padding: 0 2px;
  font-size: 0.68rem; font-weight: 900; letter-spacing: 0.2em;
  color: var(--ink-dim);
  text-align: left;
}
.section-head.collapsible { min-height: var(--tap); align-items: center; }
.section-sub {
  font-size: 0.66rem; font-weight: 600; letter-spacing: 0.04em;
  color: var(--ink-faint); text-transform: none;
}
.section-head .chevron { transition: transform 200ms ease; font-size: 0.8rem; }
.section-head.closed .chevron { transform: rotate(-90deg); }

.map-list { width: 100%; display: flex; flex-direction: column; gap: 10px; }

/* Menu drawers. The home screen shows two buttons; CREATE/JOIN and the practice
   maps live in folds that are display:none until asked for, so the page a first
   time player lands on is short enough to read in one glance. Not max-height
   animated on purpose — a fold holding a variable number of map cards has no
   height we can hardcode, and a wrong guess clips the last card. */
.menu-fold { display: none; width: 100%; flex-direction: column; gap: 10px; }
.menu-fold.open { display: flex; animation: fold-in 180ms ease-out; }
@keyframes fold-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
.fold-label {
  margin: 2px 0 0; font-size: 0.68rem; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-faint); text-align: center;
}
.big-btn.expanded { border-color: var(--accent); }
@media (prefers-reduced-motion: reduce) { .menu-fold.open { animation: none; } }
.map-card {
  display: flex; align-items: center; gap: 14px;
  width: 100%; min-height: 72px;
  padding: 12px 16px; border-radius: 16px;
  background: var(--panel); border: 1px solid var(--panel-edge);
  text-align: left;
  transition: background 150ms ease, border-color 150ms ease, transform 150ms ease;
}
.map-card.selected { border-color: var(--accent); background: rgba(198, 255, 61, 0.08); }
.map-card-swatch {
  flex: 0 0 auto; width: 44px; height: 44px; border-radius: 10px;
  background: linear-gradient(135deg, var(--sw-a, #666), var(--sw-b, #333));
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12);
}
.map-card-text { flex: 1 1 auto; min-width: 0; }
.map-card-name { font-size: 1rem; font-weight: 800; margin: 0 0 2px; }
.map-card-blurb {
  font-size: 0.78rem; color: var(--ink-dim); margin: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.map-card-go {
  flex: 0 0 auto; width: 40px; height: 40px; border-radius: 50%;
  background: var(--accent); color: var(--accent-ink);
  display: flex; align-items: center; justify-content: center;
  font-weight: 900; font-size: 1.05rem;
}
.menu-empty { color: var(--ink-faint); font-size: 0.85rem; padding: 20px 0; }
.menu-footer { margin-top: 16px; font-size: 0.66rem; color: var(--ink-faint); letter-spacing: 0.1em; }

.text-btn {
  min-width: var(--tap); height: var(--tap); padding: 0 16px;
  border-radius: 12px;
  background: var(--panel-2); border: 1px solid var(--panel-edge);
  font-weight: 800; font-size: 0.8rem; letter-spacing: 0.05em;
  color: var(--ink);
  display: flex; align-items: center; justify-content: center; gap: 6px;
}
.text-btn.primary { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.text-btn.wide { width: 100%; }
.text-btn.learn { color: var(--ink-dim); border-style: dashed; }
.text-btn.share {
  background: linear-gradient(135deg, rgba(198, 255, 61, 0.16), rgba(198, 255, 61, 0.05));
  border-color: rgba(198, 255, 61, 0.4); color: var(--accent);
}

/* =========================================================================
   LOBBY
   ========================================================================= */
.lobby-status {
  width: 100%; padding: 10px 14px; border-radius: 12px;
  font-size: 0.76rem; font-weight: 700; line-height: 1.4;
  background: rgba(255, 255, 255, 0.05); color: var(--ink-dim);
  border: 1px solid var(--panel-edge);
}
.lobby-status.good { color: var(--accent); border-color: rgba(198, 255, 61, 0.3); }
.lobby-status.warn { color: var(--warn); border-color: rgba(255, 178, 63, 0.3); }
.lobby-status.bad { color: var(--danger); border-color: rgba(255, 59, 92, 0.35); }
.lobby-status.offline { color: var(--cool); border-color: rgba(95, 200, 255, 0.28); }

.code-card {
  width: 100%; padding: 16px; border-radius: 18px;
  background: var(--panel); border: 1px solid var(--panel-edge);
  display: flex; flex-direction: column; align-items: center; gap: 8px;
}
.code-label { font-size: 0.64rem; font-weight: 900; letter-spacing: 0.24em; color: var(--ink-faint); }
.code-value {
  font-size: clamp(2.2rem, 14vw, 3rem); font-weight: 900;
  letter-spacing: 0.22em; color: var(--accent); line-height: 1;
  text-shadow: 0 0 26px rgba(198, 255, 61, 0.35);
  padding-left: 0.22em;
}
.code-link {
  font-size: 0.74rem; color: var(--ink-dim);
  -webkit-user-select: text; user-select: text;
  word-break: break-all; text-align: center;
}

.roster { width: 100%; display: flex; flex-direction: column; gap: 8px; }
.roster-row, .player-row {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px; border-radius: 14px;
  background: rgba(255, 255, 255, 0.035);
  border: 1px solid var(--panel-edge);
  min-height: 64px;
}
.roster-row.you, .player-row.you { border-color: rgba(198, 255, 61, 0.4); background: rgba(198, 255, 61, 0.06); }

.roster-thumb {
  flex: 0 0 auto; width: 44px; height: 56px;
  border-radius: 8px; overflow: hidden;
  background: #1b1b26;
  border: 1px solid var(--panel-edge);
  position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
  padding-top: 6px; gap: 3px;
}
.roster-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.roster-thumb.flat { background: #2a2a35; }
.roster-thumb .thumb-head { width: 14px; height: 14px; border-radius: 50%; background: #4a4a58; }
.roster-thumb .thumb-torso { width: 22px; height: 26px; border-radius: 6px; background: #4a4a58; }
.roster-thumb.masked::after {
  content: 'HIDDEN'; position: absolute; inset: auto 0 0 0;
  font-size: 0.44rem; font-weight: 900; letter-spacing: 0.08em;
  color: var(--ink-faint); background: rgba(0, 0, 0, 0.6); text-align: center; padding: 2px 0;
}

.roster-meta { flex: 1 1 auto; min-width: 0; }
.roster-name {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  font-size: 0.88rem; font-weight: 800;
}
.roster-sub { display: flex; align-items: center; gap: 6px; margin-top: 5px; flex-wrap: wrap; }
.roster-likes { font-size: 0.74rem; font-weight: 800; color: var(--like); }
.tag {
  font-size: 0.56rem; font-weight: 900; letter-spacing: 0.1em;
  padding: 2px 6px; border-radius: 5px;
}
.you-tag { background: var(--accent); color: var(--accent-ink); }
.bot-tag { background: rgba(255, 255, 255, 0.12); color: var(--ink-dim); }
.role-pill, .ready-pill, .found-pill, .score-pill {
  font-size: 0.6rem; font-weight: 900; letter-spacing: 0.1em;
  padding: 4px 8px; border-radius: 999px;
  border: 1px solid var(--panel-edge); color: var(--ink-faint);
}
.role-pill.hunter { color: var(--danger); border-color: rgba(255, 59, 92, 0.35); }
.role-pill.hider { color: var(--cool); border-color: rgba(95, 200, 255, 0.3); }
.ready-pill.on { color: var(--accent); border-color: rgba(198, 255, 61, 0.4); background: rgba(198, 255, 61, 0.1); }
.found-pill.found { color: var(--cool); border-color: rgba(95, 200, 255, 0.4); }
.found-pill.unfound { color: var(--danger); border-color: rgba(255, 59, 92, 0.4); }
.score-pill { color: var(--ink); }

.settings-panel {
  width: 100%;
  display: flex; flex-direction: column; gap: 6px;
  overflow: hidden;
  max-height: 2000px;
  transition: max-height 260ms ease, opacity 200ms ease;
}
.settings-panel.collapsed { max-height: 0; opacity: 0; }
.settings-panel.readonly { opacity: 0.55; pointer-events: none; }
.set-row {
  display: flex; align-items: center; gap: 12px;
  padding: 8px 12px; border-radius: 12px;
  background: rgba(255, 255, 255, 0.03);
  min-height: var(--tap);
}
.set-row.disabled { opacity: 0.35; pointer-events: none; }
.set-text { flex: 1 1 auto; min-width: 0; text-align: left; }
.set-label { font-size: 0.82rem; font-weight: 700; }
.set-hint { font-size: 0.66rem; color: var(--ink-faint); line-height: 1.35; margin-top: 2px; }
.set-ctl { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; }
.set-ctl.wrap { flex-wrap: wrap; justify-content: flex-end; max-width: 62%; }
.step-btn {
  width: var(--tap); height: var(--tap); border-radius: 10px;
  background: var(--panel-2); border: 1px solid var(--panel-edge);
  font-size: 1.2rem; font-weight: 900; color: var(--ink);
  display: flex; align-items: center; justify-content: center;
}
.step-value {
  min-width: 56px; text-align: center;
  font-size: 0.85rem; font-weight: 900; color: var(--accent);
}
.choice-btn {
  min-height: var(--tap); padding: 0 12px; border-radius: 10px;
  background: var(--panel-2); border: 1px solid var(--panel-edge);
  font-size: 0.74rem; font-weight: 800; color: var(--ink-dim);
}
.choice-btn.on { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }

.lobby-actions { display: flex; flex-direction: column; gap: 10px; margin-top: 14px; }

/* =========================================================================
   Shared top HUD (hide + seek)
   ========================================================================= */
/* The right-hand strip of the screen belongs to js/game.js's injected action column
   (I'M HIDDEN / PAINT / LOOK / POSE / EMOTE / WHISTLE / CLONE / TWIST) and the left to our
   own chrome chips, so the HUD bar keeps clear of both. */
/* Three 44px chips + gaps end at x=160 and END MATCH runs to x=252, so the top row starts
   after them — see --corner-right. Measured: at x=112 the quit chip sat directly on top of
   the timer digits, and at x=168 the seek clock sat under END MATCH (55.6 x 28 px). */
.hud-top {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: calc(14px + var(--safe-top)) calc(14px + var(--safe-right)) 0 calc(var(--corner-right) + var(--safe-left));
  pointer-events: none;
}
/* In HIDE the top-right corner is js/game.js's I'M HIDDEN button, so 375px minus the chips
   minus that button leaves ~80px — not enough for a timer. The hide row drops to its own
   band under the chips instead, left of the action column. */
/* left:10px put the countdown underneath js/game.js's left button rail at every viewport
   that is not a narrow phone — measured at 710x334, the timer digits ran x=8..107 straight
   through the WALL button at x=14..70 (CRITIQUE §12, which saw the same collision at
   375x812). The rail owns x=14..~100 for the whole hide phase, so the band starts after it. */
/* right:112px left this band 1.1 px wider than the space js/game.js's right rail leaves
   (measured at 1280x720: band ended at x=1168, the PAINT button starts at x=1166.9). The
   widest button in that rail is RESET VIEW at 113px, plus its own 14px margin. */
#screen-hide .hud-top {
  position: absolute;
  top: calc(60px + var(--safe-top)); left: calc(110px + var(--safe-left));
  right: calc(132px + var(--safe-right));
  padding: 0; gap: 12px;
}
#screen-hide .hide-hint { margin-top: calc(106px + var(--safe-top)); max-width: calc(100% - 240px); }
/* The clock and its caption are ONE readout: see js/ui.js's note on why the HIDE phase
   pill became a caption. Borderless and dim on purpose — every control in this game is a
   filled pill or a chip, so a bare grey label cannot be read as one. */
.clock-stack { display: flex; flex-direction: column; align-items: flex-start; gap: 1px; }
.clock-caption {
  font-size: 0.6rem; font-weight: 800; letter-spacing: 0.16em;
  color: var(--ink-dim); text-transform: uppercase;
  background: none; border: 0; padding: 0; pointer-events: none;
  /* .hud-text's 3px stroke closes up letters this small; same 2px as .timer-unit. */
  -webkit-text-stroke: 2px rgba(0, 0, 0, 0.9);
}
.timer-pill {
  display: flex; align-items: baseline; gap: 4px;
  font-weight: 900; font-size: 1.55rem; color: var(--ink);
}
.timer-pill.warn { color: var(--warn); }
.timer-pill.critical { color: var(--danger); animation: pulse-danger 0.6s infinite; }
.timer-unit { font-size: 0.8rem; font-weight: 700; color: var(--ink-dim); -webkit-text-stroke: 2px rgba(0,0,0,.9); }
@keyframes pulse-danger { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }

.phase-pill {
  padding: 8px 14px; border-radius: 999px;
  background: var(--panel); border: 1px solid var(--panel-edge);
  font-size: 0.66rem; font-weight: 900; letter-spacing: 0.13em;
  color: var(--ink-dim); text-align: center;
}
.phase-pill.hide { color: var(--accent); border-color: rgba(198, 255, 61, 0.35); }
.phase-pill.seek { color: var(--danger); border-color: rgba(255, 59, 92, 0.35); }

/* =========================================================================
   HIDE PHASE
   ========================================================================= */
/* flex-start + margin-top:auto on the toolbar. With space-between the hint pill floated to
   the vertical middle of the screen, straight across the player's own body. */
#screen-hide { justify-content: flex-start; }
/* Same trap as .menu-scroll: these are flex children of a full-height column, so without
   an explicit no-shrink the expanded toolbar sheet is clamped back to its collapsed height
   and the expand button silently does nothing. Measured, not assumed. */
#screen-hide > *, #screen-seek > .hud-top, #screen-seek > .seek-bottom { flex: 0 0 auto; }

.hide-hint {
  align-self: center; margin-top: 8px;
  padding: 8px 16px; border-radius: 999px;
  background: var(--panel); border: 1px solid var(--panel-edge);
  font-size: 0.74rem; color: var(--ink-dim); text-align: center;
  max-width: calc(100% - 32px);
}
.hide-hint b { color: var(--accent); }

.hide-toolbar {
  pointer-events: auto;
  /* margin-top:auto pins it to the bottom of the flex column; #screen-hide is
     justify-content:flex-start so the hint pill stays up near the timer instead of
     floating into the middle of the screen across the player's own body. */
  margin: auto auto 0;
  width: min(680px, 100%);
  display: flex; flex-direction: column;
  background: var(--panel);
  border-top: 1px solid var(--panel-edge);
  border-radius: 20px 20px 0 0;
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  overflow: hidden;
}

/* ---- the surface bars: this map's real wall / furniture / floor colours ---- */
.surface-bars {
  flex: 0 0 auto;
  display: flex; align-items: stretch;
  height: 30px;
  gap: 2px;
  padding: 4px 10px 0;
}
.surface-bar {
  flex: 1 1 0; min-width: 0;
  position: relative;
  border: 1px solid rgba(0, 0, 0, 0.35);
  border-radius: 5px;
  padding: 0;
  cursor: pointer;
  /* the bars butt together into one continuous band of the room's colours, so the
     eye can compare them against each other the way it will have to in the room */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14);
}
/* The bar you just tapped has to be unmistakable from across the room: a 2px ring at 55%
   opacity was not, and "did my tap even land" is the whole of the owner's picker report. */
.surface-bar.selected {
  border-color: #fff;
  box-shadow: 0 0 0 3px var(--accent), 0 0 10px rgba(198, 255, 61, 0.5);
  z-index: 1;
}
.surface-bar.selected::after {
  content: '✓';
  position: absolute; top: 1px; right: 3px;
  font: 900 9px/1 system-ui, sans-serif;
  color: var(--accent);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9);
}

/* ---- the loaded colour: the picker's answer, in the colour itself ---- */
.active-paint {
  flex: 0 0 auto;
  height: 34px;
  margin: 5px 10px 0;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid rgba(255, 255, 255, 0.26);
  border-radius: 8px;
  padding: 0 10px;
  cursor: pointer;
  background: #fff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18);
}
.ap-text {
  font: 800 10px/1 system-ui, sans-serif; letter-spacing: 0.06em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sb-label {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font: 800 8px/1 system-ui, sans-serif; letter-spacing: 0.07em;
  pointer-events: none;
  overflow: hidden; white-space: nowrap;
}

/* ---- minimise: stow the whole bar, leave one tab behind ---- */
.hide-toolbar {
  transition: transform 220ms ease;
}
.hide-toolbar.stowed {
  transform: translateY(110%);
  pointer-events: none;
}
.toolbar-restore {
  position: absolute; left: 50%; bottom: calc(10px + var(--safe-bottom));
  transform: translateX(-50%) translateY(140%);
  display: flex; align-items: center; gap: 8px;
  height: 38px; padding: 0 16px;
  border-radius: 999px;
  background: var(--panel); border: 1px solid var(--panel-edge);
  color: var(--ink); font: 800 11px/1 system-ui, sans-serif; letter-spacing: 0.07em;
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  opacity: 0; pointer-events: none;
  transition: transform 220ms ease, opacity 180ms ease;
}
.toolbar-restore.on { transform: translateX(-50%) translateY(0); opacity: 1; pointer-events: auto; }
.tr-dot {
  width: 16px; height: 16px; border-radius: 50%;
  background: #fff; border: 2px solid rgba(255, 255, 255, 0.5);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.4);
}

/* ---- the always-visible single row (the mobile fix) ---- */
.toolbar-quick {
  flex: 0 0 auto;
  height: var(--quick-h);
  display: flex; align-items: center; gap: 8px;
  padding: 0 10px;
}
.quick-color {
  flex: 0 0 auto; width: var(--tap); height: var(--tap);
  border-radius: 12px;
  background: var(--panel-2); border: 1px solid var(--panel-edge);
  display: flex; align-items: center; justify-content: center;
}
.quick-color-dot {
  width: 26px; height: 26px; border-radius: 50%;
  background: #fff;
  border: 2px solid rgba(255, 255, 255, 0.5);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.4);
}
/* CRITIQUE §8: `▲ MORE` sat over a live colour swatch — elementFromPoint on the last
   visible chip returned the expander, so the swatch under your finger could not be picked
   and the tray opened instead. The strip now CLIPS (a scrolled-away swatch kept its layout
   box out past the wrap's edge, straight under the expander) and the expander sits in its
   own column behind a divider, so the two can never share a pixel. */
.quick-scroll-wrap {
  flex: 1 1 auto; position: relative; min-width: 0; height: var(--tap);
  overflow: hidden;
}
.swatch-track {
  display: flex; gap: 8px; align-items: center;
  height: 100%;
  /* room for the right-hand fade, so the last swatch can always be scrolled fully clear
     of it and tapped on its own centre */
  padding-right: 26px;
  scroll-padding-inline-end: 26px;
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  touch-action: pan-x;
  scrollbar-width: none;
}
.swatch-track::-webkit-scrollbar { display: none; }
/* Fade affordances: the old 208px scroller with 456px of swatches gave no hint that
   half the palette existed (CRITIQUE #10). */
.scroll-fade {
  position: absolute; top: 0; bottom: 0; width: 26px;
  pointer-events: none; opacity: 0; transition: opacity 160ms ease;
}
.scroll-fade.on { opacity: 1; }
.scroll-fade.left { left: 0; background: linear-gradient(90deg, #12121a 20%, transparent); }
.scroll-fade.right { right: 0; background: linear-gradient(270deg, #12121a 20%, transparent); }
.scroll-more {
  position: absolute; right: 2px; top: 50%; transform: translateY(-50%);
  font-size: 0.6rem; font-weight: 900; color: var(--ink-dim);
  background: rgba(0, 0, 0, 0.6); border-radius: 999px; padding: 3px 6px;
  pointer-events: none; opacity: 0; transition: opacity 160ms ease;
}
.scroll-more.on { opacity: 1; }

.swatch {
  flex: 0 0 auto;
  width: var(--tap); height: var(--tap);
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.2);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.3);
  scroll-snap-align: start;
}
.swatch.selected { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(198, 255, 61, 0.3); }

.icon-btn {
  flex: 0 0 auto; width: var(--tap); height: var(--tap);
  border-radius: 12px;
  background: var(--panel-2); border: 1px solid var(--panel-edge);
  display: flex; align-items: center; justify-content: center;
  font-size: 1.1rem; color: var(--ink);
}
.icon-btn.active { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
/* Was a 0.8rem dim-grey ▲ with no label, hiding FILL, UNDO, the picker, the brush size and
   every pose behind it (PAINT-DIAGNOSIS #4). It is now a labelled control, and the four
   things that actually matter no longer live behind it at all. */
/* MORE and HIDE are the same KIND of control and must be the same SIZE. They were not:
   these rules were scoped to `.expander` only, so the stow button — same markup, class
   `.stow` — fell through to the bare `.icon-btn` rule and rendered its "▾ HIDE" at
   1.1rem inside a fixed 44px box, i.e. roughly twice the size of "▲ MORE" beside it and
   wide enough to spill out of the toolbar. Both classes are listed here now. */
.icon-btn.expander,
.icon-btn.stow {
  width: auto; min-width: var(--tap); padding: 0 10px;
  flex-direction: column; gap: 1px;
  font-size: 0.62rem; color: var(--ink);
  /* its own column, visually separated from the swatch strip it used to sit on top of */
  margin-left: 2px;
  border-left: 1px solid var(--panel-edge);
}
.icon-btn.expander .ex-glyph,
.icon-btn.stow .ex-glyph { font-size: 0.72rem; line-height: 1; }
.icon-btn.expander .ex-word,
.icon-btn.stow .ex-word { font-size: 0.52rem; font-weight: 900; letter-spacing: 0.08em; color: var(--ink-dim); }

/* ---- ROW ONE: the permanently visible tools ---- */
.toolbar-tools {
  flex: 0 0 auto;
  /* wraps: the row carries STEAL/FILL/UNDO/brush/zoom AND the character-size stepper, and
     on a 375px phone that is one item too many for a single line. Wrapping costs one row
     of height on small screens and keeps every control on screen, which is the trade the
     owner asked for when they went looking for SIZE and could not find it. */
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px; row-gap: 6px;
  padding: 0 10px 6px;
}
.tool-btn {
  flex: 0 0 auto;
  min-width: var(--tap); height: var(--tap); padding: 0 6px;
  border-radius: 12px;
  background: var(--panel-2); border: 1px solid var(--panel-edge);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
  color: var(--ink);
}
.tool-btn .tb-glyph { font-size: 1.02rem; line-height: 1; }
.tool-btn .tb-word { font-size: 0.5rem; font-weight: 900; letter-spacing: 0.07em; color: var(--ink-dim); }
.tool-btn.wide { padding: 0 14px; }
.tool-btn.zoom { min-width: 38px; }
.tool-btn.zoom .tb-glyph { font-size: 1.35rem; font-weight: 900; }
.tool-btn.active,
.tool-btn.active .tb-word { background: var(--accent); color: var(--accent-ink); }
.tool-btn.active { border-color: var(--accent); }
/* The eyedropper is the strongest single move in the game — it is allowed to look it. */
.tool-btn.eyedrop { border-color: rgba(111, 195, 255, 0.5); }
.tool-btn.eyedrop.active { background: #6fc3ff; border-color: #6fc3ff; }
.tool-btn.eyedrop.active .tb-word { color: #08121e; }
.brush-slider { min-width: 54px; }

/* ---- the expandable sheet ---- */
.toolbar-body {
  display: flex; flex-direction: column; gap: 10px;
  padding: 4px 10px calc(10px + var(--safe-bottom));
  max-height: 52vh; overflow-y: auto; overscroll-behavior: contain;
  transition: max-height 240ms ease, opacity 180ms ease, padding 240ms ease;
  opacity: 1;
}
.hide-toolbar.collapsed .toolbar-body {
  max-height: 0; opacity: 0; padding-top: 0; padding-bottom: 0; pointer-events: none;
}
.toolbar-row { display: flex; align-items: center; gap: 10px; }

/* ---- the colour wheel ---- */
.color-wheel-wrap { flex: 0 0 auto; display: flex; flex-direction: column; gap: 6px; align-items: center; }
.color-wheel {
  position: relative; flex: 0 0 auto;
  width: 116px; height: 116px;
  border-radius: 50%; overflow: hidden;
  box-shadow: inset 0 0 0 2px rgba(255,255,255,0.16), 0 4px 14px rgba(0,0,0,0.45);
  touch-action: none;
}
.color-wheel canvas { width: 100%; height: 100%; display: block; touch-action: none; }
.light-wrap { width: 116px; display: flex; align-items: center; }
.light-slider { height: 28px; }
.light-slider::-webkit-slider-runnable-track {
  background: linear-gradient(90deg, #000 0%, #808080 50%, #fff 100%);
}
.light-slider::-moz-range-track {
  background: linear-gradient(90deg, #000 0%, #808080 50%, #fff 100%);
}
.grid-col { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.mini-label {
  font-size: 0.52rem; font-weight: 900; letter-spacing: 0.14em; color: var(--ink-dim);
}
.mini-label.inline { flex: 0 0 auto; }
.mini-read { flex: 0 0 auto; font-size: 0.56rem; font-weight: 900; letter-spacing: 0.1em; color: var(--ink); min-width: 30px; }
.slider-labelled { flex: 1 1 auto; display: flex; align-items: center; gap: 8px; min-height: var(--tap); }
.swatch-sep { flex: 0 0 auto; width: 1px; height: 26px; background: rgba(255,255,255,0.22); }
.swatch.recent { border-color: rgba(198, 255, 61, 0.45); }
.swatch-grid.recent { grid-template-columns: repeat(auto-fill, minmax(34px, 1fr)); }
.swatch-grid.recent .swatch { height: 34px; }
.color-picker-cursor {
  position: absolute; width: 16px; height: 16px;
  margin-left: -8px; margin-top: -8px;
  border-radius: 50%; border: 2px solid #fff;
  box-shadow: 0 0 0 1px rgba(0,0,0,.7), 0 2px 6px rgba(0,0,0,.5);
  pointer-events: none;
}

/* Every swatch visible at 375px — a wrapping grid, not a clipped scroller. */
.swatch-grid {
  flex: 1 1 auto;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--tap), 1fr));
  gap: 8px;
  align-content: start;
}
.swatch-grid .swatch { width: 100%; height: var(--tap); }

.brush-size-wrap { flex: 1 1 auto; display: flex; align-items: center; gap: 8px; min-height: var(--tap); }
.brush-dot { flex: 0 0 auto; width: 20px; height: 20px; border-radius: 50%; background: var(--ink); opacity: .85; }

input[type="range"] {
  -webkit-appearance: none; appearance: none;
  flex: 1 1 auto; height: var(--tap); background: transparent; touch-action: pan-x;
}
input[type="range"]::-webkit-slider-runnable-track { height: 6px; border-radius: 999px; background: rgba(255,255,255,0.16); }
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; width: 28px; height: 28px; margin-top: -11px;
  border-radius: 50%; background: var(--accent); border: 3px solid var(--accent-ink);
  box-shadow: 0 2px 8px rgba(0,0,0,.4);
}
input[type="range"]::-moz-range-track { height: 6px; border-radius: 999px; background: rgba(255,255,255,0.16); }
input[type="range"]::-moz-range-thumb { width: 28px; height: 28px; border-radius: 50%; background: var(--accent); border: 3px solid var(--accent-ink); }

/* WRAPS, does not scroll. The hider lane now ships 13 poses; in a 355px horizontal
   scroller 8 of them were off-screen with no affordance — the exact defect the swatch
   strip had. The sheet already scrolls vertically, so let them wrap into it. */
.pose-track {
  flex: 1 1 auto; display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  min-height: var(--tap);
}
/* SIZE — a stepper, not a slider: the range is 0.72x..1.28x in 0.04 steps, so a discrete
   nudge with a readout is honest about how coarse it is, and it survives a 375px row. */
/* Its own outlined pill at the head of the row, so it does not read as another paint
   button and cannot be mistaken for the brush-size slider further along. */
.size-wrap {
  flex: 0 0 auto; display: flex; align-items: center; gap: 6px;
  min-height: var(--tap);
  padding: 0 8px 0 10px;
  border-radius: 12px;
  border: 1px solid rgba(198, 255, 61, 0.45);
  background: rgba(198, 255, 61, 0.09);
}
.size-wrap .mini-label.inline { color: var(--accent); letter-spacing: .08em; }
.tool-btn.size { min-width: 40px; width: 40px; padding: 0; }
.mini-read.size { min-width: 48px; text-align: center; font-weight: 900; color: #fff; }

.pose-btn {
  flex: 0 0 auto; min-width: var(--tap); height: var(--tap); padding: 0 14px;
  border-radius: 12px;
  background: var(--panel-2); border: 1px solid var(--panel-edge);
  font-size: 0.78rem; font-weight: 700; text-transform: capitalize; color: var(--ink-dim);
}
.pose-btn.selected { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }

/* =========================================================================
   SEEK PHASE
   ========================================================================= */
#screen-seek { justify-content: space-between; align-items: stretch; }

/* THE GAZE FRAME — edge treatment. Never over the player's body.
   COLOUR SEMANTICS (this is the core rule, drawn): being looked at is where the money is,
   so a lock with low suspicion glows LIME — the same colour as the score and the same
   colour the onboarding used for "+12/s". It only slides through amber to red as suspicion
   climbs toward recognition. A permanently red frame taught the opposite of the game.
   `--gaze-rgb` is set from js/ui.js on every setGaze/setSuspicion. */
.gaze-frame {
  position: absolute; inset: 0; pointer-events: none;
  --gaze-strength: 0.5;
}
.gaze-frame-inner {
  position: absolute; inset: 0;
  box-shadow: inset 0 0 0 0 rgba(255, 59, 92, 0);
  transition: box-shadow 140ms ease;
}
.gaze-frame.near .gaze-frame-inner {
  box-shadow: inset 0 0 0 5px rgba(255, 178, 63, 0.4), inset 0 0 44px rgba(255, 178, 63, 0.12);
}
.gaze-frame.locked .gaze-frame-inner {
  box-shadow:
    inset 0 0 0 calc(6px + 8px * var(--gaze-strength)) rgba(var(--gaze-rgb), 0.85),
    inset 0 0 calc(60px + 90px * var(--gaze-strength)) rgba(var(--gaze-rgb), 0.32);
  animation: gaze-frame-breathe 0.7s ease-in-out infinite alternate;
}
@keyframes gaze-frame-breathe { from { filter: brightness(0.86); } to { filter: brightness(1.18); } }

/* The gaze banner is NOT a flex sibling of the timer and score. Measured at 375x812 it was
   left with 137px between them and rasterised as one word per line, on top of both. It is a
   full-width strip of its own, tucked under the top row and to the LEFT of js/game.js's
   injected action column (which owns roughly x>276, y>64). */
/* Found while measuring the END MATCH collision, same class of bug: both of these ran
   from x=8 to the right rail, i.e. straight across js/game.js's left rail. Measured at
   1280x720, SEEK phase: the gaze banner covered WALL by 64x22 px, the suspicion meter
   covered LIE by 46x22 px, and the banner's right edge crossed the PAINT button by
   2.8 px. Same two numbers the hide clock band already uses: the left rail owns
   x=14..~100 and the widest right-rail button is RESET VIEW at 113px + 14px margin. */
.gaze-banner {
  position: absolute;
  top: calc(62px + var(--safe-top));
  left: calc(110px + var(--safe-left));
  right: calc(132px + var(--safe-right));
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  pointer-events: none;
}
.gaze-banner-text {
  font-size: clamp(0.72rem, 3.4vw, 0.95rem); font-weight: 900; letter-spacing: 0.1em;
  color: var(--ink-dim); text-align: center;
  transition: color 120ms ease;
}
.gaze-banner-sub {
  font-size: 0.62rem; font-weight: 700; letter-spacing: 0.06em; color: var(--ink-faint);
  text-align: center; -webkit-text-stroke: 2px rgba(0,0,0,.85); paint-order: stroke fill;
}
.gaze-banner.near .gaze-banner-text { color: var(--warn); }
.gaze-banner.locked .gaze-banner-text {
  color: #fff; text-shadow: 0 0 18px rgba(var(--gaze-rgb, 198, 255, 61), 0.95);
  animation: gaze-text-pop 0.5s ease-out;
}
/* the earning read: while a lock is paying out, the sub-line is the score colour */
.gaze-banner.locked.earning .gaze-banner-sub { color: var(--accent); }
@keyframes gaze-text-pop { 0% { transform: scale(1.22); } 100% { transform: scale(1); } }

.seek-score { position: relative; display: flex; flex-direction: column; align-items: flex-end; pointer-events: none; }
.score-value { font-size: 1.9rem; font-weight: 900; color: var(--accent); line-height: 1; }
.score-value.ticking { color: #fff; text-shadow: 0 0 18px rgba(198, 255, 61, 0.9); }
.score-label { font-size: 0.58rem; letter-spacing: 0.18em; color: var(--ink-dim); -webkit-text-stroke: 2px rgba(0,0,0,.9); }

.score-fly {
  position: absolute; right: 0; top: -4px;
  font-size: 1.05rem; font-weight: 900; color: var(--accent);
  pointer-events: none; animation: score-fly-anim 620ms ease-out forwards;
}
@keyframes score-fly-anim {
  0% { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(-30px); }
}

/* SUSPICION — chunky segments + a sentence, legible at a glance. */
.suspicion-wrap {
  position: absolute;
  top: calc(112px + var(--safe-top));
  left: calc(110px + var(--safe-left));
  right: calc(132px + var(--safe-right));
  max-width: 360px;
  pointer-events: none;
  display: flex; flex-direction: column; align-items: stretch; gap: 6px;
}
.suspicion-segs { display: flex; gap: 4px; width: 100%; }
.susp-seg {
  flex: 1 1 0; height: 12px; border-radius: 3px;
  background: rgba(255, 255, 255, 0.10);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.6);
  transition: background 120ms linear, box-shadow 120ms linear;
}
.susp-seg.on { background: var(--warn); box-shadow: 0 0 10px rgba(255, 178, 63, 0.6); }
.susp-seg.on.hot { background: var(--danger); box-shadow: 0 0 14px rgba(255, 59, 92, 0.8); }
.suspicion-caption {
  font-size: 0.66rem; font-weight: 900; letter-spacing: 0.14em; color: var(--ink-dim);
  -webkit-text-stroke: 3px rgba(0,0,0,.9); paint-order: stroke fill; text-align: center;
}
.suspicion-wrap.critical .suspicion-caption { color: var(--danger); animation: pulse-danger 0.5s infinite; }

/* off-screen hunter arrows */
.marker-layer { position: absolute; inset: 0; pointer-events: none; }
.hunter-marker {
  position: absolute; transform: translate(-50%, -50%);
  display: none; flex-direction: column; align-items: center; gap: 2px;
}
.hunter-marker.on { display: flex; }
.hm-arrow {
  width: 0; height: 0;
  border-left: 11px solid transparent;
  border-right: 11px solid transparent;
  border-bottom: 20px solid var(--warn);
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.9));
}
.hunter-marker.close .hm-arrow { border-bottom-color: var(--danger); animation: pulse-danger 0.5s infinite; }
.hm-dist {
  font-size: 0.62rem; font-weight: 900; color: var(--ink);
  -webkit-text-stroke: 3px rgba(0,0,0,.9); paint-order: stroke fill;
}

/* The bottom-centre strip is taken by js/netbridge.js's PLAY ONLINE pill, so the phase
   line sits above it and the whistle takes the free bottom-left corner. */
.seek-bottom {
  display: flex; align-items: center; justify-content: center;
  padding: 0 90px calc(86px + var(--safe-bottom));
  pointer-events: none;
}
.whistle-btn {
  position: absolute;
  left: calc(14px + var(--safe-left));
  bottom: calc(16px + var(--safe-bottom));
  width: 60px; height: 60px; border-radius: 50%;
  background: var(--panel-2); border: 1px solid var(--panel-edge);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
  color: var(--warn);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.5);
}
.whistle-glyph { font-size: 1.2rem; line-height: 1; }
.whistle-label { font-size: 0.5rem; font-weight: 900; letter-spacing: 0.1em; color: var(--ink-faint); }

/* Spotted: a TIMED sting that fades to a soft vignette, never a permanent red wall. */
.spotted-flash {
  position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(circle at 50% 50%, rgba(255,59,92,0), rgba(255,59,92,0));
  transition: background 400ms ease;
}
.spotted-flash.on { background: radial-gradient(circle at 50% 50%, rgba(255,59,92,0.02), rgba(255,59,92,0.22)); }
.spotted-flash.sting { animation: spotted-sting 900ms ease-out; }
@keyframes spotted-sting {
  0% { background: radial-gradient(circle at 50% 50%, rgba(255,59,92,0.55), rgba(255,59,92,0.85)); }
  35% { background: radial-gradient(circle at 50% 50%, rgba(255,59,92,0.10), rgba(255,59,92,0.55)); }
  100% { background: radial-gradient(circle at 50% 50%, rgba(255,59,92,0.02), rgba(255,59,92,0.22)); }
}

.gaze-pulse {
  position: absolute; inset: 0; pointer-events: none; opacity: 0;
  background: radial-gradient(circle at 50% 50%, transparent 55%, rgba(255, 59, 92, 0.5) 100%);
}
.gaze-pulse.on { animation: gaze-pulse-anim 480ms ease-out; }
@keyframes gaze-pulse-anim { 0% { opacity: 0.9; } 100% { opacity: 0; } }

.relieved-flash {
  position: absolute; inset: 0; pointer-events: none; opacity: 0;
  background: radial-gradient(circle at 50% 50%, transparent 50%, rgba(198, 255, 61, 0.26) 100%);
}
.relieved-flash.on { animation: relieved-flash-anim 900ms ease-out; }
@keyframes relieved-flash-anim { 0% { opacity: 0.85; } 100% { opacity: 0; } }

body.gaze-shake { animation: gaze-shake-anim 220ms ease-out; }
@keyframes gaze-shake-anim {
  0% { transform: translate(0, 0); }
  20% { transform: translate(-3px, 2px); }
  40% { transform: translate(3px, -2px); }
  60% { transform: translate(-2px, -2px); }
  80% { transform: translate(2px, 2px); }
  100% { transform: translate(0, 0); }
}

body.paint-mode-active #ui-root::before {
  content: ''; position: fixed; inset: 0; z-index: 5; pointer-events: none;
  box-shadow: inset 0 0 0 4px rgba(198, 255, 61, 0.55), inset 0 0 40px rgba(198, 255, 61, 0.18);
  animation: paint-edge-pulse 1.6s ease-in-out infinite;
}
@keyframes paint-edge-pulse { 0%, 100% { opacity: 0.65; } 50% { opacity: 1; } }

/* =========================================================================
   REVEAL
   ========================================================================= */
.outcome-banner {
  width: 100%; padding: 18px 16px; border-radius: 18px;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  border: 2px solid var(--panel-edge);
}
.outcome-banner.unfound {
  border-color: rgba(255, 59, 92, 0.7);
  background: linear-gradient(180deg, rgba(255, 59, 92, 0.18), rgba(255, 59, 92, 0.04));
}
.outcome-banner.found {
  border-color: rgba(95, 200, 255, 0.7);
  background: linear-gradient(180deg, rgba(95, 200, 255, 0.16), rgba(95, 200, 255, 0.04));
}
.outcome-word {
  font-size: clamp(1.6rem, 9vw, 2.4rem); font-weight: 900; letter-spacing: 0.05em; line-height: 1;
}
.outcome-banner.unfound .outcome-word { color: var(--danger); text-shadow: 0 0 30px rgba(255, 59, 92, 0.5); }
.outcome-banner.found .outcome-word { color: var(--cool); text-shadow: 0 0 30px rgba(95, 200, 255, 0.5); }
.outcome-sub { font-size: 0.74rem; color: var(--ink-dim); text-align: center; }
/* survived-but-never-looked-at: a hollow win, and it must not look like a trophy */
.outcome-banner.hollow {
  border-color: rgba(255, 255, 255, 0.18);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.01));
}
.outcome-banner.hollow .outcome-word { color: var(--ink-faint); text-shadow: none; }

.result-story {
  font-size: 1.1rem; font-weight: 800; line-height: 1.4;
  color: var(--ink); margin: 0; text-align: center;
}
.result-story .hi { color: var(--accent); }

.reveal-mount {
  width: 100%; min-height: 190px;
  border-radius: 16px; overflow: hidden;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--panel-edge);
  position: relative;
}
.reveal-placeholder {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  background: repeating-linear-gradient(135deg, rgba(255,255,255,.02) 0 12px, transparent 12px 24px);
}
.rp-title { font-size: 0.7rem; font-weight: 900; letter-spacing: 0.24em; color: var(--ink-faint); }
.rp-sub { font-size: 0.68rem; color: var(--ink-faint); }

.score-card {
  width: 100%; padding: 16px; border-radius: 18px;
  background: var(--panel); border: 1px solid var(--panel-edge);
  display: flex; flex-direction: column; gap: 10px;
}
.score-big-wrap { display: flex; align-items: baseline; justify-content: center; gap: 8px; }
.result-score-big {
  font-size: 3rem; font-weight: 900; color: var(--accent); line-height: 1;
  text-shadow: 0 0 30px rgba(198, 255, 61, 0.35);
}
.result-score-unit { font-size: 0.72rem; color: var(--ink-dim); letter-spacing: 0.16em; font-weight: 800; }

.breakdown { display: flex; flex-direction: column; gap: 4px; }
.breakdown-row {
  display: flex; align-items: center; gap: 12px;
  padding: 9px 4px;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
}
.bd-text { flex: 1 1 auto; min-width: 0; text-align: left; }
.bd-label { font-size: 0.82rem; font-weight: 700; }
.bd-hint { font-size: 0.66rem; color: var(--ink-faint); margin-top: 2px; }
.bd-value { flex: 0 0 auto; font-size: 1rem; font-weight: 900; color: var(--accent); }

.hunters-list, .players-list { width: 100%; display: flex; flex-direction: column; gap: 8px; }
.hunter-row {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px; border-radius: 14px;
  background: rgba(255, 255, 255, 0.035); border: 1px solid var(--panel-edge);
  min-height: 60px;
}
.hunter-badge {
  flex: 0 0 auto; width: 38px; height: 38px; border-radius: 50%;
  background: rgba(95, 200, 255, 0.16); color: var(--cool);
  border: 1px solid rgba(95, 200, 255, 0.4);
  display: flex; align-items: center; justify-content: center; font-weight: 900;
}
.hunter-badge.caught { background: rgba(255, 59, 92, 0.18); color: var(--danger); border-color: rgba(255, 59, 92, 0.5); }
.hunter-meta { flex: 1 1 auto; min-width: 0; text-align: left; }
.hunter-name { font-size: 0.86rem; font-weight: 800; }
.hunter-line { font-size: 0.74rem; color: var(--ink-dim); margin-top: 3px; line-height: 1.35; }
.hunter-line b { color: var(--accent); }
.hunter-closest { flex: 0 0 auto; font-size: 0.76rem; font-weight: 800; color: var(--ink-faint); }

.player-acts { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; }
.like-btn {
  width: var(--tap); height: var(--tap); border-radius: 12px;
  background: var(--panel-2); border: 1px solid var(--panel-edge);
  display: flex; align-items: center; justify-content: center; font-size: 1.2rem;
  color: var(--ink-faint);
}
.like-btn .heart { color: inherit; }
.like-btn.on { background: rgba(255, 92, 138, 0.18); border-color: var(--like); color: var(--like); }
.like-btn.on .heart { animation: like-pop 420ms ease-out; }
@keyframes like-pop { 0% { transform: scale(0.6); } 60% { transform: scale(1.4); } 100% { transform: scale(1); } }
.report-btn {
  min-width: var(--tap); height: var(--tap); padding: 0 10px; border-radius: 12px;
  background: transparent; border: 1px solid var(--panel-edge);
  color: var(--ink-faint); font-size: 0.9rem; font-weight: 800;
}
.report-btn.done { color: var(--warn); border-color: rgba(255, 178, 63, 0.5); font-size: 0.6rem; letter-spacing: 0.08em; }

.near-miss-list { width: 100%; display: flex; flex-direction: column; gap: 5px; }
.near-miss-row {
  display: flex; justify-content: space-between; gap: 10px;
  padding: 9px 12px; border-radius: 10px;
  background: rgba(255, 255, 255, 0.04);
  font-size: 0.74rem; color: var(--ink-dim);
}
.near-miss-row b { color: var(--ink); }

.result-buttons { display: flex; gap: 10px; width: 100%; }
.result-buttons .text-btn {
  flex: 1 1 0; min-width: 0; height: 52px; border-radius: 14px;
  font-size: 0.72rem; padding: 0 6px; text-align: center; line-height: 1.15;
}

/* Always-visible exit bar. Never let the reveal's length hide the way out of a round. */
.reveal-bar {
  flex: 0 0 auto;
  display: flex; flex-direction: column; gap: 8px;
  padding: 10px calc(14px + var(--safe-right)) calc(12px + var(--safe-bottom)) calc(14px + var(--safe-left));
  background: linear-gradient(180deg, rgba(8, 8, 12, 0), rgba(8, 8, 12, 0.94) 22%);
  pointer-events: auto;
}
.reveal-bar .result-buttons { max-width: 520px; margin: 0 auto; }
#screen-result .menu-scroll { padding-bottom: 8px; }

.breakdown-row.reveal, .near-miss-row.reveal, .hunter-row.reveal {
  animation: result-reveal-anim 420ms cubic-bezier(.2,.8,.2,1) both;
}
@keyframes result-reveal-anim {
  0% { opacity: 0; transform: translateY(10px); }
  100% { opacity: 1; transform: translateY(0); }
}

/* =========================================================================
   ONBOARDING — three animated pictures, ~20 words total.
   ========================================================================= */
.onboard {
  position: fixed; inset: 0; z-index: 120;
  display: none; align-items: center; justify-content: center;
  padding: 20px;
  background: rgba(4, 4, 8, 0.94);
  pointer-events: auto;
}
.onboard.open { display: flex; }
.onboard-card {
  position: relative;
  width: 100%; max-width: 420px;
  display: flex; flex-direction: column; align-items: center; gap: 12px;
}
.onboard-stage {
  position: relative;
  width: 100%; height: 200px;
  border-radius: 18px; overflow: hidden;
  background: #101018;
  border: 1px solid var(--panel-edge);
}
.ob-wall {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, #6f6656 0%, #58503f 100%);
}
.ob-wall-grain {
  position: absolute; inset: 0;
  background: repeating-linear-gradient(90deg, rgba(0,0,0,.10) 0 3px, transparent 3px 14px);
}
.ob-body {
  position: absolute; left: 50%; bottom: 18px; transform: translateX(-50%);
  width: 54px; height: 108px; border-radius: 22px 22px 10px 10px;
  background: #ffffff;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.5);
}
.ob-body-head {
  position: absolute; left: 50%; top: -24px; transform: translateX(-50%);
  width: 34px; height: 34px; border-radius: 50%;
  background: inherit;
}
.ob-body.painting .ob-body-fill {
  position: absolute; inset: 0; border-radius: inherit;
  background: #6f6656;
  animation: ob-fill 2.6s ease-in-out infinite;
}
@keyframes ob-fill {
  0% { clip-path: inset(100% 0 0 0); }
  55% { clip-path: inset(0 0 0 0); }
  90% { clip-path: inset(0 0 0 0); }
  100% { clip-path: inset(100% 0 0 0); }
}
.ob-brush {
  position: absolute; left: 50%; bottom: 26px;
  font-size: 1.8rem;
  animation: ob-brush-move 2.6s ease-in-out infinite;
}
@keyframes ob-brush-move {
  0% { transform: translate(-10px, 0); }
  55% { transform: translate(-10px, -84px); }
  100% { transform: translate(-10px, 0); }
}
/* The GESTURE slide: a finger sweeping side to side across the body, with the trail it
   leaves. The deck taught the idea of painting and never showed the action. */
.art-gesture .ob-body.painting .ob-body-fill { animation: ob-fill-sweep 3s ease-in-out infinite; }
@keyframes ob-fill-sweep {
  0% { clip-path: inset(28% 100% 40% 0); }
  45% { clip-path: inset(28% 0 40% 0); }
  85% { clip-path: inset(28% 0 40% 0); }
  100% { clip-path: inset(28% 100% 40% 0); }
}
.ob-drag-trail {
  position: absolute; left: 50%; bottom: 62px; transform: translateX(-50%);
  width: 54px; height: 4px; border-radius: 999px;
  background: rgba(198, 255, 61, 0.85);
  box-shadow: 0 0 10px rgba(198, 255, 61, 0.7);
  animation: ob-trail 3s ease-in-out infinite;
}
@keyframes ob-trail {
  0% { clip-path: inset(0 100% 0 0); }
  45%, 85% { clip-path: inset(0 0 0 0); }
  100% { clip-path: inset(0 100% 0 0); }
}
.ob-finger {
  position: absolute; left: 50%; bottom: 52px; font-size: 1.6rem;
  animation: ob-finger-move 3s ease-in-out infinite;
}
@keyframes ob-finger-move {
  0% { transform: translateX(-40px); }
  45%, 85% { transform: translateX(14px); }
  100% { transform: translateX(-40px); }
}
.ob-body.blended { background: #6f6656; box-shadow: none; }
.ob-body.blended::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.10);
}
.ob-body.behind { background: #6f6656; bottom: 18px; }
.ob-crate {
  position: absolute; left: 50%; bottom: 10px; transform: translateX(-50%);
  width: 96px; height: 74px; border-radius: 6px;
  background: linear-gradient(180deg, #7a5a34, #533c22);
  box-shadow: inset 0 0 0 3px rgba(0, 0, 0, 0.25);
}
.ob-eye {
  position: absolute; left: 22px; top: 26px;
  width: 54px; height: 34px; border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
  background: #fff;
  box-shadow: 0 0 22px rgba(255, 255, 255, 0.35);
  display: flex; align-items: center; justify-content: center;
}
.ob-eye-pupil { width: 18px; height: 18px; border-radius: 50%; background: #111; }
.ob-eye.looking-away .ob-eye-pupil { animation: ob-look-away 3s ease-in-out infinite; }
@keyframes ob-look-away {
  0%, 100% { transform: translateX(-12px); }
  50% { transform: translateX(-14px) translateY(-4px); }
}
.art-seen .ob-eye-pupil { animation: ob-stare 3s ease-in-out infinite; }
@keyframes ob-stare { 0%, 100% { transform: translateX(10px); } 50% { transform: translateX(12px); } }
.ob-beam {
  position: absolute; left: 66px; top: 30px;
  width: 130px; height: 26px;
  background: linear-gradient(90deg, rgba(255, 59, 92, 0.55), rgba(255, 59, 92, 0));
  transform: rotate(14deg); transform-origin: left center;
  animation: ob-beam-pulse 1.4s ease-in-out infinite;
}
@keyframes ob-beam-pulse { 0%, 100% { opacity: 0.35; } 50% { opacity: 0.9; } }
.ob-points {
  position: absolute; right: 12px; top: 12px;
  padding: 5px 10px; border-radius: 999px;
  background: rgba(0, 0, 0, 0.62);
  font-size: 1.15rem; font-weight: 900; color: var(--accent);
  text-shadow: 0 0 18px rgba(198, 255, 61, 0.6);
  animation: ob-points-tick 1s ease-in-out infinite;
}
.ob-points.zero {
  color: #fff; background: rgba(255, 59, 92, 0.85);
  text-shadow: none; animation: none; text-decoration: line-through;
}
@keyframes ob-points-tick { 0%, 100% { transform: translateY(0); opacity: 1; } 50% { transform: translateY(-6px); opacity: 0.75; } }
.ob-tickmark {
  position: absolute; right: 16px; bottom: 14px;
  width: 40px; height: 40px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 1.2rem; font-weight: 900;
}
.ob-tickmark.good { background: rgba(198, 255, 61, 0.2); color: var(--accent); }
.ob-tickmark.bad { background: rgba(255, 59, 92, 0.2); color: var(--danger); }

.onboard-title {
  font-size: clamp(1.05rem, 5vw, 1.3rem); font-weight: 900; letter-spacing: 0.02em;
  text-align: center; line-height: 1.25;
}
.onboard-body {
  font-size: 0.86rem; line-height: 1.5; color: var(--ink-dim); text-align: center;
  max-width: 360px;
}
.onboard-body b { color: var(--accent); }
.onboard-dots { display: flex; gap: 8px; }
.onboard-dot { width: 8px; height: 8px; border-radius: 50%; background: rgba(255,255,255,0.2); }
.onboard-dot.on { background: var(--accent); }
.onboard-skip {
  position: absolute; top: -34px; right: 0;
  min-height: var(--tap); padding: 0 12px;
  font-size: 0.68rem; font-weight: 900; letter-spacing: 0.16em; color: var(--ink-faint);
}

/* =========================================================================
   Scrollbars
   ========================================================================= */
.menu-scroll::-webkit-scrollbar, .toolbar-body::-webkit-scrollbar,
.pose-track::-webkit-scrollbar { width: 5px; height: 5px; }
.menu-scroll::-webkit-scrollbar-thumb, .toolbar-body::-webkit-scrollbar-thumb,
.pose-track::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.18); border-radius: 999px; }

/* =========================================================================
   Loading veil (CRITIQUE §7 — 4.4s of dead air after tapping a map card)
   ========================================================================= */
.load-veil {
  position: fixed; inset: 0; z-index: 200;
  display: none; align-items: center; justify-content: center;
  background: rgba(6, 7, 12, 0.92);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  pointer-events: auto;
}
.load-veil.on { display: flex; }
.load-card {
  width: min(420px, calc(100% - 48px));
  display: flex; flex-direction: column; gap: 14px; align-items: center;
  padding: 26px 24px; border-radius: 20px;
  background: var(--panel); border: 1px solid var(--panel-edge);
  text-align: center;
}
.load-title {
  font-size: 0.86rem; font-weight: 900; letter-spacing: 0.16em; color: var(--accent);
}
.load-bar {
  width: 100%; height: 5px; border-radius: 999px; overflow: hidden;
  background: rgba(255, 255, 255, 0.12);
}
.load-bar i {
  display: block; width: 38%; height: 100%; border-radius: 999px;
  background: var(--accent);
  animation: load-sweep 1.05s ease-in-out infinite;
}
@keyframes load-sweep {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(268%); }
}
.load-tip { font-size: 0.74rem; line-height: 1.45; color: var(--ink-dim); }

/* =========================================================================
   Responsive
   ========================================================================= */
/* Narrow phones: settings rows stack. Letting them wrap inside a row was measurably
   broken — the wrapped control line escaped a 44px-tall row and drew over the next
   setting (verified at 375x812, not eyeballed). */
/* Measured at 375x812, HIDE phase, live: js/game.js injects a centred keybind pill that
   occupies x=94..282, y=16..94. Our horizontal chip row ran x=12..160 y=12..56, so the QUIT
   chip sat underneath it, and our HIDE phase-pill (x=202..263, y=61..93) was underneath it
   too. We cannot move js/game.js's pill, so we vacate the centre: the chips become a
   vertical column at x=12..56 and the hide timer band drops below the pill.
   That pill is gone now (js/game.js: "THE KEYBIND STRIP IS OFF" — measured at 375x812,
   nothing is drawn across the top centre any more), but the column stays: every rule in
   this block below reserves x=0..66 for it, and putting the chips back on one line would
   move the seek HUD, the gaze banner and the suspicion meter as well. */
@media (max-width: 520px) {
  /* only while a round is on screen — on the menu and the reveal the chips sit over a
     scrolling column and a vertical stack ran straight down the middle of the FOUND banner
     (measured: banner starts at y=130, the third chip ends at y=160) */
  body.phase-round .chrome-bar { flex-direction: column; }
  /* The column owns x=12..56 down to y=160, and js/game.js's rail is at x=14..~100 from
     y=70 — measured at 375x812 the options chip sat 42x38 px on top of WALL and the quit
     chip 42x40 px on top of LIE. The rail is told to start under the column instead
     (160 + 6), and END MATCH goes beside the top chip rather than under it: at 375 the
     only other thing on that line is I'M HIDDEN at x=225, so x=64..149 is clear. */
  :root {
    --corner-btn-x: 64px;
    --corner-btn-y: 19px;
    --corner-bottom: 166px;
    --corner-right: 158px;
  }
  body.phase-round .options-pop { top: calc(12px + var(--safe-top)); left: calc(66px + var(--safe-left)); }
  /* CRITIQUE §12: on a phone the round timer, js/game.js's left-rail buttons and the coach
     mark all landed in the same corner. The rail owns x=14..~100 from y=52 down, so the
     timer band starts to the right of it. */
  #screen-hide .hud-top {
    top: calc(102px + var(--safe-top));
    left: calc(110px + var(--safe-left));
  }
  /* The HIDE phase-pill that used to be hidden here no longer exists — it is a caption on
     the clock now (js/ui.js), which costs 11px of height and collides with nothing. */
  /* the hint pill ran the full width and its right end sat under js/game.js's LOOK / POSE
     buttons (measured x>=279 at 375px wide), so it stops short of that column */
  #screen-hide .hide-hint {
    margin-top: calc(150px + var(--safe-top));
    align-self: flex-start;
    margin-left: calc(110px + var(--safe-left));
    max-width: calc(100% - 226px);
  }
  /* 66px cleared the chip COLUMN, but END MATCH is on that first line too (x=64..149),
     and the seek clock landed under it — 55.6 x 28 px, measured at 375x812. */
  #screen-seek .hud-top { padding-left: calc(var(--corner-right) + var(--safe-left)); }
  /* the chip column now owns x=12..56 for its full height, so every left-anchored seek
     readout starts to the right of it instead of under the quit chip */
  .gaze-banner, .suspicion-wrap { left: calc(66px + var(--safe-left)); }
  /* the banner grows a second line at this width — measured 375x812, it runs y=62..116,
     4px into the meter's default top of 112 */
  .suspicion-wrap { top: calc(124px + var(--safe-top)); }
}

@media (max-width: 460px) {
  .hud-top { padding-left: calc(104px + var(--safe-left)); }
  .color-wheel { width: 96px; height: 96px; }
  .light-wrap { width: 96px; }
  /* At 375px the six permanent tools plus the brush slider need every pixel. */
  .toolbar-tools { gap: 4px; row-gap: 4px; padding: 0 6px 6px; }
  /* Measured at 375x812: the tools row wraps to two lines once SIZE joins it, so every
     row above it gives back what it can. Collapsed toolbar total 228px -> 214px. */
  .active-paint { height: 28px; margin: 4px 6px 0; }
  .size-wrap { padding: 0 5px 0 7px; gap: 4px; }
  .tool-btn.size { min-width: 36px; width: 36px; }
  .mini-read.size { min-width: 42px; }
  .tool-btn { min-width: 40px; padding: 0 3px; }
  .tool-btn.zoom { min-width: 34px; }
  .brush-size-wrap { gap: 5px; }
  /* Only rows whose control is a multi-button choice need to stack; steppers and
     toggles fit beside their label at 375px and stacking them just adds scroll. */
  .set-row.stacked {
    flex-direction: column; align-items: stretch; gap: 8px;
    padding: 10px 12px; min-height: 0;
  }
  .set-row.stacked .set-ctl { justify-content: flex-start; max-width: 100%; flex-wrap: wrap; }
  .set-hint { font-size: 0.63rem; }
  /* the sheet scrolls rather than eating half the screen: with 13 poses it measured 383px
     (47% of 812) fully expanded, which puts the feet back behind the toolbar */
  .toolbar-body { max-height: 32vh; }
}

@media (max-height: 520px) {
  /* A short window makes the paint bar a huge fraction of the screen, and everything the
     framing camera reserves for it is screen the body cannot use. Compress it. */
  /* On a short window js/game.js's left and right button rails run all the way down to
     the bottom of the screen, and a full-width toolbar puts its zoom buttons underneath
     them. Measured at 710x334: the − and + were completely covered by TWIST and RESET
     VIEW. Keep 110px clear on each side. */
  .hide-toolbar { width: min(680px, calc(100% - 220px)); }
  .toolbar-quick { height: 46px; }
  .toolbar-tools { padding-bottom: 4px; }
  .tool-btn, .swatch, .quick-color, .icon-btn { height: 38px; min-width: 38px; }
  .swatch { width: 38px; }
  .quick-color { width: 38px; }
  .quick-scroll-wrap, .swatch-track, .brush-size-wrap { height: 38px; min-height: 38px; }
  .toolbar-body { max-height: 44vh; }
  .wordmark { font-size: 2.1rem; }
  .onboard-stage { height: 128px; }
  /* 70px was measured 22px inside the gaze banner (710x334: banner y=62..92). */
  .suspicion-wrap { top: calc(98px + var(--safe-top)); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; }
}


/* ------------------------------------------------------- quick match card --
   The lobby serves two modes. In quick match there is no room code to share, no
   host, and no settings anybody can change — so those cards are removed from the
   flow entirely rather than shown greyed out, which only invites tapping. */
#screen-lobby.is-quick .code-card,
#screen-lobby.is-quick .settings-panel,
#screen-lobby.is-quick .section-head.collapsible { display: none; }

.search-card {
  display: none; width: 100%; padding: 26px 18px; border-radius: 18px;
  text-align: center; background: var(--panel, rgba(255, 255, 255, 0.05));
  border: 1px solid rgba(255, 255, 255, 0.1);
}
.search-card.on { display: block; }
.search-title {
  margin-top: 14px; font-size: 1.05rem; font-weight: 900; letter-spacing: 0.14em;
  font-variant-numeric: tabular-nums;
}
.search-card.ready .search-title { color: var(--accent); }
.search-sub { margin-top: 6px; font-size: 0.74rem; color: var(--ink-dim); letter-spacing: 0.04em; }

/* Three dots, not a spinning ring: a ring reads as "loading a page", dots read as
   "waiting for people", and they cost nothing to animate on a weak phone. */
.search-spin { display: flex; justify-content: center; gap: 8px; }
.search-spin span {
  width: 9px; height: 9px; border-radius: 50%; background: var(--accent);
  animation: search-dot 1.05s ease-in-out infinite;
}
.search-spin span:nth-child(2) { animation-delay: 0.14s; }
.search-spin span:nth-child(3) { animation-delay: 0.28s; }
.search-card.ready .search-spin span { animation-duration: 0.5s; }
@keyframes search-dot {
  0%, 70%, 100% { opacity: 0.25; transform: translateY(0); }
  35% { opacity: 1; transform: translateY(-5px); }
}
@media (prefers-reduced-motion: reduce) { .search-spin span { animation: none; opacity: 0.7; } }

.lobby-wait {
  margin: 8px 0 0; text-align: center; font-size: 0.72rem;
  letter-spacing: 0.06em; color: var(--ink-dim);
}
.big-btn.disabled, .big-btn:disabled { opacity: 0.45; cursor: default; }

/* =========================================================================
   MENU — GAME CHROME
   The home screen was a settings page: a dark column of flat rectangles. A game
   menu is three things a web page is not — it stands on a picture of the world
   you are about to enter, its buttons have thickness you can press, and the
   thing you came to do is the largest object on the screen. This block
   supersedes the flat menu styling above; the flows and the markup order are
   unchanged, nothing was added to the page, and the layout rules at the top of
   this file still hold (44px targets, no hover-only affordance).
   ========================================================================= */

/* The backdrop is a real frame of the arcade map, rendered by the game's own
   engine at 1920x1080. Not stock art: what you see behind the logo is a room
   you can actually be hunted in. It is a still rather than a live scene on
   purpose — a live menu scene would pay the whole map-build cost at boot on
   exactly the phones that can least afford it. */
#screen-menu {
  background:
    /* keeps the middle of the picture dark enough for white text at any size */
    radial-gradient(120% 90% at 50% 42%, rgba(4, 4, 9, 0.12), rgba(4, 4, 9, 0.80) 78%),
    linear-gradient(180deg, rgba(6, 6, 12, 0.72) 0%, rgba(6, 6, 12, 0.20) 34%, rgba(4, 4, 8, 0.92) 100%),
    url("../assets/menu/hero-arcade.jpg") center 42% / cover no-repeat,
    #07070b;
}

.menu-inner { max-width: 460px; gap: 10px; }
.menu-hero { display: flex; flex-direction: column; align-items: center; margin-bottom: 2px; }

/* The logo. Glossy, outlined, glowing — the reference grammar for "this is a
   game". Drawn with drop-shadows rather than -webkit-text-stroke so the
   gradient fill survives the outline. */
.wordmark {
  font-size: clamp(3.1rem, 15vw, 5.2rem);
  letter-spacing: -0.045em;
  background: linear-gradient(180deg, #ffffff 6%, #dfe6f5 38%, #9fb0d0 54%, #ffffff 88%);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
  filter:
    drop-shadow(0 2px 0 rgba(0, 0, 0, 0.85))
    drop-shadow(0 6px 0 rgba(0, 0, 0, 0.55))
    drop-shadow(0 10px 26px rgba(0, 0, 0, 0.8));
  padding: 4px 2px 6px;
}
.wordmark .glyph-hide {
  background: linear-gradient(180deg, #f3ffcf 6%, #d4ff54 42%, #8fd11a 62%, #eaffb0 92%);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
  text-shadow: none;
  filter: drop-shadow(0 0 22px rgba(198, 255, 61, 0.55));
}
.tagline {
  color: rgba(238, 243, 255, 0.9);
  /* the backdrop under this line is the brightest part of the picture, so the
     shadow is doing real work — without it the tagline dissolves into the arcade */
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.95), 0 3px 14px rgba(0, 0, 0, 0.95);
  font-size: 0.78rem; letter-spacing: 0.16em;
  margin: 2px 0 6px;
}

/* Player card. The same two facts as the old pill — who you are and your
   lifetime likes — in the shape every game puts in its top-left corner. */
.profile-chip {
  align-self: center;
  height: auto; padding: 8px 16px 8px 8px;
  border-radius: 16px;
  background: linear-gradient(180deg, rgba(26, 28, 42, 0.86), rgba(11, 12, 20, 0.86));
  border: 1px solid rgba(255, 255, 255, 0.14);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 6px 20px rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(8px);
  margin-bottom: 8px;
}
.profile-dot {
  width: 40px; height: 40px; border-radius: 12px; font-size: 1.1rem;
  box-shadow: inset 0 -3px 0 rgba(0, 0, 0, 0.28), inset 0 2px 0 rgba(255, 255, 255, 0.4);
}
.profile-text { display: flex; flex-direction: column; align-items: flex-start; gap: 1px; line-height: 1.15; }
.profile-name { font-size: 0.92rem; letter-spacing: 0.02em; }
.profile-likes { font-size: 0.68rem; font-weight: 700; letter-spacing: 0.04em; color: rgba(255, 92, 138, 0.92); }

/* Buttons with thickness. The hard shadow under each one is its side wall, and
   pressing it drops the face onto that wall — the whole trick behind a button
   that feels like a button instead of a link. */
.big-btn {
  position: relative;
  min-height: 68px;
  padding: 12px 56px;
  border-radius: 20px;
  background: linear-gradient(180deg, rgba(38, 41, 60, 0.94), rgba(17, 18, 29, 0.96));
  border: 1px solid rgba(255, 255, 255, 0.14);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    inset 0 -3px 0 rgba(0, 0, 0, 0.35),
    0 5px 0 rgba(0, 0, 0, 0.55),
    0 10px 24px rgba(0, 0, 0, 0.45);
  transition: transform 90ms ease, box-shadow 90ms ease, filter 140ms ease;
  overflow: hidden;
}
.big-btn.ghost { background: linear-gradient(180deg, rgba(38, 41, 60, 0.94), rgba(17, 18, 29, 0.96)); }
.big-btn.slim { min-height: 56px; }
.big-btn:active {
  transform: translateY(4px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.14),
    inset 0 -2px 0 rgba(0, 0, 0, 0.3),
    0 1px 0 rgba(0, 0, 0, 0.55),
    0 4px 12px rgba(0, 0, 0, 0.4);
}
.big-btn.disabled:active, .big-btn:disabled:active { transform: none; }

/* PLAY is the reason the screen exists, so it is the biggest, brightest and
   loudest object on it, and the only one that glows. */
.big-btn.primary {
  min-height: 88px;
  background: linear-gradient(180deg, #eaff86 0%, #cbff45 38%, #a5e024 62%, #8ecb18 100%);
  border-color: rgba(255, 255, 255, 0.55);
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.75),
    inset 0 -5px 0 rgba(85, 130, 10, 0.55),
    0 7px 0 #4f7a0d,
    0 16px 38px rgba(198, 255, 61, 0.38);
}
.big-btn.primary:active {
  transform: translateY(6px);
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.6),
    inset 0 -3px 0 rgba(85, 130, 10, 0.5),
    0 1px 0 #4f7a0d,
    0 6px 18px rgba(198, 255, 61, 0.3);
}
.big-btn.primary .big-btn-main { font-size: 1.6rem; letter-spacing: 0.1em; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.35); }
.big-btn.primary .big-btn-sub { opacity: 0.66; font-weight: 800; }
.big-btn-main { text-shadow: 0 1px 0 rgba(0, 0, 0, 0.35); }
/* A sheen that crosses the face every few seconds. Nothing else on this page
   moves, so this is what tells you which button is the live one. */
.big-btn.primary::after {
  content: ""; position: absolute; inset: -40% -60%;
  background: linear-gradient(105deg, transparent 42%, rgba(255, 255, 255, 0.5) 50%, transparent 58%);
  transform: translateX(-120%);
  animation: btn-sheen 4.2s ease-in-out 1.2s infinite;
  pointer-events: none;
}
@keyframes btn-sheen { 0% { transform: translateX(-120%); } 34%, 100% { transform: translateX(120%); } }
@media (prefers-reduced-motion: reduce) { .big-btn.primary::after { animation: none; opacity: 0; } }

/* Small phones: the side gutters the icon needs would squeeze a long label into
   two lines, so give some of them back and shrink the shout a little. */
@media (max-width: 430px) {
  .big-btn { padding-left: 46px; padding-right: 46px; }
  .big-btn.primary { min-height: 80px; }
  .big-btn.primary .big-btn-main { font-size: 1.35rem; }
  .big-btn.primary .btn-ico { left: 14px; width: 32px; height: 32px; }
  .btn-ico { left: 12px; width: 26px; height: 26px; }
}

/* The icon sits in the left margin that the padding above reserved for it, so
   the label stays optically centred the way a game menu centres it.
   display:contents on the wrapper keeps MAIN and SUB direct children of the
   button's column, which is what the icon-less lobby buttons still expect. */
.btn-txt { display: contents; }
.btn-ico {
  position: absolute; left: 16px; top: 50%; transform: translateY(-50%);
  width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  opacity: 0.9; pointer-events: none;
}
.btn-ico svg { width: 100%; height: 100%; fill: currentColor; }
.big-btn.primary .btn-ico { left: 22px; width: 38px; height: 38px; opacity: 1; }

/* Practice: still a section head, but wearing the same glass as the buttons
   above it so the column reads as one stack of game furniture. */
#screen-menu .section-head.collapsible {
  justify-content: flex-start; gap: 10px;
  margin-top: 6px; padding: 0 16px;
  min-height: 52px; border-radius: 16px;
  background: rgba(14, 15, 24, 0.62);
  border: 1px solid rgba(255, 255, 255, 0.10);
  color: rgba(226, 232, 246, 0.8);
}
#screen-menu .section-head .chevron { margin-left: auto; }
.head-ico { width: 18px; height: 18px; display: inline-flex; opacity: 0.75; }
.head-ico svg { width: 100%; height: 100%; fill: currentColor; }

.map-card {
  background: linear-gradient(180deg, rgba(26, 28, 42, 0.9), rgba(12, 13, 21, 0.9));
  border-color: rgba(255, 255, 255, 0.12);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 4px 0 rgba(0, 0, 0, 0.45);
}
.map-card:active { transform: translateY(3px); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(0, 0, 0, 0.45); }
.join-input { background: rgba(4, 4, 10, 0.8); box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.6); }

.chip-btn {
  background: linear-gradient(180deg, rgba(38, 41, 60, 0.92), rgba(15, 16, 26, 0.92));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16), 0 4px 0 rgba(0, 0, 0, 0.5);
}
.chip-btn:active { transform: translateY(3px); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12), 0 1px 0 rgba(0, 0, 0, 0.5); }

/* Wide screens get the console layout: player card in one corner, the system
   chips in the other, the stack down the middle. The top-LEFT corner is a
   published contract during a round (--corner-btn-x and friends in :root,
   shared with the rails js/game.js draws), so this is menu-only and in-round
   layout is untouched. */
@media (min-width: 860px) {
  body.phase-menu .chrome-bar { left: auto; right: calc(16px + var(--safe-right)); top: calc(16px + var(--safe-top)); }
  body.phase-menu #screen-menu .profile-chip {
    position: fixed; z-index: 70;
    top: calc(16px + var(--safe-top)); left: calc(16px + var(--safe-left));
    margin: 0;
  }
  .menu-inner { max-width: 480px; }
}

/* -- the map strip -------------------------------------------------------
   A horizontal rail of map chips directly above PLAY, because the map is a
   thing you choose BEFORE you press the button, and a picker below the fold
   is a picker nobody knows exists. It scrolls sideways rather than wrapping:
   a wrapping grid of eight chips is taller than the button it belongs to. */
.strip-head {
  width: 100%; display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; padding: 0 4px; margin-top: 2px;
  font-size: 0.62rem; font-weight: 900; letter-spacing: 0.2em;
  color: rgba(226, 232, 246, 0.6);
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.9);
}
.strip-hint {
  font-size: 0.6rem; font-weight: 700; letter-spacing: 0.04em;
  color: rgba(226, 232, 246, 0.42); text-transform: none;
}
.map-strip {
  width: 100%;
  display: flex; gap: 8px;
  overflow-x: auto; overflow-y: hidden;
  padding: 2px 2px 8px;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.map-strip::-webkit-scrollbar { display: none; }
.map-chip {
  flex: 0 0 auto; scroll-snap-align: start;
  position: relative;
  width: 92px; min-height: 56px;          /* 56 > 44: the tap floor still holds */
  padding: 6px 8px;
  display: flex; align-items: flex-end; justify-content: center;
  border-radius: 14px;
  background:
    linear-gradient(180deg, rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.62)),
    linear-gradient(135deg, var(--sw-a, #4a4a55), var(--sw-b, #23232c));
  border: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16), 0 4px 0 rgba(0, 0, 0, 0.5);
  transition: transform 90ms ease, box-shadow 90ms ease, border-color 140ms ease;
}
.map-chip.any {
  background: rgba(12, 13, 21, 0.72);
  border-style: dashed;
  border-color: rgba(255, 255, 255, 0.22);
}
.map-chip-name {
  font-size: 0.62rem; font-weight: 900; letter-spacing: 0.08em;
  text-transform: uppercase; text-align: center;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.95);
  line-height: 1.15; max-width: 100%; overflow-wrap: anywhere;
}
.map-chip.selected {
  border-color: var(--accent); border-style: solid;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.2),
    0 0 0 1px var(--accent),
    0 4px 0 rgba(0, 0, 0, 0.5),
    0 0 18px rgba(198, 255, 61, 0.35);
}
.map-chip.selected .map-chip-name { color: var(--accent); }
.map-chip:active { transform: translateY(3px); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16), 0 1px 0 rgba(0, 0, 0, 0.5); }

/* =========================================================================
   NATIVE SKIN — the home screen stops being a web page.
   ------------------------------------------------------------------------
   OWNER: "replace current web like aesthetic with a real game.. it should not
   look like a web at all under any condition."

   The four things that made it read as a document, and what replaces each:

     a centred max-width column of full-width rows  ->  a console PLATE that
       holds every pressable, with the wordmark and the player card floating
       on the room outside it
     a hero background-image with text over it      ->  a real backdrop LAYER
       that drifts, graded so the top third of the room stays visible
     eight radii, fourteen type sizes, six gaps     ->  five radii that encode
       size, four type sizes with brutal jumps, three legal gaps
     a border-colour + soft-glow selected state     ->  selection that changes
       GEOMETRY: it rises, it breaks its own silhouette with a marker, and it
       is the brightest chip on the rail

   Everything here is transform/opacity only. No animated box-shadow, filter,
   width or border-color: this screen sits one tap away from a three.js scene
   on a mid Android and may not own the paint pipeline.

   This block supersedes the two menu blocks above it. Markup order is
   unchanged except for two new wrappers (.menu-bg, .menu-plate) and the
   layout rules at the top of this file still hold: 44px targets, nothing
   hover-only, the top-left corner contract untouched outside phase-menu.
   ========================================================================= */

/* -- the room behind everything ------------------------------------------ */
#screen-menu { background: var(--void); }

.menu-bg {
  position: absolute; inset: -3%;
  z-index: 0; pointer-events: none;
  background: url("../assets/menu/hero-arcade.jpg") center 38% / cover no-repeat;
  /* 28s is slower than anyone consciously notices and that is the point: the
     screen is never quite still, and it never asks to be looked at. One promoted
     layer, transform only, zero paint per frame. */
  animation: bg-drift 28s ease-in-out infinite alternate;
}
/* will-change on a 1280x720 texture costs ~3.5MB of GPU memory, so it is granted
   only while the menu is the screen and dropped the moment a map is building. */
body.phase-menu .menu-bg { will-change: transform; }
@keyframes bg-drift {
  from { transform: translate3d(-0.9%, 0.6%, 0) scale(1.05); }
  to   { transform: translate3d(0.9%, -0.6%, 0) scale(1.05); }
}

/* The grade. The old scrim crushed the middle of the picture to 80% black so that
   white text would survive on top of it — which threw away the only real asset on
   the screen in order to protect type that now sits on a plate instead. Three
   stops: the top third stays BRIGHT, because that is the only place a new player
   sees what this game looks like. */
.menu-bg-grade {
  position: absolute; inset: 0;
  background:
    radial-gradient(140% 100% at 50% 38%, transparent 44%, rgba(0, 0, 0, 0.62) 100%),
    linear-gradient(180deg,
      rgba(6, 7, 12, 0.18) 0%,
      rgba(6, 7, 12, 0.30) 44%,
      rgba(6, 7, 12, 0.94) 82%);
}
/* One hairline of light along the very top edge. This is the cheapest "panel, not
   page" signal in the file — every console UI has it and no website does. */
#screen-menu::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 1px;
  background: rgba(255, 255, 255, 0.06); z-index: 3; pointer-events: none;
}
.menu-scroll { position: relative; z-index: 2; }
/* A visible scrollbar track is a document. */
#screen-menu .menu-scroll { scrollbar-width: none; }
#screen-menu .menu-scroll::-webkit-scrollbar { display: none; }

/* -- the column ---------------------------------------------------------- */
.menu-inner { max-width: 420px; gap: var(--gap-group); }
/* Centred in the viewport, not parked at the top. A column pinned to the top edge
   with a third of the screen empty under it is a document that has run out of text;
   a game menu sits in the middle of the frame. align-items:center is safe here where
   the old comment warns about stretch -- every child is flex:0 0 auto, so nothing
   shrinks -- and auto margins keep the top reachable if the column outgrows the
   screen. */
#screen-menu .menu-scroll { align-items: center; }
#screen-menu .menu-inner { margin: auto 0; }

/* -- the wordmark -------------------------------------------------------- */
/* The chrome gradient is gone. A silver linear-gradient fill on a heavy sans is the
   default "gaming" treatment of amateur web work, and it cost three chained
   drop-shadow() passes over 300x80px of glyph alpha — two of them with zero blur
   radius, i.e. paying the whole blur pipeline for a hard offset. A solid fill, a
   stroke and two hard shadows extrude it for one extra glyph rasterisation.
   NOTE FOR THE OWNER: this is still the operating system's UI font. Shipping one
   18KB display face (Archivo Black, OFL) is the single largest remaining win and
   the only one that needs a file added to the repo. */
#screen-menu .wordmark {
  font-size: var(--t-word);
  font-weight: 900; letter-spacing: -0.045em; line-height: 0.92;
  background: none; -webkit-background-clip: initial; background-clip: initial;
  -webkit-text-fill-color: currentColor; color: var(--ink);
  -webkit-text-stroke: 5px var(--void); paint-order: stroke fill;
  filter: none;
  text-shadow: 0 4px 0 rgba(0, 0, 0, 0.9), 0 12px 30px rgba(0, 0, 0, 0.85);
  padding: 2px 2px 4px;
}
#screen-menu .wordmark .glyph-hide {
  background: none; -webkit-background-clip: initial; background-clip: initial;
  -webkit-text-fill-color: currentColor; color: var(--signal);
  filter: none;
  text-shadow: 0 4px 0 rgba(0, 0, 0, 0.9), 0 0 26px var(--signal-glow);
}
#screen-menu .tagline {
  font-size: var(--t-whisper); letter-spacing: 0.16em;
  color: rgba(245, 241, 234, 0.82);
  /* the grade deliberately leaves the top third of the room BRIGHT, and this line
     lands in it -- so the shadow is load-bearing, not decoration */
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.95), 0 3px 14px rgba(0, 0, 0, 0.9);
  margin: 6px 0 0;
}

/* -- the player card ----------------------------------------------------- */
/* backdrop-filter: blur(8px) deleted. There is nothing live behind this card — it
   sits on a static JPEG — so it bought zero information and cost a full-viewport
   backdrop snapshot plus a separable blur on every composite. Frosted glass over a
   photo is also the wrong vocabulary: that is iOS web chrome, not game chrome. */
#screen-menu .profile-chip {
  border-radius: var(--r-3);
  background: linear-gradient(180deg, var(--surface-raised) 0%, var(--surface) 100%);
  border: 1px solid var(--panel-edge);
  box-shadow:
    inset 0 1px 0 var(--highlight),
    0 3px 0 rgba(0, 0, 0, 0.55),
    0 8px 18px rgba(0, 0, 0, 0.5);
  backdrop-filter: none; -webkit-backdrop-filter: none;
  margin-bottom: 0;
  transition: transform 70ms cubic-bezier(.2, 0, .4, 1), box-shadow 70ms cubic-bezier(.2, 0, .4, 1);
}
#screen-menu .profile-chip:active, #screen-menu .profile-chip.pressing {
  transform: translateY(2px);
  box-shadow: inset 0 1px 0 var(--highlight), 0 1px 0 rgba(0, 0, 0, 0.55), 0 4px 10px rgba(0, 0, 0, 0.45);
}
#screen-menu .profile-dot {
  border-radius: var(--r-2);
  background: linear-gradient(180deg, var(--signal-hi), var(--signal-lo));
  color: var(--signal-ink);
}
#screen-menu .profile-name { font-size: var(--t-body); }
#screen-menu .profile-likes { font-size: var(--t-whisper); }

/* -- the console plate --------------------------------------------------- */
/* A panel has NO side wall. If it had one it would be mistaken for a button, and
   the whole point of the plate is that it is the ground the buttons stand on. */
.menu-plate {
  width: 100%;
  display: flex; flex-direction: column; align-items: stretch;
  gap: var(--gap-in);
  padding: var(--pad-plate);
  border-radius: var(--r-5);
  background: var(--plate);
  border: 1px solid var(--panel-edge);
  box-shadow:
    inset 0 1px 0 var(--highlight),
    0 18px 40px rgba(0, 0, 0, 0.55);
}
/* PLAY owns the air above and below it — the two --gap-act gaps are what stop the
   plate from reading as another list of rows. */
.menu-plate .big-btn.primary { margin-bottom: calc(var(--gap-act) - var(--gap-in)); }
.menu-plate .strip-head { margin-top: 0; }
.menu-plate .map-strip { margin-bottom: calc(var(--gap-group) - var(--gap-in)); }

/* -- the slab ------------------------------------------------------------ */
/* Every pressable object in this game is a slab: a face, a SIDE WALL, and one
   contact shadow. Nothing floats, nothing is blurred into place. Thickness is a
   function of importance, not of size — primary 8, secondary 5, chip 4, small 3.
   The wall is derived from the face (same hue, lightness x0.42) and is never
   black, because a black wall under a coloured face is a drop shadow and the eye
   reads it as a Material card. */
#screen-menu .big-btn {
  --T: 5px;
  --wall: #05060a;
  position: relative;
  min-height: 64px;
  padding: 12px 52px;
  border-radius: var(--r-4);
  background: linear-gradient(180deg, var(--surface-raised) 0%, var(--surface) 46%, #101219 100%);
  border: 1px solid var(--panel-edge);
  box-shadow:
    inset 0 1px 0 var(--highlight),
    inset 0 -3px 0 rgba(0, 0, 0, 0.32),
    0 var(--T) 0 var(--wall),
    0 calc(var(--T) * 2) calc(var(--T) * 3) rgba(0, 0, 0, 0.42);
  /* down fast and flat, up slow with a hair of overshoot: that asymmetry is the
     whole difference between "sprung" and "animated". Opacity is never touched on
     a press — fading on press is a LINK behaviour. */
  transition: transform 150ms cubic-bezier(.2, .9, .3, 1.1), box-shadow 150ms cubic-bezier(.2, .9, .3, 1.1);
  overflow: hidden;
}
#screen-menu .big-btn.ghost { background: linear-gradient(180deg, var(--surface-raised) 0%, var(--surface) 46%, #101219 100%); }
#screen-menu .big-btn:active, #screen-menu .big-btn.pressing {
  transform: translateY(calc(var(--T) - 1px));
  transition-duration: 70ms; transition-timing-function: cubic-bezier(.2, 0, .4, 1);
  box-shadow:
    inset 0 1px 0 var(--highlight),
    inset 0 -2px 0 rgba(0, 0, 0, 0.28),
    0 1px 0 var(--wall),
    0 var(--T) calc(var(--T) * 1.5) rgba(0, 0, 0, 0.38);
}

/* PLAY. The reason the screen exists, so it is the largest, the brightest, the
   thickest and the only thing that glows. */
#screen-menu .big-btn.primary {
  --T: 8px;
  --wall: var(--signal-wall);
  min-height: 88px;
  background: linear-gradient(180deg, var(--signal-hi) 0%, var(--signal) 46%, var(--signal-lo) 100%);
  border-color: rgba(255, 255, 255, 0.34);
  color: var(--signal-ink);
}
#screen-menu .big-btn.primary .big-btn-main {
  font-size: var(--t-shout); font-weight: 800; letter-spacing: 0.06em;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.28);
}
#screen-menu .big-btn.primary .big-btn-sub {
  font-size: var(--t-whisper); font-weight: 600; letter-spacing: 0.16em;
  text-transform: uppercase; opacity: 0.7;
}
#screen-menu .big-btn-main { font-size: var(--t-call); font-weight: 800; letter-spacing: 0.04em; }
#screen-menu .big-btn-sub { font-size: var(--t-whisper); font-weight: 600; letter-spacing: 0.16em; text-transform: uppercase; }

/* The glow breathes instead of a white band sliding across the face. The sheen read
   as cheap gloss on an orange face, and at 4.2s it was nervous. This is a separate
   opacity-only layer: one composite, no paint. */
#screen-menu .big-btn.primary::after {
  content: ""; position: absolute; inset: 0;
  background: radial-gradient(120% 160% at 50% 120%, rgba(255, 255, 255, 0.34), transparent 62%);
  animation: signal-breathe 6s ease-in-out infinite;
  transform: none;
  pointer-events: none;
}
@keyframes signal-breathe { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }
/* The commit beat: PLAY sinks and holds for 170ms before the screen leaves, so the
   press is something that HAPPENED rather than something that navigated. */
#screen-menu .big-btn.primary.committed { transform: translateY(7px); }
.menu-inner.committing { pointer-events: none; }

@media (max-width: 430px) {
  #screen-menu .big-btn { padding-left: 44px; padding-right: 44px; }
  #screen-menu .big-btn.primary { min-height: 80px; }
  #screen-menu .big-btn.primary .big-btn-main { font-size: 1.4rem; }
}

/* -- the map rail -------------------------------------------------------- */
#screen-menu .strip-head {
  padding: 0; margin: 0 0 2px;
  font-size: var(--t-whisper); font-weight: 800; letter-spacing: 0.16em;
  color: var(--ink-dim); text-shadow: none;
}
#screen-menu .strip-hint {
  font-size: var(--t-whisper); font-weight: 600; letter-spacing: 0.02em;
  color: var(--ink-faint); text-transform: none;
}
/* 10px of top padding is not decoration: the selected chip rises 3px and its marker
   overhangs the corner by 6px, and overflow-x:auto forces overflow-y to clip at the
   PADDING box. Without this the checkmark is sliced in half. */
#screen-menu .map-strip { gap: var(--gap-in); padding: 10px 2px 12px; }
#screen-menu .map-chip {
  --T: 4px;
  /* 104x64 gives a 102x62 content box, and 102/62 = 1.65 -- the baked frame's own
     184x112 aspect, so the film is never stretched. */
  width: 104px; min-height: 64px;
  padding: 8px;
  border-radius: var(--r-3);
  border-color: var(--panel-edge);
  /* the chip IS the room: its own two colours, under a scrim that keeps a white
     900-weight label above 7:1 on every one of the fourteen */
  background:
    linear-gradient(180deg, rgba(0, 0, 0, 0.10), rgba(0, 0, 0, 0.62)),
    linear-gradient(135deg, var(--sw-a, #4a4a55), var(--sw-b, #23232c));
  box-shadow:
    inset 0 1px 0 var(--highlight),
    0 var(--T) 0 rgba(0, 0, 0, 0.55),
    0 8px 12px rgba(0, 0, 0, 0.4);
  transition: transform 150ms cubic-bezier(.2, .9, .3, 1.1), box-shadow 150ms cubic-bezier(.2, .9, .3, 1.1);
}
/* LAUNDERETTE is eleven characters and the old rail broke it as LAUNDERET / TE --
   overflow-wrap:anywhere will happily cut a word in half. Wider chip, tighter
   tracking, and word-boundary breaking only: a name is either whole or it is not a
   name. */
#screen-menu .map-chip-name {
  font-size: var(--t-whisper); font-weight: 800; letter-spacing: 0.02em;
  color: var(--ink);
  overflow-wrap: normal; word-break: keep-all; hyphens: none;
}
#screen-menu .map-chip:active, #screen-menu .map-chip.pressing {
  transform: translateY(3px);
  transition-duration: 70ms;
  box-shadow: inset 0 1px 0 var(--highlight), 0 1px 0 rgba(0, 0, 0, 0.55);
}

/* Selection changes GEOMETRY and VALUE, never only colour. A hairline border and a
   soft halo is a web focus ring — at 92px on a phone at DPR 3 that border is a
   sub-pixel line, and a coloured glow is exactly what :focus looks like.
     it rises, and its wall grows 4 -> 7px, so it is physically taller
     it gains a solid marker that OVERHANGS its own corner, breaking the silhouette
     its scrim lifts: the chosen room is literally the brightest one
     it stamps once on the moment of choice, and never loops */
#screen-menu .map-chip.selected {
  --T: 7px;
  transform: translateY(-3px);
  border-color: var(--panel-edge-hi); border-style: solid;
  background:
    linear-gradient(180deg, rgba(0, 0, 0, 0.02), rgba(0, 0, 0, 0.34)),
    linear-gradient(135deg, var(--sw-a, #4a4a55), var(--sw-b, #23232c));
  box-shadow:
    inset 0 1px 0 var(--highlight),
    inset 0 -2px 0 var(--signal),
    0 var(--T) 0 rgba(0, 0, 0, 0.6),
    0 12px 18px rgba(0, 0, 0, 0.45);
  animation: chip-stamp 260ms cubic-bezier(.2, .9, .3, 1.2);
}
#screen-menu .map-chip.selected::after {
  content: "\2713";
  position: absolute; top: -6px; right: -6px;
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--signal); color: var(--signal-ink);
  font-size: 0.7rem; font-weight: 900; line-height: 20px; text-align: center;
  box-shadow: 0 2px 0 var(--signal-wall);
}
#screen-menu .map-chip.selected .map-chip-name { color: var(--ink); }
#screen-menu .map-chip.selected:active, #screen-menu .map-chip.selected.pressing {
  transform: translateY(2px);
}
@keyframes chip-stamp {
  0% { transform: translateY(-3px) scale(0.94); }
  55% { transform: translateY(-3px) scale(1.04); }
  100% { transform: translateY(-3px) scale(1); }
}
#screen-menu .map-chip.any {
  background: linear-gradient(180deg, var(--surface-raised), var(--surface));
  border-style: solid; border-color: var(--panel-edge);
}

/* -- the quiet row ------------------------------------------------------- */
/* HOW TO PLAY is a once-per-lifetime action that already shows itself unprompted on
   first run. At 56px button weight it competed with PLAY WITH FRIENDS for the same
   eye. A row is not a slab: no wall, no gradient, no thickness. */
.quiet-row {
  width: 100%; min-height: var(--tap);
  display: flex; align-items: center; gap: 10px;
  padding: 0 4px;
  background: none; border: none;
  font-size: var(--t-whisper); font-weight: 800; letter-spacing: 0.16em;
  color: var(--ink-faint); text-align: left;
  transition: color 120ms ease;
}
.quiet-row:active, .quiet-row.pressing { color: var(--ink-dim); }
.quiet-row .head-ico { width: 16px; height: 16px; opacity: 0.85; }

#screen-menu .section-head.collapsible {
  margin-top: 0; padding: 0 12px;
  min-height: 48px; border-radius: var(--r-3);
  background: var(--surface-sunk);
  border: 1px solid var(--panel-edge);
  color: var(--ink-dim);
  font-size: var(--t-whisper); letter-spacing: 0.16em;
}
#screen-menu .section-sub { font-size: var(--t-whisper); color: var(--ink-faint); }

/* -- the room code ------------------------------------------------------- */
#screen-menu .join-input {
  height: 48px; border-radius: var(--r-3);
  background: var(--surface-sunk);
  border: 1px solid var(--panel-edge);
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.6);
  font-size: 1.2rem; letter-spacing: 0.4em;
}
#screen-menu .text-btn {
  --T: 3px;
  height: 48px; border-radius: var(--r-2);
  background: linear-gradient(180deg, var(--surface-raised), var(--surface));
  border: 1px solid var(--panel-edge);
  font-size: var(--t-whisper); font-weight: 800; letter-spacing: 0.12em;
  box-shadow: inset 0 1px 0 var(--highlight), 0 var(--T) 0 rgba(0, 0, 0, 0.55);
  transition: transform 150ms cubic-bezier(.2, .9, .3, 1.1), opacity 160ms ease;
}
#screen-menu .text-btn:active, #screen-menu .text-btn.pressing {
  transform: translateY(2px); transition-duration: 70ms;
  box-shadow: inset 0 1px 0 var(--highlight), 0 1px 0 rgba(0, 0, 0, 0.55);
}

/* NOT `disabled`. A disabled button greys out and then ignores you, so the one thing
   on this screen that can go wrong went wrong in total silence. `.inert` looks
   unavailable and still answers the tap — two dry clicks, a shake, a buzz. */
.text-btn.inert { opacity: 0.42; }
.text-btn.inert:active, .text-btn.inert.pressing { transform: none; }
.refuse { animation: refuse-shake 260ms cubic-bezier(.36, .07, .19, .97); }
@keyframes refuse-shake {
  10%, 90% { transform: translateX(-2px); }
  20%, 80% { transform: translateX(4px); }
  30%, 50%, 70% { transform: translateX(-6px); }
  40%, 60% { transform: translateX(6px); }
}

/* -- arrival ------------------------------------------------------------- */
/* Three groups, 55ms apart. This is the single biggest "native" tell available and
   it costs one frame of work, once: a web page APPEARS, a game screen ARRIVES. */
#screen-menu.active .menu-grp {
  animation: grp-in 280ms cubic-bezier(.2, .9, .3, 1) backwards;
}
#screen-menu.active .menu-grp:nth-child(1) { animation-delay: 40ms; }
#screen-menu.active .menu-grp:nth-child(2) { animation-delay: 95ms; }
#screen-menu.active .menu-grp:nth-child(3) { animation-delay: 150ms; }
@keyframes grp-in { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }

/* -- the console layout -------------------------------------------------- */
/* At 1280x800 a 420px ribbon down the middle of the screen is a Bootstrap
   container. From 900px the plate anchors left and the room breathes on the right,
   which is how a front end is laid out when it is not a page. */
@media (min-width: 900px) {
  #screen-menu .menu-scroll {
    justify-content: flex-start; align-items: center;
    padding-left: calc(64px + var(--safe-left));
  }
  #screen-menu .menu-inner { max-width: 460px; align-items: stretch; text-align: left; }
  #screen-menu .menu-hero { align-items: flex-start; }
  #screen-menu .wordmark { font-size: clamp(3.6rem, 6vw, 5.2rem); }
  #screen-menu .profile-chip { align-self: flex-start; }
  .menu-bg { background-position: 62% 42%; }
}

@media (prefers-reduced-motion: reduce) {
  /* Held at the loop's MIDPOINT, not at 0 — stopping a drift at frame zero puts the
     picture somewhere it was never composed to sit. */
  .menu-bg { animation: none; transform: scale(1.05); }
  #screen-menu .big-btn.primary::after { animation: none; opacity: 1; }
  #screen-menu .map-chip.selected { animation: none; }
  #screen-menu.active .menu-grp { animation: none; }
  .refuse { animation: none; }
  /* the press survives: it is feedback, not decoration */
}

/* -- the chip's art ------------------------------------------------------- */
/* A baked 2 s clip of the level, 24 frames stacked into one WebP. `steps(24)` on a
   translate3d is a compositor-thread animation: after the first upload it costs the
   main thread nothing at all, which is the whole reason this is a sprite strip and
   not seven <video> elements — seven concurrent decoders is more MediaCodec
   instances than a cheap SoC exposes, and the failure mode there is a silent black
   rectangle rather than a poster.
   NOTE: overflow:hidden lives on .chip-art, NOT on .map-chip — the selected chip's
   checkmark deliberately overhangs its own corner and must not be clipped. */
.chip-art {
  position: absolute; inset: 0; z-index: 0;
  border-radius: inherit; overflow: hidden;
}
.chip-art img {
  position: absolute; left: 0; top: 0; width: 100%;
  display: block; pointer-events: none;
}
.chip-still { height: 100%; object-fit: cover; }
/* 24 frames stacked -> the strip is 2400% of the chip's height. steps(24) lands on
   frames 0..23 and never on the off-by-one blank at -100%. */
.chip-film { height: 2400%; will-change: transform; }
#screen-menu .map-chip.selected .chip-film,
#screen-menu .map-chip:hover .chip-film,
#screen-menu .map-chip:focus-visible .chip-film { animation: chip-film 2s steps(24) infinite; }
@keyframes chip-film {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(0, -100%, 0); }
}
/* The scrim sits ON TOP of the art, not under it: the Launderette's preview is a
   near-white tiled room and the label is white with a 3px shadow. */
.chip-art::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.10) 0%, rgba(0, 0, 0, 0.30) 45%, rgba(0, 0, 0, 0.78) 100%);
}
#screen-menu .map-chip.selected .chip-art::after {
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.02) 0%, rgba(0, 0, 0, 0.14) 45%, rgba(0, 0, 0, 0.62) 100%);
}
.map-chip-name { position: relative; z-index: 1; }
@media (prefers-reduced-motion: reduce) {
  #screen-menu .map-chip .chip-film { animation: none; }
}

/* ---------------------------------------------------------- whistle pings --
   The hunter's cue that a hider was just forced to whistle. Deliberately NOT a
   tracker: it fades over its life (js/game.js drives the opacity) and it is
   jittered away from the true position, so it says "over there, a moment ago"
   and never "here he is". Same arrow language as .hunter-marker on purpose. */
.whistle-layer { z-index: 3; }
.whistle-ping {
  position: absolute; transform: translate(-50%, -50%);
  display: none; flex-direction: column; align-items: center; gap: 3px;
}
.whistle-ping.on { display: flex; }
.wp-ring {
  width: 34px; height: 34px; border-radius: 50%;
  border: 3px solid var(--accent, #c6ff2e);
  box-shadow: 0 0 14px rgba(198, 255, 46, .55), inset 0 0 10px rgba(198, 255, 46, .35);
  animation: wp-pulse 1.4s ease-out infinite;
}
.wp-arrow {
  width: 0; height: 0;
  border-left: 9px solid transparent;
  border-right: 9px solid transparent;
  border-bottom: 16px solid var(--accent, #c6ff2e);
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.9));
}
/* on screen the ring IS the marker; the direction arrow is redundant there */
.whistle-ping.onscreen .wp-arrow { display: none; }
.wp-label {
  font-size: 0.6rem; font-weight: 900; letter-spacing: .06em; color: var(--accent, #c6ff2e);
  -webkit-text-stroke: 3px rgba(0,0,0,.9); paint-order: stroke fill; white-space: nowrap;
}
@keyframes wp-pulse {
  0%   { transform: scale(0.7); opacity: 1; }
  70%  { transform: scale(1.25); opacity: .35; }
  100% { transform: scale(1.4); opacity: 0; }
}

/* ZOOM gets the same outlined-pill treatment BODY has, in a different tint so the
   two minus/plus steppers in this row can never be mistaken for one another at a
   glance — which is exactly what they were before they were labelled. */
.size-wrap.zoom-wrap {
  border-color: rgba(255, 255, 255, 0.28);
  background: rgba(255, 255, 255, 0.06);
  padding: 0 6px 0 9px;
}
.size-wrap.zoom-wrap .mini-label.inline { color: var(--ink); }
.tool-btn.size.zoom { min-width: 36px; width: 36px; }
/* two-line tool words (PICK COLOUR, FILL BODY) must not stretch the row */
.tool-btn .tb-word { line-height: 1.05; text-align: center; }
.brush-size-wrap .mini-label.inline { color: var(--ink-dim); }

/* THE ROW MUST NOT WRAP. Adding the ZOOM label pushed the total past the row width
   and the ZOOM pill dropped onto a second line — which is the exact failure the BODY
   control's own comment records from its second attempt, and it makes a control look
   absent rather than merely misplaced. The brush slider is the only elastic thing in
   the row, so it is the thing that gives: it may shrink to a 90px basis, and the two
   dots either side of it fold away before anything with a word on it moves. */
/* NOWRAP ONLY WHERE IT FITS. On a phone, six labelled controls genuinely do not
   fit across 375px, and the two ways to force them are both worse than wrapping:
   an overflow scroller puts ZOOM off-screen (a control you cannot see reads as
   absent, which is the bug this row has already been through twice), and shrinking
   until it fits costs the words, which is the whole point of the change. So below
   640px it wraps ON PURPOSE, onto a reserved second line with its own row-gap.
   That is safe here because notifyChrome() measures this bar every time it changes
   and hands js/game.js the real height, so the framing camera lifts the body clear
   of two rows exactly as it does one. */
@media (min-width: 641px) { .toolbar-tools { flex-wrap: nowrap; } }
@media (max-width: 640px) { .toolbar-tools { flex-wrap: wrap; row-gap: 6px; justify-content: center; } }
.brush-size-wrap { flex: 1 1 90px; min-width: 90px; }
.brush-slider { min-width: 40px; flex: 1 1 auto; }
@media (max-width: 900px) {
  /* the dots are decoration; the slider and its label are the control */
  .brush-size-wrap .brush-dot { display: none; }
}

/* ROOM SETUP — the two match clocks, inside the PLAY WITH FRIENDS drawer.
   OWNER: "these options should appear in next screen if user selects play with
   friends". `.menu-fold` already supplies the 10px rhythm between its children, so
   the panel only has to drop the gap it would add on top of that and stop its own
   max-height transition from fighting the drawer's fold-in. */
.menu-fold .settings-panel.room-setup {
  gap: 8px;
  overflow: visible;
  max-height: none;
  transition: none;
}
.menu-fold .settings-panel.room-setup .set-row {
  background: rgba(255, 255, 255, 0.045);
}

/* ============================================================ VOICE DOCK ==
   One control, bottom-left, above the safe area, present in the lobby and in the
   round. Bottom-LEFT specifically: the right-hand side of the screen is the thumb
   that paints and the thumb that shoots. */
.voice-dock {
  position: absolute;
  left: max(14px, env(safe-area-inset-left));
  bottom: calc(14px + env(safe-area-inset-bottom));
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 10px;
  pointer-events: none;   /* the gap between the button and the label is not a target */
}
.voice-dock[hidden] { display: none; }

.voice-btn {
  pointer-events: auto;
  position: relative;
  width: 54px;
  height: 54px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.22);
  background: rgba(12, 14, 18, 0.72);
  color: #fff;
  font-size: 22px;
  line-height: 1;
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease, transform .08s ease;
  -webkit-tap-highlight-color: transparent;
}
.voice-btn:active { transform: scale(0.94); }
.voice-btn.on { border-color: #c8ff3d; background: rgba(28, 40, 12, 0.82); }
.voice-btn.muted { border-color: #ff6b6b; background: rgba(46, 16, 16, 0.82); }
.voice-btn.busy { opacity: 0.6; }

/* THE LEVEL RING. Scale, not a colour change: a ring that grows when you speak is
   legible out of the corner of the eye, mid-round, which is the only time anyone
   looks at it. --voice-level is written by renderVoice() at 10Hz. */
.voice-ring {
  position: absolute;
  inset: -6px;
  border-radius: 50%;
  border: 2px solid #c8ff3d;
  opacity: calc(var(--voice-level, 0) * 0.9);
  transform: scale(calc(1 + var(--voice-level, 0) * 0.18));
  transition: transform .1s linear, opacity .1s linear;
  pointer-events: none;
}
.voice-glyph { position: relative; }

.voice-side {
  pointer-events: none;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}
.voice-label {
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 0.09em;
  color: rgba(255, 255, 255, 0.62);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
}
.voice-label.warn { color: #ff9a6b; }

.voice-mode {
  pointer-events: auto;
  border: 1px solid rgba(255, 255, 255, 0.2);
  background: rgba(12, 14, 18, 0.72);
  color: rgba(255, 255, 255, 0.85);
  border-radius: 999px;
  padding: 3px 10px;
  font-size: 10px;
  font-weight: 900;
  letter-spacing: 0.09em;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.voice-mode[hidden] { display: none; }

/* On a phone the dock sits where the movement stick lives, so shrink it and drop
   the label — the ring and the colour already carry the state. */
@media (max-width: 520px) {
  .voice-dock { bottom: calc(96px + env(safe-area-inset-bottom)); gap: 8px; }
  .voice-btn { width: 46px; height: 46px; font-size: 19px; }
  .voice-label { display: none; }
}
