/* ui/hud.css — Overwatch-grade HUD + title menu (HUD agent).
   Art bible §10: skew -6°, 8px grid, radius 3px, accent #F99E1A, health #F2F5F7.
   Type: self-hosted Barlow Condensed (OFL) — 600 italic labels, 800 italic numerals.

   ONE system, no exceptions:
   · scale     — every dimension is a multiple of --u (1px at 1080p, proportional)
   · type      — six steps --t1..--t6, three tracking values --tr-*
   · material  — one panel fill, one blur, one edge, one accent rule per family
   · warm hues — orange = readiness · gold = damage you deal · flash = damage you take */

/* ---------------- self-hosted type (latin subsets, ~16 KB each) ---------------- */
@font-face {
  font-family: 'Barlow Condensed';
  font-style: italic;
  font-weight: 600;
  font-display: block;
  src: url('fonts/BarlowCondensed-SemiBoldItalic-latin.woff2') format('woff2');
}
@font-face {
  font-family: 'Barlow Condensed';
  font-style: italic;
  font-weight: 800;
  font-display: block;
  src: url('fonts/BarlowCondensed-ExtraBoldItalic-latin.woff2') format('woff2');
}
@font-face {
  font-family: 'Barlow Condensed';
  font-style: normal;
  font-weight: 600;
  font-display: block;
  src: url('fonts/BarlowCondensed-SemiBold-latin.woff2') format('woff2');
}

:root {
  /* --- resolution scalar: one scaled pixel. 1px at 1080p, proportional to the
         display height, floored so a short window stays usable and capped so
         8K does not turn the HUD into a billboard. --- */
  --u: clamp(0.66px, 0.0926vh, 2.2px);

  /* --- type scale: six steps, nothing may invent a seventh --- */
  --t1: calc(11 * var(--u));
  --t2: calc(14 * var(--u));
  --t3: calc(20 * var(--u));
  --t4: calc(30 * var(--u));
  --t5: calc(48 * var(--u));
  --t6: calc(68 * var(--u));
  --tr-body: 0.02em;
  --tr-label: 0.18em;
  --tr-display: 0.32em;

  /* --- one safe area --- */
  --hud-inset: calc(40 * var(--u));

  /* --- colour: three warm values, hard-assigned --- */
  --hud-white: #f2f5f7;
  --hud-orange: #f99e1a; /* readiness · ammo · score · ability ready */
  --hud-gold: #ffc873;   /* damage YOU deal · low-ammo warning */
  --hud-flash: #ff4b4b;  /* damage you TAKE — nothing else */
  --hud-alarm: #ff3b47;  /* empty magazine · overheat · elimination hitmarker */
  --hud-hot: #ff6b33;    /* the step between amber and alarm */
  --hud-lag: #ff6b6b;    /* health you just lost, still shown */
  --hud-red: #e8402a;    /* elimination semantics (banner rule, killfeed rule) */
  --hud-shimmer: #9fe8dc;

  /* --- one panel material ---
     r3: at 0.72 alpha / 6px blur the backdrop still dominated, so the same
     "glass" measured (60,59,57) warm over cobble and (56,67,77) cool over sky —
     a 2.1x luminance swing across one frame. 0.92 alpha plus a desaturating,
     darkening blur pins every panel inside ~10 luminance units of every other,
     whatever is behind it. --- */
  --hud-panel: rgba(10, 12, 16, 0.86);
  --hud-blur: blur(10px) saturate(0.5) brightness(0.72);
  /* one dot-matrix: 0.045/6u read as polka dots at 1080p, 0.028/8u reads as texture */
  --hud-dots: radial-gradient(rgba(255, 255, 255, 0.028) 1px, transparent 1.1px);
  --hud-dot-pitch: calc(8 * var(--u));
  --hud-edge:
    inset 0 1px 0 rgba(255, 255, 255, 0.125),
    inset 0 -1px 0 rgba(0, 0, 0, 0.35),
    0 2px 10px rgba(0, 0, 0, 0.35);
  --hud-rule: calc(3 * var(--u));
  --hud-skew: skewX(-6deg);
  --hud-font: 'Barlow Condensed', sans-serif;

  /* --- the hero accent (HUD.setHero writes these from hero.palette) ---
     The HUD's chrome does NOT change per hero — critique round 1 collapsed it
     to three warm values and four hero hues in the chrome would put it back.
     The accent is spent on exactly four things: the select card's rule, the
     hero name plate on that card, the card's portrait frame, and KESTREL's
     charge bar. Defaults are VESPER's, so an un-skinned HUD is never unstyled. */
  --hero-primary: #e8e4da;
  --hero-secondary: #2b2e33;
  --hero-accent: #f2762b;
  --hero-emissive: #63f2e8;
  /* --hero-hue is the IDENTITY colour (hud.js HERO_HUE): four distinct hues,
     one per hero, driving the weapon name, the panel bottom rules, the ready
     ability glyph, the ultimate ring and the whole select card. #F99E1A is now
     reserved for CROSS-hero chrome only — objective strip, elimination banner,
     ultimate-cast banner — so the hero colour actually means something. */
  --hero-hue: #f99e1a;
}

#hud-root {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 10;
  font-family: var(--hud-font);
  font-weight: 600; /* labels */
  font-style: italic;
  letter-spacing: var(--tr-body);
  color: var(--hud-white);
  user-select: none;
  overflow: hidden;
}

#hud-root.hidden { display: none; }

/* the single panel treatment: dark glass + 1px top edge + dot matrix.
   Every panel in the product uses this and nothing else. */
#hud-root .panel,
#hud-root .ability,
#hud-root .kf-row,
.menu-mode {
  background-color: var(--hud-panel);
  background-image: var(--hud-dots);
  background-size: var(--hud-dot-pitch) var(--hud-dot-pitch);
  border-radius: 3px;
  box-shadow: var(--hud-edge);
  backdrop-filter: var(--hud-blur);
  transform: var(--hud-skew);
}

/* one denominator component — health, ammo and the objective counter print the
   "of N" the SAME way: no space, one slash weight, 55% alpha, 0.52em. */
#hud-root .den {
  font-size: 0.52em;
  font-weight: 800;
  line-height: 1;
  opacity: 0.55;
  margin-left: 0.1em;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}
#hud-root .den i { font-style: inherit; }

/* ---------------- one icon system ----------------
   Every ability, weapon and ultimate glyph is the same 24x24 master (hud.js
   ICO): 2u stroke, round caps, one solid accent shape. The contour is TWO 1px
   drop-shadows on the svg, so the stroke keeps its full mass and the glyph
   survives on cream plaster as well as on the panel. */
#hud-root .ico,
#menu-root .ico {
  display: block;
  width: 100%;
  height: 100%;
  filter: drop-shadow(0 0 1px rgba(11, 16, 23, 0.95)) drop-shadow(0 1px 1.5px rgba(0, 0, 0, 0.7));
}

/* ---------------- crosshair ----------------
   Signature reticle: 4 cardinal ticks in a 5–13px annulus, a 3px centre dot and
   a 22px identity ring at 45% alpha, so the hit-confirm X (19–27u, diagonals
   only) can never share its footprint.
   TWO rules keep it crisp. (1) The contour is a DIRECTIONAL drop-shadow on the
   whole group, never a `box-shadow: 0 0 0 1px` ring — a ring on a 2px tick makes
   the graphic 4px wide and half of it black (r3 read as four dominoes).
   (2) The reticle is the ONE element that opts out of --u: at 1080p --u is
   1.00008px, so 5u/8u landed on four different subpixel phases and the arms
   measured 8.1/8.75/9.4/10px. Integer px per breakpoint, identical rasterization
   on all four arms. */
@property --ch-push {
  syntax: '<length>';
  inherits: true;
  initial-value: 0px;
}
@property --ch-spread {
  syntax: '<number>';
  inherits: true;
  initial-value: 0;
}
@property --ch-charge {
  syntax: '<number>';
  inherits: true;
  initial-value: 0;
}
@property --ch-heat {
  syntax: '<number>';
  inherits: true;
  initial-value: 0;
}
.hud-crosshair {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 0;
  height: 0;
  --ch-dot: 3px;
  --ch-tick: 2px;
  --ch-len: 9px;
  --ch-gap: 6px;
  --ch-push: 0px;
  --ch-spread: 0;
  --ch-charge: 0;
  --ch-heat: 0;
  --ch-ink: rgba(255, 255, 255, 0.96);
  /* the resting gap opens toward 14px under sustained fire, and the per-shot
     snap rides on top of it as --ch-push */
  --ch-open: calc(var(--ch-gap) + 8px * var(--ch-spread) + var(--ch-push));
  transition: --ch-push 0.12s cubic-bezier(0.22, 1, 0.36, 1),
    --ch-spread 0.12s linear, --ch-charge 0.06s linear, --ch-heat 0.1s linear;
  /* a real 1px contour on every mark: two stacked hard shadows, not one soft
     blur — the reticle has to survive a white wall and LUMEN's own ult bloom */
  filter:
    drop-shadow(0 0 1px rgba(0, 0, 0, 0.7)) drop-shadow(0 0 1px rgba(0, 0, 0, 0.7))
    drop-shadow(0 1px 1px rgba(0, 0, 0, 0.55));
}
@media (min-height: 1400px) {
  .hud-crosshair { --ch-dot: 4px; --ch-tick: 3px; --ch-len: 13px; --ch-gap: 9px; }
}
/* every reticle part is hidden by default; each mode switches its own on */
.hud-crosshair .ch-dot,
.hud-crosshair .ch-tick,
.hud-crosshair .ch-brk,
.hud-crosshair .ch-cnr,
.hud-crosshair .ch-spread { display: none; position: absolute; }

/* ---- VESPER / auto-hitscan: a four-line cross, no ring, no dot ---- */
.hud-crosshair.ret-cross .ch-tick { display: block; background: var(--ch-ink); }
.hud-crosshair .t {
  left: calc(var(--ch-tick) / -2);
  top: calc(-1 * (var(--ch-open) + var(--ch-len)));
  width: var(--ch-tick);
  height: var(--ch-len);
}
.hud-crosshair .b {
  left: calc(var(--ch-tick) / -2);
  top: var(--ch-open);
  width: var(--ch-tick);
  height: var(--ch-len);
}
.hud-crosshair .l {
  top: calc(var(--ch-tick) / -2);
  left: calc(-1 * (var(--ch-open) + var(--ch-len)));
  height: var(--ch-tick);
  width: var(--ch-len);
}
.hud-crosshair .r {
  top: calc(var(--ch-tick) / -2);
  left: var(--ch-open);
  height: var(--ch-tick);
  width: var(--ch-len);
}

/* ---- KESTREL / charge-hitscan: a 1.5px centre dot inside four corner
   brackets that converge 22px → 8px as the rail charges, then go solid gold ---- */
.hud-crosshair.ret-charge .ch-dot,
.hud-crosshair.ret-charge .ch-cnr { display: block; }
.hud-crosshair.ret-charge {
  --ch-dot: 3px;
  --ch-r: calc(22px - 14px * var(--ch-charge));
  --ch-arm: 7px;
  --ch-col: var(--ch-ink);
}
.hud-crosshair.ret-charge.charged { --ch-col: var(--hud-gold); }
.hud-crosshair.ret-charge .ch-dot {
  left: calc(var(--ch-dot) / -2);
  top: calc(var(--ch-dot) / -2);
  width: var(--ch-dot);
  height: var(--ch-dot);
  border-radius: 50%;
  background: var(--ch-col);
}
.hud-crosshair .ch-cnr {
  width: var(--ch-arm);
  height: var(--ch-arm);
  border: 0 solid var(--ch-col);
}
.hud-crosshair .ch-cnr.tl {
  left: calc(-1 * var(--ch-r)); top: calc(-1 * var(--ch-r));
  border-top-width: 2px; border-left-width: 2px;
}
.hud-crosshair .ch-cnr.tr {
  left: calc(var(--ch-r) - var(--ch-arm)); top: calc(-1 * var(--ch-r));
  border-top-width: 2px; border-right-width: 2px;
}
.hud-crosshair .ch-cnr.bl {
  left: calc(-1 * var(--ch-r)); top: calc(var(--ch-r) - var(--ch-arm));
  border-bottom-width: 2px; border-left-width: 2px;
}
.hud-crosshair .ch-cnr.br {
  left: calc(var(--ch-r) - var(--ch-arm)); top: calc(var(--ch-r) - var(--ch-arm));
  border-bottom-width: 2px; border-right-width: 2px;
}

/* ---- BRAMBLE / pellet-hitscan: a four-arc circle at the pellet spread ---- */
.hud-crosshair.ret-spread .ch-spread { display: block; }
.hud-crosshair .ch-spread {
  --ch-rad: 26px;
  left: calc(-1 * var(--ch-rad));
  top: calc(-1 * var(--ch-rad));
  width: calc(2 * var(--ch-rad));
  height: calc(2 * var(--ch-rad));
  border-radius: 50%;
  border: 2px solid var(--ch-ink);
  /* four arcs, not a ring: the gaps are where the pellets are not */
  -webkit-mask-image: conic-gradient(from 22.5deg, #000 0 45deg, transparent 45deg 90deg,
    #000 90deg 135deg, transparent 135deg 180deg, #000 180deg 225deg,
    transparent 225deg 270deg, #000 270deg 315deg, transparent 315deg 360deg);
  mask-image: conic-gradient(from 22.5deg, #000 0 45deg, transparent 45deg 90deg,
    #000 90deg 135deg, transparent 135deg 180deg, #000 180deg 225deg,
    transparent 225deg 270deg, #000 270deg 315deg, transparent 315deg 360deg);
}

/* ---- LUMEN / beam: two vertical brackets that close ±10px → ±4px as the beam
   locks on, and take the heat ramp as they climb toward the vent ---- */
.hud-crosshair.ret-beam .ch-brk { display: block; }
.hud-crosshair.ret-beam {
  --ch-jaw: calc(10px - 6px * var(--ch-heat));
  --ch-col: var(--ch-ink);
}
/* the reticle carries the vent warning too */
.hud-crosshair.ret-beam.hot { --ch-col: var(--hud-alarm); }
.hud-crosshair .ch-brk {
  width: 2px;
  height: 16px;
  top: -8px;
  background: var(--ch-col);
}
.hud-crosshair .ch-brk.bl { left: calc(-1 * var(--ch-jaw) - 2px); }
.hud-crosshair .ch-brk.br { left: var(--ch-jaw); }
.hud-crosshair.ret-beam .ch-brk::before,
.hud-crosshair.ret-beam .ch-brk::after {
  content: '';
  position: absolute;
  width: 5px;
  height: 2px;
  background: var(--ch-col);
}
.hud-crosshair.ret-beam .ch-brk.bl::before { left: 0; top: 0; }
.hud-crosshair.ret-beam .ch-brk.bl::after { left: 0; bottom: 0; }
.hud-crosshair.ret-beam .ch-brk.br::before { right: 0; top: 0; }
.hud-crosshair.ret-beam .ch-brk.br::after { right: 0; bottom: 0; }

/* reload clock under the reticle — the player never has to look away from the
   fight to know when the magazine is back */
.hud-crosshair .ch-reload {
  position: absolute;
  left: calc(-20 * var(--u));
  top: calc(-20 * var(--u));
  width: calc(40 * var(--u));
  height: calc(40 * var(--u));
  opacity: 0;
  overflow: visible;
}
.hud-crosshair.reloading .ch-reload { opacity: 1; }
.hud-crosshair .chr-case { fill: none; stroke: rgba(6, 10, 16, 0.35); stroke-width: 4; }
.hud-crosshair .chr-fill {
  fill: none;
  stroke: var(--hud-gold);
  stroke-width: 3;
  stroke-linecap: butt;
}
/* per-shot snap on top of the sustained bloom (feel-spec §3) */
.hud-crosshair.bloom { animation: chbloom 0.24s cubic-bezier(0.22, 1, 0.36, 1); }
@keyframes chbloom {
  0% { --ch-push: 0px; }
  25% { --ch-push: 5px; }
  100% { --ch-push: 0px; }
}
.hud-crosshair.expand { --ch-push: 3px; }

/* kill flash: slim red ring, 200ms burst, ends at opacity 0 — never held */
.hud-crosshair .ch-killring {
  position: absolute;
  left: calc(-22 * var(--u));
  top: calc(-22 * var(--u));
  width: calc(44 * var(--u));
  height: calc(44 * var(--u));
  border: 1.5px solid var(--hud-red);
  border-radius: 50%;
  box-shadow: 0 0 4px rgba(232, 64, 42, 0.5);
  opacity: 0;
}
.hud-crosshair .ch-killring.go { animation: killring 0.2s cubic-bezier(0.22, 1, 0.36, 1) forwards; }
@keyframes killring {
  0% { opacity: 0.95; transform: scale(0.1); }
  100% { opacity: 0; transform: scale(1); }
}

/* hitmarker (feel-spec §3): four THIN white strokes at 45° under a soft black
   drop-shadow contour — not four rotated slabs. It rides at 0.92 opacity so it
   never fully hides the face it is drawn over, and its annulus (19–27u,
   diagonals) is disjoint from the reticle's (5–13u, cardinals), so the two
   graphics are unambiguous without dimming either one.
   Colour law: white = a hit · #FFF0C8 (hot cream, one step inside the warm
   family) = a crit · --hud-red = an elimination. #FF4B4B stays reserved for
   damage you TAKE. */
.hud-hitmarker {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 0;
  height: 0;
  opacity: 0;
  --hm-w: 2.4;
  --hm-col: #ffffff;
}
/* ONE svg, four <line>s at ±45° running radius 19→27u, round caps. Rotated CSS
   boxes with box-shadow rings stair-step along the diagonal; SVG strokes do not.
   The contour is a drop-shadow on the svg, so the stroke keeps its full mass. */
.hud-hitmarker .hm-x {
  position: absolute;
  left: calc(-32 * var(--u));
  top: calc(-32 * var(--u));
  width: calc(64 * var(--u));
  height: calc(64 * var(--u));
  overflow: visible;
  filter: drop-shadow(0 0 1.5px rgba(0, 0, 0, 0.85));
}
.hud-hitmarker .hm-x line {
  stroke: var(--hm-col);
  stroke-width: var(--hm-w);
}
/* one graphic, two lengths: a crit is the SAME four slashes 1.4x longer */
.hud-hitmarker .hm-l { display: none; }
.hud-hitmarker.crit .hm-s,
.hud-hitmarker.kill .hm-s { display: none; }
.hud-hitmarker.crit .hm-l,
.hud-hitmarker.kill .hm-l { display: block; }
.hud-hitmarker.crit { --hm-col: var(--hud-gold); --hm-w: 2.6; }
.hud-hitmarker.kill { --hm-col: var(--hud-alarm); --hm-w: 2.8; }
/* expand 1.0→1.33 over 40ms easeOut, hold 30ms, fade 80ms → 150ms total */
.hud-hitmarker.show { animation: hitpop 0.15s cubic-bezier(0.22, 1, 0.36, 1) forwards; }
/* an elimination gets the extra 1.3x pop on top of the red */
.hud-hitmarker.show.kill { animation: hitpopkill 0.19s cubic-bezier(0.22, 1, 0.36, 1) forwards; }
/* .pin must out-specify .show.kill or a frozen capture holds the hitmarker
   mid-fade (it rendered at 27% alpha, which reads as a smudge) */
.hud-hitmarker.pin,
.hud-hitmarker.pin.kill { animation: none; opacity: 0.92; transform: scale(1.33); }
.hud-hitmarker.pin.kill { transform: scale(1.45); }
@keyframes hitpop {
  0% { opacity: 0.9; transform: scale(1); }
  26.7% { opacity: 0.9; transform: scale(1.33); }
  46.7% { opacity: 0.9; transform: scale(1.33); }
  100% { opacity: 0; transform: scale(1.33); }
}
@keyframes hitpopkill {
  0% { opacity: 0.95; transform: scale(1); }
  26.3% { opacity: 0.95; transform: scale(1.45); }
  52.6% { opacity: 0.95; transform: scale(1.45); }
  100% { opacity: 0; transform: scale(1.45); }
}

/* ---------------- top-center objective strip ---------------- */
#hud-root .hud-top {
  position: absolute;
  left: 50%;
  top: calc(24 * var(--u));
  transform: translateX(-50%) var(--hud-skew);
  display: flex;
  align-items: center;
  gap: calc(8 * var(--u));
  height: calc(38 * var(--u));
  padding: 0 calc(16 * var(--u)) calc(6 * var(--u));
  overflow: hidden;
}
.hud-top .top-clover {
  width: calc(18 * var(--u));
  height: calc(18 * var(--u));
  flex: none;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.6));
}
.hud-top .top-mode,
.hud-top .top-label {
  font-size: var(--t1);
  letter-spacing: var(--tr-label);
  color: rgba(242, 245, 247, 0.85);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}
.hud-top .top-div {
  width: 1px;
  height: calc(14 * var(--u));
  background: rgba(255, 255, 255, 0.28);
  margin: 0 calc(4 * var(--u));
}
.hud-top .top-count {
  display: inline-flex;
  align-items: baseline;
  font-size: var(--t3);
  line-height: 1;
  margin-left: calc(-2 * var(--u));
}
.hud-top .top-score {
  font-size: 1em;
  font-weight: 800;
  line-height: 1;
  color: var(--hud-orange);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
  font-variant-numeric: tabular-nums;
}
/* PROGRESS, not decoration: a 2u track inset from the panel's edges, so an
   unfilled run is always visible and the mark can never be mistaken for the
   decorative --hud-rule that underlines four other panels. */
.hud-top .top-bar {
  position: absolute;
  left: calc(14 * var(--u));
  right: calc(14 * var(--u));
  bottom: calc(5 * var(--u));
  height: calc(2 * var(--u));
  background: rgba(255, 255, 255, 0.15);
  border-radius: 1px;
  overflow: hidden;
}
.hud-top .top-fill {
  width: 0%;
  height: 100%;
  background: var(--hud-orange);
  box-shadow: 0 0 6px rgba(249, 158, 26, 0.8);
}

/* ---------------- fps (TOP RIGHT, and only when it is asked for) -----------
   docs/settings.md §6.6, and the brief: top right, toggled by the `showFps`
   setting, DEFAULT OFF. Off is not a taste call — it is guardrail 1. A counter
   that defaults on is a counter in every `?shot` pose and every critique frame,
   which is exactly the r5 finding ("a debug artifact in a portfolio still")
   that put the old readout behind `?debug` in the first place. `?debug` now
   SEEDS the class rather than gating the element, so the developer flag still
   works and is no longer the only way to see the number.

   POSITION IS MEASURED, NOT GUESSED. The top-right rail is already occupied:
   `.hud-net` sits at 16u with a 30u height, `.hud-killfeed` at 56u. The free
   corner above them is 16u and this chip is 30u — it does not fit. So the chip
   CLAIMS the 16u slot and pushes the rail down by a constant 38u (30u chip +
   the 8u grid gap) whenever it is on. Constant, not a `:has()` on whether the
   ping chip is visible, because 56u already reserves the ping chip's slot
   whether or not it is showing. That is the existing convention and it stays.

   MATERIAL: this chip is `.hud-net`'s SIBLING and is built from the same parts
   — 30u height, 3px radius, the dot matrix, the -6° shear, a 2u bottom rule and
   deliberately NO backdrop-filter (a readout of how fast the game is running
   must never itself cost a full-screen sample every frame). The numeral is
   `--t3` to match the ping chip's, not the `--t2` §6.6 suggests: the two chips
   stack 8u apart in one rail, and two instruments in one rail at two type steps
   is precisely the sloppiness art bible §12.10 names. Everything §6.6 makes
   load-bearing — the 16u slot, the 30u height, the constant 38u push, the
   tabular numerals, the shear, off by default — is exact. */
