/* Scorched2k6 — HUD and chrome.
   The 3D scene owns the whole viewport; everything here floats above it. */

:root {
  --bg: #05070c;
  --panel: rgba(10, 14, 22, 0.78);
  --panel-edge: rgba(140, 180, 230, 0.16);
  --ink: #e8eef7;
  --ink-dim: #93a4bb;
  --accent: #ffc94e;
  --fire: #ff6a3c;
  --good: #4ede8a;
  --radius: 10px;
  --shadow: 0 8px 32px rgba(0, 0, 0, 0.55);
  --font: "Segoe UI", system-ui, -apple-system, sans-serif;
  --mono: "JetBrains Mono", "Cascadia Mono", ui-monospace, monospace;
}

* { box-sizing: border-box; }

/* The `hidden` attribute is only `display:none` in the UA stylesheet, so ANY
   author-level `display` on the same element silently defeats it. That is not
   a hypothetical: `.shop { display:grid }` left the armoury permanently on top
   of the game at z-index 20, swallowing every click, and its Close button —
   which only toggles `hidden` — did nothing. Enforce it once, for every
   element, so no future panel can reintroduce the same trap. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  height: 100%;
  overflow: hidden;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  user-select: none;
  -webkit-user-select: none;
}

#game {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none;
}

#hud { position: fixed; inset: 0; pointer-events: none; }
.hud { position: absolute; inset: 0; display: flex; flex-direction: column; }

.panel {
  background: var(--panel);
  border: 1px solid var(--panel-edge);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  backdrop-filter: blur(14px) saturate(1.3);
  -webkit-backdrop-filter: blur(14px) saturate(1.3);
  pointer-events: auto;
}

/* ------------------------------------------------------------------ top */
.hud-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
  padding: 14px 16px;
}

.players { padding: 10px 12px; min-width: 230px; display: grid; gap: 6px; }