.hud-fps {
  position: absolute;
  right: var(--hud-inset);
  top: calc(16 * var(--u));
  display: none;
  align-items: center;
  gap: calc(6 * var(--u));
  box-sizing: border-box;
  min-width: calc(80 * var(--u));
  height: calc(30 * var(--u));
  padding: 0 calc(11 * var(--u)) 0 calc(11 * var(--u));
  border-radius: 3px;
  border-bottom: calc(2 * var(--u)) solid rgba(242, 245, 247, 0.22);
  background-color: rgba(10, 12, 16, 0.72);
  background-image: var(--hud-dots);
  background-size: var(--hud-dot-pitch) var(--hud-dot-pitch);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 2px 8px rgba(0, 0, 0, 0.35);
  transform: var(--hud-skew);
  font-family: var(--hud-font);
  font-weight: 600;
  font-style: italic;
  justify-content: flex-end;
}
#hud-root.fps-on .hud-fps { display: flex; }
#hud-root.fps-on .hud-net { top: calc(54 * var(--u)); }
#hud-root.fps-on .hud-killfeed { top: calc(94 * var(--u)); }
.hud-fps .fps-n {
  font-size: var(--t3);
  font-weight: 800;
  line-height: 1;
  color: rgba(242, 245, 247, 0.9);
  font-variant-numeric: tabular-nums;
  min-width: calc(30 * var(--u));
  text-align: right;
}
.hud-fps .fps-u {
  font-size: var(--t1);
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: rgba(242, 245, 247, 0.45);
}

/* ---------------- PAUSED (docs/settings.md §6.2) ---------------------------
   The match HUD does not go away, it goes QUIET. Frozen health and ammo under a
   dim veil is the read of a paused match; a blank screen is the read of a
   broken one, and online the match is still running behind the dialog. The
   crosshair and the hit feedback go because there is nothing to aim with while
   the pointer is unlocked, and the crosshair sits exactly where the dialog
   does. */
#hud-root.paused { opacity: 0.35; }
#hud-root.paused .hud-crosshair,
#hud-root.paused .hud-hitmarker,
#hud-root.paused .hud-dmg-layer { opacity: 0; }

/* ---------------- health (bottom-left) ----------------
   Bar-dominant: 238px of segmented bar against a 48u numeral and a 56u hero
   plate. Trimmed from 563px so the bottom-left and bottom-right clusters land
   within ~20% of each other in area instead of 2:1. */
.hud-health {
  position: absolute;
  left: var(--hud-inset);
  bottom: var(--hud-inset);
  display: flex;
  align-items: baseline;
  gap: calc(12 * var(--u));
  padding: calc(10 * var(--u)) calc(12 * var(--u)) calc(10 * var(--u)) calc(10 * var(--u));
  border-bottom: var(--hud-rule) solid var(--hud-orange);
}
/* the plate art paints its identity accent with currentColor, so the hero's own
   hue reaches the portrait without this file ever naming one */
.hud-health .hp-portrait,
.hud-killfeed .kf-chip { color: var(--hero-accent); }
/* the plate you PICK is the plate you play with, framed and back-lit in the
   hero's own hue exactly as the select card frames it */
.hud-health .hp-portrait {
  position: relative;
  width: calc(58 * var(--u));
  height: calc(58 * var(--u));
  flex: none;
  border-radius: 2px;
  overflow: hidden;
  box-shadow: inset 0 0 0 calc(1.5 * var(--u)) var(--hero-hue), 0 2px 6px rgba(0, 0, 0, 0.45);
}
.hud-health .hp-portrait::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 50% 38%, var(--hero-hue) 0%, transparent 62%);
  opacity: 0.22;
  pointer-events: none;
}
.hud-health .hp-face { display: block; width: 100%; height: 100%; }
.hud-health .hp-num {
  display: flex;
  align-items: baseline;
  font-size: var(--t5);
  font-weight: 800;
  line-height: 1;
  min-width: calc(84 * var(--u));
  justify-content: flex-end;
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.7);
  font-variant-numeric: tabular-nums;
}
/* NO container border and NO per-cell stroke — a light frame plus a dark cell
   divider put a double line at each end and a graph-paper grid across the
   middle. The 2px gap IS the divider. Cell width is an INTEGER px per
   breakpoint: 8 cells sized in --u (1.00008px at 1080p) rasterized to
   39/39/39/39/38/38/40/38, and a 2px pitch error is unmissable in a row of
   identical boxes.
   r5 (major + minor): the cells are SKEWED 12° into parallelograms so the bar
   is a HUD graphic rather than eight plain white rectangles; the empty cell is
   #23282F so "empty" is unambiguous against the panel (it used to be white at
   0.20, light enough to read as filled-but-dark); the boundary cell fills
   proportionally so the bar can never over-report; and a #FF6B6B LAG layer
   holds the run you just lost for 400 ms before wiping at 220 HP/s. */
.hud-health .segments {
  position: relative;
  display: flex;
  gap: 3px;
  padding-right: 5px;   /* room for the skew's overhang */
  overflow: hidden; /* clips the regen sweep only — there is no frame here */
  --seg-cell: 28px;
  --seg-h: 20px;
}
@media (max-height: 899px) { .hud-health .segments { --seg-cell: 19px; --seg-h: 14px; } }
@media (min-height: 1400px) { .hud-health .segments { --seg-cell: 38px; --seg-h: 27px; } }
.hud-health .seg {
  position: relative;
  flex: 0 0 var(--seg-cell);
  width: var(--seg-cell);
  height: var(--seg-h);
  background: #23282f;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05);
  overflow: hidden;
  /* the panel already carries -6deg, so -6 more lands the cells at the 12deg
     parallelogram the art direction asks for, visibly sheared against the
     plate's own edge */
  transform: skewX(-6deg);
}
.hud-health .seg .fill,
.hud-health .seg .lag {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 100%;
}
.hud-health .seg .lag { background: var(--hud-lag); opacity: 0.45; }
.hud-health .seg .fill {
  /* health stays WHITE (art bible §10) and takes the hero hue as a foot, so the
     bar says whose health it is without ceasing to read as health */
  background: linear-gradient(180deg, #ffffff 0%, #f2f5f7 68%, var(--hero-hue) 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9);
}
/* the leading filled cell is the one the next hit takes: +20% brightness and a
   hero-hue edge, so the bar says WHOSE health it is without recolouring health */
.hud-health .seg.lead .fill {
  filter: brightness(1.2);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.95), inset -2px 0 0 var(--hero-hue);
}
.hud-health .seg.flash .fill {
  background: var(--hud-flash);
  box-shadow: 0 0 8px rgba(255, 75, 75, 0.8);
}
/* the cell the next hit will take pulses for 0.2 s every time you are hit */
.hud-health .seg.lead.hit .fill { animation: leadhit 0.2s ease-out 1; }
@keyframes leadhit {
  0% { filter: brightness(2.1); }
  100% { filter: brightness(1.2); }
}
/* regen shimmer sweep (#9FE8DC) across the bar */
.hud-health .segments::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    105deg,
    transparent 30%,
    rgba(159, 232, 220, 0.85) 48%,
    rgba(255, 255, 255, 0.95) 52%,
    rgba(159, 232, 220, 0.85) 56%,
    transparent 74%
  );
  transform: translateX(-110%);
  opacity: 0;
  pointer-events: none;
}
.hud-health .segments.regen::after { animation: shimmer 0.9s ease-in-out; }
@keyframes shimmer {
  0% { transform: translateX(-110%); opacity: 1; }
  100% { transform: translateX(110%); opacity: 1; }
}

/* SHIFT is a movement binding, not an ability: a 24u keyhint chip above the
   health plate rather than a tile in the ability row no select card lists. */
.hud-sprint {
  position: absolute;
  left: var(--hud-inset);
  bottom: calc(var(--hud-inset) + 82 * var(--u));
  display: flex;
  align-items: center;
  gap: calc(7 * var(--u));
  height: calc(24 * var(--u));
  padding: 0 calc(9 * var(--u)) 0 calc(6 * var(--u));
  border-radius: 3px;
  background: rgba(10, 12, 16, 0.7);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
  transform: var(--hud-skew);
}
.hud-sprint .kbd {
  display: inline-block;
  padding: calc(2 * var(--u)) calc(7 * var(--u)) calc(2 * var(--u));
  border-radius: 2px;
  background: rgba(28, 34, 44, 0.95);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18);
  font-size: var(--t1);
  font-style: normal;
  letter-spacing: 0;
  color: rgba(242, 245, 247, 0.9);
}
.hud-sprint .sp-lbl {
  font-size: var(--t1);
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: rgba(242, 245, 247, 0.5);
}

/* critical health */
.hud-health.low .hp-num { color: var(--hud-flash); animation: lowpulse 1s ease-in-out infinite; }
.hud-health.low { box-shadow: inset 0 0 24px rgba(232, 64, 42, 0.35), var(--hud-edge); }
@keyframes lowpulse { 50% { opacity: 0.55; } }

/* ---------------- ultimate gauge (bottom centre) ----------------
   r5 (major): the wide shallow arc is gone. It was a 470x25 sliver of a 190u
   arc rendered as 2.5x11 dashes, sitting under the plaza's strung bunting at
   the same size, spacing, rhythm and hue as the pennants — and its empty track
   read BRIGHTER than its charged run over sunlit cobble, so the gauge's
   polarity inverted with the backdrop.
   An 84u ring instead, with a dark casing under both track and fill so it holds
   on any background, and READY as a FORM change (filled disc + gold outer ring
   + a drawn Q inside it + a 0.9s 1.0→1.06 pulse) rather than a hue change. */
.hud-ult {
  position: absolute;
  left: 50%;
  bottom: calc(38 * var(--u));
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(5 * var(--u));
}
.hud-ult .ult-gauge {
  position: relative;
  width: calc(84 * var(--u));
  height: calc(84 * var(--u));
  transition: transform 0.18s ease-out;
}
.hud-ult .ult-ring {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
  filter: drop-shadow(0 calc(2 * var(--u)) calc(4 * var(--u)) rgba(0, 0, 0, 0.6));
}
.hud-ult .ur-case { fill: none; stroke: #0e1319; stroke-width: 11; opacity: 0.92; }
.hud-ult .ur-well { fill: rgba(10, 13, 18, 0.55); }
.hud-ult .ur-track { fill: none; stroke: rgba(255, 255, 255, 0.12); stroke-width: 7; }
.hud-ult .ur-fill {
  fill: none;
  stroke: var(--hero-hue);
  stroke-width: 7;
  stroke-linecap: butt;
}
.hud-ult .ur-disc { fill: var(--hero-hue); display: none; }
.hud-ult .ur-drain { fill: none; stroke: var(--hero-hue); display: none; }
.hud-ult .ur-halo { fill: none; stroke: var(--hud-gold); stroke-width: 3; display: none; }
/* the readout lives INSIDE the ring */
.hud-ult .ult-mid {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.hud-ult .ult-pct {
  font-size: calc(22 * var(--u));
  font-weight: 800;
  line-height: 1;
  color: rgba(242, 245, 247, 0.82);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85);
  font-variant-numeric: tabular-nums;
}
.hud-ult .ult-q { display: none; }
.hud-ult .ult-q .kbd-q {
  display: block;
  width: calc(26 * var(--u));
  height: calc(30 * var(--u));
  color: #0e1319;
}
.hud-ult .ult-read {
  padding: calc(2 * var(--u)) calc(10 * var(--u)) calc(3 * var(--u));
  border-radius: 2px;
  background: rgba(10, 12, 16, 0.78);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
  transform: var(--hud-skew);
}
.hud-ult .ult-label {
  font-size: var(--t1);
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: rgba(242, 245, 247, 0.6);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
}

/* ---- four states, four SHAPES (the meter is the only gauge on the HUD that
   changes what a key does, so "can I press Q" must be answerable in a glance
   with the frame downsampled to 320px) ---- */
.hud-ult.empty .ult-label,
.hud-ult.empty .ult-pct { opacity: 0.6; }
/* READY — the ring becomes a DISC with the key prompt drawn inside it */
.hud-ult.ready .ur-disc,
.hud-ult.ready .ur-halo { display: block; }
.hud-ult.ready .ult-pct { display: none; }
.hud-ult.ready .ult-q { display: block; }
.hud-ult.ready .ult-gauge { animation: ultready 0.9s ease-in-out infinite alternate; }
.hud-ult.ready .ult-label { color: var(--hud-gold); }
@keyframes ultready {
  from { transform: scale(1); }
  to { transform: scale(1.06); }
}
/* ACTIVE — the disc DRAINS counter-clockwise over the ultimate's own seconds */
.hud-ult.active .ur-drain { display: block; }
/* the seconds sit ON the draining disc, so they take the disc's own dark ink */
.hud-ult.active .ult-pct { color: #0e1319; text-shadow: none; }
.hud-ult.active .ult-read {
  background: var(--hero-hue);
}
.hud-ult.active .ult-label { color: #0e1319; text-shadow: none; }

/* ---------------- ultimate cast banner ----------------
   Deliberately NOT the elimination banner: that one is red-ruled, victim-named
   and lives at 64%. This is the announcement of something you did — warm rule,
   two lines, and it sits at 34% so the two can never stack on one another. */
.hud-ultbanner {
  position: absolute;
  left: 50%;
  top: 34%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  opacity: 0;
}
.hud-ultbanner .ub-plate {
  position: relative;
  display: flex;
  align-items: center;
  padding: calc(10 * var(--u)) calc(40 * var(--u)) calc(12 * var(--u));
  transform: var(--hud-skew);
}
.hud-ultbanner .ub-scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: rgba(8, 12, 18, 0.82);
  backdrop-filter: var(--hud-blur);
  -webkit-mask-image: linear-gradient(
    90deg, rgba(0, 0, 0, 0) 0, #000 calc(52 * var(--u)),
    #000 calc(100% - 52 * var(--u)), rgba(0, 0, 0, 0) 100%);
  mask-image: linear-gradient(
    90deg, rgba(0, 0, 0, 0) 0, #000 calc(52 * var(--u)),
    #000 calc(100% - 52 * var(--u)), rgba(0, 0, 0, 0) 100%);
}
.hud-ultbanner .ub-lines { display: flex; flex-direction: column; align-items: center; }
.hud-ultbanner .ub-kicker {
  font-size: var(--t1);
  letter-spacing: var(--tr-display);
  text-indent: var(--tr-display);
  color: rgba(255, 200, 115, 0.8);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.85);
}
.hud-ultbanner .ub-name {
  margin-top: calc(2 * var(--u));
  font-size: var(--t5);
  font-weight: 800;
  line-height: 1;
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  color: var(--hud-white);
  text-shadow:
    0 0 calc(3 * var(--u)) rgba(11, 16, 23, 0.9),
    0 calc(2 * var(--u)) 0 rgba(43, 46, 51, 0.9),
    0 0 calc(22 * var(--u)) rgba(249, 158, 26, 0.45);
}
.hud-ultbanner .ub-bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: var(--hud-rule);
  background: var(--hud-orange);
  box-shadow: 0 0 calc(8 * var(--u)) rgba(249, 158, 26, 0.6);
  -webkit-mask-image: linear-gradient(
    90deg, rgba(0, 0, 0, 0) 0, #000 calc(52 * var(--u)),
    #000 calc(100% - 52 * var(--u)), rgba(0, 0, 0, 0) 100%);
  mask-image: linear-gradient(
    90deg, rgba(0, 0, 0, 0) 0, #000 calc(52 * var(--u)),
    #000 calc(100% - 52 * var(--u)), rgba(0, 0, 0, 0) 100%);
  transform-origin: 50% 50%;
  transform: scaleX(0);
}
/* pop to full at 0.12s — the same offset main.js freezes HUD animations at, so
   a staged ?shot capture lands on the held frame and not mid-fade */
.hud-ultbanner.show { animation: ubpop 1.9s linear forwards; }
.hud-ultbanner.show .ub-bar { animation: ubwipe 1.9s linear forwards; }
@keyframes ubpop {
  0% { opacity: 0; transform: translateX(-50%) scale(1.18); }
  6.3% { opacity: 1; transform: translateX(-50%) scale(1); }
  84% { opacity: 1; transform: translateX(-50%) scale(1); }
  100% { opacity: 0; transform: translateX(-50%) scale(1); }
}
@keyframes ubwipe {
  0% { transform: scaleX(0); }
  6.3% { transform: scaleX(1); }
  100% { transform: scaleX(1); }
}

/* ---------------- bottom-right cluster: abilities + ammo ---------------- */
.hud-br {
  position: absolute;
  right: var(--hud-inset);
  bottom: var(--hud-inset);
  display: flex;
  align-items: flex-end;
  gap: calc(16 * var(--u));
}

/* ability cells with a full-tile conic cooldown wipe.
   r5 (critical): the r4 tile inscribed a CIRCLE inside a rounded square — a
   visibly lighter disc floating inside a dark box, curved edge and all — with
   the polarity inverted (more cooldown left = brighter tile) and the countdown
   numeral stacked on top of the glyph so neither was legible. */
.abilities {
  display: flex;
  gap: calc(8 * var(--u));
}
.ability {
  position: relative;
  width: calc(56 * var(--u));
  height: calc(56 * var(--u));
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(5 * var(--u));
  padding: calc(8 * var(--u)) calc(6 * var(--u)) calc(6 * var(--u));
  border-bottom: var(--hud-rule) solid var(--hero-hue);
  overflow: visible;
  --cd: 0;
}
/* the sweep is clipped to the TILE's own rounded rect and fills all four
   corners — it is not a shape floating inside the tile */
.ability .ab-clip {
  position: absolute;
  inset: 0;
  border-radius: 3px;
  overflow: hidden;
  clip-path: inset(0 round 3px);
  z-index: 0;
}
.ability .ab-sweep {
  position: absolute;
  /* oversized so the conic's corners cover a sheared 56u box completely */
  inset: -40%;
  opacity: 0;
  /* the REMAINING sector is the dark overlay: the tile REFILLS to its normal
     material as the ability comes off cooldown */
  background: conic-gradient(
    from 0deg,
    rgba(0, 0, 0, 0.72) 0 calc(var(--cd) * 1turn),
    rgba(255, 255, 255, 0.09) calc(var(--cd) * 1turn) 1turn
  );
}
.ability.cooling .ab-sweep { opacity: 1; }
/* the boundary of the wipe gets a hairline so the step is unmistakable */
.ability.cooling { border-bottom-color: rgba(255, 255, 255, 0.22); }
/* READY glyphs carry the HERO's hue; on cooldown they step to a flat 35% grey
   so the corner numeral is the thing you read */
.ability .ico {
  position: relative;
  width: calc(24 * var(--u));
  height: calc(24 * var(--u));
  flex: none;
  color: var(--hero-hue);
  z-index: 1;
  transition: color 0.15s, opacity 0.15s;
}
.ability.cooling .ico { color: #9aa1a8; opacity: 0.35; transform: translateY(calc(2 * var(--u))); }
/* a ready ability breathes at 1.5 Hz on its rule — the tile says "spend me" */
.ability:not(.cooling) { animation: abready 0.667s ease-in-out infinite alternate; }
@keyframes abready {
  from { box-shadow: var(--hud-edge); }
  to { box-shadow: var(--hud-edge), 0 calc(3 * var(--u)) calc(9 * var(--u)) -2px var(--hero-hue); }
}
/* key label */
.ability .ab-key {
  position: relative;
  z-index: 2;
  font-size: var(--t1);
  font-style: normal; /* upright so RMB/SHIFT stay legible at chip size */
  line-height: calc(13 * var(--u));
  letter-spacing: var(--tr-label);
  color: rgba(242, 245, 247, 0.88);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.85);
  text-indent: var(--tr-label);
}
/* the countdown is a CHIP in the tile's top-right corner, out of the glyph's
   box entirely, at 16u tabular */
.ability .ab-cd {
  position: absolute;
  right: calc(3 * var(--u));
  top: calc(3 * var(--u));
  min-width: calc(17 * var(--u));
  padding: calc(1 * var(--u)) calc(3 * var(--u)) calc(2 * var(--u));
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 3;
  border-radius: 2px;
  background: rgba(6, 9, 14, 0.9);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.14);
  font-size: calc(16 * var(--u));
  font-weight: 800;
  line-height: 1.05;
  color: var(--hud-white);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9);
  font-variant-numeric: tabular-nums;
}
.ability.cooling .ab-cd { display: flex; }
.ability.cooling { animation: none; }
/* ready flash when a cooldown completes */
.ability .ab-flash {
  position: absolute;
  inset: 0;
  border-radius: 3px;
  background: radial-gradient(circle, rgba(255, 255, 255, 0.9) 0%, rgba(249, 158, 26, 0.5) 60%, transparent 100%);
  opacity: 0;
}
.ability .ab-flash.go { animation: abflash 0.45s ease-out forwards; }
@keyframes abflash {
  0% { opacity: 1; }
  100% { opacity: 0; }
}

/* ammo panel — grown (180u wide, 80u numeral) so the bottom-right cluster is not
   a third of the bottom-left one */
.hud-ammo {
  position: relative;
  text-align: right;
  min-width: calc(190 * var(--u));
  box-sizing: border-box;
  padding: calc(8 * var(--u)) calc(16 * var(--u)) calc(9 * var(--u)) calc(24 * var(--u));
  border-bottom: var(--hud-rule) solid var(--hero-hue);
}
/* the weapon NAME is the hero's hue — one of the four things the identity
   colour is spent on in play (r5: every hero printed the same #F99E1A here) */
.hud-ammo .weapon-name {
  font-size: var(--t2);
  letter-spacing: var(--tr-label);
  color: var(--hero-hue);
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.7);
  padding-bottom: calc(4 * var(--u));
  border-bottom: 1px solid rgba(255, 255, 255, 0.14);
}
.hud-ammo .ammo-count {
  display: flex;
  align-items: baseline;
  justify-content: flex-end;
  font-size: calc(76 * var(--u));
  line-height: 1;
}
/* THE RAMP CLIMBS IN CHROMA as it gets dangerous (r5: the r4 ramp ran backwards
   — #FFC873 is lighter and lower-chroma than #F99E1A, so the numeral got softer
   as the magazine emptied):
     full → #F2F5F7 · <33% → #F99E1A · <15% → #FF6B33 · empty → #FF3B47 + pulse */
.hud-ammo .ammo-count .cur {
  font-size: 1em;
  font-weight: 800;
  line-height: 0.88;
  color: var(--hud-white);
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.75);
  font-variant-numeric: tabular-nums;
  display: inline-block;
  transform-origin: 100% 50%;
}
.hud-ammo .ammo-count.low .cur { color: var(--hud-orange); }
.hud-ammo .ammo-count.crit-low .cur {
  color: var(--hud-hot);
  animation: ammolow 0.333s ease-in-out infinite alternate;
}
@keyframes ammolow {
  from { transform: scale(1); }
  to { transform: scale(1.06); }
}
.hud-ammo .ammo-count.dry .cur {
  color: var(--hud-alarm);
  animation: ammopulse 0.5s ease-in-out infinite alternate;
}
@keyframes ammopulse {
  from { transform: scale(1); text-shadow: 0 2px 8px rgba(0, 0, 0, 0.75); }
  to { transform: scale(1.08); text-shadow: 0 2px 8px rgba(0, 0, 0, 0.75), 0 0 14px rgba(255, 59, 71, 0.6); }
}
/* a dry mag also flashes the whole block at ~6 Hz for 0.4 s */
.hud-ammo:has(.ammo-count.dry) { animation: dryflash 0.4s steps(1) 1; }
@keyframes dryflash {
  0%, 33.3%, 66.6% { box-shadow: var(--hud-edge), inset 0 0 0 100vmax rgba(255, 59, 71, 0.2); }
  16.6%, 50%, 83.3%, 100% { box-shadow: var(--hud-edge); }
}
/* quick amber flash the instant the mag runs dry */
.hud-ammo .ammo-count .cur.mag-empty { animation: magempty 0.12s linear 1; }
@keyframes magempty {
  0%, 65% { color: var(--hud-gold); text-shadow: 0 0 12px rgba(255, 200, 115, 0.9), 0 2px 8px rgba(0, 0, 0, 0.75); }
  100% { color: var(--hud-alarm); }
}
/* ---- ammo MODE: magazine (everyone) vs heat (LUMEN) ----
   The heat readout REPLACES the ammo block rather than sitting beside it: same
   panel, same numeral, same escalation — the denominator becomes a unit label
   and the danger INVERTS (urgency rises with the number instead of falling). */
.hud-ammo .ammo-count .unit { display: none; }
.hud-ammo.mode-heat .ammo-count .den { display: none; }
.hud-ammo.mode-heat .ammo-count .unit {
  display: inline-block;
  font-size: 0.26em;
  font-weight: 800;
  line-height: 1;
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  margin-left: 0.14em;
  opacity: 0.55;
  font-style: italic;
}
/* ONE gauge component for both weapon economies (r5, minor: a 6px gold slab on
   KESTREL against a 3px grey line on LUMEN — the same affordance drawn as two
   unrelated widgets). 5u tall, full panel width inside the gutters, captioned
   with what it MEASURES, and its row is RESERVED on every hero so the ammo
   numeral sits on the same baseline across the whole roster. */
.hud-ammo .ammo-gauge {
  display: flex;
  align-items: center;
  gap: calc(8 * var(--u));
  margin: calc(6 * var(--u)) 0 calc(6 * var(--u));
  visibility: hidden;
}
.hud-ammo.mode-heat .ammo-gauge,
.hud-ammo.show-charge .ammo-gauge { visibility: visible; }
.hud-ammo .ag-cap {
  font-size: var(--t1);
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: rgba(242, 245, 247, 0.55);
  flex: none;
}
.hud-ammo .ammo-bar {
  position: relative;
  display: block;
  flex: 1 1 auto;
  height: calc(5 * var(--u));
  background: rgba(255, 255, 255, 0.12);
  border-radius: 1px;
  box-shadow: 0 0 0 1px #0e1319;
  overflow: hidden;
}
.hud-ammo .ammo-bar i {
  display: block;
  width: 0%;
  height: 100%;
  background: var(--hero-hue);
}
/* the danger zone is MARKED, so the direction of danger is visible without
   reading the number */
.hud-ammo .ag-tick { display: none; }
.hud-ammo.mode-heat .ag-tick {
  display: block;
  position: absolute;
  left: 85%;
  top: 0;
  bottom: 0;
  width: calc(2 * var(--u));
  background: var(--hud-alarm);
}
.hud-ammo.mode-heat.warn .ammo-bar i { background: var(--hud-orange); }
.hud-ammo.mode-heat.hot .ammo-bar i,
.hud-ammo.mode-heat.over .ammo-bar i { background: var(--hud-alarm); }
/* heat climbs the SAME red end the magazine empties into */
.hud-ammo.mode-heat .ammo-count.crit-low .cur { color: var(--hud-alarm); }
.hud-ammo.show-charge.charged .ammo-bar i {
  background: var(--hud-gold);
  box-shadow: 0 0 calc(7 * var(--u)) rgba(255, 200, 115, 0.8);
}
/* RELOADING is a state of the PANEL. It used to float above it as orange text on
   bare sky, the same hue and nearly the same size as the weapon name below it,
   so the two stacked and read as a two-line title. */
.hud-ammo .reload {
  position: absolute;
  left: calc(16 * var(--u));
  right: calc(16 * var(--u));
  bottom: calc(14 * var(--u));
  display: none;
  flex-direction: column;
  align-items: center;
  gap: calc(6 * var(--u));
}
.hud-ammo .reload.show { display: flex; }
.hud-ammo.reloading .ammo-count { visibility: hidden; }
.hud-ammo.reloading .den { opacity: 0.3; }
.hud-ammo .rl-word {
  font-size: calc(13 * var(--u));
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: rgba(255, 255, 255, 0.6);
  animation: reloadpulse 0.7s ease-in-out infinite alternate;
}
.hud-ammo .rl-bar {
  display: block;
  width: 100%;
  height: calc(4 * var(--u));
  background: rgba(255, 255, 255, 0.12);
  box-shadow: 0 0 0 1px #0e1319;
  border-radius: 1px;
  overflow: hidden;
}
.hud-ammo .rl-bar i { display: block; width: 0%; height: 100%; background: var(--hud-gold); }
/* heat mode: the gauge CAPTION becomes the OVERHEAT chip — same row, same
   component, no second widget and nothing to collide with */
.hud-ammo.mode-heat.alarm .ag-cap {
  padding: calc(1 * var(--u)) calc(6 * var(--u)) calc(2 * var(--u));
  border-radius: 2px;
  background: var(--hud-alarm);
  color: #10141a;
  font-weight: 800;
  font-style: normal;
  text-indent: 0;
  letter-spacing: 0.06em;
  animation: reloadpulse 0.42s ease-in-out infinite alternate;
}

/* ---------------- killfeed (top-right) ---------------- */
.hud-killfeed {
  position: absolute;
  right: var(--hud-inset);
  top: calc(56 * var(--u));
  display: flex;
  flex-direction: column;
  gap: calc(6 * var(--u));
  align-items: flex-end;
}
.hud-killfeed .kf-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: calc(7 * var(--u));
  height: max(36px, calc(38 * var(--u)));
  padding: 0 calc(12 * var(--u)) 0 calc(10 * var(--u));
  font-size: var(--t3);
  letter-spacing: var(--tr-label);
  animation: kfslide 0.15s cubic-bezier(0.22, 1, 0.36, 1);
}
/* portrait chips: the row's shape signature. At 30px with 14px type the feed
   downsampled to two grey smudges, which is exactly the viewing condition it
   exists for. */
.hud-killfeed .kf-chip {
  box-sizing: border-box;
  width: calc(28 * var(--u));
  height: calc(28 * var(--u));
  flex: none;
  padding: calc(2 * var(--u));
  border-radius: 3px;
  background: #0e1720;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);
  overflow: hidden;
}
.hud-killfeed .kf-chip svg { display: block; width: 100%; height: 100%; border-radius: 1px; }
/* the rule is coloured by ALLEGIANCE, not by "someone died": ice blue when the
   killer is you, alarm red when it is not. r5 (minor): both rows carried the
   same red even though YOU were the killer in both, and red universally reads
   as "you died". */
.hud-killfeed .kf-row::before {
  content: '';
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--hud-rule);
  background: var(--hud-alarm);
  border-radius: 3px 0 0 3px;
}
.hud-killfeed .kf-row.mine::before { background: #4fc3d9; }
.hud-killfeed .kf-row.out { animation: kfout 0.3s ease-out forwards; }
/* DIRECTION: the killer is full white, the victim 65% — you can read who killed
   whom without parsing the glyph in the middle */
.hud-killfeed .kf-killer {
  color: #ffffff;
  font-weight: 800;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7);
}
.hud-killfeed .kf-row.mine .kf-killer { color: #9fe0ef; }
.hud-killfeed .kf-victim { color: rgba(242, 245, 247, 0.65); text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7); }
/* weapon glyph: the KILLING HERO's own 24-grid icon in a fixed square column, so
   rows never zigzag and a LUMEN kill never prints a rifle */
.hud-killfeed .kf-wpn {
  position: relative;
  width: calc(22 * var(--u));
  height: calc(22 * var(--u));
  flex: none;
  color: rgba(242, 245, 247, 0.92);
}
/* the crit indicator rides ON the weapon glyph as a filled dot rather than
   being a fifth element in the reading order */
.hud-killfeed .kf-crit { display: none; }
.hud-killfeed .kf-row.crit .kf-crit {
  display: block;
  position: absolute;
  right: calc(-2 * var(--u));
  top: calc(-1 * var(--u));
  width: calc(7 * var(--u));
  height: calc(7 * var(--u));
  border-radius: 50%;
  background: var(--hud-gold);
  box-shadow: 0 0 0 1px #0b1017;
}
@keyframes kfslide {
  from { transform: var(--hud-skew) translateX(48px); opacity: 0; }
  to { transform: var(--hud-skew) translateX(0); opacity: 1; }
}
@keyframes kfout {
  to { opacity: 0; transform: var(--hud-skew) translateX(24px); }
}

/* ---------------- elimination banner: one feathered plate, one red edge -----
   The lockup was [icon][gap][word] centred as a ROW, which put the WORD 15u
   right of screen centre while the reticle sat dead centre — three axes in one
   graphic. A mirror spacer of the icon's width now balances the row, so the
   plate AND the type share the true centre axis. The scrim's feather is a FIXED
   46u, never a percentage: a percentage feather scales with the word and walked
   back over the last glyphs ("...ED" sat on bare cobble). */
/* r5 (major + minor): the plate was a translucent, unblurred 80% scrim whose
   left and right edges feathered to nothing — the plaza's joint lines and the
   tram rail read straight through the word, and the plate dissolved rather than
   ended. It is now the SAME material as every other panel in the product, with
   defined edges, and it has moved off the viewmodel's quadrant (at 64% its
   right third landed under the rifle silhouette). */
.hud-banner {
  position: absolute;
  left: 50%;
  top: 25%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  opacity: 0;
}
.hud-banner .bn-plate {
  position: relative;
  display: flex;
  align-items: center;
  gap: calc(12 * var(--u));
  /* r4 verification: the plate was 27.6% of frame width carrying 334px of ink,
     so it read as a black bar rather than a callout. Word stepped down one size
     and the side padding halved. */
  padding: calc(9 * var(--u)) calc(32 * var(--u)) calc(11 * var(--u));
  transform: var(--hud-skew);
}
.hud-banner .bn-pad { width: calc(20 * var(--u)); flex: none; }
/* the HUD's own plate material: solid #0E1319 at 96%, the 1px dot matrix, a
   2px radius and a 12px blur so any residual show-through is soft rather than
   structural */
.hud-banner .bn-scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: 2px;
  background-color: rgba(14, 19, 25, 0.96);
  background-image: var(--hud-dots);
  background-size: var(--hud-dot-pitch) var(--hud-dot-pitch);
  backdrop-filter: blur(12px) saturate(0.5) brightness(0.7);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 calc(4 * var(--u)) calc(14 * var(--u)) rgba(0, 0, 0, 0.5);
}
/* the underbar wipes in left→right with the pop, at exactly the plate's width */
.hud-banner .bn-bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: calc(2 * var(--hud-rule));
  background: var(--hud-red);
  border-radius: 0 0 2px 2px;
  box-shadow: 0 0 calc(6 * var(--u)) rgba(232, 64, 42, 0.55);
  transform-origin: 0 50%;
  transform: scaleX(0);
}
.hud-banner.show .bn-bar { animation: bnwipe 1.27s linear forwards; }
@keyframes bnwipe {
  0% { transform: scaleX(0); }
  9.4% { transform: scaleX(1); }
  100% { transform: scaleX(1); }
}
.hud-banner .bn-icon {
  width: calc(28 * var(--u));
  height: calc(28 * var(--u));
  flex: none;
  fill: var(--hud-red);
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.7));
}
/* Trailing tracking is compensated with TEXT-INDENT. A negative right margin
   inside a `flex-direction: column; align-items: center` parent centres the
   MARGIN box, so each line gets pushed right by half its own compensation
   (+7.7px on ELIMINATED, +1.3px on the victim → two stacked lines 6px out of
   register). text-indent shifts the first glyph right by exactly what the
   trailing space adds on the right and leaves the box width alone. */
.hud-banner .bn-lines { display: flex; flex-direction: column; align-items: center; }
.hud-banner .bn-text {
  font-size: var(--t4);
  font-weight: 800;
  line-height: 1;
  letter-spacing: 0.24em;
  text-indent: 0.24em;
  color: var(--hud-white);
  text-shadow: 0 0 calc(3 * var(--u)) rgba(11, 16, 23, 0.9), 0 calc(2 * var(--u)) 0 rgba(43, 46, 51, 0.9);
}
.hud-banner .bn-victim {
  margin-top: calc(3 * var(--u));
  font-size: var(--t2);
  font-weight: 600;
  line-height: 1;
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: rgba(242, 245, 247, 0.7);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.85);
}
.hud-banner.no-victim .bn-victim { display: none; }
/* pop 1.15→1.0 over 120ms, hold 900ms, fade 250ms (feel-spec §3) → 1.27s total */
.hud-banner.show { animation: bannerpop 1.27s linear forwards; }
/* a 6u upward slide + an opacity ramp that COMPLETES before the frame is held —
   never a permanent 0.8 alpha */
@keyframes bannerpop {
  0% { opacity: 0; transform: translateX(-50%) translateY(calc(6 * var(--u))) scale(1.08); }
  9.4% { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
  80.3% { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
  100% { opacity: 0; transform: translateX(-50%) translateY(0) scale(1); }
}

/* ---------------- floating damage numbers ----------------
   Gold = damage you deal (art bible §10). A crit is a STEP INSIDE the warm
   family — bigger, with a hot-cream emboss — never the alarm red, which is
   reserved for damage you take. The contour is a thin text-stroke plus two soft
   shadows; the old eight-offset opaque ring was a mobile-game sticker outline
   and stair-stepped on every diagonal. */
.hud-dmg-layer { position: absolute; inset: 0; }
.hud-dmg {
  position: absolute;
  font-size: calc(30 * var(--u));
  font-weight: 800;
  color: var(--hud-gold);
  -webkit-text-stroke: 1.2px rgba(11, 16, 23, 0.9);
  paint-order: stroke fill;
  text-shadow:
    0 0 3px rgba(11, 16, 23, 0.9),
    0 2px 0 rgba(11, 16, 23, 0.85),
    0 3px 6px rgba(0, 0, 0, 0.55);
  transform: translate(-50%, -90%);
  animation: dmgfloat 0.7s linear forwards;
  font-variant-numeric: tabular-nums;
}
/* A crit is a STEP CHANGE, not a 15% size bump: 1.85x type, hot cream over a
   warm #C97F14 contour, a 1.55 spawn pop and two gold chevrons above the
   numeral — a shape difference as well as a size one. Red stays reserved for
   damage you TAKE (art bible §10 assigns #FF4B4B there). */
.hud-dmg.crit {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 0.9;
  color: #fff0c8;
  font-size: calc(40 * var(--u));
  -webkit-text-stroke: 1.6px #c97f14;
  text-shadow:
    0 0 calc(3 * var(--u)) rgba(11, 16, 23, 0.85),
    0 calc(3 * var(--u)) calc(6 * var(--u)) rgba(0, 0, 0, 0.55);
  animation: dmgfloatcrit 0.7s linear forwards;
}
/* same float, bigger spawn: 1.0→1.55→1.0 over 140 ms */
@keyframes dmgfloatcrit {
  0% { opacity: 1; translate: 0 0; scale: 1; }
  8% { scale: 1.45; }
  20% { scale: 1; }
  50% { translate: 5px -44px; }
  57% { opacity: 1; }
  100% { opacity: 0; translate: 11px -60px; scale: 1; }
}
/* pop 1.0→1.3→1.0, apex ~50%, opaque to 57%, out at 700ms */
@keyframes dmgfloat {
  0% { opacity: 1; translate: 0 0; scale: 1; }
  8% { scale: 1.3; }
  16% { scale: 1; }
  50% { translate: 5px -44px; }
  57% { opacity: 1; }
  100% { opacity: 0; translate: 11px -60px; scale: 1; }
}

/* ---------------- damage vignette + directional wedges ---------------- */
.hud-vignette {
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at center, transparent 52%, rgba(200, 30, 20, 0.5) 100%);
  opacity: 0;
}
.hud-vignette.show { animation: vigdecay 1.2s linear forwards; }
@keyframes vigdecay {
  0% { opacity: 1; }
  25% { opacity: 0.42; }
  60% { opacity: 0.12; }
  100% { opacity: 0; }
}
.hud-wedges { position: absolute; inset: 0; }
.hud-wedges .wedge {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 120vmax;
  height: 120vmax;
  margin: -60vmax 0 0 -60vmax;
  /* r1 verification: at 0.45 peak x 0.15 residual the wedge rendered at ~0.07
     effective and was literally invisible in the combat capture. Peak raised to
     0.9, the solid band widened 34deg -> 55deg, and the mask's inner edge pulled
     from 26% to 18% so the band starts inside the safe area instead of off the
     frame edge. */
  background: conic-gradient(
    from -30deg,
    transparent 0deg,
    rgba(230, 40, 25, 0.9) 10deg 65deg,
    transparent 78deg
  );
  -webkit-mask-image: radial-gradient(circle, transparent 0 18%, rgba(0, 0, 0, 0.9) 46%);
  mask-image: radial-gradient(circle, transparent 0 18%, rgba(0, 0, 0, 0.9) 46%);
  animation: vigdecay 1.2s linear forwards;
}

/* ================= main menu ================= */
#menu-root {
  position: fixed;
  inset: 0;
  z-index: 20;
  font-family: var(--hud-font);
  font-weight: 600;
  font-style: italic;
  letter-spacing: var(--tr-body);
  user-select: none;
}
#menu-root.hidden { display: none; }

.menu-bg {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: linear-gradient(180deg, #141a22 0%, #1a2330 55%, #232e3e 100%);
}

/* warm rim light along the bottom — the plaza's lamp glow below the horizon */
.m-glow {
  position: absolute;
  left: -10%;
  right: -10%;
  bottom: -32vh;
  height: 72vh;
  background: radial-gradient(ellipse at 50% 100%, rgba(242, 118, 43, 0.45) 0%, rgba(242, 118, 43, 0.14) 45%, transparent 70%);
}

/* subtle hex-ish dot texture */
.m-hex {
  position: absolute;
  inset: 0;
  background-image:
    radial-gradient(rgba(255, 255, 255, 0.028) 1px, transparent 1.2px),
    radial-gradient(rgba(255, 255, 255, 0.018) 1px, transparent 1.2px);
  background-size: 24px 24px, 24px 24px;
  background-position: 0 0, 12px 12px;
}

/* layered skyline built from the map's actual landmarks (SVG in hud.js) */
.m-skyline-svg {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  width: 100%;
  height: 52vh;
  display: block;
}
.m-skyline-svg .msl-hills { animation: msldrift 30s ease-in-out infinite alternate; --amt: 5px; }
.m-skyline-svg .msl-mid { animation: msldrift 26s ease-in-out infinite alternate; --amt: 9px; }
.m-skyline-svg .msl-far { animation: msldrift 22s ease-in-out infinite alternate; --amt: 14px; }
.m-skyline-svg .msl-near { animation: msldrift 20s ease-in-out infinite alternate; --amt: 20px; }
@keyframes msldrift {
  from { transform: translateX(calc(-1 * var(--amt, 8px))); }
  to { transform: translateX(var(--amt, 8px)); }
}

/* drifting warm embers rising off the plaza glow */
.m-embers { position: absolute; inset: 0; pointer-events: none; }
.m-embers .ember {
  position: absolute;
  left: var(--x, 50%);
  bottom: var(--y, 8vh);
  width: var(--s, 3px);
  height: var(--s, 3px);
  border-radius: 50%;
  background: var(--c, #ffc873);
  box-shadow: 0 0 6px 1px rgba(255, 180, 90, 0.55);
  opacity: 0;
  animation: emberrise var(--dur, 12s) linear var(--delay, 0s) infinite;
}
@keyframes emberrise {
  0% { transform: translate(0, 0); opacity: 0; }
  12% { opacity: var(--o, 0.65); }
  55% { opacity: var(--o, 0.65); transform: translate(calc(var(--dx, 20px) * 0.6), -22vh); }
  100% { transform: translate(var(--dx, 20px), -44vh); opacity: 0; }
}

.m-vig {
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at 50% 42%, transparent 45%, rgba(4, 7, 12, 0.55) 100%);
}

/* mode card (top-left): same panel material as the in-game HUD. The accent tab
   is an overlay bar on the panel's own box (a border-left bowed around the 3px
   radius and left a background gap between rule and plate). */
.menu-mode {
  position: absolute;
  left: var(--hud-inset);
  top: var(--hud-inset);
  display: flex;
  align-items: center;
  gap: calc(12 * var(--u));
  padding: calc(9 * var(--u)) calc(20 * var(--u)) calc(12 * var(--u)) calc(17 * var(--u));
}
.menu-mode::before {
  content: '';
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--hud-rule);
  background: var(--hud-orange);
  border-radius: 3px 0 0 3px;
}
.menu-mode .mode-clover {
  width: calc(26 * var(--u));
  height: calc(26 * var(--u));
  flex: none;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.6));
}
.menu-mode .mode-title {
  font-size: var(--t2);
  letter-spacing: var(--tr-label);
  color: rgba(242, 245, 247, 0.92);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}
.menu-mode .mode-sub {
  margin-top: calc(2 * var(--u));
  font-size: var(--t1);
  letter-spacing: var(--tr-label);
  color: var(--hud-orange);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}

.m-center {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0;
  padding-bottom: 4vh;
}

/* ONE column scrim behind the whole menu stack. Everything below PLAY used to
   sit on bare plaza: CREDITS landed on a lit target ring at ~2:1 contrast and
   the keybind strip had no backing at all (grey labels at ~1.5:1 over cobble,
   with a green bush showing through the inside of the WASD cap). The mask
   feathers the darkened blur as well as the fill, so the column has no edge. */