.player {
  display: grid;
  grid-template-columns: 10px 1fr 76px 34px;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  padding: 3px 4px;
  border-radius: 6px;
  transition: background 0.2s;
}
.player.active { background: rgba(255, 201, 78, 0.13); box-shadow: inset 0 0 0 1px rgba(255,201,78,0.28); }
.player.dead { opacity: 0.34; filter: grayscale(1); }
.pcolor { width: 10px; height: 10px; border-radius: 3px; box-shadow: 0 0 8px currentColor; }
.pname { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pname em { font-style: normal; font-size: 9px; opacity: 0.55; letter-spacing: 0.08em; }
.pbar {
  position: relative; height: 10px; border-radius: 5px;
  background: rgba(255, 255, 255, 0.09); overflow: hidden;
}
.pbar i { position: absolute; inset: 0 auto 0 0; border-radius: 5px; transition: width 0.25s; }
/* What the vehicle is WEARING: the force field along the top edge, bolted
   plate along the bottom, the hull colour still reading between them.

   Both live INSIDE the bar now, and that is a bug fix rather than a taste.
   The shield band was written at `top:-3px` on a box that clips at
   `overflow:hidden`, so it occupied the three pixels immediately ABOVE the
   clip and has never once been drawn on screen — measured, not guessed. The
   plate had no band at all, which is what backlog #28 is about. No glow on
   either: a 3 px band bleeding light onto the hull fill two pixels away
   costs more legibility than it buys. */
.pbar b, .pbar u {
  position: absolute; left: 0; height: 3px; border-radius: 2px;
  transition: width 0.25s;
}
.pbar b { top: 0; background: #5ac8ff; }
.pbar u { bottom: 0; background: linear-gradient(90deg, #8fa4bb, #e3ecf7); }
.php { text-align: right; font-family: var(--mono); font-size: 12px; color: var(--ink-dim); }

.env { padding: 10px 14px; display: flex; gap: 18px; align-items: center; }
.env-wind { display: flex; align-items: center; gap: 10px; }
.wind-dial {
  width: 46px; height: 46px; border-radius: 50%;
  border: 1px solid var(--panel-edge);
  display: grid; place-items: center;
  background: radial-gradient(circle at 50% 45%, rgba(90,160,220,0.16), transparent 70%);
}
.wind-arrow {
  width: 30px; height: 3px; border-radius: 2px;
  background: linear-gradient(90deg, transparent, var(--accent));
  position: relative; transition: transform 0.5s cubic-bezier(.2,.8,.2,1);
}
.wind-arrow::after {
  content: ''; position: absolute; right: -1px; top: -3px;
  border: 4.5px solid transparent; border-left-color: var(--accent);
}
.env-lines { display: flex; flex-direction: column; }
.env-label { font-size: 9px; letter-spacing: 0.16em; color: var(--ink-dim); }
.env-value { font-family: var(--mono); font-size: 17px; font-weight: 600; }

/* ------------------------------------------------------------------ log */
.hud-log {
  position: absolute; left: 16px; top: 50%; transform: translateY(-50%);
  display: flex; flex-direction: column; gap: 4px; max-width: 290px;
  pointer-events: none;
}
.log-line {
  font-size: 12px; padding: 4px 9px; border-radius: 5px;
  background: rgba(6, 10, 16, 0.6); border-left: 2px solid var(--ink-dim);
  color: var(--ink-dim); animation: slideIn 0.3s ease-out;
}
.log-fire { border-left-color: var(--fire); color: #ffcfae; }
.log-kill { border-left-color: #ff3b30; color: #ffb3ae; font-weight: 600; }
.log-bonus { border-left-color: var(--good); color: #b6ffd4; }
.log-defend { border-left-color: #5ac8ff; color: #b8e6ff; }
.log-round { border-left-color: var(--accent); color: #ffe7ae; font-weight: 600; }
.log-hurt { border-left-color: #ff8c42; color: #ffd2b3; }
@keyframes slideIn { from { opacity: 0; transform: translateX(-14px); } to { opacity: 1; } }

/* --------------------------------------------------------------- bottom */
.hud-bottom { margin-top: auto; padding: 0 16px 16px; display: flex; justify-content: center; }

/* MEASURED, then trimmed. At 1280x720 this panel was 760 x 229 px — with the
   16px the row below adds, 245 px of a 720 px screen, a third of the height
   gone before a single shell is drawn. It is now min(684px) x ~212: 10 percent
   narrower, 7 percent shorter.

   The height came out of the padding, the row gap and the power meter, and
   NOT out of the aim dial. The dial is the aiming instrument; a smaller one is
   a less precise one, and the bar in CLAUDE.md puts readability above every
   other consideration in this panel. Trim chrome, never the instruments. */
.controls {
  padding: 10px 14px 11px; width: min(684px, 100%);
  display: flex; flex-direction: column; gap: 8px;
  transition: opacity 0.25s;
  /* The 3x3 weapon picker is absolutely positioned at bottom:100% so it opens
     UPWARD, over the battlefield rather than over the controls it was launched
     from. That needs a positioned ancestor here, or it would anchor to the
     viewport and drift as the panel resizes. */
  position: relative;
}
.controls.disabled { opacity: 0.42; pointer-events: none; }

.turn-owner { display: flex; align-items: center; gap: 10px; font-size: 14px; font-weight: 600; }
/* The name is the only elastic thing in this row, so it is the one that gives.
   The roster already ellipses names for the same reason; this row did not, and
   it now carries a plate readout beside the pips on a panel that is only
   `min(684px, 100%)` wide. A long callsign must shorten, not push the action
   points off the end of the panel. */
#turnName { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dot { width: 11px; height: 11px; border-radius: 50%; box-shadow: 0 0 10px currentColor; }
.ap { margin-left: auto; display: flex; align-items: center; gap: 3px; }
.ap i {
  width: 9px; height: 15px; border-radius: 2px;
  background: rgba(255, 255, 255, 0.13); display: inline-block;
}
.ap i.on { background: var(--accent); box-shadow: 0 0 7px rgba(255, 201, 78, 0.6); }
.ap span { margin-left: 8px; font-family: var(--mono); font-size: 12px; color: var(--ink-dim); }

/* ---- bolted plate ------------------------------------------------------
   Present only while there is plate on the rack — see paintArmour(). Cool and
   metallic on purpose: the accent yellow belongs to action points and the
   blue to force fields, and plate is neither.

   The ½ and the track say two different things and are spaced apart so they
   read as two things. The ½ is the EFFECT and it is binary: every hit is
   halved for as long as any plate is left, and it does not fade as the plate
   wears. The track is the STOCK, and that does drain. Merged into one bar
   they would have said "half a bar, half the protection", which is precisely
   what bolted plate does not do. */
.armour {
  display: flex; align-items: center; gap: 7px; flex: none;
  padding: 2px 8px; border-radius: 999px;
  background: rgba(190, 212, 238, 0.12);
  border: 1px solid rgba(190, 212, 238, 0.3);
  line-height: 1; cursor: help;
}
.armour b {
  font-family: var(--mono); font-size: 13px; font-weight: 700; color: #e6eefa;
  text-shadow: 0 0 9px rgba(190, 225, 255, 0.6);
}
.armour-track {
  width: 40px; height: 5px; border-radius: 3px; overflow: hidden;
  background: rgba(0, 0, 0, 0.42);
}
.armour-track i {
  display: block; height: 100%; width: 0; border-radius: 3px;
  background: linear-gradient(90deg, #8fa4bb, #e3ecf7);
  transition: width 0.25s;
}
.armour-left {
  font-family: var(--mono); font-size: 11px; font-weight: 600; color: var(--ink-dim);
}

/* ---- aim dial ---------------------------------------------------------- */
.aim-row { display: grid; grid-template-columns: auto auto 1fr; gap: 16px; align-items: center; }

.dial {
  position: relative; width: 62px; height: 62px; border-radius: 50%;
  background: radial-gradient(circle at 50% 45%, rgba(90,150,210,0.14), rgba(0,0,0,0.35));
  border: 1px solid var(--panel-edge);
}
.dial-face {
  position: absolute; inset: 5px; border-radius: 50%;
  /* Horizon line, so elevation is readable against a reference. */
  background: linear-gradient(to bottom, transparent calc(50% - 1px),
    rgba(255,255,255,0.16) calc(50% - 1px), rgba(255,255,255,0.16) calc(50% + 1px), transparent calc(50% + 1px));
}
.dial-arrow {
  position: absolute; left: 50%; top: 50%; width: 26px; height: 3px;
  margin-top: -1.5px; border-radius: 2px; transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--accent), #fff);
  box-shadow: 0 0 9px rgba(255, 201, 78, 0.75);
  /* No transition: the arrow is set every frame from the barrel angle, and a
     transition makes the computed value lag the real aim. */
  will-change: transform;
}
.dial-arrow::after {
  content: ''; position: absolute; right: -6px; top: -3.5px;
  border: 5px solid transparent; border-left-color: #fff;
}
.dial-hub {
  position: absolute; left: 50%; top: 50%; width: 8px; height: 8px;
  margin: -4px 0 0 -4px; border-radius: 50%; background: var(--accent);
  box-shadow: 0 0 8px var(--accent);
}

.aim-readout { display: grid; gap: 3px; justify-items: start; min-width: 86px; }
.aim-readout output {
  font-family: var(--mono); font-size: 21px; font-weight: 700; color: var(--accent);
}
.aim-readout output.depressed { color: #ff9a6c; }
.elev-buttons { display: flex; gap: 4px; }
.elev-buttons button {
  width: 30px; height: 22px; border-radius: 5px; cursor: pointer; font-size: 11px;
  background: rgba(255,255,255,0.07); color: var(--ink); border: 1px solid var(--panel-edge);
}
.elev-buttons button:hover { background: rgba(255,255,255,0.16); }

.move-block { display: grid; gap: 4px; }
.move-block .ctrl-label em { font-style: normal; opacity: 0.55; margin-left: 5px; }

/* ---- jump ---------------------------------------------------------------- */
.btn-jump {
  min-width: 58px; padding: 8px 10px; font-size: 10.5px;
  background: rgba(110, 200, 255, 0.14); border-color: rgba(110, 200, 255, 0.4);
}
.btn-jump:hover:not(:disabled) { background: rgba(110, 200, 255, 0.26); }
.btn-jump.charging { animation: jumpPulse 0.35s ease-in-out infinite alternate; }
@keyframes jumpPulse { to { box-shadow: 0 0 18px rgba(110, 200, 255, 0.8); } }

.jump-meter {
  height: 5px; border-radius: 3px; margin-top: 4px; overflow: hidden;
  background: rgba(255, 255, 255, 0.07); opacity: 0.45; transition: opacity 0.15s;
}
.jump-meter.charging { opacity: 1; }
.jump-fill {
  height: 100%; width: 0; border-radius: 3px;
  background: linear-gradient(90deg, #3d86b5, #7fe3ff);
  box-shadow: 0 0 10px rgba(110, 200, 255, 0.7);
}

/* ---- power charge meter ------------------------------------------------ */
.power-block { display: grid; gap: 3px; }
.power-block .ctrl-label em {
  font-style: normal; opacity: 0.55; margin-left: 6px; letter-spacing: 0;
  text-transform: none; font-size: 10px;
}
.power-meter {
  position: relative; height: 22px; border-radius: 6px; overflow: hidden;
  background: rgba(255, 255, 255, 0.07); border: 1px solid var(--panel-edge);
}
.power-fill {
  position: absolute; inset: 0 auto 0 0; width: 0;
  background: linear-gradient(90deg, #3a7a4a 0%, #b8d84a 45%, #ffb648 72%, #ff4a2e 100%);
  box-shadow: 0 0 14px rgba(255, 140, 60, 0.5);
}
.power-meter.charging .power-fill { box-shadow: 0 0 22px rgba(255, 170, 60, 0.9); }
.power-meter.charging { border-color: rgba(255, 201, 78, 0.6); }
.power-ticks {
  position: absolute; inset: 0; pointer-events: none;
  background: repeating-linear-gradient(90deg,
    transparent 0 calc(10% - 1px), rgba(0, 0, 0, 0.32) calc(10% - 1px), rgba(0, 0, 0, 0.32) 10%);
}
/* Where the last shot of this turn left.
   Deliberately the quietest thing in the meter: two pixels of cool hairline,
   no glow, no number, no motion of its own. The live fill is warm, wide and
   moving and keeps every bit of the attention; this is only found by an eye
   that goes looking for it. The dark 1 px ring is what lets the same hairline
   stay legible on the empty track AND on the bright end of the fill when the
   sweep runs over it. */
/* Ten pixels of hit box, two pixels of ink. The hairline and its dark ring are
   painted by the gradient rather than by the element's own background and
   shadow, so the box can be wide enough to hover without the mark getting any
   louder: it now carries its own name (paintPowerMark) and a 2 px target is a
   tooltip nobody would ever see. */
.power-mark {
  position: absolute; top: 0; bottom: 0; left: 0;
  width: 10px; margin-left: -5px; pointer-events: auto; cursor: help;
  background: linear-gradient(90deg,
    transparent 0 3px,
    rgba(0, 0, 0, 0.45) 3px 4px,
    rgba(232, 238, 247, 0.6) 4px 6px,
    rgba(0, 0, 0, 0.45) 6px 7px,
    transparent 7px);
  animation: markIn 0.22s ease-out;
}
/* Caps, so it reads as a MARK and not as the edge of a second fill. */
.power-mark::before, .power-mark::after {
  content: ''; position: absolute; left: 2px;
  width: 6px; height: 3px; border-radius: 1px;
  background: rgba(232, 238, 247, 0.88);
}
.power-mark::before { top: 0; }
.power-mark::after { bottom: 0; }
@keyframes markIn { from { opacity: 0; transform: scaleY(0.35); } }
@media (prefers-reduced-motion: reduce) { .power-mark { animation: none; } }

.power-meter output {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-family: var(--mono); font-size: 13px; font-weight: 700; color: #fff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85);
  /* It covers the whole meter and is painted last, so without this it eats
     every hover in the gauge — including the last-shot mark's tooltip, which
     is the one thing in here that has something to say on hover. */
  pointer-events: none;
}
.btn-fire.charging { animation: firePulse 0.5s ease-in-out infinite alternate; }
@keyframes firePulse { to { box-shadow: 0 3px 26px rgba(255, 140, 60, 0.85); } }

.ctrl-grid { display: grid; gap: 8px; }
.ctrl { display: grid; grid-template-columns: 58px 1fr; align-items: center; gap: 10px; }
.ctrl-label { font-size: 10px; letter-spacing: 0.14em; color: var(--ink-dim); }
.ctrl-input { display: flex; align-items: center; gap: 5px; }
.ctrl-input button {
  width: 24px; height: 24px; border-radius: 5px; cursor: pointer;
  background: rgba(255, 255, 255, 0.07); color: var(--ink);
  border: 1px solid var(--panel-edge); font-size: 12px; line-height: 1;
}
.ctrl-input button:hover { background: rgba(255, 255, 255, 0.15); }
.ctrl-input button:active { transform: translateY(1px); }
.ctrl-input output {
  width: 58px; text-align: right; font-family: var(--mono);
  font-size: 14px; font-weight: 600; color: var(--accent);
}

input[type=range] {
  flex: 1; -webkit-appearance: none; appearance: none;
  height: 5px; border-radius: 3px; background: rgba(255, 255, 255, 0.13);
  outline: none; cursor: pointer;
}
input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none; width: 15px; height: 15px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 9px rgba(255, 201, 78, 0.7);
  border: 2px solid #2a2214; cursor: grab;
}
input[type=range]::-moz-range-thumb {
  width: 15px; height: 15px; border-radius: 50%; background: var(--accent);
  border: 2px solid #2a2214; cursor: grab;
}

/* The narrower panel took its 76 px out of this row, and the row gives it back
   from the wrong place: the vignette is `flex: 1`, so it absorbs the whole loss
   while three fixed-width buttons keep every pixel. Measured, the armed
   weapon's name started ellipsing at 684 px ("Mini-missile" cut to "Mini-mis…")
   — the one thing on this row the player actually has to read.
   So the buttons pay instead: a tighter gap and less side padding, which costs
   nothing but air, and the name fits again. */
.actions { display: flex; gap: 6px; align-items: center; }
.actions .btn { padding-left: 12px; padding-right: 12px; }
/* ------------------------------------------------- weapon vignette + picker */
/* Replaced a <select>. That control showed one line of text, hid the rest of
   the arsenal behind a click, could not be driven by a gamepad at all, and —
   because its height grew with the option list — kept shoving the panel around
   underneath the real-time clock. This is a FIXED size whatever the player
   carries, which is the point. */
.weapon-vignette {
  flex: 1; display: flex; align-items: stretch; gap: 0;
  min-width: 0;                    /* so a long weapon name shrinks instead of
                                      pushing FIRE off the row */
  height: 42px; border-radius: 8px; overflow: hidden;
  border: 1px solid var(--panel-edge);
  /* Tinted by the armed weapon's own category hue, set per-frame from JS.
     The colour shift is readable at the edge of vision, where the name is
     not — which is what makes cycling feel like it did something. */
  background:
    linear-gradient(90deg,
      hsl(var(--wv-hue, 210) 55% 42% / 0.22),
      hsl(var(--wv-hue, 210) 55% 42% / 0.06));
  transition: background 140ms linear;
}
.wv-step, .wv-face, .wv-open {
  background: transparent; border: 0; color: var(--ink);
  font-family: var(--font); cursor: pointer; padding: 0;
}
.wv-step {
  width: 26px; flex: 0 0 26px; font-size: 17px; line-height: 1;
  color: var(--ink-dim);
}
.wv-step:hover:not(:disabled) { background: rgba(255, 255, 255, 0.09); color: var(--ink); }
.wv-step:disabled { opacity: 0.25; cursor: default; }
.wv-face {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px;
  padding: 0 6px; text-align: left;
}
.wv-face:hover { background: rgba(255, 255, 255, 0.06); }
.wv-icon {
  flex: 0 0 26px; width: 26px; height: 26px;
  display: grid; place-items: center;
}
.wv-icon svg { display: block; width: 100%; height: 100%; }
.wv-lines { display: grid; gap: 1px; min-width: 0; }
.wv-name {
  font-size: 12px; font-weight: 600; line-height: 1.15;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.wv-meta {
  font-size: 10px; color: var(--ink-dim); display: flex; gap: 6px;
  font-family: var(--mono);
}
.wv-meta b { color: var(--accent); font-weight: 600; }
.wv-meta i { font-style: normal; }
.wv-open {
  width: 30px; flex: 0 0 30px; display: grid; place-items: center;
  border-left: 1px solid var(--panel-edge);
}
.wv-open:hover { background: rgba(255, 255, 255, 0.09); }
/* Nine dots, because the button opens a 3x3 grid and saying so with the
   shape costs nothing and needs no translation. */
.wv-dots {
  display: grid; grid-template-columns: repeat(3, 3px); gap: 2px;
}
.wv-dots i { width: 3px; height: 3px; border-radius: 1px; background: var(--ink-dim); }
.wv-open:hover .wv-dots i { background: var(--accent); }

/* Anchored above the controls rather than thrown over the whole screen: in
   real time the shot clock does not stop for a menu, so the battlefield and
   the trajectory arc stay visible while the player picks. */
.weapon-picker {
  position: absolute; bottom: 100%; left: 0; right: 0; margin-bottom: 8px;
  padding: 10px; display: grid; gap: 8px; z-index: 8;
}
.wp-head { display: flex; align-items: center; gap: 8px; }
.wp-title {
  font-size: 10px; letter-spacing: 0.16em; color: var(--ink-dim); font-weight: 700;
}
.wp-page { font-family: var(--mono); font-size: 11px; color: var(--ink-dim); margin-left: auto; }
.wp-page-btn, .wp-close {
  background: rgba(255, 255, 255, 0.07); border: 1px solid var(--panel-edge);
  color: var(--ink); border-radius: 6px; cursor: pointer;
  width: 24px; height: 22px; line-height: 1; font-size: 13px; padding: 0;
}
.wp-page-btn:hover, .wp-close:hover { background: rgba(255, 255, 255, 0.14); }
.wp-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.wp-cell {
  position: relative; display: grid; gap: 3px; justify-items: center;
  padding: 8px 6px 7px; border-radius: 8px; cursor: pointer;
  background: hsl(var(--wp-hue, 210) 55% 42% / 0.14);
  border: 1px solid var(--panel-edge);
  color: var(--ink); font-family: var(--font);
}
.wp-cell:hover { background: hsl(var(--wp-hue, 210) 55% 50% / 0.26); }
.wp-cell.on { border-color: var(--accent); background: hsl(var(--wp-hue, 210) 55% 46% / 0.32); }
/* Cursor and armed are different facts — the keyboard can be hovering a
   weapon that is not the one loaded — so they must not share a colour. */
.wp-cell.cursor { outline: 2px solid var(--ink); outline-offset: -2px; }
.wp-cell.poor { opacity: 0.45; }
.wp-icon { width: 40px; height: 40px; display: grid; place-items: center; }
.wp-icon svg { display: block; width: 100%; height: 100%; }
.wp-name {
  font-size: 10.5px; font-weight: 600; line-height: 1.15; text-align: center;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.wp-meta { font-family: var(--mono); font-size: 9.5px; color: var(--ink-dim); display: flex; gap: 5px; }
.wp-meta b { color: var(--accent); font-weight: 600; }
.wp-meta i { font-style: normal; }
/* Held, and unusable: a plate on a full rack. It replaces the AP cost rather
   than joining it — the reason this tile is dead is the rack, and the price
   of an action the player cannot take is not the news. */
.wp-meta i.wp-full { color: #ff9a6c; font-weight: 700; letter-spacing: 0.04em; }
.wp-cell kbd {
  position: absolute; top: 3px; left: 4px; font-family: var(--mono);
  font-size: 8.5px; color: var(--ink-dim); opacity: 0.7;
}
.wp-hint { font-size: 9.5px; color: var(--ink-dim); text-align: center; }

.btn {
  padding: 9px 16px; border-radius: 7px; cursor: pointer;
  background: rgba(255, 255, 255, 0.08); color: var(--ink);
  border: 1px solid var(--panel-edge);
  font-size: 12px; font-weight: 700; letter-spacing: 0.07em;
  transition: background 0.15s, transform 0.08s;
}
.btn:hover:not(:disabled) { background: rgba(255, 255, 255, 0.16); }
.btn:active:not(:disabled) { transform: translateY(1px); }
.btn:disabled { opacity: 0.35; cursor: not-allowed; }

.btn-fire {
  background: linear-gradient(180deg, #ff8a4c, #e6412a);
  border-color: rgba(255, 160, 110, 0.5); color: #fff;
  box-shadow: 0 3px 16px rgba(230, 65, 42, 0.4);
  min-width: 104px;
}
.btn-fire:hover:not(:disabled) { background: linear-gradient(180deg, #ffa066, #f2543a); }
.btn-move { min-width: 44px; padding: 8px 12px; }

.move-row { display: flex; align-items: center; gap: 8px; }
.fuel {
  flex: 1; height: 16px; border-radius: 8px; position: relative;
  background: rgba(255, 255, 255, 0.07); border: 1px solid var(--panel-edge);
  overflow: hidden;
}
.fuel-fill {
  position: absolute; inset: 0 auto 0 0;
  background: linear-gradient(90deg, #3a7a4a, #6ede8a);
  transition: width 0.2s;
}
.fuel span {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-family: var(--mono); font-size: 10px; color: #d8ffe4;
  text-shadow: 0 1px 2px rgba(0,0,0,0.7);
}

/* ----------------------------------------------------------------- shop */
.shop {
  position: absolute; inset: 0; z-index: 20;
  background: rgba(3, 5, 9, 0.82); backdrop-filter: blur(10px);
  display: grid; place-items: center; pointer-events: auto;
  animation: fadeIn 0.2s;
}
@keyframes fadeIn { from { opacity: 0; } }
.shop-inner {
  width: min(1060px, 94vw); max-height: 86vh; display: flex; flex-direction: column;
  background: var(--panel); border: 1px solid var(--panel-edge);
  border-radius: 14px; box-shadow: var(--shadow); overflow: hidden;
}
.shop-inner header {
  display: flex; align-items: center; gap: 16px;
  padding: 16px 20px; border-bottom: 1px solid var(--panel-edge);
}
.shop-inner h2 { margin: 0; font-size: 19px; letter-spacing: 0.05em; }
.shop-inner .cash {
  margin-left: auto; font-family: var(--mono); font-size: 17px;
  color: var(--good); font-weight: 600;
}
.shop-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(228px, 1fr));
  gap: 10px; padding: 16px 20px; overflow-y: auto;
}
.shop-item {
  text-align: left; padding: 11px 13px; border-radius: 9px; cursor: pointer;
  background: rgba(255, 255, 255, 0.045); border: 1px solid var(--panel-edge);
  color: var(--ink); font-family: var(--font);
  transition: background 0.15s, transform 0.08s, border-color 0.15s;
}
.shop-item:hover { background: rgba(255, 255, 255, 0.1); border-color: rgba(255,201,78,0.4); }
.shop-item:active { transform: translateY(1px); }
/* Unaffordable, not absent. At 0.4 opacity these read as missing from the
   list entirely — a player reported the top-tier weapons simply were not
   there. They stay legible; only the price is marked. */
.shop-item.poor { opacity: 0.78; border-style: dashed; }
.shop-item.poor .si-cost { color: #ff6b5a; }
.shop-item.poor .si-name { color: var(--ink-dim); }
.shop-item.poor:hover { border-color: rgba(255, 107, 90, 0.5); }
.si-top { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.si-name { font-weight: 700; font-size: 13.5px; }
.si-cost { font-family: var(--mono); font-size: 12px; color: var(--good); }
.si-meta {
  display: flex; flex-wrap: wrap; gap: 7px; margin: 6px 0 5px;
  font-size: 10.5px; color: var(--ink-dim); font-family: var(--mono);
}
.si-cat {
  padding: 1px 6px; border-radius: 4px; letter-spacing: 0.05em;
  background: rgba(255, 255, 255, 0.08); text-transform: uppercase; font-size: 9px;
}
.cat-ballistic { background: rgba(255, 130, 70, 0.22); color: #ffb894; }
.cat-cluster   { background: rgba(255, 90, 200, 0.2);  color: #ffabe0; }
.cat-rolling   { background: rgba(200, 190, 120, 0.2); color: #e4dda8; }
.cat-digging   { background: rgba(200, 150, 90, 0.2);  color: #e8c396; }
.cat-earth     { background: rgba(110, 200, 255, 0.2); color: #a8dcff; }
.cat-incendiary{ background: rgba(255, 70, 30, 0.24);  color: #ffa585; }
.cat-beam      { background: rgba(255, 60, 110, 0.22); color: #ff9db8; }
.cat-defense   { background: rgba(90, 220, 160, 0.2);  color: #9deec8; }
.si-owned { color: var(--accent); font-weight: 700; }
/* Says the rack is full on the armour tile. It does NOT stop the purchase —
   plate is stock and a spare is a reasonable buy — it stops the surprise. */
.si-full { color: #ff9a6c; font-weight: 700; letter-spacing: 0.04em; }
.si-desc { margin: 0; font-size: 11px; line-height: 1.42; color: var(--ink-dim); }

/* The armoury is keyboard-driven too — see `shopKey` in ui/hud.js. The cursor
   has to be visible from across the panel and must not read as "already
   bought", so it is a ring rather than a fill. */
.shop-item.cursor {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent), 0 4px 18px rgba(255, 201, 78, 0.18);
}
.shop-hint {
  padding: 7px 20px 10px; font-size: 10px; color: var(--ink-dim);
  text-align: center; border-top: 1px solid var(--panel-edge);
}
.shop-hint:empty { display: none; }

/* --------------------------------------------------------------- banner */
.banner {
  position: absolute; top: 26%; left: 50%; transform: translate(-50%, -50%);
  text-align: center; pointer-events: auto; z-index: 15;
  padding: 22px 42px; border-radius: 14px;
  background: rgba(6, 9, 15, 0.7); border: 1px solid var(--panel-edge);
  backdrop-filter: blur(12px); box-shadow: var(--shadow);
}
.banner h1 {
  margin: 0 0 6px; font-size: 34px; letter-spacing: 0.05em;
  text-shadow: 0 3px 24px rgba(0, 0, 0, 0.8);
}
.banner p { margin: 0; color: var(--ink-dim); font-size: 14px; }
.banner p.warn { color: #ffb4a0; }
.banner .btn { margin-top: 16px; }
.banner.pop { animation: pop 0.42s cubic-bezier(.2, 1.5, .4, 1); }
@keyframes pop {
  from { transform: translate(-50%, -50%) scale(0.85); opacity: 0; }
  to { transform: translate(-50%, -50%) scale(1); opacity: 1; }
}

/* ----------------------------------------------------------------- menu */
#menu {
  position: fixed; inset: 0; z-index: 40; overflow-y: auto;
  background: radial-gradient(1000px 620px at 50% -8%, #1a2839, #05070c 68%);
}
.menu {
  min-height: 100%; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 22px; padding: 40px 20px;
}
.menu-brand { text-align: center; }
.menu-brand h1 {
  margin: 0; font-size: clamp(38px, 7vw, 62px); font-weight: 800; letter-spacing: 0.1em;
  background: linear-gradient(180deg, #fff, #ff9a4c);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.menu-brand h1 span { opacity: 0.75; }
.menu-brand p { margin: 6px 0 0; color: var(--ink-dim); font-size: 13.5px; }

.menu-card {
  width: min(520px, 100%); padding: 24px;
  background: var(--panel); border: 1px solid var(--panel-edge);
  border-radius: 14px; box-shadow: var(--shadow);
  backdrop-filter: blur(14px); display: grid; gap: 14px;
}
.menu-card h2 { margin: 0; font-size: 17px; letter-spacing: 0.05em; }
.menu-card h3 { margin: 6px 0 0; font-size: 11px; letter-spacing: 0.16em;
  color: var(--ink-dim); text-transform: uppercase; }

.field { display: grid; gap: 5px; }
.field > span { font-size: 10px; letter-spacing: 0.14em; color: var(--ink-dim); text-transform: uppercase; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.menu-card input, .menu-card select {
  width: 100%; padding: 10px 12px; border-radius: 8px; font-size: 14px;
  background: rgba(255, 255, 255, 0.06); color: var(--ink);
  border: 1px solid var(--panel-edge); font-family: var(--font);
}
.menu-card input:focus, .menu-card select:focus {
  outline: none; border-color: rgba(255, 201, 78, 0.55);
}
.menu-card option { background: #0d1119; }

.menu-actions { display: grid; gap: 9px; }
.menu-actions.row { grid-auto-flow: column; justify-content: end; }
.menu-actions.row.wrap { grid-auto-flow: row; grid-template-columns: auto 1fr; justify-content: stretch; }
.btn.big {
  padding: 14px 18px; text-align: left; display: grid; gap: 2px; font-size: 14px;
}
.btn.big small { font-size: 10.5px; font-weight: 400; letter-spacing: 0; color: var(--ink-dim); }
.btn.btn-fire.big small { color: rgba(255, 255, 255, 0.75); }
.btn.tiny { padding: 5px 9px; font-size: 10px; }
.inline-select { max-width: 160px; }

.code-row { display: grid; grid-template-columns: 1fr auto; gap: 8px; }
.code-row input { font-family: var(--mono); font-size: 19px; letter-spacing: 0.2em; text-align: center; }
.code-badge {
  display: flex; align-items: center; gap: 12px; padding: 12px 16px;
  border-radius: 10px; background: rgba(255, 201, 78, 0.09);
  border: 1px solid rgba(255, 201, 78, 0.28);
}
.code-badge span { font-size: 9.5px; letter-spacing: 0.16em; color: var(--ink-dim); }
.code-badge b { font-family: var(--mono); font-size: 25px; letter-spacing: 0.22em; color: var(--accent); }
.code-badge .btn { margin-left: auto; }

.roster { display: grid; gap: 6px; }
.rost {
  display: flex; align-items: center; gap: 10px; padding: 9px 12px;
  border-radius: 8px; background: rgba(255, 255, 255, 0.045);
  border: 1px solid var(--panel-edge); font-size: 13px;
}
.rost.empty { opacity: 0.35; border-style: dashed; }
.rc { width: 11px; height: 11px; border-radius: 3px; box-shadow: 0 0 8px currentColor; }
.rn { flex: 1; }
.rtag {
  font-size: 9px; letter-spacing: 0.1em; padding: 2px 7px; border-radius: 4px;
  background: rgba(255, 255, 255, 0.08); color: var(--ink-dim); text-transform: uppercase;
}
.rtag.human { background: rgba(78, 222, 138, 0.16); color: var(--good); }

.lobby-list { display: grid; gap: 6px; max-height: 210px; overflow-y: auto; }
.lobby-item {
  display: grid; grid-template-columns: 1fr auto auto; gap: 12px; align-items: center;
  padding: 10px 12px; border-radius: 8px; cursor: pointer; text-align: left;
  background: rgba(255, 255, 255, 0.045); border: 1px solid var(--panel-edge);
  color: var(--ink); font-family: var(--font); font-size: 13px;
}
.lobby-item:hover { background: rgba(255, 255, 255, 0.1); border-color: rgba(255,201,78,0.4); }
.li-code { font-family: var(--mono); color: var(--accent); letter-spacing: 0.1em; }
.li-count { font-family: var(--mono); color: var(--ink-dim); font-size: 12px; }
.hint { margin: -4px 0 0; font-size: 11.5px; color: var(--ink-dim); }
.hint.warn { color: #ffb4a0; }

.names { display: grid; gap: 6px; }
.name-row { display: grid; grid-template-columns: 12px 1fr; gap: 9px; align-items: center; }
.name-row .swatch { width: 12px; height: 12px; border-radius: 3px; box-shadow: 0 0 8px currentColor; }
.name-row input { padding: 7px 10px; font-size: 13px; }

/* ------------------------------------------------------- vehicle picker */
/* Eight machines, six multipliers each, compared at a glance.
   The bar rendering is the one `public/doc/vehicles.html` invented and both
   now share through `ui/chassisBars.js`: 1.00 is the centre line, a bonus
   grows right and a penalty grows left, so an advantage and a cost are two
   SHAPES rather than two numbers to compare digit by digit.
   Everything here is scoped under `.veh-card` on purpose — `.bar` already
   means something else inside the loading card. */

/* Three columns of tiles do not fit a 520 px setup card, and two of them are
   not enough to compare shapes across the roster. */
.menu-card.wide { width: min(1180px, 100%); }

/* This one screen is dense, so it takes the chrome back. Measured, not
   guessed: at 1366x768 the picker stood at 875 px against a 768 px viewport,
   which put DEPLOY below the fold on the commonest laptop there is. Trimming
   the padding, the gaps and the wordmark returns 130 px and it fits.
   Trimmed again when the tiles got their machines: a picture takes a column
   out of the tile, the one-liner rewraps taller, and the screen pays that
   twice — once per grid row. Measured across all four locales and one, two and
   four hotseat seats, the worst case (German, four seats) lands at 760 px
   against 768. Every pixel below comes out of chrome — outer padding, the gaps
   between blocks, the leading of the bar rows — and none out of the bars, the
   blurbs or the balance line. */
.menu.wide { padding: 12px 20px; gap: 11px; }
.menu.wide .menu-brand h1 { font-size: clamp(28px, 4vw, 38px); }
.menu.wide .menu-brand p { font-size: 12px; }
.menu.wide .menu-card { padding: 18px 20px; gap: 9px; }

.veh-strip { display: flex; flex-wrap: wrap; gap: 8px; }
.veh-seat {
  display: flex; align-items: center; gap: 8px; padding: 5px 10px;
  border-radius: 8px; font-size: 12px;
  background: rgba(255, 255, 255, 0.045); border: 1px solid var(--panel-edge);
}
.veh-seat.on { border-color: rgba(255, 201, 78, 0.5); background: rgba(255, 201, 78, 0.1); }
.veh-seat-pick { color: var(--ink-dim); }
.veh-strip .swatch { width: 11px; height: 11px; border-radius: 3px; flex: none; }

.veh-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 8px;
  /* The thumbnail size, in one place, because the renderer needs the same two
     numbers (`TILE_W`/`TILE_H` in ui/chassisPreview.js) to size its buffer. */
  --thumb-w: 116px;
  --thumb-h: 84px;
}

/* The tile is a real <button>: one control the mouse, the Tab key, the arrow
   keys and the pad all press the same way. That is also why the stat rows are
   spans — a <div> inside a <button> is not phrasing content. */
/* Three rows: heading, blurb, bars. The blurb takes the slack so the bar
   block sits on the floor of every tile — otherwise a machine whose one-liner
   wraps to three lines pushes its bars a line lower than its neighbours', and
   the whole point of this screen is comparing bar shapes ACROSS tiles. */
.veh-card {
  display: grid; grid-template-rows: auto 1fr auto; gap: 7px; text-align: left;
  padding: 11px 12px; border-radius: 10px; cursor: pointer;
  background: rgba(255, 255, 255, 0.045); border: 1px solid var(--panel-edge);
  color: var(--ink); font-family: var(--font); font-size: 13px;
}
.veh-card:hover { background: rgba(255, 255, 255, 0.09); border-color: rgba(140, 180, 230, 0.34); }
.veh-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Selected takes the SEAT's colour, not a house accent: the colour on this
   tile is the colour the machine will actually be, which is the whole answer
   to "two of us took the same vehicle". */
.veh-card.on {
  border-color: var(--seat); background: rgba(255, 255, 255, 0.1);
  box-shadow: inset 0 0 0 1px var(--seat), 0 0 22px -10px var(--seat);
}
.veh-card.on .veh-name { color: var(--seat); }

/* ---- the machine, beside the words that describe it --------------------- */
/* `.art` is set only when there is a WebGL context to draw with, so a machine
   without one keeps yesterday's tile instead of eight reserved holes.
   Beside the text, never above it: stacking the picture over the name costs
   76-147 px per tile — 200-290 px of screen — which pushes DEPLOY and half the
   roster off a laptop and defeats the one job this screen has, comparing eight
   machines at a glance. Spanning the name and blurb rows costs the height of
   neither: measured, the name-plus-blurb block is over 106 px tall in every
   locale at this column width, so the picture's height is free and only its
   width is paid for. */
.veh-grid.art .veh-card {
  grid-template-columns: var(--thumb-w) 1fr;
  grid-template-rows: auto 1fr auto;
  column-gap: 11px;
}
.veh-grid.art .veh-top { grid-column: 2; grid-row: 1; }
.veh-grid.art .veh-blurb { grid-column: 2; grid-row: 2; }
/* The bars stay full width: they are the instrument this screen compares
   ACROSS tiles, and a narrower track is a coarser instrument. */
.veh-grid.art .bars { grid-column: 1 / 3; grid-row: 3; }
.veh-thumb {
  grid-column: 1; grid-row: 1 / 3;
  width: var(--thumb-w); height: var(--thumb-h);
  align-self: start; display: block;
}
/* Transparent, so the machine sits on the tile and inherits its hover and
   selected states rather than sitting in a box that ignores them. */
.veh-thumb-canvas { display: block; width: 100%; height: 100%; }

/* A machine that has not been drawn yet.
   Shader compilation is queued ahead of all eight draws — it must be, because a
   program's key includes the output colour space — so clicking SKIRMISH then
   DEPLOY in one breath reaches this screen before any machine exists. Without
   this, that is eight empty reserved columns, which reads as broken rather than
   as loading. The canvas keeps its own transparency; the slot behind it does
   the waiting. */
.veh-thumb { border-radius: 8px; }
.veh-thumb:has(.veh-thumb-canvas:not(.drawn)) {
  background: linear-gradient(105deg,
    rgba(255, 255, 255, 0.05) 30%,
    rgba(140, 180, 230, 0.13) 50%,
    rgba(255, 255, 255, 0.05) 70%) 0 0 / 300% 100%;
  animation: vehWait 1.25s ease-in-out infinite;
}
@keyframes vehWait { to { background-position: -150% 0; } }
@media (prefers-reduced-motion: reduce) {
  .veh-thumb:has(.veh-thumb-canvas:not(.drawn)) { animation: none; }
}

.veh-top { display: flex; align-items: center; gap: 8px; }
.veh-name { font-weight: 700; font-size: 13.5px; letter-spacing: 0.02em; }
.veh-dots { display: flex; gap: 4px; margin-left: auto; }
.veh-dots .swatch { width: 8px; height: 8px; border-radius: 2px; display: block; }
/* The seat currently choosing, so a tile several players share still says
   which of those dots is you. */
.veh-dots .swatch.me { width: 10px; height: 10px; box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.55); }
.veh-blurb { font-size: 11px; line-height: 1.4; color: var(--ink-dim); }

.veh-card .bars { display: grid; gap: 2px; margin-top: 1px; }
/* `line-height: 1` on rows whose text is 9 px uppercase and a mono multiplier —
   neither has a descender to clip — is 12 px of tile height back per card, and
   the row is still taller than the 7 px track it holds. */
.veh-card .bar {
  display: grid; grid-template-columns: 1fr 96px 38px; gap: 8px;
  align-items: center; font-size: 9px; line-height: 1;
}
.veh-card .bar-label {
  color: var(--ink-dim); letter-spacing: 0.04em; text-transform: uppercase;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The fill is absolute, so the track has to be the positioned ancestor. */
.veh-card .bar-track {
  position: relative; height: 7px; border-radius: 4px;
  background: rgba(255, 255, 255, 0.09);
}
/* The 1.00 line. It has to be the brightest thing in the track or the bars
   stop being deviations and turn back into eight unrelated smudges. */
.veh-card .bar-track::after {
  content: ''; position: absolute; left: 50%; top: -3px; bottom: -3px;
  width: 1px; background: rgba(255, 255, 255, 0.5);
}
.veh-card .bar-fill { position: absolute; top: 0; bottom: 0; border-radius: 4px; }
.veh-card .bar-fill.up { background: linear-gradient(90deg, #3f7dd8, var(--good)); }
.veh-card .bar-fill.down { background: linear-gradient(90deg, var(--fire), #b8452c); }
.veh-card .bar-val { font-family: var(--mono); font-size: 10px; text-align: right; }

/* A stat nothing in this rule set reads. Greyed rather than removed: the
   machine has to read the same in both rule sets, and a bar that vanishes
   between two matches looks like a bug rather than like an explanation. */
.veh-card .bar.dead { opacity: 0.3; }
.veh-card .bar.dead .bar-fill { background: rgba(255, 255, 255, 0.3); }
.veh-card .bar.dead .bar-val { text-decoration: line-through; }

.veh-fair {
  margin: 0; font-size: 11.5px; line-height: 1.45; color: var(--ink-dim);
  border-left: 2px solid var(--good); padding-left: 10px;
}
/* The controller hint rides beside the buttons rather than adding a fourth
   grey paragraph under a screen that already asks the player to read three. */
.menu-actions.row.veh-actions {
  grid-auto-flow: column; grid-template-columns: 1fr auto auto;
  justify-content: stretch; align-items: center;
}
.veh-actions .hint { margin: 0; font-size: 11px; }

/* Hotseat handover — deliberately large and unmissable, because its whole job
   is to stop the next player acting before they have actually taken over. */
.handoff { display: grid; gap: 4px; place-items: center; }
.handoff-dot {
  width: 16px; height: 16px; border-radius: 50%; margin-bottom: 4px;
  box-shadow: 0 0 18px currentColor;
}
.handoff h1 { font-size: 30px; margin: 0; }
.handoff p { margin: 2px 0 6px; }

.menu-err { margin: 0; min-height: 16px; color: #ffb4a0; font-size: 12px; }
.dim { color: var(--ink-dim); font-size: 12px; margin: 4px 0 0; }
.menu-foot {
  font-size: 11px; display: flex; align-items: center; gap: 16px;
}
.menu-foot a { color: var(--ink-dim); text-decoration: none; }
.menu-foot a:hover { color: var(--accent); }

/* ------------------------------------------------------------- language */
/* A flag alone is a country, not a language, so the control is a real
   <select> carrying both the flag and the language's own name — keyboard and
   screen-reader navigable, and readable by someone who does not recognise the
   flag. The emoji sits outside the select because platforms disagree about
   whether they will render one inside a closed <select> at all; the option
   list keeps its own copy for the open state. */
.lang-select {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 4px 9px 4px 7px; border-radius: 999px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--panel-edge);
  cursor: pointer; pointer-events: auto;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.lang-select:hover { border-color: rgba(255, 201, 78, 0.45); background: rgba(255, 255, 255, 0.08); }
.lang-select:focus-within { border-color: rgba(255, 201, 78, 0.7); }

.lang-flag { font-size: 15px; line-height: 1; }

.lang-select select {
  appearance: none; -webkit-appearance: none;
  background: transparent; border: 0; outline: none;
  color: var(--ink); font-family: var(--font); font-size: 12px;
  letter-spacing: 0.02em; cursor: pointer;
  padding: 0 14px 0 0;
  /* The chevron, drawn rather than imported so the control stays offline. */
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-dim) 50%),
                    linear-gradient(135deg, var(--ink-dim) 50%, transparent 50%);
  background-position: right 5px center, right 1px center;
  background-size: 4px 4px, 4px 4px;
  background-repeat: no-repeat;
}
.lang-select option { background: #0d1119; color: var(--ink); }

/* In the HUD it sits in the environment strip beside WIND and ROUND, where a
   full language name would push the panel across the sky. The flag becomes the
   visible face and the real <select> is laid transparently over the whole
   control: still focusable, still announced by its aria-label, still opens the
   full flag-plus-name list — but it costs 34 pixels instead of 110. Shrinking
   the select's own text to zero would have been shorter to write and would
   have hidden the control from anyone whose browser draws that text as the
   accessible name. */
.lang-select.compact { position: relative; padding: 4px 16px 4px 8px; gap: 0; }
.lang-select.compact .lang-flag { font-size: 16px; }
.lang-select.compact select {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; padding: 0; cursor: pointer;
}
.lang-select.compact::after {
  content: ''; position: absolute; right: 6px; top: 50%;
  margin-top: -1.5px;
  border: 3.5px solid transparent; border-top-color: var(--ink-dim);
  pointer-events: none;
}
.lang-select.compact:focus-within::after { border-top-color: var(--accent); }

/* The menu foot is not a .panel, so the control has to carry its own contrast
   against the background gradient there. */
.menu-lang { margin-left: auto; }

/* -------------------------------------------------------------- loading */
#loading {
  position: fixed; inset: 0; z-index: 50; display: grid; place-items: center;
  background: radial-gradient(circle at 50% 40%, #16202e, #05070c 70%);
}
.load-inner { text-align: center; }
.load-inner h1 {
  margin: 0 0 20px; font-size: 46px; letter-spacing: 0.12em; font-weight: 800;
  background: linear-gradient(180deg, #fff, #ff9a4c);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.load-inner h1 span { color: transparent; opacity: 0.7; }
.load-inner .bar {
  width: 240px; height: 3px; border-radius: 2px; margin: 0 auto 14px;
  background: rgba(255, 255, 255, 0.1); overflow: hidden;
}
.load-inner .bar i {
  display: block; height: 100%; width: 40%; border-radius: 2px;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  animation: sweep 1.1s ease-in-out infinite;
}
@keyframes sweep { from { transform: translateX(-100%); } to { transform: translateX(350%); } }
.load-inner p { color: var(--ink-dim); font-size: 13px; margin: 0; }
.err { max-width: 720px; text-align: left; padding: 20px; }
.err pre { font-size: 11px; color: #ff9d8a; overflow: auto; white-space: pre-wrap; }

@media (max-width: 720px) {
  .hud-log { display: none; }
  .players { min-width: 160px; font-size: 11px; }
  .controls { padding: 10px; }
  .ctrl-input button { width: 30px; height: 30px; }
}