.m-stack {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: calc(40 * var(--u)) calc(40 * var(--u)) calc(64 * var(--u));
}
.m-stack > *:not(.m-scrim) { position: relative; z-index: 1; }
.m-scrim {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: min(calc(920 * var(--u)), 94vw);
  transform: translateX(-50%);
  background: linear-gradient(
    90deg, transparent 0, rgba(10, 14, 20, 0.62) 24%,
    rgba(10, 14, 20, 0.62) 76%, transparent 100%);
  /* most of the darkening is the FILTER, not the fill, so the plaza still reads
     faintly through the column instead of being replaced by a black slab */
  backdrop-filter: blur(6px) brightness(0.42) saturate(0.55);
  /* the mask feathers the BLUR as well as the fill, so the column has no edge —
     both axes, composited, or the darkening ends on a visible rectangle */
  -webkit-mask-image:
    linear-gradient(90deg, transparent 0, #000 26%, #000 74%, transparent 100%),
    linear-gradient(180deg, transparent 0, #000 17%, #000 82%, transparent 100%);
  mask-image:
    linear-gradient(90deg, transparent 0, #000 26%, #000 74%, transparent 100%),
    linear-gradient(180deg, transparent 0, #000 17%, #000 82%, transparent 100%);
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
  z-index: 0;
}

/* soft teal halo behind the logotype */
.m-halo {
  position: absolute;
  top: 50%;
  left: 50%;
  width: calc(900 * var(--u));
  height: calc(360 * var(--u));
  transform: translate(-50%, -78%);
  background: radial-gradient(ellipse, rgba(99, 242, 232, 0.08) 0%, transparent 65%);
  pointer-events: none;
}

/* logotype: drawn letterforms (hud.js), single extrude along the italic axis */
.menu-logo {
  position: relative;
  display: block;
  line-height: 0;
}
.menu-logo-svg {
  display: block;
  width: clamp(calc(520 * var(--u)), 56vw, calc(1035 * var(--u)));
  height: auto;
}

/* orange kick slash under the logotype */
.m-kick {
  width: calc(240 * var(--u));
  height: calc(4 * var(--u));
  margin-top: calc(20 * var(--u));
  background: linear-gradient(90deg, transparent, #f99e1a 18%, #f99e1a 82%, transparent);
  transform: skewX(-30deg);
  box-shadow: 0 0 8px rgba(249, 158, 26, 0.45);
}

.menu-tag {
  margin-top: calc(18 * var(--u));
  font-size: var(--t2);
  letter-spacing: var(--tr-display);
  text-indent: var(--tr-display);
  color: rgba(228, 238, 250, 0.62);
  transform: skewX(-6deg);
}

/* PLAY: the product's primary CTA, built out of the SAME panel material as
   everything else — gradient face, top bevel, an INSET bottom lip (a separate
   band desynced from the -6° shear and left a stair notch at the lower-left
   corner), the dot matrix at 0.05, the art bible's dark anchor #2B2E33 for the
   label, and a chevron so the button points somewhere. Box trimmed from 265px
   to ~185px so it is not 2.6x wider than its own content. */
/* ONE button system, locked to a 56u height and a shared bottom baseline. r5
   (major): BACK was 105x42 with square corners and no rule while LOCK IN was
   231x62 with a bevel and a bottom edge — "the clearest web-form tell on the
   screen" — and the menu's PLAY was narrower than the secondary row beneath it. */
.menu-play,
.hs-lock {
  pointer-events: auto;
  cursor: pointer;
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  height: calc(56 * var(--u));
  gap: calc(6 * var(--u));
  margin-top: calc(40 * var(--u));
  font-family: inherit;
  font-weight: 800;
  font-style: italic;
  font-size: var(--t4);
  letter-spacing: 0.28em;
  color: #2b2e33;
  background-color: #f99e1a;
  background-image:
    radial-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1.1px),
    linear-gradient(180deg, #ffb13d 0%, #f99e1a 62%, #e08b12 100%);
  background-size: var(--hud-dot-pitch) var(--hud-dot-pitch), auto;
  border: none;
  border-radius: 3px;
  padding: 0 calc(26 * var(--u));
  transform: skewX(-6deg);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    inset 0 calc(-3 * var(--u)) 0 rgba(120, 66, 4, 0.6),
    0 calc(5 * var(--u)) calc(14 * var(--u)) rgba(0, 0, 0, 0.45);
  transition: transform 0.12s ease-out, filter 0.12s;
}
/* trailing letter-space compensated with text-indent (never a negative margin) */
.menu-play .mp-label,
.hs-lock .mp-label { text-indent: 0.28em; }
/* the primary CTA is WIDER than the secondary row beneath it (320u vs 316u) */
.menu-play { width: calc(320 * var(--u)); }
.hs-lock { width: calc(240 * var(--u)); }
/* solid filled triangles, matched to the letterforms' stroke weight — the
   hairline chevrons disappeared against extremely heavy type */
.menu-play .mp-chev,
.hs-lock .mp-chev { width: calc(12 * var(--u)); height: calc(16 * var(--u)); flex: none; }
.menu-play:hover,
.hs-lock:hover {
  filter: brightness(1.08);
  transform: skewX(-6deg) translateY(calc(-1 * var(--u)));
}
.menu-play:active,
.hs-lock:active {
  transform: skewX(-6deg) translateY(calc(2 * var(--u)));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    inset 0 calc(-3 * var(--u)) 0 rgba(120, 66, 4, 0.6),
    0 calc(2 * var(--u)) calc(8 * var(--u)) rgba(0, 0, 0, 0.45);
}

/* secondary actions: the real panel material, not a 45%-alpha tint that let the
   plaza read straight through the word CREDITS */
.menu-sub {
  display: flex;
  gap: calc(16 * var(--u));
  margin-top: calc(26 * var(--u));
}
/* the secondary material: the SAME dark panel every other plate in the product
   uses, WITH the 3u accent rule, at the SAME 56u height as the primary CTA — so
   BACK and LOCK IN read as one family instead of two products */
.menu-ghost,
.hs-back {
  pointer-events: auto;
  cursor: pointer;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: calc(56 * var(--u));
  font-family: inherit;
  font-weight: 600;
  font-style: italic;
  font-size: var(--t3);
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  padding: 0 calc(22 * var(--u));
  color: rgba(242, 245, 247, 0.9);
  background-color: var(--hud-panel);
  background-image: var(--hud-dots);
  background-size: var(--hud-dot-pitch) var(--hud-dot-pitch);
  border: none;
  border-bottom: var(--hud-rule) solid var(--hud-orange);
  border-radius: 3px;
  box-shadow: var(--hud-edge);
  backdrop-filter: var(--hud-blur);
  transform: skewX(-6deg);
  transition: filter 0.15s, color 0.15s;
}
.menu-ghost { min-width: calc(150 * var(--u)); }
.hs-back { width: calc(140 * var(--u)); }
.menu-ghost:hover,
.hs-back:hover {
  color: #ffffff;
  filter: brightness(1.35);
}

/* the hint strip sits on its OWN plate: over dark foliage the '<' chip nearly
   disappeared (r5, minor) */
.menu-hint {
  box-sizing: border-box;
  margin-top: calc(30 * var(--u));
  min-width: calc(480 * var(--u));
  height: calc(32 * var(--u));
  padding: 0 calc(16 * var(--u));
  border-radius: 3px;
  background: rgba(14, 19, 25, 0.7);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
  font-size: var(--t1);
  letter-spacing: var(--tr-label);
  color: rgba(242, 245, 247, 0.82);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7);
  font-style: italic;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(10 * var(--u));
  transform: skewX(-6deg);
}
/* solid keycaps — the hollow 1px outline let a bush show through the WASD cap.
   UPRIGHT, letter-spacing 0: keycaps are never set in the italic display face.
   The rebind rows share this block LITERALLY rather than by imitation: the cap
   a player is looking at on the settings screen is the same object as the cap in
   the title screen's hint strip, so the two can never drift. */
.menu-hint .kbd,
.settings-screen .st-cap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: calc(3 * var(--u)) calc(9 * var(--u)) calc(2 * var(--u));
  border: none;
  border-radius: 3px;
  background: rgba(12, 17, 25, 0.92);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.16),
    inset 0 calc(-2 * var(--u)) 0 rgba(0, 0, 0, 0.45);
  color: rgba(242, 245, 247, 0.92);
  font-size: calc(13 * var(--u));
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: 0;
  font-style: normal;
}
/* the Q is DRAWN (hud.js KEY_Q): at 13px the display face's Q loses its tail
   entirely and the game's most important key prompt renders as a zero */
.menu-hint .kbd-q,
.settings-screen .st-cap.kbd-q,
.hud-sprint .kbd-q { padding-left: calc(7 * var(--u)); padding-right: calc(7 * var(--u)); }
.kbd-q .kbd-q,
.menu-hint .kbd-q svg,
.settings-screen .st-cap.kbd-q svg,
.hud-sprint .kbd-q svg {
  display: block;
  width: calc(11 * var(--u));
  height: calc(13 * var(--u));
  color: currentColor;
}
.menu-hint .hint-dot {
  width: calc(3 * var(--u));
  height: calc(3 * var(--u));
  border-radius: 50%;
  background: rgba(220, 232, 248, 0.35);
  margin: 0 calc(4 * var(--u));
}

.menu-version {
  position: absolute;
  right: var(--hud-inset);
  bottom: var(--hud-inset);
  font-size: var(--t1);
  letter-spacing: var(--tr-label);
  font-style: italic;
  color: rgba(220, 232, 248, 0.4);
  transform: skewX(-6deg);
}
.menu-copy {
  position: absolute;
  left: var(--hud-inset);
  bottom: var(--hud-inset);
  font-size: var(--t1);
  letter-spacing: var(--tr-label);
  font-style: italic;
  color: rgba(220, 232, 248, 0.24);
  transform: skewX(-6deg);
}

/* ================= character select =================
   docs/heroes.md §7.4. Same screen as the title — same live plaza behind it,
   same panel material, same -6° shear, same type scale, same orange CTA — with
   the logotype stack swapped for the roster. PLAY does not cut away from the
   game; it steps forward inside it.

   Every string is roster data (name, role, tagline, heroSummaryLines). The card
   spends the hero accent on exactly the three places the palette contract
   allows: the selected card's rule, the name plate's edge, and the portrait
   frame — plus the stat-row labels §7.4 asks for. Everything else is the same
   three warm HUD values, so four heroes side by side still read as one product
   rather than four brands. Dark paints (KESTREL's #2F7F7C) are lifted with a
   brightness filter rather than being substituted: the hue stays the hero's,
   the VALUE belongs to the HUD. */
#menu-root .hs-screen { display: none; }
#menu-root.selecting .m-center,
#menu-root.moding .m-center,
#menu-root.queueing .m-center,
/* SETTINGS from the TITLE replaces the title stack exactly as MODE SELECT does.
   From the PAUSE dialog `.menu-bg` is gone entirely (see below), so this rule
   is scoped to the title case to keep the two paths independent. */
#menu-root.optioning:not(.paused) .m-center { display: none; }
/* PAUSED puts the dialog over the LIVE GAME, so the whole menu backdrop —
   gradient, skyline, embers, title stack, mode chip — comes off. It is the one
   screen in the product that is not drawn over the menu's own plaza. */
#menu-root.paused .menu-bg { display: none; }
/* a SETTINGS screen wearing a mode chip is a claim about a match it is not
   part of; `.moding` already sets the precedent for retiring it */
#menu-root.optioning .menu-mode { display: none; }
/* While the dialog is up, a click ANYWHERE outside it falls through to
   #game-canvas, whose click handler calls resume() (docs/settings.md §5.6).
   Without this #menu-root's full-screen box would eat every one of them and the
   belt-and-braces resume would silently not exist. Scoped away from
   `.optioning` so the settings screen on top of the dialog still takes clicks. */
#menu-root.paused:not(.optioning) { pointer-events: none; }
#menu-root.paused:not(.optioning) .pz-panel { pointer-events: auto; }
/* the title screen's mode chip names ONE of the two modes on offer — a screen
   that asks CHOOSE YOUR MODE cannot already be wearing an answer. On the QUEUE
   screen it stays: by then the mode IS the answer, and the chip is the only
   thing still naming what you are waiting for while the title stack is gone. */
#menu-root.moding .menu-mode { display: none; }
#menu-root.selecting .hero-select,
#menu-root.moding .mode-select,
#menu-root.queueing .queue-screen,
#menu-root.optioning .settings-screen,
#menu-root.paused:not(.optioning) .pause-screen {
  position: absolute;
  inset: 0;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: calc(30 * var(--u));
  padding: calc(64 * var(--u)) calc(32 * var(--u)) calc(58 * var(--u));
}
/* the plaza stays LIVE behind the roster — pushed back a stop, never replaced:
   a flat black wash here would undo the whole reason the menu renders the game */
.hs-screen .hs-veil {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(6, 9, 14, 0.22) 0%, rgba(6, 9, 14, 0) 26%,
      rgba(6, 9, 14, 0) 62%, rgba(5, 8, 12, 0.42) 100%),
    radial-gradient(ellipse 92% 76% at 50% 54%,
      rgba(6, 9, 14, 0.24) 0%, rgba(6, 9, 14, 0.44) 58%, rgba(5, 8, 12, 0.6) 100%);
  backdrop-filter: blur(2px) brightness(0.8) saturate(0.96);
}
.hs-screen > *:not(.hs-veil) { position: relative; z-index: 1; }

.hs-screen .hs-head {
  display: flex;
  flex-direction: column;
  align-items: center;
}
/* r5 (minor): at 0.35em tracking a screen TITLE reads as an eyebrow label, and
   a 90px dash under a 347px title reads as an orphan. 44u/800 at 0.14em, on a
   240u rule. */
.hs-screen .hs-title {
  font-size: calc(44 * var(--u));
  font-weight: 800;
  line-height: 1;
  letter-spacing: 0.14em;
  text-indent: 0.14em;
  color: var(--hud-white);
  text-shadow: 0 calc(2 * var(--u)) calc(8 * var(--u)) rgba(0, 0, 0, 0.8);
  transform: skewX(-6deg);
}
.hs-screen .hs-kick {
  width: calc(240 * var(--u));
  height: calc(2 * var(--u));
  margin-top: calc(13 * var(--u));
  background: linear-gradient(90deg, transparent, #f99e1a 14%, #f99e1a 86%, transparent);
  transform: skewX(-30deg);
  box-shadow: 0 0 calc(8 * var(--u)) rgba(249, 158, 26, 0.45);
}

.hero-select .hs-cards {
  display: flex;
  align-items: stretch;
  gap: calc(14 * var(--u));
}
.hero-select .hs-card {
  position: relative;
  box-sizing: border-box;
  /* fixed at the design width, but allowed to shrink rather than push the row
     off a narrow window — --u tracks HEIGHT, so a short wide window is safe and
     a tall narrow one is not */
  flex: 0 1 calc(316 * var(--u));
  min-width: 0;
  display: flex;
  flex-direction: column;
  padding: 0 0 calc(20 * var(--u));
  font-family: inherit;
  font-weight: 600;
  font-style: italic;
  text-align: left;
  color: var(--hud-white);
  border: none;
  border-bottom: var(--hud-rule) solid rgba(242, 245, 247, 0.16);
  border-radius: 3px;
  background-color: var(--hud-panel);
  background-image: var(--hud-dots);
  background-size: var(--hud-dot-pitch) var(--hud-dot-pitch);
  box-shadow: var(--hud-edge);
  backdrop-filter: var(--hud-blur);
  transform: var(--hud-skew);
  transform-origin: bottom center;
  cursor: pointer;
  pointer-events: auto;
  transition: transform 0.16s cubic-bezier(0.22, 1, 0.36, 1), border-color 0.16s;
  /* the identity hue lifted toward the HUD white until it clears the panel it
     sits on: same hue, one value. */
  --hero-lift: color-mix(in srgb, var(--hero-hue) 78%, #f2f5f7);
  /* ONE interior gutter on all four edges */
  --hs-gutter: calc(20 * var(--u));
}
/* r5 (critical): unselected kits measured 2.70:1 on ability names and 3.04:1 on
   taglines — three of the four cards were unreadable, on a screen that exists so
   the player can COMPARE kits. The dim is now capped near 55% luminance instead
   of 25%; the selected card's lift comes from SCALE and its border, never from
   crushing the other three. */
.hero-select .hs-card > * { opacity: 0.84; transition: opacity 0.16s ease-out; }
.hero-select .hs-card:hover > * { opacity: 0.94; }
.hero-select .hs-card.sel > * { opacity: 1; }
/* The lift is BORDER + LIGHT, never geometry: any translate or scale moves the
   role, name and tagline off the baselines the other three sit on, and that
   broken horizontal rhythm is exactly what r5 filed. The card gains a full
   accent frame, an accent rule, a lifted panel and a hero-hue glow; the only
   thing that moves is the portrait art inside its own box, which carries no
   baselines. */
.hero-select .hs-card.sel {
  opacity: 1;
  border-bottom-color: var(--hero-hue);
  background-color: rgba(16, 20, 27, 0.9);
  box-shadow:
    inset 0 0 0 calc(2 * var(--u)) var(--hero-hue),
    inset 0 1px 0 rgba(255, 255, 255, 0.2),
    0 calc(10 * var(--u)) calc(30 * var(--u)) rgba(0, 0, 0, 0.6),
    0 0 calc(26 * var(--u)) -6px var(--hero-hue);
}
.hero-select .hs-card.sel .hs-art .hp-face { transform: skewX(6deg) scale(1.18); }
.hero-select .hs-card.sel .hs-art::before { opacity: 0.4; }

/* portrait: square, so the 64-grid bust needs no crop maths, and counter-sheared
   inside the sheared card — the FRAME is a parallelogram, the face is not.
   scale(1.13) covers the triangles the counter-shear opens at the corners. */
.hero-select .hs-art {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border-radius: 3px 3px 0 0;
  background: #0c1219;
  color: var(--hero-accent);
}
.hero-select .hs-art::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background: radial-gradient(circle at 50% 38%, var(--hero-hue) 0%, transparent 58%);
  opacity: 0.24;
  pointer-events: none;
}
.hero-select .hs-art .hp-face {
  display: block;
  width: 100%;
  height: 100%;
  transform: skewX(6deg) scale(1.13);
}
.hero-select .hs-art::after {
  content: '';
  position: absolute;
  z-index: 3;
  inset: 0;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);
  pointer-events: none;
}
.hero-select .hs-card.sel .hs-art::after {
  box-shadow: inset 0 0 0 calc(1.5 * var(--u)) var(--hero-hue);
}
/* ---- one three-light rig over all four busts ----
   r5 (critical) is that the portraits are flat vector icons with "zero
   lighting, zero depth, zero rim light" and that the four are drawn in four
   different illustration languages. The real fix is rendering them from the
   in-game meshes, which needs a WebGPU offscreen pass this file does not own
   (recorded as a known issue). What DOES belong here is the light: one warm key
   raking from camera-left at ~35 degrees, one cool fill from camera-right and a
   contact shade — composited in soft-light so it modulates the art's own values
   instead of washing over them. Because it is ONE overlay on all four cards it
   also does the thing that makes a roster read as a single shoot: the four are
   lit identically.

   It is keyed on `.hs-screen`, not `.hero-select`, because the MODE cards wear
   the same rig: two screens lit by ONE lamp is the whole reason the mode screen
   is built as a sibling rather than as a lookalike. */
.hs-screen .hs-rig {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  mix-blend-mode: soft-light;
  background:
    linear-gradient(122deg,
      rgba(255, 228, 184, 0.95) 0%, rgba(255, 228, 184, 0.34) 30%, rgba(255, 228, 184, 0) 50%),
    radial-gradient(ellipse 80% 68% at 93% 86%,
      rgba(84, 118, 156, 0.85) 0%, rgba(84, 118, 156, 0) 66%),
    radial-gradient(ellipse 120% 60% at 50% 108%,
      rgba(6, 9, 14, 0.9) 0%, rgba(6, 9, 14, 0) 60%);
}
.hs-screen .hs-shade {
  z-index: 1;
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 34%;
  background: linear-gradient(180deg, rgba(8, 11, 16, 0) 0%, rgba(8, 11, 16, 0.5) 62%, rgba(8, 11, 16, 0.72) 100%);
}
.hero-select .hs-idx {
  z-index: 2;
  position: absolute;
  left: var(--hs-gutter);
  top: calc(10 * var(--u));
  font-size: var(--t2);
  font-weight: 800;
  line-height: 1;
  color: rgba(242, 245, 247, 0.4);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
}
/* the role label gets its OWN chip in the hero hue: it used to sit raw on the
   portrait art with zero left padding, and BRUISER landed as grey text on
   BRAMBLE's orange pauldron */
.hero-select .hs-role {
  z-index: 2;
  position: absolute;
  left: var(--hs-gutter);
  bottom: calc(30 * var(--u));
  padding: calc(2 * var(--u)) calc(8 * var(--u)) calc(3 * var(--u));
  border-radius: 3px;
  background: color-mix(in srgb, var(--hero-hue) 18%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--hero-hue) 45%, transparent);
  font-size: var(--t1);
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: #f2f5f7;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
}

/* name plate: rides UP over the portrait's lower edge on its own accent rule —
   the one place the card is allowed to shout the hero's own colour */
.hero-select .hs-plate {
  position: relative;   /* .hs-art is positioned; without this it paints over */
  z-index: 2;
  align-self: flex-start;
  margin: calc(-17 * var(--u)) 0 0 calc(10 * var(--u));
  padding: calc(4 * var(--u)) calc(13 * var(--u)) calc(5 * var(--u));
  background: rgba(10, 14, 20, 0.94);
  border-left: var(--hud-rule) solid var(--hero-hue);
  border-radius: 0 2px 2px 0;
  box-shadow: 0 calc(2 * var(--u)) calc(8 * var(--u)) rgba(0, 0, 0, 0.5);
}
.hero-select .hs-name {
  font-size: var(--t4);
  font-weight: 800;
  line-height: 1;
  letter-spacing: 0.1em;
  text-indent: 0.1em;
  color: var(--hud-white);
}
/* the tagline is FLAVOUR and is now clearly subordinate to the kit data below
   it (r5: the tagline's cap height out-ranked the ability name's) */
.hero-select .hs-tag {
  min-height: calc(32 * var(--u));
  margin-top: calc(10 * var(--u));
  padding: 0 var(--hs-gutter);
  font-size: calc(14 * var(--u));
  line-height: 1.2;
  letter-spacing: var(--tr-body);
  color: rgba(242, 245, 247, 0.7);
}

/* stat rows: glyph · ability name · stat line. The type ramp is reset so two
   levels actually exist — name 19u/800 over stat 15u/600 — and the numeric
   value is right-aligned to the card's own gutter, so the row spans the full
   card instead of leaving 45% dead panel with a full-width rule above it. */
.hero-select .hs-rows {
  display: flex;
  flex-direction: column;
  gap: calc(9 * var(--u));
  margin-top: calc(12 * var(--u));
  padding: calc(12 * var(--u)) var(--hs-gutter) 0;
  border-top: 1px solid rgba(255, 255, 255, 0.09);
}
.hero-select .hs-row {
  display: flex;
  align-items: center;
  gap: calc(10 * var(--u));
}
.hero-select .hs-ic {
  width: calc(26 * var(--u));
  height: calc(26 * var(--u));
  flex: none;
  color: var(--hero-lift);
}
.hero-select .hs-txt { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; }
.hero-select .hs-lbl {
  display: flex;
  align-items: baseline;
  gap: calc(6 * var(--u));
  font-size: calc(19 * var(--u));
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: 0.08em;
  color: var(--hero-lift);
}
.hero-select .hs-key {
  font-style: normal;
  font-size: 0.78em;
  letter-spacing: 0;
  color: rgba(242, 245, 247, 0.4);
}
.hero-select .hs-val {
  align-self: flex-end;
  text-align: right;
  margin-top: calc(2 * var(--u));
  font-size: calc(15 * var(--u));
  line-height: 1.1;
  letter-spacing: 0.03em;
  color: rgba(242, 245, 247, 0.86);
  font-variant-numeric: tabular-nums;
}

.hs-screen .hs-foot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(18 * var(--u));
}
/* one baseline: both buttons are 56u tall and bottom-aligned */
.hs-screen .hs-actions {
  display: flex;
  align-items: flex-end;
  gap: calc(18 * var(--u));
}
.hs-screen .hs-lock { margin-top: 0; }
.hs-screen .hs-hint { margin-top: 0; }
.menu-hint .kbd.kbd-ic {
  display: inline-flex;
  align-items: center;
  padding: calc(3 * var(--u)) calc(6 * var(--u)) calc(3 * var(--u));
}
.menu-hint .kbd-chev {
  display: block;
  width: calc(9 * var(--u));
  height: calc(9 * var(--u));
  color: rgba(242, 245, 247, 0.92);
}
/* The roster lifts in as ONE object. It is deliberately NOT a per-card stagger:
   main.js freezes #menu-root with `animation-delay: -0.12s; animation-play-state:
   paused`, so a staggered entrance would capture four cards each stopped at a
   different point of its own transform — and a per-card keyframe's implicit 100%
   resolves to that card's own transform, which for the selected card includes
   its raise. One transform on the container, settled by 40% (= the 0.12s the
   capture freezes at), leaves nothing to go wrong. */
#menu-root.selecting .hero-select,
#menu-root.moding .mode-select,
#menu-root.queueing .queue-screen,
#menu-root.optioning .settings-screen,
#menu-root.paused:not(.optioning) .pause-screen { animation: hsin 0.3s cubic-bezier(0.22, 1, 0.36, 1) backwards; }
@keyframes hsin {
  0% { opacity: 0; transform: translateY(calc(14 * var(--u))); }
  40%, 100% { opacity: 1; transform: translateY(0); }
}

/* ================= MODE SELECT (docs/modes.md §8.3) =================
   A SIBLING of the character select, inside the same #menu-root, over the same
   live plaza. Every structural rule it needs — the veil, the head, the title,
   the kick rule, the shade, the foot, the actions, the hint strip — is the
   `.hs-screen` rule above, shared with the roster screen, so the type scale and
   the -6deg shear are identical BY CONSTRUCTION. What is local to this file is
   only what a MODE card is: a wider art block, a 16:9 poster mark instead of a
   bust, and two cards instead of four.

   PALETTE — the load-bearing rule of the screen. The cards carry NO identity
   hue. Three values exist here and nowhere else on it:
     · #F99E1A  accent — the selected rule, the frame, the name plate edge,
                the row bullets, the CONFIRM button (from .hs-lock)
     · #F2F5F7  white  — every piece of type that is not a label
     · #FFC873  gold   — the poster mark and the stat-row labels, i.e. exactly
                the role the hero card gives its --hero-lift
   Critique round 1 collapsed the HUD to three warm values. A "practice orange"
   and a "duel blue" would put it straight back, so the card RESETS --hero-hue
   and --hero-accent locally: whatever hero is selected behind this screen, it
   cannot leak a fifth hue onto a mode card. */
/* The ROW carries the width, not the cards. A flex item's max-content
   contribution is capped by its own max-content size, so `flex: 0 1 <basis>`
   on a card whose longest line is 215u collapses it to 215u however large the
   basis is — the roster row survives that only because 316u happens to be near
   its own text measure. Sizing the row and letting the two cards divide it is
   immune to how long a mode's stat line happens to be. */
.mode-select .ms-cards {
  display: flex;
  align-items: stretch;
  justify-content: center;
  gap: calc(20 * var(--u));
  width: calc(1306 * var(--u));
  max-width: 100%;
}
.mode-select .ms-card {
  position: relative;
  box-sizing: border-box;
  /* two cards, so each is ~1.65x a hero card: the row lands at 1080u wide
     against the roster's 1306u, which keeps both screens' card rows inside the
     same optical block when they are laid side by side. */
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  padding: 0 0 calc(20 * var(--u));
  font-family: inherit;
  font-weight: 600;
  font-style: italic;
  text-align: left;
  color: var(--hud-white);
  border: none;
  border-bottom: var(--hud-rule) solid rgba(242, 245, 247, 0.16);
  border-radius: 3px;
  background-color: var(--hud-panel);
  background-image: var(--hud-dots);
  background-size: var(--hud-dot-pitch) var(--hud-dot-pitch);
  box-shadow: var(--hud-edge);
  backdrop-filter: var(--hud-blur);
  transform: var(--hud-skew);
  transform-origin: bottom center;
  cursor: pointer;
  pointer-events: auto;
  transition: border-color 0.16s, background-color 0.16s, box-shadow 0.16s;
  /* the mode screen's whole palette, declared once and inherited downward */
  --ms-accent: #f99e1a;
  --ms-lift: #ffc873;
  --hs-gutter: calc(22 * var(--u));
}
/* r5 (critical) was "three of the four cards are unreadable" at a 25% dim. The
   contract's "unselected at 55%" is spent where it belongs — on the ART, which
   is the identity read — while the SPEC SHEET stays legible on both cards,
   because comparing the two modes is the only reason this screen exists. */
.mode-select .ms-card > *:not(.ms-art) { opacity: 0.82; transition: opacity 0.16s ease-out; }
.mode-select .ms-card:hover > *:not(.ms-art) { opacity: 0.93; }
.mode-select .ms-card.sel > * { opacity: 1; }
/* The lift is BORDER + LIGHT, never geometry — the same decision the roster
   screen made for the same reason: a translate moves this card's name, tagline
   and three stat rows off the baselines the other card sits on, and that broken
   horizontal rhythm is exactly what r5 filed. */
.mode-select .ms-card.sel {
  border-bottom-color: var(--ms-accent);
  background-color: rgba(16, 20, 27, 0.9);
  box-shadow:
    inset 0 0 0 calc(2 * var(--u)) var(--ms-accent),
    inset 0 1px 0 rgba(255, 255, 255, 0.2),
    0 calc(10 * var(--u)) calc(30 * var(--u)) rgba(0, 0, 0, 0.6),
    0 0 calc(26 * var(--u)) -6px var(--ms-accent);
}

/* --- card art: a 16:9 poster plate, counter-sheared inside the sheared card
   exactly as the hero bust is, on the SAME #0C1219 ground and the SAME
   horizon-band structure the four portraits are built on, so the two screens'
   art blocks read as one production. --- */
.mode-select .ms-art {
  position: relative;
  display: block;
  width: 100%;
  /* 2:1, not 2.35:1 — two cards make a shorter block than four, and the roster
     screen's optical mass has to be matched or the two screens read as different
     sizes of product. The 32u each card gains goes to the art. */
  aspect-ratio: 2 / 1;
  overflow: hidden;
  border-radius: 3px 3px 0 0;
  /* A VALUE RAMP, not a flat plate. The four hero portraits work because a
     near-white shell sits on a near-black ground; the first pass at this card
     had every pixel between #0C1219 and #1B2634 and the mark was the only value
     in the frame. This is the plaza's own late-afternoon read compressed into
     six stops — cool sky, a lifted horizon at 52%, then the ground falling away
     — so the mark is backlit by a horizon instead of floating on a rectangle.
     Neutral values only: this screen's palette assertion is that no chromatic
     identity beyond the three warm HUD values appears on a card. */
  background: linear-gradient(180deg,
    #0b1119 0%, #121b26 26%, #1d2a39 46%,
    #2b3b4d 52%, #16202b 57%, #0d141c 82%, #0a0f16 100%);
  opacity: 0.55;
  transition: opacity 0.16s ease-out;
}
.mode-select .ms-card:hover .ms-art { opacity: 0.76; }
.mode-select .ms-card.sel .ms-art { opacity: 1; }
/* the rig is authored for a 1:1 portrait plate, where a bust breaks the key up.
   Run at full strength across a 2.35:1 slab it blooms into the top-left corner
   and reads as a lens artefact, so the poster format takes the same lamp at
   lower power and lets the horizon band carry the light instead. */
.mode-select .hs-rig { opacity: 0.6; }
/* the card stands on the game's OWN skyline — the same layered silhouette the
   title screen draws — rather than on a flat plate. It is the cheapest honest
   depth available to a DOM card, it costs no new asset, and it is what stops
   the art block reading as "a vector icon on a dark rectangle", which is the
   note the four hero portraits are still carrying. The second card takes a
   different crop of it, so the two are two vantage points on one plaza. */
.mode-select .ms-sky {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  opacity: 0.72;
}
.mode-select .ms-sky-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* held to a NEUTRAL dark silhouette on purpose: the same #0C1219 / #1B2634
     value family the four hero portraits are built on. The skyline is depth,
     not a hue — this screen's palette assertion is that no chromatic identity
     value other than the three warm HUD ones appears on a card. The only
     saturated pixels it keeps are its warm #FFC873 window lights, which are on
     the list. */
  filter: saturate(0.24) brightness(1.28) contrast(1.22);
  opacity: 0.95;
  transform: scale(1.85);
  transform-origin: 50% 88%;
}
/* the clock face and hands are a title-screen focal point; two identical pale
   discs floating in two card backdrops read as artefacts */
.mode-select .ms-sky-svg circle,
.mode-select .ms-sky-svg line { display: none; }
.mode-select .ms-card:nth-child(2) .ms-sky-svg { transform: scale(2.2) translate(-14%, 2%); }
/* the horizon HAZE + the mark's backlight. Warm lamp #FFC873 laid along the
   skyline at 52% and the HUD accent behind the mark — both on the three-value
   list, both spent as LIGHT rather than as a card identity, so the two cards
   glow the same colour and differ only in what they are showing. */
.mode-select .ms-glow {
  position: absolute;
  inset: 0;
  z-index: 1;
  background:
    radial-gradient(ellipse 46% 13% at 50% 57%, rgba(255, 200, 115, 0.7) 0%, rgba(255, 200, 115, 0) 74%),
    radial-gradient(ellipse 86% 30% at 50% 56%, rgba(255, 200, 115, 0.2) 0%, rgba(255, 200, 115, 0) 78%),
    radial-gradient(circle at 50% 42%, var(--ms-accent) 0%, transparent 46%);
  opacity: 0.44;
}
/* the horizon seat + a corner vignette. The flat near-black plate behind the
   hero busts was filed twice ("backdrop inside the frame is a flat near-black"),
   and this one has to hold a single mark at 150u, so it gets structure: the
   ground darkens under the skyline and the four corners fall away, which is what
   puts the mark in the middle of a lit frame instead of on a tile. */
.mode-select .ms-grid {
  position: absolute;
  inset: 0;
  z-index: 1;
  background:
    linear-gradient(180deg, rgba(11, 16, 23, 0) 50%, rgba(11, 16, 23, 0.55) 60%, rgba(8, 12, 18, 0.9) 100%),
    radial-gradient(ellipse 78% 88% at 50% 50%, rgba(0, 0, 0, 0) 40%, rgba(0, 0, 0, 0.5) 100%);
}
/* THE FLOOR. One perspective-transformed grid running back to the horizon the
   .ms-glow band sits on, so the card has a ground plane and a vanishing point
   rather than a wallpaper. Fades out before it reaches the horizon line, which
   is what stops the two grids fighting where they meet. */
.mode-select .ms-floor {
  position: absolute;
  left: 0;
  right: 0;
  top: 55%;
  bottom: 0;
  z-index: 1;
  overflow: hidden;
  perspective: calc(150 * var(--u));
  perspective-origin: 50% 0%;
  -webkit-mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, #000 26%, #000 74%, rgba(0, 0, 0, 0) 100%);
  mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, #000 26%, #000 74%, rgba(0, 0, 0, 0) 100%);
}
.mode-select .ms-floor::before {
  content: '';
  position: absolute;
  left: -60%;
  right: -60%;
  top: 0;
  height: calc(300 * var(--u));
  transform: rotateX(74deg);
  transform-origin: 50% 0%;
  background:
    repeating-linear-gradient(90deg,
      rgba(255, 255, 255, 0.22) 0 calc(1.5 * var(--u)), transparent calc(1.5 * var(--u)) calc(52 * var(--u))),
    repeating-linear-gradient(0deg,
      rgba(255, 255, 255, 0.2) 0 calc(2 * var(--u)), transparent calc(2 * var(--u)) calc(30 * var(--u)));
}
.mode-select .ms-mark {
  position: absolute;
  z-index: 2;
  left: 50%;
  top: 45%;
  width: calc(150 * var(--u));
  height: calc(150 * var(--u));
  transform: translate(-50%, -50%) skewX(6deg);
  color: var(--ms-lift);
  /* gold on a gold horizon haze is the one place this card can lose its subject,
     so the mark carries its OWN dark halo before the cast shadow — separation by
     value, which is the only separation available when hue is spoken for. */
  filter:
    drop-shadow(0 0 calc(3 * var(--u)) rgba(6, 9, 14, 0.9))
    drop-shadow(0 calc(4 * var(--u)) calc(10 * var(--u)) rgba(0, 0, 0, 0.75));
}
/* the 24-grid master's 1u solid contour is sized for a 26px row glyph; at 136u
   it would rasterize as a 5px black outline, so the poster use thins it. */
.mode-select .ms-mark .ico-solid { stroke-width: 0.42; }
.mode-select .ms-art::after {
  content: '';
  position: absolute;
  z-index: 3;
  inset: 0;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);
  pointer-events: none;
}
.mode-select .ms-card.sel .ms-art::after {
  box-shadow: inset 0 0 0 calc(1.5 * var(--u)) var(--ms-accent);
}
.mode-select .ms-idx {
  z-index: 3;
  position: absolute;
  left: var(--hs-gutter);
  top: calc(10 * var(--u));
  font-size: var(--t2);
  font-weight: 800;
  line-height: 1;
  color: rgba(242, 245, 247, 0.58);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
}
/* mode.kicker — the eyebrow opposite the index, in gold at label scale */
.mode-select .ms-kick {
  z-index: 3;
  position: absolute;
  right: var(--hs-gutter);
  top: calc(10 * var(--u));
  font-size: var(--t1);
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: var(--ms-lift);
  opacity: 0.85;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
}
/* mode.subtitle in the .hs-role chip shape, so the two screens put the same
   kind of word in the same place at the same size */
.mode-select .ms-role {
  z-index: 3;
  position: absolute;
  left: var(--hs-gutter);
  bottom: calc(30 * var(--u));
  padding: calc(2 * var(--u)) calc(8 * var(--u)) calc(3 * var(--u));
  border-radius: 3px;
  background: color-mix(in srgb, var(--ms-accent) 18%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ms-accent) 45%, transparent);
  font-size: var(--t1);
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: #f2f5f7;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
}

.mode-select .ms-plate {
  position: relative;
  z-index: 2;
  align-self: flex-start;
  margin: calc(-17 * var(--u)) 0 0 calc(10 * var(--u));
  padding: calc(4 * var(--u)) calc(13 * var(--u)) calc(5 * var(--u));
  background: rgba(10, 14, 20, 0.94);
  border-left: var(--hud-rule) solid var(--ms-accent);
  border-radius: 0 2px 2px 0;
  box-shadow: 0 calc(2 * var(--u)) calc(8 * var(--u)) rgba(0, 0, 0, 0.5);
}
.mode-select .ms-name {
  font-size: var(--t4);
  font-weight: 800;
  line-height: 1;
  letter-spacing: 0.1em;
  text-indent: 0.1em;
  color: var(--hud-white);
}
.mode-select .ms-tag {
  min-height: calc(32 * var(--u));
  margin-top: calc(10 * var(--u));
  padding: 0 var(--hs-gutter);
  font-size: calc(14 * var(--u));
  line-height: 1.2;
  letter-spacing: var(--tr-body);
  color: rgba(242, 245, 247, 0.7);
}
.mode-select .ms-rows {
  display: flex;
  flex-direction: column;
  gap: calc(9 * var(--u));
  margin-top: calc(12 * var(--u));
  padding: calc(12 * var(--u)) var(--hs-gutter) 0;
  border-top: 1px solid rgba(255, 255, 255, 0.09);
}
.mode-select .ms-row {
  display: flex;
  align-items: center;
  gap: calc(10 * var(--u));
}
/* the row marker is the product's own PLAY_CHEV, not a third copy of the mode
   glyph: three identical 26u icons down one column reads as a bug, and a solid
   accent tick reads as a spec sheet. It is the same triangle the two CTAs on
   this screen carry, so it costs no new vocabulary. */
.mode-select .ms-ic {
  width: calc(26 * var(--u));
  height: calc(26 * var(--u));
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ms-accent);
}
.mode-select .ms-ic .mp-chev {
  display: block;
  width: calc(9 * var(--u));
  height: calc(12 * var(--u));
  opacity: 0.9;
  filter: drop-shadow(0 1px 1.5px rgba(0, 0, 0, 0.7));
}
.mode-select .ms-txt {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: calc(16 * var(--u));
  flex: 1 1 auto;
  min-width: 0;
}
.mode-select .ms-lbl {
  font-size: calc(19 * var(--u));
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: 0.08em;
  color: var(--ms-lift);
}
.mode-select .ms-val {
  flex: none;
  text-align: right;
  white-space: nowrap;
  font-size: calc(15 * var(--u));
  line-height: 1.1;
  letter-spacing: 0.03em;
  color: rgba(242, 245, 247, 0.86);
  font-variant-numeric: tabular-nums;
}

/* ---- THREE cards (docs/netcode.md §6.10) --------------------------------
   Everything above this point is authored for exactly TWO cards: the row is
   1306u, each card takes half of it at ~643u, and the art is 2:1. Drop a third
   card into that and each falls to 422u while the art keeps a 2:1 ratio, which
   collapses the poster block to a 211u letterbox and breaks the row's optical
   mass against the roster screen's.

   `:has(> :nth-child(3))` (Chrome 105+, Safari 15.4+, Firefox 121+ — the whole
   WebGPU floor) means a two-card build never sees a byte of this and
   `?shot=modeselect`'s two-card framing is untouched.

   The measure does NOT change: 1306u is the ROSTER row's exact width, so the
   two screens' card blocks remain the same block. What changes is the ratio.
   Three 422u cards with 16:9 art gives 422 x 237 = 100,183u2 of poster per
   card; a hero card's 1:1 art is 316 x 316 = 99,856u2. Within 0.3% — the mode
   card's optical mass is now a hero card's, which is exactly the relationship
   the two-card layout had at 1.65x. Nothing here was chosen by eye. */
.mode-select .ms-cards:has(> :nth-child(3)) {
  gap: calc(20 * var(--u));
}
.mode-select .ms-cards:has(> :nth-child(3)) .ms-card {
  /* the gutter drops to the ROSTER's 20u: at 422u this card is in the hero
     card's width family, so it takes the hero card's interior too */
  --hs-gutter: calc(20 * var(--u));
}
.mode-select .ms-cards:has(> :nth-child(3)) .ms-art { aspect-ratio: 16 / 9; }
/* the mark scales WITH the art (150u on a 321u plate -> 111u on a 237u plate),
   so the poster's subject occupies the same fraction of its frame on both
   layouts instead of ballooning when the plate shrinks */
.mode-select .ms-cards:has(> :nth-child(3)) .ms-mark {
  width: calc(111 * var(--u));
  height: calc(111 * var(--u));
}
/* a third vantage point on the same plaza — the two-card build gives card 2 its
   own crop for exactly this reason, and three identical skylines would read as
   a repeated asset */
.mode-select .ms-cards:has(> :nth-child(3)) .ms-card:nth-child(3) .ms-sky-svg {
  transform: scale(2.55) translate(9%, 4%);
}
/* At 643u the label and its value share a line comfortably. At 422u they do
   not: the longest stat line in the roster is 41 characters and it overruns the
   measure, which would either wrap the LABEL or spill the card. The fix is not
   a smaller type step, it is the ROSTER's own row treatment — label over a
   right-aligned value — which this card is now the same width as. Two screens,
   one row component, again by construction rather than by eye. */
.mode-select .ms-cards:has(> :nth-child(3)) .ms-txt {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  gap: 0;
}
.mode-select .ms-cards:has(> :nth-child(3)) .ms-val {
  align-self: flex-end;
  margin-top: calc(2 * var(--u));
  white-space: normal;
  text-wrap: balance;
}

/* ---- a mode that cannot be played right now ------------------------------
   docs/netcode.md §7: with no game server reachable the ONLINE card is greyed
   and given a reason. It is NOT disabled — a greyed card that silently eats the
   click is the exact "nothing happened" failure the whole online flow is
   designed around. Clicking it walks the normal path and lands on the queue
   screen's `unavailable` state, which prints the same sentence at full size.

   The treatment spends NO hue: the art desaturates, a fine diagonal hatch (the
   dot matrix's angled sibling) lies over it, and the reason sits on a gold
   hairline. The screen's three-value palette assertion still holds. */
.mode-select .ms-out,
.mode-select .ms-note { display: none; }
/* desaturate, do not extinguish. The art already sits at 0.55 when the card is
   not selected; darkening it on top of that erases the poster and the card
   reads as broken rather than as closed. Pulling the colour out is enough — the
   warm horizon is the only chroma on the plate and losing it IS the signal. */
.mode-select .ms-card.unavail .ms-art { filter: grayscale(0.94) brightness(0.88); }
.mode-select .ms-card.unavail > *:not(.ms-art):not(.ms-out):not(.ms-note) { opacity: 0.6; }
.mode-select .ms-card.unavail.sel > *:not(.ms-art):not(.ms-out):not(.ms-note) { opacity: 0.76; }
/* both overlays mirror the art block's own box: same width, same aspect-ratio,
   so they track the 2:1 and 16:9 layouts without either one knowing about the
   other */
.mode-select .ms-out,
.mode-select .ms-note {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  aspect-ratio: 2 / 1;
  border-radius: 3px 3px 0 0;
}
.mode-select .ms-cards:has(> :nth-child(3)) .ms-out,
.mode-select .ms-cards:has(> :nth-child(3)) .ms-note { aspect-ratio: 16 / 9; }
.mode-select .ms-card.unavail .ms-out {
  display: block;
  z-index: 3;
  background: repeating-linear-gradient(-52deg,
    rgba(242, 245, 247, 0.055) 0 calc(2 * var(--u)),
    rgba(0, 0, 0, 0) calc(2 * var(--u)) calc(9 * var(--u)));
}
.mode-select .ms-card.unavail .ms-note {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  z-index: 4;
  box-sizing: border-box;
  padding: 0 var(--hs-gutter);
  text-align: center;
}
/* when the reason IS the subject, the poster steps back for it */
.mode-select .ms-card.unavail .ms-mark { opacity: 0.4; }
/* the reason sits on its own plate. Over a poster mark, a text-shadow is not
   separation — the sentence a player is being asked to act on gets the same
   dark ground every other readout in the product gets. */
.mode-select .ms-note b {
  padding: calc(5 * var(--u)) calc(14 * var(--u)) calc(6 * var(--u));
  border-radius: 2px;
  background: rgba(8, 11, 16, 0.9);
  box-shadow: inset 0 0 0 1px rgba(255, 200, 115, 0.26);
  font-style: italic;
  font-size: var(--t2);
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: var(--hud-white);
  text-shadow: 0 calc(2 * var(--u)) calc(6 * var(--u)) rgba(0, 0, 0, 0.95);
}

/* ================= QUEUE / MATCHMAKING (docs/netcode.md §6.10) =============
   The third `.hs-screen`. Every structural rule it stands on — the veil, the
   head, the title, the kick rule, the foot, the actions, the hint strip, the
   entrance transform — is shared with the two screens either side of it, so the
   type scale and the -6deg shear match by construction and not by eye. Only
   what is unique to WAITING lives here.

   The screen has one job beyond looking like the rest of the product: it must
   never read as stopped. Three things guarantee that, and all three are honest:
   a four-step rail saying where in the flow you are, a clock counting UP (the
   only progress number a queue can state without lying), and a slow orbit
   proving the client is still alive. There is no indeterminate spinner here,
   because a spinner says "something is happening" and cannot say what.

   PALETTE: the same three warm values the mode screen is held to — #F99E1A,
   #F2F5F7, #FFC873 — and nothing else. */
.queue-screen .q-body {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(26 * var(--u));
}

/* the sub-line under the kick rule: the mode you are queued for, the opponent
   you were matched with, or the reason it did not work. One slot, three jobs,
   so the head block never changes height between states. */
.queue-screen .q-sub {
  margin-top: calc(15 * var(--u));
  max-width: calc(760 * var(--u));
  font-size: var(--t3);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  text-align: center;
  color: rgba(242, 245, 247, 0.6);
  text-shadow: 0 calc(2 * var(--u)) calc(6 * var(--u)) rgba(0, 0, 0, 0.8);
  transform: skewX(-6deg);
}
/* the one moment this screen raises its voice: the opponent's name */
.queue-screen.sub-accent .q-sub {
  font-weight: 800;
  color: var(--hud-orange);
  text-shadow:
    0 0 calc(16 * var(--u)) rgba(249, 158, 26, 0.35),
    0 calc(2 * var(--u)) calc(6 * var(--u)) rgba(0, 0, 0, 0.85);
}

/* ---- the dial: an instrument, not a spinner ---- */
.queue-screen .q-dial {
  position: relative;
  box-sizing: border-box;
  width: calc(212 * var(--u));
  height: calc(212 * var(--u));
  display: flex;
  align-items: center;
  justify-content: center;
}
/* the ring is crisp; the FILL is not. A flat dark disc here read as a sticker
   pasted over the plaza and undid the one thing this screen is supposed to be —
   a screen with the game alive behind it, not a modal. The fill is a vignette
   that reaches zero at the rim, so the clock gets its dark ground and the ring
   still has the plaza showing through underneath it. */
.queue-screen .q-track {
  position: absolute;
  inset: 0;
  box-sizing: border-box;
  border: calc(2 * var(--u)) solid rgba(242, 245, 247, 0.2);
  border-radius: 50%;
  background:
    radial-gradient(closest-side at 50% 44%, rgba(249, 158, 26, 0.09) 0%, rgba(249, 158, 26, 0) 62%),
    radial-gradient(closest-side, rgba(8, 11, 16, 0.72) 0%, rgba(8, 11, 16, 0.52) 58%, rgba(8, 11, 16, 0) 100%);
}
/* 24 minute-ticks outside the ring. This is the detail that makes the dial read
   as a piece of the HUD's instrument family (the ult ring, the reload arc, the
   ammo gauge) rather than as a loading graphic borrowed from a web page. */
.queue-screen .q-track::before {
  content: '';
  position: absolute;
  inset: calc(-15 * var(--u));
  border-radius: 50%;
  background: repeating-conic-gradient(from 0deg,
    rgba(242, 245, 247, 0.44) 0deg 0.6deg, rgba(0, 0, 0, 0) 0.6deg 15deg);
  -webkit-mask: radial-gradient(closest-side, rgba(0, 0, 0, 0) 0 89%, #000 89.5%);
  mask: radial-gradient(closest-side, rgba(0, 0, 0, 0) 0 89%, #000 89.5%);
}
/* THE SWEEP. A 120deg arc of accent travelling the tick ring once every 9s, on
   a compositor-only rotate — no layout, no paint outside its own layer, and it
   is the only animated thing on the screen. It is deliberately slower than any
   spinner: a fast spin reads as "working hard", a slow orbit reads as "still
   listening", and the second one is the truth about a matchmaking queue.
   The keyframes start at -118deg so that the capture freeze (-0.12s, paused)
   parks the bright head just past 12 o'clock instead of at an arbitrary angle. */
.queue-screen .q-sweep {
  position: absolute;
  inset: calc(-15 * var(--u));
  border-radius: 50%;
  background: conic-gradient(from 0deg,
    rgba(249, 158, 26, 0) 0deg,
    rgba(249, 158, 26, 0.1) 44deg,
    rgba(249, 158, 26, 0.55) 96deg,
    rgba(255, 200, 115, 0.95) 118deg,
    rgba(249, 158, 26, 0) 121deg,
    rgba(249, 158, 26, 0) 360deg);
  -webkit-mask: radial-gradient(closest-side, rgba(0, 0, 0, 0) 0 88%, #000 88.5%);
  mask: radial-gradient(closest-side, rgba(0, 0, 0, 0) 0 88%, #000 88.5%);
  animation: qsweep 9s linear infinite;
}
@keyframes qsweep {
  from { transform: rotate(-118deg); }
  to { transform: rotate(242deg); }
}
.queue-screen .q-face {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  transform: var(--hud-skew);
}
.queue-screen .q-clock {
  font-size: var(--t5);
  font-weight: 800;
  line-height: 1;
  letter-spacing: 0.02em;
  color: var(--hud-white);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 calc(2 * var(--u)) calc(10 * var(--u)) rgba(0, 0, 0, 0.85);
}
.queue-screen .q-cap {
  margin-top: calc(7 * var(--u));
  font-size: var(--t1);
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: rgba(242, 245, 247, 0.48);
}
/* the stopped states put the MODE'S OWN poster mark in the dial. A screen that
   could not reach a server still shows you the thing you asked for, which is
   what separates "unavailable" from "broken". */
.queue-screen .q-mark {
  position: absolute;
  z-index: 2;
  display: none;
  width: calc(104 * var(--u));
  height: calc(104 * var(--u));
  color: rgba(255, 200, 115, 0.44);
  transform: skewX(0deg);
  filter: drop-shadow(0 calc(3 * var(--u)) calc(8 * var(--u)) rgba(0, 0, 0, 0.7));
}
.queue-screen .q-mark .ico { width: 100%; height: 100%; display: block; }
.queue-screen .q-mark .ico-solid { stroke-width: 0.5; }
.queue-screen.is-idle .q-sweep,
.queue-screen.is-idle .q-face { display: none; }
.queue-screen.is-idle .q-mark { display: block; }
/* stopped, not erased: the ring and its ticks stay drawn at half strength so the
   dial reads as the same instrument with nothing running through it. Dropping
   them left a grey smudge on the screen a player sees when the game could not
   reach a server, which is the one screen that must not look broken. */
.queue-screen.is-idle .q-track {
  border-color: rgba(242, 245, 247, 0.16);
  background: radial-gradient(closest-side,
    rgba(8, 11, 16, 0.66) 0%, rgba(8, 11, 16, 0.46) 58%, rgba(8, 11, 16, 0) 100%);
}
.queue-screen.is-idle .q-track::before { opacity: 0.5; }
/* OPPONENT FOUND: the ring completes in the accent. The screen's one punctuation
   mark — the thing you waited for, arriving. */
.queue-screen[data-state='found'] .q-track,
.queue-screen[data-state='heroselect'] .q-track {
  border-color: var(--hud-orange);
  box-shadow: 0 0 calc(26 * var(--u)) -4px rgba(249, 158, 26, 0.55);
}
.queue-screen[data-state='found'] .q-sweep,
.queue-screen[data-state='heroselect'] .q-sweep { display: none; }

/* ---- the step rail: where you are in a flow you did not choose to be in ---- */
.queue-screen .q-steps {
  display: flex;
  align-items: center;
  gap: calc(9 * var(--u));
}
.queue-screen.is-idle .q-steps { display: none; }
/* t2, not the t1 label step: this rail is the answer to "is anything actually
   happening", which is the question the screen exists to answer, so it outranks
   the note underneath it. */
.queue-screen .q-step {
  display: flex;
  align-items: center;
  gap: calc(8 * var(--u));
  font-size: var(--t2);
  font-weight: 800;
  letter-spacing: var(--tr-label);
  color: rgba(242, 245, 247, 0.36);
  transform: var(--hud-skew);
  transition: color 0.2s ease-out;
}
.queue-screen .q-step b { font-style: italic; text-indent: var(--tr-label); }
.queue-screen .q-pip {
  width: calc(9 * var(--u));
  height: calc(9 * var(--u));
  flex: none;
  background: rgba(242, 245, 247, 0.24);
  transform: rotate(45deg);
}
.queue-screen .q-link {
  width: calc(26 * var(--u));
  height: calc(2 * var(--u));
  background: rgba(242, 245, 247, 0.16);
  transform: var(--hud-skew);
}
.queue-screen .q-step.done { color: rgba(242, 245, 247, 0.66); }
.queue-screen .q-step.done .q-pip { background: rgba(249, 158, 26, 0.75); }
.queue-screen .q-step.now { color: var(--hud-white); }
.queue-screen .q-step.now .q-pip {
  background: var(--hud-orange);
  box-shadow: 0 0 calc(11 * var(--u)) rgba(249, 158, 26, 0.85);
  animation: qpip 1.1s ease-in-out infinite alternate;
}
/* the FROM state is the lit one, so the capture freeze at -0.12s catches the
   active step at full strength rather than mid-fade */
@keyframes qpip {
  from { opacity: 1; }
  to { opacity: 0.45; }
}

/* ---- the numbers. Never invented: every one is a field the server sent. ---- */
.queue-screen .q-stats {
  display: flex;
  align-items: flex-start;
  gap: calc(36 * var(--u));
}
/* reserved, not removed, before the server has told us anything — the head, the
   dial and the note must not shift under the player when the first
   S_QUEUE_STATE lands */
.queue-screen:not(.has-stats) .q-stats { visibility: hidden; }
.queue-screen.is-idle .q-stats { display: none; }
.queue-screen .q-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: calc(128 * var(--u));
  transform: var(--hud-skew);
}
.queue-screen .q-stat b {
  font-style: italic;
  font-size: var(--t4);
  font-weight: 800;
  line-height: 1;
  color: var(--hud-white);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 calc(2 * var(--u)) calc(6 * var(--u)) rgba(0, 0, 0, 0.8);
}
.queue-screen .q-stat i {
  margin-top: calc(6 * var(--u));
  font-style: italic;
  font-size: var(--t1);
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: rgba(242, 245, 247, 0.42);
}

/* ---- the note: what is being done, or what still works ---- */
.queue-screen .q-note {
  max-width: calc(720 * var(--u));
  font-size: var(--t2);
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  text-align: center;
  line-height: 1.25;
  color: rgba(242, 245, 247, 0.52);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85);
  transform: var(--hud-skew);
}
.queue-screen.is-idle .q-note { color: rgba(255, 200, 115, 0.78); }

/* ---- actions ----
   TRY AGAIN only exists where trying again is a real option. CANCEL never
   disappears — it goes inert for the ~1.2s the match is being built, because a
   button that vanishes takes the foot's height with it and the screen jumps at
   the exact moment the player is reading the opponent's name. */
.queue-screen .q-again { display: none; }
/* .hs-lock is 240u, sized for CONFIRM / LOCK IN. At t4 with 0.28em tracking
   TRY AGAIN is 9 glyphs and wrapped to two lines inside it, which broke the
   shared 56u button baseline the whole screen family stands on. */
.queue-screen.has-retry .q-again {
  display: inline-flex;
  width: calc(300 * var(--u));
}
.queue-screen .q-hint-b { display: none; }
.queue-screen.has-retry .q-hint-b {
  display: inline-flex;
  align-items: center;
  gap: calc(10 * var(--u));
}
.queue-screen.is-locked .q-cancel {
  opacity: 0.34;
  pointer-events: none;
}
.queue-screen.is-locked .q-hint { opacity: 0.5; }

/* ================= duel scoreline (docs/modes.md §8.4) =================
   Top centre, 16u down, hard-capped at 420u so it can never reach the killfeed
   (which starts at the right inset, 56u down) and never touches the elimination
   banner at 25% height. The objective strip shares this axis, so exactly one of
   the two is ever mounted — see `.scored` below. */
#hud-root .hud-score { display: none; }
#hud-root.scored .hud-top { display: none; }
/* HUGS its content up to the 420u cap. The first pass pinned the width at 420u
   and let two `flex:1` sides centre themselves inside it, which parked ~60u of
   empty panel outside each label — a readout that is visibly wider than the
   thing it reads is the classic "sized by guess" tell (art bible §12.10). */
#hud-root .hud-score.show {
  display: flex;
  position: absolute;
  left: 50%;
  top: calc(16 * var(--u));
  transform: translateX(-50%) var(--hud-skew);
  box-sizing: border-box;
  width: max-content;
  min-width: calc(292 * var(--u));
  max-width: calc(420 * var(--u));
  align-items: center;
  gap: calc(4 * var(--u));
  padding: calc(7 * var(--u)) calc(18 * var(--u)) calc(9 * var(--u));
  border-bottom: var(--hud-rule) solid rgba(242, 245, 247, 0.16);
}
/* `0 1 auto`, not `1 1 0`: the panel is max-content sized now, and a growing
   item under intrinsic sizing scales the whole container by its flex ratio. The
   sides size to their own text and shrink only when the 420u cap bites. */
.hud-score .sc-side {
  display: flex;
  flex-direction: column;
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
}
.hud-score .sc-side.l { align-items: flex-end; text-align: right; }
.hud-score .sc-side.r { align-items: flex-start; text-align: left; }
.hud-score .sc-lbl {
  font-size: calc(17 * var(--u));
  font-weight: 800;
  line-height: 1;
  letter-spacing: 0.14em;
  color: rgba(242, 245, 247, 0.92);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7);
  white-space: nowrap;
}
.hud-score .sc-sub {
  margin-top: calc(3 * var(--u));
  font-size: var(--t1);
  line-height: 1;
  letter-spacing: var(--tr-label);
  color: rgba(242, 245, 247, 0.5);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: clip;
}
.hud-score.no-sub .sc-sub { display: none; }
.hud-score .sc-col {
  flex: 0 0 calc(60 * var(--u));
  display: flex;
  flex-direction: column;
  align-items: center;
}
.hud-score .sc-num {
  font-size: calc(44 * var(--u));
  font-weight: 800;
  line-height: 0.92;
  color: var(--hud-white);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 calc(2 * var(--u)) calc(6 * var(--u)) rgba(0, 0, 0, 0.7);
}
/* PROGRESS, not decoration — the same 2u track the objective strip uses, so a
   score of 7 also reads as "7 of 11" without a denominator anywhere. */
.hud-score .sc-bar {
  width: calc(52 * var(--u));
  height: calc(2 * var(--u));
  margin-top: calc(5 * var(--u));
  background: rgba(255, 255, 255, 0.15);
  border-radius: 1px;
  overflow: hidden;
}
.hud-score .sc-bar i {
  display: block;
  width: 0%;
  height: 100%;
  background: rgba(242, 245, 247, 0.55);
}
.hud-score .sc-div {
  flex: none;
  width: 1px;
  height: calc(30 * var(--u));
  background: rgba(255, 255, 255, 0.22);
  margin: 0 calc(3 * var(--u));
}
/* THE WIN CONDITION. The two progress rules answer "who is ahead"; a match has a
   second question — "how long is this" — and eleven is the answer to it. It hangs
   under the panel's own bottom rule at label scale so it never competes with the
   numbers, and it is a text node fed from mode.win.target. */
.hud-score .sc-goal {
  position: absolute;
  left: 50%;
  bottom: calc(-15 * var(--u));
  /* +3u compensates the panel's own -6deg shear: the tab hangs ~26u below the
     shear origin, so a plain -50% lands it 3u left of the numbers it belongs to */
  transform: translateX(calc(-50% + 3 * var(--u)));
  padding: calc(2 * var(--u)) calc(9 * var(--u)) calc(3 * var(--u));
  border-radius: 0 0 2px 2px;
  background: rgba(14, 19, 26, 0.82);
  font-size: calc(10 * var(--u));
  font-weight: 700;
  line-height: 1;
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  white-space: nowrap;
  color: rgba(242, 245, 247, 0.42);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
}
.hud-score .sc-goal b {
  font-style: inherit;
  font-weight: 800;
  color: rgba(255, 200, 115, 0.9);
}
.hud-score.no-goal .sc-goal { display: none; }
/* the LEADING side carries the accent. A tie leaves both white — the readout's
   one job is "who is ahead", and a tie is an honest answer to it. */
.hud-score.lead-l .sc-col.l .sc-num,
.hud-score.lead-r .sc-col.r .sc-num { color: var(--hud-orange); }
.hud-score.lead-l .sc-col.l .sc-bar i,
.hud-score.lead-r .sc-col.r .sc-bar i {
  background: var(--hud-orange);
  box-shadow: 0 0 calc(6 * var(--u)) rgba(249, 158, 26, 0.8);
}
.hud-score.lead-l .sc-side.l .sc-lbl,
.hud-score.lead-r .sc-side.r .sc-lbl { color: #ffffff; }
/* MATCH POINT: 1.2 Hz on the side that is one elimination away — including when
   that side is the CPU, which is exactly when the player needs to know. */
.hud-score.mp-l .sc-col.l .sc-num,
.hud-score.mp-r .sc-col.r .sc-num { animation: scmp 0.417s ease-in-out infinite alternate; }
@keyframes scmp {
  from { opacity: 1; text-shadow: 0 calc(2 * var(--u)) calc(6 * var(--u)) rgba(0, 0, 0, 0.7); }
  to { opacity: 0.62; text-shadow: 0 0 calc(14 * var(--u)) rgba(249, 158, 26, 0.9); }
}

/* ================= death card (docs/modes.md §8.5) ================= */
.hud-death {
  position: absolute;
  left: 50%;
  top: 58%;
  transform: translateX(-50%);
  display: flex;
  opacity: 0;
}
.hud-death .dc-plate {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: calc(9 * var(--u)) calc(30 * var(--u)) calc(12 * var(--u));
  transform: var(--hud-skew);
}
.hud-death .dc-scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: 2px;
  background-color: rgba(14, 19, 25, 0.94);
  background-image: var(--hud-dots);
  background-size: var(--hud-dot-pitch) var(--hud-dot-pitch);
  backdrop-filter: blur(12px) saturate(0.5) brightness(0.7);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 calc(4 * var(--u)) calc(14 * var(--u)) rgba(0, 0, 0, 0.5);
}
.hud-death .dc-bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: var(--hud-rule);
  background: var(--hud-red);
  border-radius: 0 0 2px 2px;
  box-shadow: 0 0 calc(6 * var(--u)) rgba(232, 64, 42, 0.55);
}
.hud-death .dc-lines { display: flex; flex-direction: column; align-items: center; }
.hud-death .dc-kill {
  display: flex;
  align-items: baseline;
  gap: calc(8 * var(--u));
  font-size: var(--t2);
  font-weight: 600;
  line-height: 1;
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: rgba(242, 245, 247, 0.62);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85);
}
.hud-death .dc-who {
  font-style: inherit;
  font-size: var(--t4);
  font-weight: 800;
  line-height: 1;
  letter-spacing: 0.1em;
  color: var(--hud-white);
}
.hud-death .dc-resp {
  margin-top: calc(6 * var(--u));
  font-size: var(--t1);
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: rgba(242, 245, 247, 0.55);
}
.hud-death .dc-sec {
  font-style: inherit;
  font-size: var(--t4);
  font-weight: 800;
  line-height: 0.9;
  letter-spacing: 0;
  color: var(--hud-orange);
  font-variant-numeric: tabular-nums;
  margin-left: calc(4 * var(--u));
  vertical-align: calc(-4 * var(--u));
}
/* settled by 0.12s — the offset main.js freezes ?shot captures at */
.hud-death.show { animation: dcin 0.3s cubic-bezier(0.22, 1, 0.36, 1) forwards; }
@keyframes dcin {
  0% { opacity: 0; transform: translateX(-50%) translateY(calc(8 * var(--u))); }
  40%, 100% { opacity: 1; transform: translateX(-50%) translateY(0); }
}

/* ================= match over (docs/modes.md §8.5) =================
   A BAND at 42% height, never a full-screen wash: the plaza keeps rendering
   behind it because it is the best-looking thing in the frame. VICTORY and
   DEFEAT differ in VALUE (gold vs white at 70%), never in hue. */
.hud-matchover {
  position: absolute;
  left: 0;
  right: 0;
  top: 42%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
}
.hud-matchover .mo-band {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  background:
    linear-gradient(90deg, rgba(9, 12, 17, 0) 0%, rgba(9, 12, 17, 0.86) 18%,
      rgba(9, 12, 17, 0.9) 82%, rgba(9, 12, 17, 0) 100%),
    var(--hud-dots);
  background-size: auto, var(--hud-dot-pitch) var(--hud-dot-pitch);
  backdrop-filter: blur(10px) saturate(0.5) brightness(0.7);
}
/* the band's edges fade out with the band itself — a 1px inset rule would run
   the full box width and print two hairlines across frame where the fill has
   already gone to zero */
.hud-matchover .mo-band::before,
.hud-matchover .mo-band::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.16) 20%,
    rgba(255, 255, 255, 0.16) 80%, rgba(255, 255, 255, 0) 100%);
}
.hud-matchover .mo-band::before { top: 0; }
.hud-matchover .mo-band::after {
  bottom: 0;
  background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.5) 20%,
    rgba(0, 0, 0, 0.5) 80%, rgba(0, 0, 0, 0) 100%);
}
.hud-matchover .mo-inner {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: calc(26 * var(--u)) calc(40 * var(--u)) calc(28 * var(--u));
  transform: var(--hud-skew);
}
.hud-matchover .mo-title {
  font-size: var(--t6);
  font-weight: 800;
  line-height: 1;
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  color: var(--hud-gold);
  text-shadow:
    0 0 calc(20 * var(--u)) rgba(255, 200, 115, 0.35),
    0 calc(3 * var(--u)) calc(10 * var(--u)) rgba(0, 0, 0, 0.85);
}
.hud-matchover.lost .mo-title {
  color: rgba(242, 245, 247, 0.7);
  text-shadow: 0 calc(3 * var(--u)) calc(10 * var(--u)) rgba(0, 0, 0, 0.85);
}
/* the final scoreline at 2x the live one, so the number you finished on is the
   second thing you read after the word */
.hud-matchover .mo-score {
  display: flex;
  align-items: center;
  gap: calc(20 * var(--u));
  margin-top: calc(10 * var(--u));
}
.hud-matchover .mo-score b {
  font-style: italic;
  font-size: calc(88 * var(--u));
  font-weight: 800;
  line-height: 0.9;
  font-variant-numeric: tabular-nums;
  color: rgba(242, 245, 247, 0.55);
  text-shadow: 0 calc(3 * var(--u)) calc(10 * var(--u)) rgba(0, 0, 0, 0.8);
}
.hud-matchover.won .mo-l,
.hud-matchover.lost .mo-r { color: var(--hud-orange); }
.hud-matchover .mo-dash {
  width: calc(26 * var(--u));
  height: calc(3 * var(--u));
  background: rgba(242, 245, 247, 0.3);
}
.hud-matchover .mo-sub {
  margin-top: calc(8 * var(--u));
  font-size: var(--t2);
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: rgba(242, 245, 247, 0.6);
}
.hud-matchover.no-sub .mo-sub { display: none; }
.hud-matchover .mo-hint {
  margin-top: calc(18 * var(--u));
  min-width: calc(280 * var(--u));
  transform: none;   /* .mo-inner already carries the shear */
}
.hud-matchover.show { animation: moin 0.4s cubic-bezier(0.22, 1, 0.36, 1) forwards; }
@keyframes moin {
  0% { opacity: 0; transform: translateY(-50%) scale(1.04); }
  30%, 100% { opacity: 1; transform: translateY(-50%) scale(1); }
}

/* ================= pickup toast ================= */
.hud-toast {
  position: absolute;
  left: 50%;
  bottom: calc(214 * var(--u));   /* clear of the ultimate ring at bottom centre */
  transform: translateX(-50%);
  display: flex;
  opacity: 0;
}
.hud-toast .tst-plate {
  position: relative;
  display: flex;
  align-items: center;
  padding: calc(6 * var(--u)) calc(20 * var(--u)) calc(7 * var(--u));
  transform: var(--hud-skew);
  border-bottom: calc(2 * var(--u)) solid var(--hud-gold);
  border-radius: 2px;
}
.hud-toast .tst-scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: 2px;
  background-color: rgba(14, 19, 25, 0.92);
  background-image: var(--hud-dots);
  background-size: var(--hud-dot-pitch) var(--hud-dot-pitch);
  backdrop-filter: blur(10px) saturate(0.5) brightness(0.72);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12), 0 calc(3 * var(--u)) calc(12 * var(--u)) rgba(0, 0, 0, 0.5);
}
.hud-toast .tst-text {
  font-size: var(--t3);
  font-weight: 800;
  line-height: 1;
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: var(--hud-gold);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85);
}
.hud-toast.show { animation: tstpop 1.4s linear forwards; }
@keyframes tstpop {
  0% { opacity: 0; transform: translateX(-50%) translateY(calc(8 * var(--u))); }
  8.6% { opacity: 1; transform: translateX(-50%) translateY(0); }
  82% { opacity: 1; transform: translateX(-50%) translateY(0); }
  100% { opacity: 0; transform: translateX(-50%) translateY(calc(-4 * var(--u))); }
}

/* ================= low-health edge pulse =================
   The same #FF4B4B the wedge and the health bar use for "damage you take", at
   low alpha on the frame edge. It breathes rather than flashes: a flash is an
   EVENT, and this is a STATE. */
.hud-low {
  position: absolute;
  inset: 0;
  --low: 0;
  opacity: 0;
  background: radial-gradient(ellipse 74% 66% at 50% 50%, rgba(255, 75, 75, 0) 52%, rgba(255, 75, 75, 0.75) 100%);
}
.hud-low.on { animation: lowbreath 0.85s ease-in-out infinite alternate; }
@keyframes lowbreath {
  from { opacity: calc(var(--low) * 0.34); }
  to { opacity: calc(var(--low) * 0.8); }
}

/* ================= ONLINE: connection quality chip =======================
   docs/netcode.md §6.10. Mounted only when `mode.hud.netPanel` is true, so it
   does not exist in either offline mode and cannot appear in any existing
   ?shot pose.

   PLACEMENT. The contract says "top-right under the killfeed". Measured, the
   killfeed anchors at 56u and grows DOWNWARD to 270u at five rows, so "under"
   it is either an overlap or a chip stranded halfway down the right edge. It
   therefore shares the SCORELINE's 16u top baseline instead: score in the
   centre, connection on the right, one rule across the top of the frame — which
   is the composition the rest of the HUD already uses, and it leaves 14u of air
   above the killfeed's first row in every state.

   COST. No backdrop-filter. This element is on screen for an entire match, and
   a persistent backdrop blur is a full-screen sample every frame — a readout of
   how fast the game is must not itself cost frame time. The numerals are
   tabular and the box has a min-width, so 38 -> 137 ms rewrites one text node
   and triggers no reflow. */
.hud-net {
  position: absolute;
  right: var(--hud-inset);
  top: calc(16 * var(--u));
  display: none;
  align-items: center;
  gap: calc(7 * var(--u));
  box-sizing: border-box;
  min-width: calc(112 * var(--u));
  height: calc(30 * var(--u));
  padding: 0 calc(11 * var(--u)) 0 calc(9 * var(--u));
  border-radius: 3px;
  border-bottom: calc(2 * var(--u)) solid rgba(242, 245, 247, 0.22);
  background-color: rgba(10, 12, 16, 0.72);
  background-image: var(--hud-dots);
  background-size: var(--hud-dot-pitch) var(--hud-dot-pitch);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 2px 8px rgba(0, 0, 0, 0.35);
  transform: var(--hud-skew);
  font-size: var(--t1);
  color: rgba(242, 245, 247, 0.9);
}
.hud-net.show { display: flex; }
/* three rising bars, the universal signal read. Lit count comes from the
   quality bucket (hud.js NET_QUALITY), never from the raw ping — the
   thresholds live in one place and it is not this file. */
.hud-net .net-bars {
  display: flex;
  align-items: flex-end;
  gap: calc(2 * var(--u));
  height: calc(13 * var(--u));
}
.hud-net .net-bars i {
  width: calc(3 * var(--u));
  background: rgba(242, 245, 247, 0.2);
  border-radius: 1px;
}
.hud-net .net-bars i:nth-child(1) { height: 46%; }
.hud-net .net-bars i:nth-child(2) { height: 73%; }
.hud-net .net-bars i:nth-child(3) { height: 100%; }
.hud-net[data-bars='1'] .net-bars i:nth-child(-n + 1),
.hud-net[data-bars='2'] .net-bars i:nth-child(-n + 2),
.hud-net[data-bars='3'] .net-bars i:nth-child(-n + 3) { background: currentColor; }
.hud-net .net-num {
  font-size: var(--t3);
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  min-width: calc(30 * var(--u));
  text-align: right;
  color: currentColor;
}
.hud-net .net-unit {
  font-size: var(--t1);
  letter-spacing: var(--tr-label);
  color: rgba(242, 245, 247, 0.45);
}
/* loss is named ONLY when there is loss. A permanent "0% LOSS" trains the eye
   to skip the one line that would have explained the stutter. */
.hud-net .net-loss { display: none; }
.hud-net.lossy .net-loss {
  display: inline;
  margin-left: calc(3 * var(--u));
  padding-left: calc(8 * var(--u));
  border-left: 1px solid rgba(255, 255, 255, 0.14);
  font-size: var(--t1);
  letter-spacing: var(--tr-label);
  color: var(--hud-gold);
}
.hud-net.lossy .net-loss b { font-style: italic; font-weight: 800; }
/* Colour is a WARNING, not a label: two of the four buckets are the HUD's plain
   white, because a connection that is fine should not be wearing a colour. */
.hud-net.q-good,
.hud-net.q-fair { color: var(--hud-white); }
.hud-net.q-poor { color: var(--hud-gold); border-bottom-color: rgba(255, 200, 115, 0.55); }
.hud-net.q-bad {
  color: var(--hud-red);
  border-bottom-color: var(--hud-red);
  animation: netbad 1.1s ease-in-out infinite alternate;
}
@keyframes netbad {
  from { box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 2px 8px rgba(0, 0, 0, 0.35); }
  to { box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 0 calc(14 * var(--u)) rgba(232, 64, 42, 0.55); }
}

/* ================= ONLINE: connection banner =============================
   docs/netcode.md §6.10 / §7. Centre, at 12% height — clear of the scoreline
   above it and nowhere near the crosshair at 50%, because this element appears
   during live play and must never cover the thing you are aiming with.

   It carries the match-over card's plate treatment (scrim, dots, shear, an
   accent rule at the foot) one type step down, so it reads as the same family
   of announcement rather than as an error dialog.

   Every kind carries a SECOND line. "OPPONENT DISCONNECTED" alone is the frozen
   screen with a caption on it; "OPPONENT DISCONNECTED / HOLDING THE MATCH OPEN
   FOR THEM" is an explanation. The countdown is mirrored by a rule that empties,
   so the player can see how much of the grace is left without having to
   remember what it started at. */
.hud-conn {
  position: absolute;
  left: 50%;
  top: 12%;
  transform: translateX(-50%);
  display: flex;
  opacity: 0;
}
.hud-conn .cn-plate {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: calc(360 * var(--u));
  padding: calc(9 * var(--u)) calc(28 * var(--u)) calc(12 * var(--u));
  transform: var(--hud-skew);
}
.hud-conn .cn-scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: 2px;
  background-color: rgba(14, 19, 25, 0.94);
  background-image: var(--hud-dots);
  background-size: var(--hud-dot-pitch) var(--hud-dot-pitch);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 calc(4 * var(--u)) calc(14 * var(--u)) rgba(0, 0, 0, 0.5);
}
.hud-conn .cn-lines { display: flex; flex-direction: column; align-items: center; }
.hud-conn .cn-head {
  display: flex;
  align-items: baseline;
  gap: calc(10 * var(--u));
}
.hud-conn .cn-word {
  font-style: italic;
  font-size: var(--t3);
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: 0.1em;
  text-indent: 0.1em;
  color: var(--hud-white);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
}
.hud-conn .cn-dash {
  width: calc(14 * var(--u));
  height: calc(2 * var(--u));
  background: rgba(242, 245, 247, 0.34);
}
.hud-conn .cn-count {
  font-style: italic;
  font-size: var(--t4);
  font-weight: 800;
  line-height: 0.9;
  color: var(--hud-orange);
  font-variant-numeric: tabular-nums;
}
.hud-conn.no-count .cn-dash,
.hud-conn.no-count .cn-count { display: none; }
.hud-conn .cn-sub {
  margin-top: calc(5 * var(--u));
  font-size: var(--t1);
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: rgba(242, 245, 247, 0.58);
}
/* the grace, drawn. Width is written by connectionBanner() and nothing else. */
.hud-conn .cn-bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: var(--hud-rule);
  border-radius: 0 0 2px 2px;
  background: rgba(242, 245, 247, 0.12);
  overflow: hidden;
}
.hud-conn .cn-bar b {
  display: block;
  height: 100%;
  width: 0%;
  background: var(--hud-orange);
  transition: width 0.2s linear;
}
/* with no countdown there is nothing to drain, so the foot rule goes solid in
   the tone colour rather than sitting as an empty track — every plate in this
   product carries a coloured rule, and a banner missing one reads as a box */
.hud-conn.no-count .cn-bar b { display: none; }
.hud-conn.warn.no-count .cn-bar { background: var(--hud-gold); }
.hud-conn.alert.no-count .cn-bar { background: var(--hud-red); }
/* two tones only, both already in the palette: gold for "degraded, still
   playing", the HUD's existing elimination red for "the match itself is at
   risk". No new hue enters the in-match frame. */
.hud-conn.warn .cn-bar b { background: var(--hud-gold); }
.hud-conn.warn .cn-count { color: var(--hud-gold); }
.hud-conn.alert .cn-bar b,
.hud-conn.alert .cn-count { color: var(--hud-red); }
.hud-conn.alert .cn-bar b { background: var(--hud-red); }
.hud-conn.alert .cn-scrim { box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), inset 0 0 0 1px rgba(232, 64, 42, 0.34), 0 calc(4 * var(--u)) calc(14 * var(--u)) rgba(0, 0, 0, 0.5); }
/* settled by 0.12s, the offset ?shot captures freeze at */
.hud-conn.show { animation: cnin 0.3s cubic-bezier(0.22, 1, 0.36, 1) forwards; }
@keyframes cnin {
  0% { opacity: 0; transform: translateX(-50%) translateY(calc(-8 * var(--u))); }
  40%, 100% { opacity: 1; transform: translateX(-50%) translateY(0); }
}

/* ================= SETTINGS (docs/settings.md §6.4) =======================
   The FOURTH `.hs-screen`. Every structural rule it stands on — the veil, the
   head, the title, the kick rule, the foot, the actions, the hint strip, the
   entrance transform, the keycaps — is shared with the three screens beside it,
   so the type scale and the -6deg shear match by construction and not by eye.
   That sharing is the whole defence against the one failure mode this screen
   has: a settings panel that reads like a browser form.

   The three things that would have made it read like one, and what is here
   instead:
     · a stock <input type=range>  -> the track, the fill, the DEFAULT DATUM and
       the thumb are drawn from divs on the 8px grid, in the HUD's own bevel
       language (the ammo gauge and the ult ring are the family).
     · a stock <input type=checkbox> -> a 78u switch built the same way, whose
       ON state is the accent and whose OFF state is the HUD's dead-track grey.
     · a flat list of labels -> a GROUP RAIL of four plates carrying the
       registry's own blurbs, which gives the left third real mass and turns
       "four sections" into a composition.

   PALETTE: #F99E1A accent, #F2F5F7 white, #FFC873 gold. Nothing else. Not one
   hero hue reaches this screen, exactly as on MODE SELECT.

   EVERY STRING AND EVERY ROW IS REGISTRY DATA — labels, blurbs, ranges, steps,
   formats, sections, keycaps and rejection copy. There is no setting id and no
   key-code literal in hud.js and none here either. */

/* Opened FROM the pause dialog the screen sits over the LIVE, FULLY LIT game
   rather than over the menu's own darkened plaza, and the shared veil is tuned
   for the latter. A deeper veil here is not decoration: it is what keeps the
   0.42-alpha help copy readable over sunlit plaster. */
.settings-screen.from-pause .hs-veil {
  background:
    linear-gradient(180deg, rgba(5, 8, 13, 0.4) 0%, rgba(5, 8, 13, 0.3) 30%,
      rgba(5, 8, 13, 0.34) 62%, rgba(4, 7, 11, 0.56) 100%),
    radial-gradient(ellipse 92% 76% at 50% 52%,
      rgba(5, 8, 13, 0.34) 0%, rgba(5, 8, 13, 0.5) 58%, rgba(4, 7, 11, 0.62) 100%);
  backdrop-filter: blur(7px) brightness(0.5) saturate(0.8);
}

.settings-screen .st-sub {
  margin-top: calc(15 * var(--u));
  max-width: calc(760 * var(--u));
  font-size: var(--t2);
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  text-align: center;
  color: rgba(242, 245, 247, 0.5);
  text-shadow: 0 calc(2 * var(--u)) calc(6 * var(--u)) rgba(0, 0, 0, 0.8);
  transform: var(--hud-skew);
}

.settings-screen .st-body {
  display: flex;
  align-items: stretch;
  gap: calc(26 * var(--u));
}

/* ---- the group rail. Four plates, not four tabs: a tab strip is the browser
        idiom and it would have been the loudest tell on the screen. ---- */
.settings-screen .st-rail {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: calc(10 * var(--u));
  width: calc(312 * var(--u));
  flex: none;
}
.settings-screen .st-tab {
  pointer-events: auto;
  cursor: pointer;
  position: relative;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: calc(12 * var(--u));
  width: 100%;
  height: calc(66 * var(--u));
  padding: 0 calc(16 * var(--u)) 0 calc(22 * var(--u));
  border: none;
  border-radius: 3px;
  background-color: var(--hud-panel);
  background-image: var(--hud-dots);
  background-size: var(--hud-dot-pitch) var(--hud-dot-pitch);
  box-shadow: var(--hud-edge);
  backdrop-filter: var(--hud-blur);
  transform: skewX(-6deg);
  text-align: left;
  font-family: inherit;
  outline: none;
  transition: filter 0.15s;
}
/* the accent tab, drawn as an overlay bar on the plate's own box — a
   border-left bows around the 3px radius, which is why `.menu-mode` does it
   this way too */
.settings-screen .st-tab-rule {
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--hud-rule);
  border-radius: 3px 0 0 3px;
  background: rgba(242, 245, 247, 0.14);
}
.settings-screen .st-tab.on .st-tab-rule {
  background: var(--hud-orange);
  box-shadow: 0 0 calc(11 * var(--u)) rgba(249, 158, 26, 0.6);
}
.settings-screen .st-tab-txt {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
}
.settings-screen .st-tab-name {
  font-style: italic;
  font-weight: 800;
  font-size: var(--t3);
  line-height: 1;
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: rgba(242, 245, 247, 0.7);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
  transition: color 0.15s;
}
.settings-screen .st-tab-blurb {
  margin-top: calc(6 * var(--u));
  font-style: italic;
  font-size: var(--t1);
  line-height: 1;
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: rgba(242, 245, 247, 0.36);
}
.settings-screen .st-tab .mp-chev {
  width: calc(10 * var(--u));
  height: calc(14 * var(--u));
  flex: none;
  color: var(--hud-orange);
  opacity: 0;
  transition: opacity 0.15s;
}
.settings-screen .st-tab.on { filter: brightness(1.24); }
.settings-screen .st-tab.on .mp-chev { opacity: 1; }
.settings-screen .st-tab.on .st-tab-name { color: var(--hud-white); }
.settings-screen .st-tab:hover { filter: brightness(1.4); }
.settings-screen .st-tab:focus-visible { box-shadow: var(--hud-edge), inset 0 0 0 1px rgba(249, 158, 26, 0.7); }

/* ---- the panel. Height is RESERVED, not fitted: MOUSE has two rows and
        CONTROLS has twelve, and the head, the foot and the rail must not walk
        up and down the screen as the player moves between them. Same rule the
        queue screen's stat block follows. ---- */
.settings-screen .st-panel {
  position: relative;
  box-sizing: border-box;
  width: calc(902 * var(--u));
  min-height: calc(432 * var(--u));
  max-height: calc(560 * var(--u));
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.settings-screen .st-group { display: none; }
.settings-screen .st-group.on {
  display: flex;
  flex-direction: column;
  max-height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: rgba(249, 158, 26, 0.5) transparent;
}
.settings-screen .st-group.on::-webkit-scrollbar { width: calc(4 * var(--u)); }
.settings-screen .st-group.on::-webkit-scrollbar-track { background: rgba(255, 255, 255, 0.04); }
.settings-screen .st-group.on::-webkit-scrollbar-thumb { background: rgba(249, 158, 26, 0.5); border-radius: 2px; }
.settings-screen .st-rows {
  display: flex;
  flex-direction: column;
  gap: calc(16 * var(--u));
  margin: auto 0;
}

/* ---- a value row: the SAME plate every other panel in the product wears ---- */
.settings-screen .st-row {
  position: relative;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: calc(20 * var(--u));
  padding: calc(19 * var(--u)) calc(16 * var(--u)) calc(19 * var(--u)) calc(22 * var(--u));
  border-radius: 3px;
  border-bottom: calc(2 * var(--u)) solid rgba(242, 245, 247, 0.16);
  background-color: var(--hud-panel);
  background-image: var(--hud-dots);
  background-size: var(--hud-dot-pitch) var(--hud-dot-pitch);
  box-shadow: var(--hud-edge);
  backdrop-filter: var(--hud-blur);
  transform: skewX(-6deg);
}
/* a row that no longer holds its shipped value says so on its rule AND with the
   dot — one signal for the glance, one for the read */
.settings-screen .st-row.mod { border-bottom-color: var(--hud-orange); }
.settings-screen .st-lbl {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
}
.settings-screen .st-name {
  display: flex;
  align-items: center;
  gap: calc(9 * var(--u));
  font-style: italic;
  font-weight: 800;
  font-size: var(--t3);
  line-height: 1;
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: var(--hud-white);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85);
}
.settings-screen .st-mod {
  width: calc(7 * var(--u));
  height: calc(7 * var(--u));
  flex: none;
  border-radius: 50%;
  background: var(--hud-orange);
  box-shadow: 0 0 calc(9 * var(--u)) rgba(249, 158, 26, 0.85);
  opacity: 0;
  transition: opacity 0.15s;
}
.settings-screen .st-row.mod .st-mod { opacity: 1; }
.settings-screen .st-blurb {
  margin-top: calc(7 * var(--u));
  font-style: italic;
  font-size: var(--t1);
  line-height: 1.15;
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: rgba(242, 245, 247, 0.42);
}
.settings-screen .st-restart {
  align-self: flex-start;
  margin-top: calc(7 * var(--u));
  font-style: italic;
  font-size: var(--t1);
  letter-spacing: var(--tr-label);
  color: var(--hud-gold);
}
.settings-screen .st-ctl { flex: none; display: flex; align-items: center; }
.settings-screen .st-val {
  flex: none;
  min-width: calc(96 * var(--u));
  text-align: right;
  font-style: italic;
  font-weight: 800;
  font-size: var(--t4);
  line-height: 1;
  color: var(--hud-orange);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 calc(2 * var(--u)) calc(6 * var(--u)) rgba(0, 0, 0, 0.8);
}
/* OFF is a state, not a warning: it gets the dead-track grey, so the accent on
   this screen only ever means "on" or "moved" */
.settings-screen .st-row[data-type='toggle']:not(.on) .st-val { color: rgba(242, 245, 247, 0.42); }

/* ---- the slider. Drawn, never a stock range input: a native thumb is the
        single loudest browser-form tell available and no amount of
        -webkit-appearance gets it onto the 8px grid. ---- */
.settings-screen .st-slider {
  position: relative;
  box-sizing: border-box;
  display: block;
  width: calc(304 * var(--u));
  height: calc(30 * var(--u));
  pointer-events: auto;
  cursor: pointer;
  outline: none;
  touch-action: none;
}
.settings-screen .st-track {
  position: absolute;
  left: calc(8 * var(--u));
  right: calc(8 * var(--u));
  top: 50%;
  height: calc(6 * var(--u));
  transform: translateY(-50%);
  border-radius: 1px;
  background: rgba(242, 245, 247, 0.15);
  box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.55);
}
.settings-screen .st-fill {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 0;
  border-radius: 1px;
  background: linear-gradient(90deg, rgba(249, 158, 26, 0.55), var(--hud-orange));
  box-shadow: 0 0 calc(10 * var(--u)) rgba(249, 158, 26, 0.45);
}
/* THE DATUM: where the shipped value sits. Without it a slider can only answer
   "is this default" by being reset, which is the question a settings screen is
   asked most often. */
.settings-screen .st-def {
  position: absolute;
  top: calc(-5 * var(--u));
  bottom: calc(-5 * var(--u));
  left: 0;
  width: calc(2 * var(--u));
  margin-left: calc(-1 * var(--u));
  background: rgba(242, 245, 247, 0.5);
}
.settings-screen .st-thumb {
  position: absolute;
  top: 50%;
  left: 0;
  width: calc(12 * var(--u));
  height: calc(24 * var(--u));
  transform: translate(-50%, -50%);
  border-radius: 2px;
  background-image: linear-gradient(180deg, #ffb13d 0%, #f99e1a 62%, #e08b12 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    inset 0 calc(-2 * var(--u)) 0 rgba(120, 66, 4, 0.6),
    0 calc(2 * var(--u)) calc(7 * var(--u)) rgba(0, 0, 0, 0.55);
}
.settings-screen .st-slider:hover .st-thumb { filter: brightness(1.12); }
.settings-screen .st-slider:focus-visible .st-track {
  box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(249, 158, 26, 0.75);
}

/* ---- the toggle: a BREAKER, not a browser switch.
        The first cut of this control was a soft grey capsule with a pale
        rounded knob that slid inside it — which is, exactly, the platform
        checkbox every OS has shipped since 2013, and it was the single loudest
        browser-form tell left on the screen (§6.1 names it by name). The value
        column already prints ON/OFF, so the control's job is not to say the
        word again: it is to show a THROW, the way the ammo gauge shows a level
        and the ult ring shows a charge.
        So: a hard 3px slot in the panel material with an inset dark rule, a
        square-shouldered steel block that occupies one half of it, three
        engraved grip lines across that block, and an accent rule under the slot
        that lights with the throw. Off is dead steel on a dark track; on is the
        same block thrown right, in the accent, over a warmed track. Nothing
        here is round and nothing here is grey plastic. ---- */
.settings-screen .st-toggle {
  position: relative;
  display: block;
  box-sizing: border-box;
  width: calc(82 * var(--u));
  height: calc(30 * var(--u));
  pointer-events: auto;
  cursor: pointer;
  outline: none;
}
.settings-screen .st-sw {
  position: absolute;
  inset: 0;
  border-radius: 3px;
  background-color: rgba(6, 8, 12, 0.72);
  background-image: var(--hud-dots);
  background-size: var(--hud-dot-pitch) var(--hud-dot-pitch);
  box-shadow:
    inset 0 1px 0 rgba(0, 0, 0, 0.6),
    inset 0 0 0 1px rgba(255, 255, 255, 0.07),
    /* the slot's own accent rule — the same 2u bottom rule the value rows and
       the ping chip wear, so the control is HUD furniture by construction */
    inset 0 calc(-2 * var(--u)) 0 rgba(242, 245, 247, 0.16);
  transition: background-color 0.14s, box-shadow 0.14s;
}
.settings-screen .st-sw b {
  position: absolute;
  top: calc(3 * var(--u));
  bottom: calc(4 * var(--u));
  left: calc(3 * var(--u));
  width: calc(38 * var(--u));
  border-radius: 2px;
  /* NEUTRAL graphite, not the blue-grey steel this was first cut in: the screen
     is held to three warm values and a cool neutral is still a fourth hue when
     it is the only chromatic mass in the row. */
  background-image: linear-gradient(180deg, #7b7f84 0%, #4e5257 58%, #34383d 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.28),
    inset 0 calc(-2 * var(--u)) 0 rgba(0, 0, 0, 0.45),
    0 calc(2 * var(--u)) calc(6 * var(--u)) rgba(0, 0, 0, 0.55);
  transition: left 0.14s cubic-bezier(0.22, 1, 0.36, 1), background-image 0.14s;
}
/* the grip: three 1px flutes, the detail that makes it a machined part rather
   than a filled rectangle. Centred, and the same idea as the ammo gauge tick. */
.settings-screen .st-sw b::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 28%;
  bottom: 28%;
  width: calc(9 * var(--u));
  transform: translateX(-50%);
  background-image: repeating-linear-gradient(90deg,
    rgba(0, 0, 0, 0.42) 0 1px, transparent 1px calc(4 * var(--u)));
}
.settings-screen .st-row.on .st-sw {
  background-color: rgba(249, 158, 26, 0.14);
  box-shadow:
    inset 0 1px 0 rgba(0, 0, 0, 0.5),
    inset 0 0 0 1px rgba(249, 158, 26, 0.28),
    inset 0 calc(-2 * var(--u)) 0 var(--hud-orange);
}
.settings-screen .st-row.on .st-sw b {
  left: calc(41 * var(--u));
  background-image: linear-gradient(180deg, #ffb13d 0%, #f99e1a 62%, #e08b12 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    inset 0 calc(-2 * var(--u)) 0 rgba(120, 66, 4, 0.6),
    0 0 calc(11 * var(--u)) rgba(249, 158, 26, 0.5);
}
.settings-screen .st-row.on .st-sw b::after { background-image: repeating-linear-gradient(90deg,
    rgba(96, 52, 4, 0.5) 0 1px, transparent 1px calc(4 * var(--u))); }
.settings-screen .st-toggle:hover .st-sw b { filter: brightness(1.16); }
.settings-screen .st-toggle:focus-visible .st-sw {
  box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(249, 158, 26, 0.75);
}

/* ---- the enum control. No enum setting ships in this build; the renderer is
        here so the seventh setting is a registry entry and nothing else. ---- */
.settings-screen .st-enum { display: flex; gap: calc(6 * var(--u)); }
.settings-screen .st-opt {
  pointer-events: auto;
  cursor: pointer;
  border: none;
  border-radius: 3px;
  padding: calc(6 * var(--u)) calc(14 * var(--u));
  background: rgba(255, 255, 255, 0.05);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.07);
  font-family: inherit;
  font-style: italic;
  font-size: var(--t2);
  letter-spacing: var(--tr-label);
  color: rgba(242, 245, 247, 0.6);
  outline: none;
}
.settings-screen .st-opt.on { background: rgba(249, 158, 26, 0.2); box-shadow: inset 0 0 0 1px var(--hud-orange); color: var(--hud-white); }

/* ---- the per-row reset. Reserved, never removed: it holds its box at zero
        opacity so a row does not change width when the pointer crosses it. ---- */
.settings-screen .st-rowreset {
  flex: none;
  box-sizing: border-box;
  width: calc(30 * var(--u));
  height: calc(30 * var(--u));
  padding: calc(5 * var(--u));
  border: none;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.05);
  color: rgba(242, 245, 247, 0.42);
  pointer-events: auto;
  cursor: pointer;
  opacity: 0;
  outline: none;
  transition: opacity 0.15s, color 0.15s, background 0.15s;
}
.settings-screen .st-row:hover .st-rowreset,
.settings-screen .st-row.mod .st-rowreset,
.settings-screen .st-rowreset:focus-visible { opacity: 1; }
.settings-screen .st-rowreset:hover { color: var(--hud-orange); background: rgba(249, 158, 26, 0.16); }
.settings-screen .st-rowreset .ico { width: 100%; height: 100%; }

/* ---- keybinds. Two columns off `bindSections()`, so MOVEMENT sits beside
        COMBAT + SYSTEM and twelve actions never need a scrollbar. ---- */
/* TWO COLUMNS, balanced by the browser rather than assigned by hand: a CSS grid
   flows section 3 back under section 1, which parked SYSTEM under MOVEMENT and
   left a hole where COMBAT ended. Multicol balances on HEIGHT, so the tall
   section takes a column and the short ones share the other — and it stays
   right for four sections as well as three, which a hand-placed grid would
   not. `break-inside: avoid` is what keeps a section whole. */
.settings-screen .st-secs {
  columns: 2;
  column-gap: calc(22 * var(--u));
  margin: auto 0;
}
.settings-screen .st-sec {
  break-inside: avoid;
  display: flex;
  flex-direction: column;
  gap: calc(7 * var(--u));
  padding-bottom: calc(18 * var(--u));
}
.settings-screen .st-sec-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 0 calc(12 * var(--u)) calc(3 * var(--u)) calc(4 * var(--u));
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  transform: skewX(-6deg);
}
.settings-screen .st-sec-head b {
  font-style: italic;
  font-size: var(--t1);
  font-weight: 800;
  letter-spacing: var(--tr-display);
  text-indent: var(--tr-display);
  color: rgba(249, 158, 26, 0.8);
}
.settings-screen .st-sec-cols { display: flex; gap: calc(8 * var(--u)); }
.settings-screen .st-sec-cols i {
  display: block;
  width: calc(98 * var(--u));
  text-align: center;
  font-style: italic;
  font-size: calc(9 * var(--u));
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: rgba(242, 245, 247, 0.3);
}
.settings-screen .st-row.bind {
  gap: calc(12 * var(--u));
  padding: calc(6 * var(--u)) calc(12 * var(--u)) calc(6 * var(--u)) calc(16 * var(--u));
  border-bottom-width: 1px;
  border-bottom-color: rgba(242, 245, 247, 0.1);
}
.settings-screen .st-row.bind .st-name {
  font-size: var(--t2);
  font-weight: 600;
  color: rgba(242, 245, 247, 0.88);
}
.settings-screen .st-row.bind .st-blurb { margin-top: calc(3 * var(--u)); }
.settings-screen .st-binds { display: flex; gap: calc(8 * var(--u)); flex: none; }
.settings-screen .st-slot {
  position: relative;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: calc(98 * var(--u));
  height: calc(32 * var(--u));
  padding: 0;
  border: none;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.035);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.07);
  font-family: inherit;
  pointer-events: auto;
  cursor: pointer;
  outline: none;
  transition: background 0.13s, box-shadow 0.13s;
}
.settings-screen .st-slot:hover,
.settings-screen .st-slot:focus-visible {
  background: rgba(249, 158, 26, 0.14);
  box-shadow: inset 0 0 0 1px rgba(249, 158, 26, 0.6);
}
.settings-screen .st-cap { min-width: calc(48 * var(--u)); }
.settings-screen .st-slot:not(.set) .st-cap {
  background: transparent;
  box-shadow: none;
  color: rgba(242, 245, 247, 0.34);
}
/* CAPTURING. The row says what it is waiting for and how to get out of it, in
   the row, because a modal for one keystroke is worse than the keystroke. */
.settings-screen .st-slot.capturing {
  background: rgba(249, 158, 26, 0.22);
  box-shadow: inset 0 0 0 1px var(--hud-orange);
  animation: stcap 0.62s ease-in-out infinite alternate;
}
.settings-screen .st-slot.capturing .st-cap {
  background: transparent;
  box-shadow: none;
  min-width: 0;
  padding: 0;
  font-size: calc(9 * var(--u));
  font-weight: 800;
  letter-spacing: 0.08em;
  color: var(--hud-orange);
}
@keyframes stcap {
  from { background-color: rgba(249, 158, 26, 0.22); }
  to { background-color: rgba(249, 158, 26, 0.06); }
}
.settings-screen .st-x {
  position: absolute;
  right: calc(2 * var(--u));
  top: calc(2 * var(--u));
  width: calc(12 * var(--u));
  height: calc(12 * var(--u));
  color: rgba(242, 245, 247, 0.5);
  opacity: 0;
  transition: opacity 0.13s, color 0.13s;
}
.settings-screen .st-slot.set:hover .st-x,
.settings-screen .st-slot.set:focus-visible .st-x { opacity: 1; }
.settings-screen .st-x:hover { color: var(--hud-red); }
.settings-screen .st-x .ico { width: 100%; height: 100%; }
/* LOCKED: PAUSE / MENU is on the screen precisely so the player is told how
   pause works and why that key is not theirs to move. Greyed, never hidden. */
.settings-screen .st-row.locked { opacity: 0.6; }
.settings-screen .st-row.locked .st-slot {
  cursor: default;
  background: transparent;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05);
}
/* UNBOUND is a legitimate state (docs/settings.md §9 rule 5): a code the player
   cleared is never silently restored, so the row is NAMED instead. */
.settings-screen .st-ub { display: none; }
.settings-screen .st-row.unbound { border-bottom-color: var(--hud-orange); }
.settings-screen .st-row.unbound .st-ub {
  display: inline-block;
  font-style: italic;
  font-size: calc(9 * var(--u));
  font-weight: 800;
  letter-spacing: 0.16em;
  color: var(--hud-orange);
}
/* the row that just LOST a key. bindCode() steals rather than refusing, so the
   only job left to the UI is making the steal impossible to miss. */
.settings-screen .st-row.stolen { animation: ststeal 0.9s ease-out; }
@keyframes ststeal {
  0% { border-bottom-color: var(--hud-orange); background-color: rgba(249, 158, 26, 0.3); }
  100% { border-bottom-color: var(--hud-orange); background-color: var(--hud-panel); }
}

/* ---- the toast: what the steal cost, named. RIGHT-anchored under the panel,
        because the persistent UNBOUND warning lives centred in the foot and the
        two appear together — the toast says what just happened, the warning
        says what is still true, and they must not stack on each other. ---- */
.settings-screen .st-toast {
  position: absolute;
  right: 0;
  top: 100%;
  margin-top: calc(6 * var(--u));
  padding: calc(7 * var(--u)) calc(18 * var(--u)) calc(8 * var(--u));
  border-radius: 0 3px 3px 0;
  /* the SAME leading rule the foot's UNBOUND strip wears, so the pair reads as
     one voice saying two things — what the keypress just did, and what is still
     true afterwards — rather than as two unrelated notices */
  border-left: var(--hud-rule) solid var(--hud-orange);
  border-bottom: calc(2 * var(--u)) solid var(--hud-orange);
  background-color: rgba(14, 19, 25, 0.94);
  background-image: var(--hud-dots);
  background-size: var(--hud-dot-pitch) var(--hud-dot-pitch);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 calc(4 * var(--u)) calc(14 * var(--u)) rgba(0, 0, 0, 0.55);
  font-size: var(--t2);
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  white-space: nowrap;
  color: var(--hud-white);
  pointer-events: none;
  opacity: 0;
  transform: skewX(-6deg);
}
.settings-screen .st-toast.warn {
  border-bottom-color: var(--hud-gold);
  border-left-color: var(--hud-gold);
  color: var(--hud-gold);
}
.settings-screen .st-toast.show { animation: sttoast 2.6s ease-out forwards; }
@keyframes sttoast {
  0% { opacity: 0; }
  9%, 84% { opacity: 1; }
  100% { opacity: 0; }
}

/* ---- the foot. EVERY ROW IN IT IS RESERVED, and that is the rule that matters
        more than any of its styling: this screen is vertically centred, so a
        strip that appears only sometimes moves the TITLE, the rail and all
        twelve bind rows up the frame at the exact moment the player is reading
        them. The first cut did that twice — once when an action went UNBOUND
        (a 15px lurch) and once when RESET ALL armed (27px) — and a hero screen
        that jumps under the pointer is a hero screen that reads as a web page.
        So the warning holds its box at zero opacity, and the armed confirm is a
        ROW of the same 56u height as the actions it replaces. ---- */
.settings-screen .st-warn {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  height: calc(26 * var(--u));
  padding: 0 calc(14 * var(--u));
  border-left: var(--hud-rule) solid var(--hud-orange);
  background: rgba(249, 158, 26, 0.1);
  font-size: var(--t2);
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: var(--hud-orange);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85);
  transform: skewX(-6deg);
  /* reserved, not removed — see above */
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.15s;
}
.settings-screen .st-warn.on { opacity: 1; visibility: visible; }
/* BACK is the primary CTA on this screen — there is no APPLY, because every
   setting applies live, and a screen whose loudest button is a destructive
   reset is a screen that will get one clicked. */
.settings-screen .st-done { width: calc(360 * var(--u)); }
.settings-screen .st-reset-group,
.settings-screen .st-reset-all { width: auto; padding: 0 calc(20 * var(--u)); }
.settings-screen .hs-foot > .st-armed { display: none; }
.settings-screen.armed .hs-foot > .hs-actions { display: none; }
/* ONE ROW, 56u — the exact height of the action row it stands in for, so the
   foot does not grow and the whole screen does not re-centre itself under the
   player at the moment they are being asked a destructive yes/no question. The
   pause dialog's LEAVE MATCH confirm reserves its height for the same reason;
   one confirm idiom, used twice. */
.settings-screen.armed .hs-foot > .st-armed {
  display: flex;
  align-items: center;
  justify-content: center;
  height: calc(56 * var(--u));
  gap: calc(24 * var(--u));
}
.settings-screen .st-armed-q {
  font-style: italic;
  font-weight: 800;
  font-size: var(--t3);
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: var(--hud-gold);
  text-shadow: 0 calc(2 * var(--u)) calc(6 * var(--u)) rgba(0, 0, 0, 0.85);
  transform: skewX(-6deg);
}
.settings-screen .st-arm-yes { width: calc(230 * var(--u)); }
/* the hint strip says what THIS group answers to, and while a row is listening
   it says only that */
.settings-screen .st-hint > span { display: none; }
.settings-screen .st-hint > span { align-items: center; gap: calc(10 * var(--u)); }
.settings-screen[data-kind='settings'] .st-h-set { display: inline-flex; }
.settings-screen[data-kind='binds'] .st-h-bind { display: inline-flex; }
.settings-screen.capturing[data-kind='settings'] .st-h-set,
.settings-screen.capturing[data-kind='binds'] .st-h-bind { display: none; }
.settings-screen.capturing .st-h-cap { display: inline-flex; color: var(--hud-gold); }
/* ARMED outranks both — Escape disarms the reset instead of leaving. */
.settings-screen.armed .st-h-set,
.settings-screen.armed .st-h-bind,
.settings-screen.armed .st-h-cap { display: none; }
.settings-screen.armed .st-h-arm { display: inline-flex; color: var(--hud-gold); }

/* ================= PAUSE DIALOG (docs/settings.md §6.5) ===================
   The FIFTH `.hs-screen`, and the only one that is a CENTRED PANEL rather than
   a full bleed — because the match has to read behind it. That is not a taste
   note. Online the world cannot freeze (§5.7): the server's clock keeps running
   and the opponent keeps moving, so the player is still in the fight and a
   dialog that hid the fight would be lying to them. The LIVE strip says so in
   words and the veil says so by staying out of the way. */
.pause-screen .hs-veil {
  background: radial-gradient(ellipse 76% 68% at 50% 50%,
    rgba(4, 7, 12, 0.66) 0%, rgba(4, 7, 12, 0.44) 52%, rgba(4, 7, 12, 0.6) 100%);
  backdrop-filter: blur(2px) brightness(0.62) saturate(0.9);
}
/* LIVE: less veil and no blur at all. Not being able to see the opponent who is
   shooting you is worse than a busier frame. */
.pause-screen.live .hs-veil {
  background: radial-gradient(ellipse 76% 68% at 50% 50%,
    rgba(4, 7, 12, 0.44) 0%, rgba(4, 7, 12, 0.24) 52%, rgba(4, 7, 12, 0.42) 100%);
  backdrop-filter: brightness(0.84) saturate(0.95);
}
.pause-screen .pz-panel {
  position: relative;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: calc(484 * var(--u));
  padding: calc(30 * var(--u)) calc(36 * var(--u)) calc(26 * var(--u));
  border-radius: 3px;
  border-bottom: var(--hud-rule) solid var(--hud-orange);
  transform: var(--hud-skew);
}
.pause-screen .pz-scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: 3px 3px 0 0;
  background-color: var(--hud-panel);
  background-image: var(--hud-dots);
  background-size: var(--hud-dot-pitch) var(--hud-dot-pitch);
  box-shadow: var(--hud-edge), 0 calc(10 * var(--u)) calc(34 * var(--u)) rgba(0, 0, 0, 0.6);
  backdrop-filter: var(--hud-blur);
}
/* the panel already carries the -6°; its head must not carry it twice. The
   -30deg kick composes to -24 inside it, so the rule keeps its shipped angle. */
.pause-screen .hs-title { transform: none; }
.pause-screen .hs-kick { transform: skewX(-24deg); }
.pause-screen .pz-mode {
  margin-top: calc(13 * var(--u));
  font-size: var(--t2);
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: rgba(242, 245, 247, 0.5);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85);
}
/* THE HONEST PART. Shown only when the match has a remote opponent, because
   only then is it true. */
.pause-screen .pz-live { display: none; }
.pause-screen.live .pz-live {
  display: block;
  box-sizing: border-box;
  width: 100%;
  margin-top: calc(22 * var(--u));
  padding: calc(7 * var(--u)) calc(12 * var(--u)) calc(8 * var(--u));
  border-left: var(--hud-rule) solid var(--hud-gold);
  background: rgba(255, 200, 115, 0.1);
  /* t2, not the t1 label step: this line is the one piece of copy on the dialog
     that changes what the player should DO, so it outranks the mode name above
     it. At t1 it measured as decoration on the panel's edge. */
  font-size: var(--t2);
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: 0.1em;
  text-indent: 0.1em;
  text-align: center;
  color: var(--hud-gold);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
  animation: pzlive 1.5s ease-in-out infinite alternate;
}
@keyframes pzlive {
  from { background-color: rgba(255, 200, 115, 0.08); }
  to { background-color: rgba(255, 200, 115, 0.2); }
}

/* ---- the buttons. `.menu-play` and `.menu-ghost` verbatim, so RESUME is the
        same object as PLAY and the two ghosts are the same object as BACK.
        All that is overridden is the width and the second shear. ---- */
.pause-screen .pz-actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(12 * var(--u));
  width: 100%;
  margin-top: calc(26 * var(--u));
}
.pause-screen .menu-play,
.pause-screen .menu-ghost {
  width: 100%;
  min-width: 0;
  margin-top: 0;
  transform: none;
}
.pause-screen .menu-play {
  font-size: var(--t4);
  letter-spacing: 0.18em;
}
.pause-screen .menu-play .mp-label { text-indent: 0.18em; }
.pause-screen .menu-play:hover { transform: translateY(calc(-1 * var(--u))); }
.pause-screen .menu-play:active { transform: translateY(calc(2 * var(--u))); }
.pause-screen .pz-resume-sub {
  display: none;
  margin-top: calc(-4 * var(--u));
  font-style: italic;
  font-size: var(--t1);
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: rgba(242, 245, 247, 0.5);
}
.pause-screen.live .pz-resume-sub { display: block; }
/* LEAVE MATCH is the only element on either new screen allowed the kill-red,
   and it gets it as a LEFT RULE — never a fill. A red button is a button that
   gets clicked; a red edge is a button that gets read first.
   SQUARE the leading corners. The plate's 3px radius bowed the rule away from
   the box at both ends, so it captured as a short red tick floating beside the
   button rather than as an edge ON it — an artefact read, which is worse than
   no accent at all. Flush and full-height, it reads as a deliberate marking. */
.pause-screen .pz-leave {
  border-bottom-color: rgba(242, 245, 247, 0.18);
  border-left: var(--hud-rule) solid var(--hud-red);
  border-radius: 0 3px 3px 0;
  /* the ghost plate is within a few luminance units of the panel behind it, so
     a rule on its edge alone had nothing to sit ON and read as a stray mark in
     the frame. A short wash off the rule attaches it to the button — still not
     a fill: it is gone by a third of the way across. */
  background-image:
    linear-gradient(90deg, rgba(232, 64, 42, 0.14) 0%, rgba(232, 64, 42, 0) 30%),
    var(--hud-dots);
  /* the ghost's shared `background-size` is the DOT PITCH, and it applies to
     every layer — left unset the wash tiles at 8u and the button comes out
     striped. Per-layer sizes, in the same order as the images. */
  background-size: 100% 100%, var(--hud-dot-pitch) var(--hud-dot-pitch);
  background-repeat: no-repeat, repeat;
  color: rgba(242, 245, 247, 0.8);
}
.pause-screen .pz-leave:hover { color: var(--hud-red); }
/* RESUMING…: every button goes inert while the browser decides. Chrome refuses
   a re-lock for about a second after a user-initiated exit, so this state is
   normal rather than exceptional and it must not look like a hang. */
.pause-screen.resuming .pz-actions { pointer-events: none; }
.pause-screen.resuming .menu-ghost { opacity: 0.4; }
.pause-screen.resuming .pz-resume { animation: pzwait 0.8s ease-in-out infinite alternate; }
@keyframes pzwait {
  from { filter: brightness(0.86) saturate(0.7); }
  to { filter: brightness(1.06) saturate(1); }
}

/* ---- LEAVE MATCH confirms. It never fires on the first click, Escape cancels,
        and CANCEL takes focus so Enter and Space answer with the safe one. ---- */
.pause-screen .pz-confirm { display: none; }
.pause-screen.confirming .pz-actions { display: none; }
.pause-screen.confirming .pz-confirm {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 100%;
  /* the same height the three-button row occupies (3 x 56u + 2 x 12u), so the
     panel does not shrink and re-centre itself under the player at the exact
     moment they are being asked a yes/no question */
  min-height: calc(192 * var(--u));
  margin-top: calc(26 * var(--u));
}
.pause-screen .pz-q {
  font-style: italic;
  font-weight: 800;
  font-size: var(--t3);
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  color: var(--hud-white);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85);
}
.pause-screen .pz-q-sub {
  margin-top: calc(7 * var(--u));
  font-size: var(--t1);
  letter-spacing: var(--tr-label);
  text-indent: var(--tr-label);
  text-align: center;
  color: rgba(242, 245, 247, 0.45);
}
.pause-screen .pz-cactions {
  display: flex;
  gap: calc(12 * var(--u));
  width: 100%;
  margin-top: calc(18 * var(--u));
}
.pause-screen .pz-cactions .menu-ghost { flex: 1 1 0; padding: 0 calc(10 * var(--u)); }
/* CONFIRM carries the same marking as the button that raised it — same rule,
   same width, same squared leading corners — so the red is one idea seen twice
   rather than two different red things. */
.pause-screen .pz-yes {
  border-bottom-color: rgba(242, 245, 247, 0.18);
  border-left: var(--hud-rule) solid var(--hud-red);
  border-radius: 0 3px 3px 0;
  background-image:
    linear-gradient(90deg, rgba(232, 64, 42, 0.14) 0%, rgba(232, 64, 42, 0) 38%),
    var(--hud-dots);
  background-size: 100% 100%, var(--hud-dot-pitch) var(--hud-dot-pitch);
  background-repeat: no-repeat, repeat;
}
.pause-screen .pz-yes:hover { color: var(--hud-red); }

/* the hint is a LINE, not a fourth button: at full panel width under three
   full-width buttons the shared `.menu-hint` plate reads as a disabled one */
.pause-screen .pz-hint {
  margin-top: calc(26 * var(--u));
  min-width: 0;
  width: auto;
  padding: 0 calc(14 * var(--u));
  background: rgba(14, 19, 25, 0.55);
  transform: none;
}
/* both states are authored, one shows. The child selectors are deliberate and
   they are not decoration: `.pz-hint > span` carries a type selector, so a
   two-class show rule LOSES the specificity fight against it and the strip
   renders as an empty plate. Matching depth on both sides is the fix. */
.pause-screen .pz-hint > span { display: none; align-items: center; gap: calc(10 * var(--u)); }
.pause-screen .pz-hint > .pz-h-run { display: inline-flex; }
.pause-screen.confirming .pz-hint > .pz-h-run { display: none; }
.pause-screen.confirming .pz-hint > .pz-h-conf { display: inline-flex; color: var(--hud-gold); }
