/* ============================================================================
   FRAG ARENA — UI/HUD stylesheet. "Neon autopsy at 120fps."
   Implements _work/ui/DESIGN-spec.md tokens + components over the Babylon canvas.
   Filename is load-bearing: scripts/stamp-build.mjs regexes styles-v*.css and
   rewrites the ?v= stamp — keep the pattern.
   MUST-PRESERVE ids/classes/body-classes from _work/ui/AUDIT-current-ui.md are
   preserved verbatim below; only presentation is changed.
   ============================================================================ */

/* ============================================================================
   HUD2030 TOKENS — _work/hud2030/DESIGN.md ("signal, not chrome").
   Use these (--x30-*) for all reworked surfaces. Legacy tokens below
   (--bone, --glow-*, --clip-notch, --skew, DSEG7) are DEPRECATED.
   Custom properties only — no rules in this block.
   ============================================================================ */
:root {
    /* ---- surfaces: cool graphite glass, floating (alpha, never blur) ---- */
    --x30-void: #050708;                      /* page/canvas base, splash bg  */
    --x30-surface-0: rgba(9, 11, 14, 0.72);   /* in-game HUD plates           */
    --x30-surface-1: rgba(14, 17, 22, 0.84);  /* menu rows, chips, killfeed   */
    --x30-surface-2: #101319;                 /* modals / settings (opaque)   */
    --x30-surface-3: #161B22;                 /* inputs, raised rows          */
    --x30-el-1: 0 2px 10px rgba(0, 0, 0, 0.35);   /* floating plate shadow    */
    --x30-el-2: 0 10px 32px rgba(0, 0, 0, 0.45);  /* modal shadow             */

    /* ---- ink: ONE cool white, hierarchy by alpha only ---- */
    --x30-ink: #F4F7F9;                            /* hero numerals, values   */
    --x30-ink-hi: rgba(244, 247, 249, 0.92);       /* titles, active labels   */
    --x30-ink-mid: rgba(244, 247, 249, 0.62);      /* labels, captions, units */
    --x30-ink-low: rgba(244, 247, 249, 0.38);      /* disabled, ghost text    */
    --x30-hairline: rgba(244, 247, 249, 0.10);     /* 1px rules/dividers      */
    --x30-hairline-strong: rgba(244, 247, 249, 0.20); /* input borders        */

    /* ---- accent system: Solana lineage, strict law (DESIGN.md §1) ---- */
    --x30-accent: #14F195;                    /* THE interactive/brand green  */
    --x30-accent-dim: rgba(20, 241, 149, 0.55);
    --x30-accent-tint: rgba(20, 241, 149, 0.09);   /* selection wash          */
    --x30-violet: #9945FF;                    /* ambient identity ONLY        */
    --x30-violet-tint: rgba(153, 69, 255, 0.10);
    --x30-grad-brand: linear-gradient(90deg, #14F195, #9945FF); /* 1x/screen, hairline only */

    /* ---- color law (unified — replaces red/amber/cyan drift) ---- */
    --x30-threat: #FF3B46;                    /* the ONLY red                 */
    --x30-threat-tint: rgba(255, 59, 70, 0.12);
    --x30-ally: #7FE9FF;                      /* self/allies/own systems      */
    --x30-ally-dim: rgba(127, 233, 255, 0.55);
    --x30-feedback: #FFFFFF;                  /* own hit confirms, crosshair  */
    --x30-feedback-gold: #FFC24B;             /* kills, headshots, streaks    */
    --x30-warn: #FFB020;                      /* mid-tier warnings            */

    /* ---- data-emit glow budget (text-shadow only, live data only) ---- */
    --x30-emit-accent: 0 0 8px rgba(20, 241, 149, 0.45);
    --x30-emit-ally: 0 0 8px rgba(127, 233, 255, 0.45);
    --x30-emit-threat: 0 0 8px rgba(255, 59, 70, 0.50);
    --x30-emit-gold: 0 0 8px rgba(255, 194, 75, 0.50);

    /* ---- shape ---- */
    --x30-r-1: 2px;      /* chips, tags        */
    --x30-r-2: 6px;      /* plates, rows, btns */
    --x30-r-3: 10px;     /* modals             */
    --x30-r-pill: 999px;
    --x30-edge: 2px;     /* the only accent stroke (state-encoding left edge) */

    /* ---- type ---- */
    --x30-font-display: 'Chakra Petch', 'Arial Narrow', sans-serif;
    --x30-font-num: 'Barlow Condensed', 'DIN Alternate', 'Arial Narrow', sans-serif;
    --x30-font-ui: 'Inter', system-ui, -apple-system, sans-serif;
    --x30-track-label: 0.14em;   /* Inter 600 uppercase micro labels          */
    --x30-track-display: 0.08em; /* Chakra 600 uppercase display              */
    --x30-size-micro: 10px;  --x30-size-label: 11px;  --x30-size-body: 14px;
    --x30-size-sub: 16px;    --x30-size-h3: 20px;     --x30-size-h2: 26px;
    --x30-size-num-l: 40px;  --x30-size-num-xl: 44px; --x30-size-timer: 22px;

    /* ---- motion ---- */
    --x30-t-fast: 120ms;   /* hover/press, color state changes                */
    --x30-t-base: 180ms;   /* reveals, row enter, modal open                  */
    --x30-t-slow: 280ms;   /* announcements in                                */
    --x30-t-exit: 140ms;   /* ALL exits — leave faster than you arrive        */
    --x30-ease-out: cubic-bezier(0.16, 1, 0.3, 1);      /* every entrance     */
    --x30-ease-inout: cubic-bezier(0.65, 0, 0.35, 1);   /* position changes   */
    --x30-ease-exit: cubic-bezier(0.7, 0, 0.84, 0);     /* every exit         */
}

:root {
    color-scheme: dark;

    /* ---- Color tokens (DESIGN 2.1) ---- */
    --sol-teal: #14F195;
    --sol-mid: #00D18F;
    --sol-purple: #9945FF;
    --grad-sol: linear-gradient(100deg, #14F195 0%, #00D18F 55%, #9945FF 100%);
    --grad-sol-v: linear-gradient(180deg, #14F195 0%, #00D18F 55%, #9945FF 100%);
    --blood: #C81E1E;
    --blood-bright: #FF3333;
    --blood-dark: #7A1010;
    --bone: #ECE6D2;
    --bone-dim: #A8A395;
    --ink: #1B1512;
    --bg-0: #07090C;
    --bg-1: #0B0E12;
    --bg-2: #131820;
    --bg-3: #1C2430;
    --line: #2A3342;
    --scrim: rgba(7, 9, 12, 0.72);
    --scrim-heavy: rgba(7, 9, 12, 0.88);
    --warn: #FFB020;
    /* amber = high-damage / utility accent (bit 2 heavy-hit tier, bit 8 status) */
    --amber: #FFB02E;

    /* ---- Halo: Combat Evolved ammo HUD (client-only presentation) ---- */
    --halo-cyan: var(--hud-self);   /* legacy alias -> single cyan contract (bit 14) */
    --halo-cyan-dim: #6FD9E8;    /* reserve / separator */
    --halo-cyan-deep: #3FA9C4;   /* frame / brackets */
    --halo-tint: rgba(30, 120, 150, 0.22);   /* cyan wash over the scrim */
    --glow-halo: 0 0 6px rgba(127, 233, 255, .65), 0 0 18px rgba(111, 217, 232, .35);

    /* ---- Spacing / radii / angles (DESIGN 2.3) ---- */
    --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
    --sp-5: 24px; --sp-6: 32px; --sp-7: 48px; --sp-8: 64px;
    --r-0: 0; --r-1: 4px; --r-full: 999px;
    --skew: -12deg;
    --notch: 12px;

    /* ---- Glow & shadow (DESIGN 2.4) ---- */
    --glow-teal: 0 0 8px rgba(20, 241, 149, .55), 0 0 24px rgba(20, 241, 149, .25);
    --glow-purple: 0 0 8px rgba(153, 69, 255, .55), 0 0 24px rgba(153, 69, 255, .25);
    --glow-blood: 0 0 10px rgba(255, 51, 51, .6), 0 0 28px rgba(200, 30, 30, .3);
    --text-shadow-hud: 0 1px 0 #07090C, 0 0 6px rgba(7, 9, 12, .9);
    --edge-hairline: inset 0 0 0 1px var(--line);

    /* ---- Motion (DESIGN 5) ---- */
    --ease-snap: cubic-bezier(.2, .9, .3, 1);
    --ease-exit: cubic-bezier(.6, 0, .9, .4);

    /* ---- Fonts (DESIGN 2.2) ---- */
    --font-display: 'Chakra Petch', 'Arial Narrow', Impact, sans-serif;
    --font-hud: 'Teko', 'Arial Narrow', sans-serif;
    --font-ui: 'Inter', system-ui, -apple-system, sans-serif;
    /* HUD numerals: Barlow Condensed reads a "0" as a zero (Teko's renders as "U").
       Used for ammo/health/grenade counts with tabular-nums so widths never shift. */
    --font-numeral: 'Barlow Condensed', 'DIN Alternate', var(--font-hud);

    /* ---- HUD REWORK color law (HUD-REWORK-SPEC SHARED CONTRACT) ----
       Flat scrim backplates (NO glow); calm self-colors muted so they never
       out-shout the world; --hud-crit red always wins on critical states. */
    --hud-scrim:  rgba(6, 10, 15, 0.82);   /* flat backplate, NO glow                 */
    --hud-self:   var(--x30-ally);   /* self/active systems: ammo, weapon accent      */
    --hud-vitals: var(--x30-accent); /* health accent — SOL brand green               */
    --hud-util:   var(--x30-warn);   /* utility/cooldown: grenades — one gold-warn    */
    --hud-crit:   var(--x30-threat); /* CRITICAL: low HP, empty mag — always wins     */
    --hud-warn:   var(--x30-warn);   /* mid-tier warn (health 31-60, low-ammo)        */
    --hud-over:   #FFCF6B;   /* overheal amber readout                                */

    /* clipped bottom-right corner (panels/buttons) */
    --clip-notch: polygon(0 0, 100% 0, 100% calc(100% - var(--notch)), calc(100% - var(--notch)) 100%, 0 100%);
    /* mirrored: clipped top-left (kill-feed rows) */
    --clip-notch-tl: polygon(var(--notch) 0, 100% 0, 100% 100%, 0 100%, 0 var(--notch));

    font-family: var(--font-ui);

    /* ---- Menu background hook (Part C). Drop art in by uncommenting + pointing this
       var at the file; the dark scrim gradient over it (.entry-bg::after) keeps the
       menu legible with any art. Empty today → falls back to the existing overlay
       scrim. Example:
       --menu-bg: url('../assets/brand/menu-bg.jpg'); */
    --menu-bg: none;
}

* { box-sizing: border-box; }

/* ============================================================================
   RETRO LAYER — seven-segment phosphor + CRT tokens (owner verdict on HUD2030:
   "far more retro — segment displays and stuff"). DSEG7 is REVIVED for NUMERALS
   ONLY (it has no readable letterforms); Chakra/Inter keep every label. Glow is
   cheap text-shadow (≤8px, never filter/blur); scanlines are one repeating
   gradient layered over existing plate fills. Use these tokens, not literals.
   ============================================================================ */
@font-face {
    font-family: 'DSEG7';
    src: url('../assets/fonts/DSEG7Classic-Bold.woff2') format('woff2');
    font-weight: 700;
    font-style: normal;
    font-display: swap;
}
:root {
    /* ---- segment phosphor palette (amber neutral / green healthy / red crit) */
    --seg-font: 'DSEG7', var(--x30-font-num);
    --seg-amber: #FFB02E;
    --seg-green: var(--x30-accent);
    --seg-red: var(--x30-threat);
    --seg-cyan: var(--x30-ally);
    --seg-ghost: rgba(244, 247, 249, 0.07);   /* unlit-segment "888" underlay    */
    --seg-glow-amber: 0 0 6px rgba(255, 176, 46, 0.45);
    --seg-glow-green: 0 0 6px rgba(20, 241, 149, 0.45);
    --seg-glow-red:   0 0 7px rgba(255, 59, 70, 0.55);
    --seg-glow-cyan:  0 0 6px rgba(127, 233, 255, 0.45);

    /* ---- CRT chrome: ~3px scanlines, top-edge bevel highlight ---- */
    --crt-scanlines: repeating-linear-gradient(0deg,
        rgba(0, 0, 0, 0.20) 0px, rgba(0, 0, 0, 0.20) 1px, transparent 1px, transparent 3px);
    --crt-bevel: inset 0 1px 0 rgba(244, 247, 249, 0.07);
}

html, body {
    width: 100%;
    height: 100%;
    margin: 0;
    overflow: hidden;
    background: var(--bg-0);
    color: var(--x30-ink-hi);
    overscroll-behavior: none;
}

button, input, select { font: inherit; color: inherit; }

/* Focus-visible: 2px teal outline, offset 2px — on everything interactive
   (DESIGN 2.4, non-negotiable for menus). */
button:focus-visible,
input:focus-visible,
select:focus-visible,
a:focus-visible {
    outline: 2px solid var(--sol-teal);
    outline-offset: 2px;
}

.visually-quiet {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

#game-shell, #main-canvas {
    position: fixed;
    inset: 0;
    width: 100%;
    height: 100%;
}

#main-canvas { display: block; touch-action: none; outline: none; }

/* ============================================================================
   HUD LAYER (z10) — DESIGN 3
   ============================================================================ */

#arena-hud {
    position: fixed;
    inset: 0;
    z-index: 10;
    pointer-events: none;
    user-select: none;
}

#top-hud {
    position: absolute;
    top: calc(var(--sp-5) + env(safe-area-inset-top));
    left: calc(var(--sp-5) + env(safe-area-inset-left));
    right: calc(var(--sp-5) + env(safe-area-inset-right));
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
}
/* In-game strip must never ghost through the menu scrim (same gating pattern
   as #touch-controls below). */
body:not(.arena-entered) #top-hud { visibility: hidden; }

.brand-lockup, .match-strip { display: flex; align-items: center; }
.brand-lockup { gap: 10px; min-width: 0; opacity: 0.6; }

.brand-mark { width: 30px; height: 30px; display: block; }

.brand-name {
    color: var(--x30-ink-low);
    font-family: var(--font-display);
    font-weight: 600;
    font-size: 14px;
    letter-spacing: var(--x30-track-display);
    text-transform: uppercase;
    text-shadow: none;
}

/* connection chip (top-left cluster: ping + build). Restyled skewed scrim chip.
   BIT 8: data-state drives a status color system so connection reads at a glance
   (green=online, amber pulse=connecting, red pulse=offline). Legacy
   .connection-connected/.connection-disconnected classes kept as fallbacks. */
.match-strip {
    --status: var(--x30-warn); /* default = connecting warn */
    min-height: 28px;
    padding: 0 12px;
    gap: 10px;
    background: var(--x30-surface-0);
    border-radius: var(--x30-r-1);
    box-shadow: var(--x30-el-1);
    clip-path: none;
    color: var(--x30-ink-mid);
    font-family: var(--x30-font-ui);
    font-size: var(--x30-size-label);
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}
.match-strip[data-state="online"]  { --status: var(--x30-accent); }
.match-strip[data-state="connecting"] { --status: var(--x30-warn); }
.match-strip[data-state="offline"] { --status: var(--x30-threat); }

.status-dot {
    width: 6px; height: 6px;
    border-radius: var(--x30-r-pill);
    background: var(--status);
    box-shadow: none;
    flex: none;
}
.match-strip[data-state]:not([data-state="online"]) .status-dot {
    animation: status-pulse 1s ease-in-out infinite;
}
@keyframes status-pulse { 50% { opacity: 0.35; } }
/* legacy state classes (kept working if JS still toggles them) — flat, no glow */
.connection-connected .status-dot { background: var(--x30-accent); }
.connection-disconnected .status-dot { background: var(--x30-threat); }
#connection-label { color: var(--status); display: inline-block; min-width: 12ch; }

/* latency readout (BIT 8 — #ping-ms is new markup; JS writes the value + is-bad) */
#ping-ms {
    font-weight: 600;
    color: var(--x30-ink);
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}
#ping-ms small { font-size: 10px; color: var(--x30-ink-low); margin-left: 2px; }
#ping-ms.is-bad { color: var(--x30-warn); }
#frag-count, #player-count { color: var(--x30-ink); font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }

.status-divider { width: 1px; height: 12px; margin: 0 2px; background: var(--x30-hairline); }

/* ---- Crosshair (DESIGN 3.9 / CROSSHAIR-proposal.md) — per-weapon, spread-reactive.
   Inline SVG (keeps the #crosshair id + centering anchor + death-fade). data-weapon
   picks the reticle; --xhair-spread (px, JS per-frame from predicted heat) drives the
   dynamic-cross gap; --xhair-ring (px) is the shotgun cone radius (set on switch/resize).
   NOTE: the 140ms transition is scoped to OPACITY ONLY — the spread/gap must move
   instantly per-frame (no easing/rubber-band); the death-fade opacity still eases. ---- */
#crosshair {
    --xhair-color: var(--x30-feedback);   /* #FFFFFF — crosshair is own-feedback */
    --xhair-outline: var(--x30-void);     /* #050708 ink backing */
    --xhair-spread: 4px;   /* dynamic-cross inner-edge gap (JS-driven) */
    --xhair-ring: 49px;    /* shotgun cone radius in px (JS-driven, @1080p ref) */
    position: absolute;
    top: 50%; left: 50%;
    /* 128px box over a -64..64 viewBox → 1 SVG user unit = 1 CSS px, so the
       JS-computed spread/ring values (px) map 1:1 into the reticle geometry.
       overflow:visible lets the shotgun ring (r≈49) and SMG bloom draw past 64. */
    width: 128px; height: 128px;
    transform: translate(-50%, -50%);
    opacity: 1;
    overflow: visible;
    pointer-events: none;
    transition: opacity 140ms ease;
    /* BIT 1: dual ink halo (tight hard edge + soft outer) so the reticle reads on
       both bright floor and dark sky — the old single 1px shadow washed out. */
    filter: drop-shadow(0 0 0.5px #07090C) drop-shadow(0 0 2px rgba(7, 9, 12, .85));
}
/* every reticle stroke gets an ink outline UNDER the fill (paint-order) for
   bright-scene legibility — thin filter halos alone wash out (BIT 1). */
#crosshair .xh-tick,
#crosshair .xh-dot {
    fill: var(--xhair-color);
    stroke: var(--xhair-outline);
    stroke-width: 1;
    paint-order: stroke fill;
}
/* shotgun ring: ink outline backing (BIT 1) + bone stroke on top; radius (r)
   driven by JS via --xhair-ring, at the true pellet-cone angle so "fit the target
   inside the ring → all pellets connect" holds. .xh-ring-outline is optional new
   markup; the base .xh-ring-circle still works if only one circle is present. */
#crosshair .xh-ring-outline { stroke: var(--xhair-outline); stroke-width: 3.5; r: var(--xhair-ring); opacity: .85; fill: none; }
#crosshair .xh-ring-circle { stroke: var(--xhair-color); stroke-width: 2; r: var(--xhair-ring); fill: none; }
/* dynamic-cross ticks slide outward by the live spread gap (instant, no transition) */
#crosshair .xh-tick-up    { transform: translateY(calc(-1 * var(--xhair-spread))); }
#crosshair .xh-tick-down  { transform: translateY(var(--xhair-spread)); }
#crosshair .xh-tick-left  { transform: translateX(calc(-1 * var(--xhair-spread))); }
#crosshair .xh-tick-right { transform: translateX(var(--xhair-spread)); }
/* per-weapon group visibility (weapon indices: 0 Rifle · 1 SMG · 2 Shotgun · 3 Pistol) */
#crosshair .xh { display: none; }
#crosshair[data-weapon="0"] .xh-cross,
#crosshair[data-weapon="1"] .xh-cross,
#crosshair[data-weapon="3"] .xh-cross { display: block; }
#crosshair[data-weapon="2"] .xh-ring { display: block; }
/* Pistol (3) has no center dot; SMG (1) dot is mandatory (aim point at full bloom) */
#crosshair[data-weapon="3"] .xh-cross .xh-dot { display: none; }

/* ---- Hit marker (DESIGN 3.9 / 5.1) — feedback white on hit, gold on kill ---- */
#hit-marker {
    position: absolute;
    top: 50%; left: 50%;
    width: 26px; height: 26px;
    transform: translate(-50%, -50%);
    opacity: 0;
    pointer-events: none;
    z-index: 20;
}
#hit-marker i {
    position: absolute;
    top: 50%; left: 50%;
    width: 13px; height: 2px;
    margin: -1px 0 0 -6.5px;
    background: var(--x30-feedback);
    box-shadow: 0 0 4px rgba(7, 9, 12, .85);
}
#hit-marker i:nth-child(1) { transform: rotate(45deg); }
#hit-marker i:nth-child(2) { transform: rotate(-45deg); }

/* Doom-impact retiming (RECOIL-FEEDBACK-proposal §5.1): GROW-IN with a short
   full-bright HOLD so rapid fire reads clearly. hit = bone, heavy = amber tier,
   kill = blood + skull. Durations match the JS marker timers
   (Simulator._showHitMarker): hit 160ms, heavy 170ms, KILL 450ms. */
#hit-marker.hit-active { animation: hit-pop 160ms ease-out; }
/* heavy hit (≥20 predicted dmg — pistol/shotgun): amber = high-damage tier
   (Apex/CoD color language) + thicker ticks so the tier reads instantly. */
#hit-marker.hit-active.hit-active-heavy { animation: hit-pop-heavy 170ms ease-out; }
#hit-marker.hit-active.hit-active-heavy i {
    height: 3px; width: 15px; margin: -1.5px 0 0 -7.5px;
    background: var(--x30-warn);
    box-shadow: 0 0 6px rgba(255, 176, 32, 0.45), 0 0 4px rgba(7, 9, 12, .85);
}
#hit-marker.kill-active { animation: kill-pop 450ms ease-out; }
#hit-marker.kill-active i {
    height: 3px; width: 17px; margin: -1.5px 0 0 -8.5px;
    background: var(--x30-feedback-gold);
    box-shadow: 0 0 8px rgba(255, 194, 75, 0.50), 0 0 4px rgba(7, 9, 12, .85);
}
/* HEADSHOT marker (#33): authoritative head-hit gets a distinct GOLD tick treatment that
   reads stronger than a heavy body hit, layered over whatever base marker the shot earned
   (.hit-active or .kill-active). Simulator._showHitMarker toggles .headshot-active alongside
   the "Headshot!" announcer (FragLayer) + the kill-feed HEADSHOT badge. */
#hit-marker.headshot-active i {
    background: var(--x30-feedback-gold);
    box-shadow: 0 0 8px rgba(255, 194, 75, 0.50), 0 0 4px rgba(7, 9, 12, .85);
    height: 3px;
}
/* skull micro-icon that pops above the crosshair on a KILL confirm only (spec §5.1).
   BIT 2: .hm-skull is now an inline SVG styled as a FILL target (not a radial mask)
   — deterministic across browsers, no mask-composite eye-hole bug. Hidden
   (opacity 0) unless kill-active. */
#hit-marker .hm-skull {
    position: absolute;
    left: 50%; top: -18px;
    width: 14px; height: 14px;
    margin-left: -7px;
    opacity: 0;
    pointer-events: none;
    fill: var(--x30-feedback-gold);
    filter: drop-shadow(0 0 4px rgba(255, 194, 75, 0.50));
}
#hit-marker.kill-active .hm-skull { animation: skull-pop 450ms ease-out; }

@keyframes hit-pop {
    0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.55); }
    22%  { opacity: 1; transform: translate(-50%, -50%) scale(1.1); }  /* overshoot pop */
    40%  { transform: translate(-50%, -50%) scale(1); }
    65%  { opacity: 1; transform: translate(-50%, -50%) scale(1); }    /* ~40ms bright hold */
    100% { opacity: 0; transform: translate(-50%, -50%) scale(1); }
}
@keyframes hit-pop-heavy {
    0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.55); }
    22%  { opacity: 1; transform: translate(-50%, -50%) scale(1.32); }
    40%  { transform: translate(-50%, -50%) scale(1.25); }
    65%  { opacity: 1; transform: translate(-50%, -50%) scale(1.25); }
    100% { opacity: 0; transform: translate(-50%, -50%) scale(1.25); }
}
@keyframes kill-pop {
    0% { opacity: 0; transform: translate(-50%, -50%) scale(0.7) rotate(0deg); }
    11% { opacity: 1; transform: translate(-50%, -50%) scale(1.3) rotate(20deg); } /* ~50ms grow to 1.3 */
    36% { opacity: 1; transform: translate(-50%, -50%) scale(1.3) rotate(45deg); } /* hold */
    62% { opacity: 1; transform: translate(-50%, -50%) scale(1.15) rotate(62deg); } /* opacity hold */
    100% { opacity: 0; transform: translate(-50%, -50%) scale(1) rotate(90deg); }  /* out */
}
@keyframes skull-pop {
    0% { opacity: 0; transform: translateY(6px) scale(0.6); }
    16% { opacity: 1; transform: translateY(0) scale(1); }
    62% { opacity: 1; transform: translateY(0) scale(1); }
    100% { opacity: 0; transform: translateY(-4px) scale(1); }
}

/* ---- Health (DESIGN 3.5): bottom-left scrim plate ---- */
#health-panel {
    position: absolute;
    /* Halo-CE vitals: shield/health reads top-LEFT, just below the brand/score
       strip (#top-hud). --sp-8 clears the ~30px strip with a small gap. */
    top: calc(var(--sp-8) + env(safe-area-inset-top));
    left: calc(var(--sp-5) + env(safe-area-inset-left));
    min-width: 228px;
    padding: 14px 20px;
    background: var(--x30-surface-0);
    border-radius: var(--x30-r-2);
    box-shadow: var(--x30-el-1);
    border-left: var(--x30-edge) solid var(--x30-accent);
    clip-path: none;
    transition: border-left-color var(--x30-t-fast) ease;
}

/* ---- Bottom-right vitals stack (HUD-REWORK-SPEC): grenade plate (top) over the
   weapon/ammo plate (bottom), flowing in a flex column. One anchor kills the old
   66px magic offset on #grenade-panel; the children are now position:relative. ---- */
#hud-right-group {
    position: absolute;
    /* Halo-CE vitals: weapon/ammo + grenades read top-RIGHT, just below the
       brand/score strip. Grenade plate stacks above the weapon plate. */
    top: calc(var(--sp-8) + env(safe-area-inset-top));
    right: calc(var(--sp-5) + env(safe-area-inset-right));
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 8px;
}
/* ---- Halo: Combat Evolved ammo counter (client-only presentation) ----
   Top-right corner, just BELOW the match-status strip so it never collides with
   the top-left brand/connection cluster or the health panel (bottom-left). A
   translucent cyan-tinted plate with an angular clipped corner + a cyan bracket
   frame, a big Teko magazine count in Halo cyan, a dimmer reserve line, and a
   weapon-class silhouette to the left. The #weapon-name / #magazine-ammo /
   #reserve-ammo / .ammo-separator / #reload-state ids + the .is-reloading toggle
   are unchanged — Simulator._updateHud still drives them. */
/* CHROME DIET (HUD-REWORK-SPEC): flat --hud-scrim plate + ONE cyan accent border.
   No outer glow, no inset glow, no gradient wash, no corner brackets, no clip-path
   — only the DATA (numbers, icon) emits light. Flows inside #hud-right-group. */
/* ---- CARRIED-WEAPON STRIP (playtest 2026-07-25: players picked weapons up without
   noticing and kept firing the pistol). Sits directly above the ammo panel: one cell
   per in-play roster slot, NOT-owned reads as a dim ghost, owned reads in ink, and the
   equipped one takes the brand green + its select key. Cells are built by
   Simulator._buildWeaponStrip(); this is presentation only. ---- */
#weapon-strip {
    display: flex;
    justify-content: flex-end;
    gap: 6px;
    pointer-events: none; /* vertical spacing comes from #hud-right-group's own gap */
}
#weapon-strip .wslot {
    display: flex;
    align-items: baseline;
    gap: 5px;
    padding: 3px 8px;
    border-radius: var(--x30-r-1, 4px);
    background: var(--x30-surface-0);
    box-shadow: var(--x30-el-1);
    border-left: 2px solid transparent;
    /* not owned: present but clearly unavailable, so the strip doubles as a map of
       what there is to find rather than hiding it */
    opacity: 0.34;
    transition: opacity var(--x30-t-fast) ease, border-left-color var(--x30-t-fast) ease,
                background var(--x30-t-fast) ease;
}
#weapon-strip .wslot.owned { opacity: 1; }
#weapon-strip .wslot.active {
    border-left-color: var(--x30-accent);
    background: var(--x30-surface-1, var(--x30-surface-0));
}
#weapon-strip .wslot-key {
    font-family: var(--font-hud, inherit);
    font-size: 11px;
    line-height: 1;
    color: var(--x30-ink-low);
}
#weapon-strip .wslot-name {
    font-size: 11px;
    letter-spacing: 0.06em;
    line-height: 1;
    color: var(--x30-ink);
}
#weapon-strip .wslot.active .wslot-key,
#weapon-strip .wslot.active .wslot-name { color: var(--x30-accent); }
/* phones: the name text is what costs width — keep the keys, drop the words */
@media (max-width: 560px) {
    #weapon-strip .wslot-name { display: none; }
    #weapon-strip .wslot { padding: 3px 7px; }
}

#weapon-panel {
    position: relative;
    display: flex;
    align-items: center;
    gap: 14px;
    min-width: 228px;
    padding: 12px 20px 12px 17px;
    text-align: right;
    background: var(--x30-surface-0);
    border: none;
    border-left: var(--x30-edge) solid var(--x30-ally);
    border-radius: var(--x30-r-2);
    box-shadow: var(--x30-el-1);
    clip-path: none;
    filter: none;
    transition: border-left-color var(--x30-t-fast) ease, opacity var(--x30-t-fast) ease;
}

/* Weapon-class silhouette. Loaded as a CSS mask (clean alpha shape in the SVG)
   so it can be tinted Halo-cyan here — an <img>-loaded SVG can't resolve
   currentColor/CSS vars (see public/assets/brand/skull.svg). data-weapon on
   #weapon-panel (set by Simulator._applyWeaponIcon) picks the source. */
#weapon-icon {
    flex: 0 0 auto;
    width: 68px; height: 36px;
    background-color: var(--x30-ally-dim);
    /* CHROME DIET: no icon drop-shadow glow */
    -webkit-mask: var(--weap-icon, none) center / contain no-repeat;
            mask: var(--weap-icon, none) center / contain no-repeat;
    /* BIT 4/P4: real background-color transition so low-ammo/empty icon recolor
       animates (the old `-webkit-mask-image 0s` was dead code). */
    transition: background-color 150ms ease;
}
#weapon-panel[data-weapon="0"] { --weap-icon: url("../assets/hud/weap-rifle.svg"); }
#weapon-panel[data-weapon="1"] { --weap-icon: url("../assets/hud/weap-smg.svg"); }
#weapon-panel[data-weapon="2"] { --weap-icon: url("../assets/hud/weap-shotgun.svg"); }
#weapon-panel[data-weapon="3"] { --weap-icon: url("../assets/hud/weap-pistol.svg"); }
#weapon-panel[data-weapon="4"] { --weap-icon: url("../assets/hud/weap-sniper.svg"); }

.weapon-readout { display: flex; flex-direction: column; align-items: flex-end; }

.hud-cluster { display: flex; align-items: center; gap: 10px; }
.hud-skull { display: none; width: 26px; height: 26px; opacity: 0.9; filter: drop-shadow(0 0 2px rgba(5,7,8,0.9)); }

.health-readout { display: flex; align-items: baseline; gap: 6px; }

#health-value {
    font-family: var(--font-numeral);
    font-weight: 600;
    font-size: var(--x30-size-num-xl);
    line-height: 0.9;
    letter-spacing: 0.02em;
    color: var(--x30-ink);
    text-shadow: none;
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
    transition: color var(--x30-t-fast) ease;
}
.health-unit {
    font-family: var(--x30-font-ui);
    font-weight: 600;
    font-size: var(--x30-size-micro);
    letter-spacing: var(--x30-track-label);
    color: var(--x30-ink-mid);
}

/* ---- Health track (BIT 3): three-layer bar in a clipped frame ----
   #health-ghost (delayed catch-up damage smear) sits UNDER #health-fill (instant,
   zero-lag on damage). JS drives --hp / --hp-ghost (scaleX) + toggles
   body.mid-health / body.overheal. 25HP tick marks via ::after. */
.health-track {
    position: relative;
    width: 168px;
    height: 6px;
    margin-top: 10px;
    overflow: hidden;
    border: none;
    border-radius: var(--x30-r-1);
    background: var(--x30-hairline);
}
.health-track > span,
#health-ghost, #health-fill {
    position: absolute;
    inset: 0;
    display: block;
    transform-origin: left center;
    will-change: transform;
}
/* damage smear: lags the fill, so a burst-damage loss is briefly readable; on
   heal it's covered by the fill and auto-hides (no direction test needed). */
#health-ghost {
    transform: scaleX(var(--hp-ghost, 1));
    background: rgba(244, 247, 249, 0.85);
    transition: transform .45s cubic-bezier(.2, .75, .3, 1) .15s;
}
#health-fill {
    transform: scaleX(var(--hp, 1)); /* transform stays instant — zero-lag on damage */
    width: 100%; height: 100%;
    background: var(--x30-accent);
    box-shadow: none;
    transition: transform 0ms, background-color 150ms ease;
}

/* threshold recolor (BIT 3): mid-health (31-60) amber warn, low-health (<=30)
   crit red. JS toggles body.mid-health / body.low-health. */
body.mid-health #health-fill { background: var(--x30-warn); box-shadow: none; }
body.mid-health #health-value { color: var(--x30-warn); }
body.low-health #health-fill { background: var(--x30-threat); box-shadow: none; }
/* crit readout stays FULLY OPAQUE (legible when it matters most) and breathes via
   a bounded glow — THE one permitted HUD loop (1.1s cadence, ≤8px emit). */
body.low-health #health-value { color: var(--x30-threat); animation: hp-glow 1.1s ease-in-out infinite; }
body.low-health .hud-skull { display: block; animation: hp-pulse .9s ease-in-out infinite; }

@keyframes hp-glow {
    0%, 100% { text-shadow: 0 0 2px rgba(255, 59, 70, 0.20); }
    50%      { text-shadow: var(--x30-emit-threat); }
}

/* state edge: the 2px plate edge encodes the health state */
body.mid-health #health-panel { border-left-color: var(--x30-warn); }
body.low-health #health-panel { border-left-color: var(--x30-threat); }
body.overheal   #health-panel { border-left-color: var(--x30-feedback-gold); }

/* COLOR-LAW HIERARCHY: at critical HP the red health + red vignette must own the
   peripheral read — the cyan weapon plate recedes (cheap opacity, no filter). */
body.low-health #weapon-panel { opacity: 0.6; }

@keyframes hp-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } }

/* Phase 4 mega-health OVERHEAL (>100): gold "power" state distinct from the base
   bar, so 100-150 HP is obvious. JS toggles body.overheal in Simulator._updateHud. */
body.overheal #health-fill {
    background: var(--x30-feedback-gold);
    box-shadow: none;
}
body.overheal #health-value { color: var(--x30-feedback-gold); text-shadow: var(--x30-emit-gold); }

/* ---- Pickup FF: ARMOR bar (under the health track) ----------------------------
   A slim cyan/steel bar + numeral, hidden until the player actually has armor
   (Simulator toggles body.has-armor). Green-armor soaks 50% of incoming damage. */
#armor-row {
    display: none;
    align-items: center;
    gap: 6px;
    margin-top: 6px;
}
body.has-armor #armor-row { display: flex; }
#armor-value {
    font-family: var(--font-numeral);
    font-weight: 600;
    font-size: var(--x30-size-sub);
    line-height: 1;
    color: var(--x30-ally);
    text-shadow: none;
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
    min-width: 1.6em;
    text-align: right;
}
.armor-unit {
    font-family: var(--x30-font-ui);
    font-weight: 600;
    font-size: 9px;
    letter-spacing: var(--x30-track-label);
    color: var(--x30-ink-mid);
}
.armor-track {
    position: relative;
    flex: 1;
    height: 6px;
    overflow: hidden;
    border: none;
    border-radius: var(--x30-r-1);
    background: var(--x30-hairline);
}
#armor-fill {
    position: absolute;
    inset: 0;
    display: block;
    transform-origin: left center;
    transform: scaleX(0);
    will-change: transform;
    background: var(--x30-ally);
    box-shadow: none;
    transition: transform 120ms ease;
}

/* ---- Pickup FF: UDAMAGE powerup buff -------------------------------------------
   A magenta badge + countdown top-centre, plus a full-screen edge tint, both shown
   only while body.udamage-active (Simulator drives the timer text + the class). */
#udamage-badge {
    display: none;
    position: absolute;
    top: 96px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 30;
    align-items: center;
    gap: 8px;
    padding: 5px 12px;
    border: none;
    border-left: var(--x30-edge) solid var(--x30-violet);
    border-radius: var(--x30-r-1);
    background: var(--x30-surface-1);
    box-shadow: var(--x30-el-1);
    pointer-events: none;
}
body.udamage-active #udamage-badge { display: flex; }
#udamage-badge .ud-label {
    font-family: var(--x30-font-ui);
    font-weight: 600;
    font-size: var(--x30-size-micro);
    letter-spacing: var(--x30-track-label);
    color: var(--x30-ink-hi);
    text-shadow: none;
}
#udamage-timer {
    font-family: var(--font-numeral);
    font-weight: 600;
    font-size: var(--x30-size-sub);
    color: var(--x30-ink);
    text-shadow: 0 0 8px rgba(153, 69, 255, 0.45); /* the whitelisted emit */
    font-variant-numeric: tabular-nums;
}
#udamage-tint {
    position: absolute;
    inset: 0;
    z-index: 5;
    pointer-events: none;
    opacity: 0;
    transition: opacity 200ms ease;
    box-shadow: inset 0 0 160px rgba(153, 69, 255, 0.26);
}
body.udamage-active #udamage-tint {
    opacity: 1;
    animation: udamage-pulse 1.4s ease-in-out infinite;
}
/* PERF: static shadow, pulse OPACITY only (box-shadow anim repaints every frame) */
@keyframes udamage-pulse {
    0%, 100% { opacity: 0.75; }
    50%      { opacity: 1; }
}

/* small weapon-name label (Halo puts the class name quietly above the count) */
#weapon-name {
    font-family: var(--x30-font-ui);
    font-weight: 600;
    font-size: var(--x30-size-label);
    line-height: 1;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    color: var(--x30-ink-mid);
    opacity: 1;
    text-shadow: none;
}

#ammo-counter {
    display: flex;
    align-items: baseline;
    justify-content: flex-end;
    gap: 6px;
    margin-top: 1px;
    font-family: var(--font-numeral);
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}
/* dominant magazine count — Barlow Condensed so "30" never reads as "3U". White
   digits ARE the light (chrome diet: data emits, frame doesn't). min-width locks
   the width so 10->9 (digit-count change) doesn't shift the right-aligned layout. */
#magazine-ammo {
    font-family: var(--font-numeral);
    font-size: var(--x30-size-num-l);
    line-height: 0.86;
    letter-spacing: 0.01em;
    font-weight: 600;
    color: var(--x30-ink);
    min-width: 2ch;
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
    text-shadow: none;
    transition: color 120ms ease, text-shadow 120ms ease, opacity 120ms ease;
}
.ammo-separator, #reserve-ammo {
    font-family: var(--font-numeral);
    font-size: var(--x30-size-sub);
    font-weight: 600;
    color: var(--x30-ink-mid);
    opacity: 1;
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
    text-shadow: none;
}
/* BIT 4/P5: keep the separator (faint) so "30" gap "120" doesn't read as "30120";
   drop the reserve baseline hack (#ammo-counter is already align-items:baseline). */
.ammo-separator { display: inline; opacity: 0.4; }
#reserve-ammo { position: relative; }

/* reload label (DESIGN 3.6): shows under the count when reloading. COLOR LAW
   (BIT 4/P1): reload is a NEUTRAL action -> amber, NOT crit-red. Red is reserved
   for empty/damage. Amber pulse (not a hard blink) reads calmly. */
#reload-state {
    min-height: 14px;
    margin-top: 3px;
    color: var(--x30-feedback-gold);
    font-family: var(--x30-font-ui);
    font-size: var(--x30-size-micro);
    font-weight: 600;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    text-shadow: none;
    opacity: 0;
    transform: translateY(2px);
    transition: opacity 120ms ease, transform 120ms ease;
}
/* BIT 4/P2: while reloading, dim the (now-meaningless) magazine number; the steady
   gold label + gold plate edge carry the state (loop law: no pulse). */
#weapon-panel.is-reloading #magazine-ammo { animation: none; opacity: 0.35; }
#weapon-panel.is-reloading #reload-state {
    opacity: 1;
    transform: translateY(0);
}
@keyframes reload-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }

/* AMMO STATE TIERS (BIT 4/P0): amber at <=25% mag, crit-red at empty. Icon color
   follows. JS toggles .is-low-ammo / .is-empty on #weapon-panel. Empty reuses the
   existing hp-pulse keyframes. */
#weapon-panel.is-low-ammo #magazine-ammo {
    color: var(--x30-warn);
    text-shadow: none;
}
#weapon-panel.is-low-ammo #weapon-icon { background-color: var(--x30-warn); }
#weapon-panel.is-empty #magazine-ammo {
    color: var(--x30-threat);
    text-shadow: var(--x30-emit-threat);
    animation: none;
}
#weapon-panel.is-empty #weapon-icon { background-color: var(--x30-threat); }
#weapon-panel.is-empty #reload-state {
    color: var(--x30-threat);
    text-shadow: none;
}
/* empty mag is a critical state — the plate edge goes red (color law) */
#weapon-panel.is-empty { border-left-color: var(--x30-threat); }
/* reloading is neutral: the plate edge goes gold, NOT red (color law) */
#weapon-panel.is-reloading { border-left-color: var(--x30-feedback-gold); }

/* Phase 3: frag-grenade charge indicator. Small plate sitting directly ABOVE the
   weapon panel (bottom-right), matching the Halo-ish HUD chrome. #grenade-count is
   driven by Simulator._updateHud from the networked grenadeCharges. */
/* CHROME DIET (HUD-REWORK-SPEC): flat --hud-scrim plate + ONE amber accent border
   (utility color law). No edge hairline, no clip-path. Flows inside
   #hud-right-group (top of the stack); position:relative, no magic 66px offset. */
#grenade-panel {
    position: relative;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 17px;
    background: var(--x30-surface-0);
    border: none;
    border-left: var(--x30-edge) solid var(--x30-warn);
    border-radius: var(--x30-r-2);
    box-shadow: var(--x30-el-1);
    clip-path: none;
    font-family: var(--font-numeral);
}
#grenade-icon {
    display: block;
    width: 20px; height: 20px;
    color: var(--x30-warn);        /* utility amber, one token */
    text-shadow: none;             /* icon is chrome-adjacent: no emit */
}
#grenade-icon svg { display: block; width: 100%; height: 100%; }
#grenade-count {
    font-family: var(--font-numeral);
    font-size: var(--x30-size-timer);
    font-weight: 600;
    letter-spacing: 0;
    color: var(--x30-ink);
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}
/* capacity pips: flat token pips — full = warn fill, empty = hairline outline.
   JS toggles .is-full per pip + sets #grenade-panel[data-charges]. */
.grenade-pips { display: flex; gap: 4px; }
.grenade-pips .pip {
    width: 7px; height: 14px;
    border-radius: var(--x30-r-1);                          /* 2px */
    box-shadow: inset 0 0 0 1px var(--x30-hairline-strong); /* empty = outline */
    transform: none;
}
.grenade-pips .pip.is-full {
    background: var(--x30-warn);
    box-shadow: none;                                       /* data is flat here */
}
#grenade-panel[data-charges="0"] .pip { box-shadow: inset 0 0 0 1px var(--x30-hairline); }
/* EMPTY STATE (both the legacy .is-empty class and data-charges="0"): drained
   utility is NEUTRAL (muted), never red — red is for critical/damage only. */
#grenade-panel.is-empty,
#grenade-panel[data-charges="0"] {
    border-left-color: rgba(255, 176, 32, 0.25);
    opacity: 0.55;
}
#grenade-panel.is-empty #grenade-count,
#grenade-panel[data-charges="0"] #grenade-count {
    color: var(--x30-ink-low);
    text-shadow: none;
}

/* ---- Death / respawn card (DESIGN 4e) ----
   BIT 6: real scale-slam entry (260ms) that's actually visible; exit is a fast
   120ms opacity-only fade (no motion) so it clears for the respawn countdown.
   visibility toggle keeps the stale card out of the a11y tree between deaths. */
#combat-state {
    position: absolute;
    top: 44%; left: 50%;
    width: min(560px, calc(100vw - 40px));
    transform: translate(-50%, -50%);
    text-align: center;
    z-index: 20;
    visibility: visible;
    filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.6));
    transition: opacity var(--x30-t-exit) var(--x30-ease-exit);
}
#combat-state.combat-state-hidden {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity var(--x30-t-exit) var(--x30-ease-exit), visibility 0s var(--x30-t-exit);
}
#combat-state:not(.combat-state-hidden) {
    animation: combat-state-in var(--x30-t-slow) var(--x30-ease-out) both;
}
@keyframes combat-state-in {
    from { opacity: 0; transform: translate(-50%, calc(-50% + 6px)); }
    to   { opacity: 1; transform: translate(-50%, -50%); }
}
.combat-skull {
    width: 48px; height: 48px;
    opacity: 0.85;
    /* pre-tinted asset (skull-threat.svg, fill = x30-threat): CSS filter tinting
       of the <img> flattened the sculpt into a pale pink blob, so the threat
       color is baked into the SVG. No glow — elevation lives on the container. */
    margin-bottom: 10px;
}
#combat-title {
    font-family: var(--x30-font-display);
    font-weight: 600;
    font-style: normal;
    font-size: clamp(28px, 5vh, 38px);
    line-height: 1;
    letter-spacing: 0.3em;
    text-indent: 0.3em; /* recenters tracked text */
    text-transform: uppercase;
    color: var(--x30-threat);
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
}
#combat-subtitle {
    margin-top: 12px;
    color: var(--x30-ink-mid);
    font-family: var(--x30-font-ui);
    font-size: var(--x30-size-label);
    font-weight: 600;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
}

/* ---- Damage flash (DESIGN 5.1 / BIT 7) ----
   Directional incoming-damage wash: --dmg-angle (rad, JS sets on the hit event)
   drives a conic slice toward the hit direction, over a soft red radial base.
   .hit re-triggers the fade animation. Legacy body.player-hit / body.own-death
   remain wired below for the health-driven paths. */
#damage-flash {
    position: fixed;
    inset: 0;
    z-index: 20;
    pointer-events: none;
    opacity: 0;
    background:
        conic-gradient(from calc(var(--dmg-angle, 0rad) - 0.5rad) at 50% 50%,
            transparent 0rad,
            rgba(255, 59, 70, 0.5) 0.5rad,
            transparent 1rad),
        radial-gradient(ellipse at center,
            rgba(255, 59, 70, 0.15) 0%,
            rgba(120, 10, 16, 0.4) 100%);
    transition: opacity 300ms ease-out;
}
#damage-flash.hit { animation: dmg-flash 180ms ease-out; }
@keyframes dmg-flash { 0% { opacity: 1; } 100% { opacity: 0; } }
/* incoming-damage flash SCALED by damage taken (spec §5.1: 10dmg≈15%, 50+≈35%).
   FragLayer.onDamageTaken sets --damage-wash = clamp(damage/40, 0.35, 1) on the SAME
   hit event, so a chip reads faint and a big hit reads hard. */
body.player-hit #damage-flash { opacity: calc(0.35 * var(--damage-wash, 1)); transition-duration: 0ms; }

/* own death: a stronger red wash that HOLDS until respawn (FragLayer toggles
   body.own-death; --damage-wash scales its punch — preserve the var). */
body.own-death #damage-flash {
    opacity: calc(0.85 * var(--damage-wash, 1));
    transition-duration: 200ms;
    background: radial-gradient(circle at center, rgba(120, 14, 18, 0.35) 0%, rgba(255, 59, 70, 0.72) 120%);
}
/* persistent low-HP edge tint layered on the damage-flash (kept from DESIGN 3.5) */
body.low-health #damage-flash {
    opacity: 0.15;
    transition-duration: 400ms;
    background: radial-gradient(circle at center, transparent 58%, rgba(255, 59, 70, 0.55) 120%);
}

/* ---- Low-HP peripheral vignette (HUD-REWORK-SPEC / color law / BIT 7) ----
   Persistent red edge glow that owns the peripheral read when health is critical.
   JS toggles .active at <=30% HP (and clears it on respawn/death). Slow pulse so it
   reads as "danger", not a static tint. */
#low-hp-vignette {
    position: fixed;
    inset: 0;
    z-index: 5;
    pointer-events: none;
    box-shadow: inset 0 0 100px rgba(255, 59, 70, 0.85);
    opacity: 0;
    transition: opacity 400ms ease;
}
#low-hp-vignette.active {
    opacity: 1;
    animation: low-hp-pulse 1.2s ease-in-out infinite;
}
@keyframes low-hp-pulse {
    0%, 100% { opacity: 0.55; }
    50%      { opacity: 1; }
}

/* ============================================================================
   FRAG LAYER (z20) — kill feed / frag banner / damage arc (FragLayer.js).
   Restyle only; class hooks preserved.
   ============================================================================ */

#kill-feed {
    position: absolute;
    /* Sits BELOW the top-right vitals stack (#hud-right-group) so the two never
       collide now that the weapon/ammo panel owns the top-right corner. */
    top: calc(208px + env(safe-area-inset-top));
    right: calc(var(--sp-5) + env(safe-area-inset-right));
    display: flex;
    flex-direction: column;
    gap: 5px;
    align-items: flex-end;
    max-width: 320px;
    pointer-events: none;
    z-index: 20;
}

/* BIT 9: overflow guard so a long callsign can't blow out the 320px column.
   HUD2030 §5c: surface-1 row, radius 4, soft elevation — no notch, no hairline ring. */
.kill-feed-row {
    display: flex;
    align-items: center;
    gap: 8px;
    height: 26px;
    max-width: 100%;
    padding: 0 10px;
    background: var(--x30-surface-1);
    border-radius: 4px;
    box-shadow: var(--x30-el-1);
    font-family: var(--x30-font-ui);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.01em;
    color: var(--x30-ink);
    white-space: nowrap;
    overflow: hidden;
    opacity: 0;
    transform: translateX(8px);
    transition: opacity var(--x30-t-base) var(--x30-ease-out),
                transform var(--x30-t-base) var(--x30-ease-out);
}
.kill-feed-row.kf-in { opacity: 1; transform: translateX(0); }
.kill-feed-row.kf-out {
    opacity: 0;
    transform: translateX(8px);
    transition-duration: var(--x30-t-exit);
    transition-timing-function: var(--x30-ease-exit);
}

.kill-feed-row .kf-killer, .kill-feed-row .kf-victim {
    max-width: 120px;
    overflow: hidden;
    text-overflow: ellipsis;
}
.kill-feed-row .kf-killer { color: var(--x30-ink); }
.kill-feed-row .kf-victim {
    color: var(--x30-ink-low);
    text-decoration: line-through;
    text-decoration-color: rgba(244, 247, 249, 0.30);
    opacity: 1; /* alpha lives in the token, not opacity */
}
.kill-feed-row .kf-weapon {
    color: var(--x30-ink-mid);
    font-family: var(--x30-font-ui);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    flex: none;
}

/* headshot chip: gold owns confirms (color law); chip is chrome — no emit */
.kill-feed-row .kf-headshot {
    flex: none;
    font-family: var(--x30-font-ui);
    font-size: 9px;
    font-weight: 600;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    color: var(--x30-feedback-gold);
    padding: 1px 5px;
    border-radius: var(--x30-r-1);
    box-shadow: inset 0 0 0 1px rgba(255, 194, 75, 0.35);
}

/* rows involving YOU — split the state edge (§5c): own kills = accent edge,
   own deaths = threat edge. Inset shadow, not border-left, so no 2px text shift. */
.kill-feed-row.kill-feed-mine { background: var(--x30-surface-1); }
.kill-feed-row.kf-own-kill {
    box-shadow: inset var(--x30-edge) 0 0 var(--x30-accent), var(--x30-el-1);
}
.kill-feed-row.kf-own-kill .kf-killer { color: var(--x30-accent); }
.kill-feed-row.kf-own-kill .kf-victim { color: var(--x30-ink-low); }
.kill-feed-row.kf-own-death {
    box-shadow: inset var(--x30-edge) 0 0 var(--x30-threat), var(--x30-el-1);
}
.kill-feed-row.kf-own-death .kf-victim {
    color: var(--x30-threat);
    text-decoration: none; /* "You" reads as alarm, not corpse */
}

/* suicide: a suicide is noise, not signal — no edge, no strike (FragLayer adds
   .kill-feed-suicide on the row). */
.kill-feed-row.kill-feed-suicide {
    border-left: 0;
    background: var(--x30-surface-1);
    box-shadow: var(--x30-el-1);
}
.kill-feed-row.kill-feed-suicide .kf-victim { text-decoration: none; color: var(--x30-ink-low); }

/* ---- Frag banner (HUD2030 §5c) — the kill payoff. Weight through type, not a
   red box: Chakra italic (its one sanctioned home), gold verb + ink name.
   FragLayer adds .fb-in (contract); legacy .frag-banner-show still works. ---- */
#frag-banner {
    position: absolute;
    top: 18%; left: 50%;
    z-index: 21;
    padding: 0;
    text-align: center;
    background: none;
    color: var(--x30-feedback-gold);
    font-family: var(--x30-font-display);
    font-weight: 700;
    font-style: italic;
    font-size: clamp(24px, 4vh, 30px);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    text-shadow: var(--x30-emit-gold),
                 0 1px 2px rgba(0, 0, 0, 0.8);
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transform: translate(-50%, -50%) scale(0.98);
}
#frag-banner .fb-verb { color: var(--x30-feedback-gold); }
#frag-banner .fb-name { color: var(--x30-ink); font-style: italic; }
#frag-banner.fb-in,
#frag-banner.frag-banner-show { animation: frag-banner-pop 2700ms linear both; }
@keyframes frag-banner-pop {                       /* 2700ms: in 280 / hold / out 140 */
    0%     { opacity: 0; transform: translate(-50%, calc(-50% + 6px)) scale(0.98); }
    10.4%  { opacity: 1; transform: translate(-50%, -50%) scale(1); }
    94.8%  { opacity: 1; transform: translate(-50%, -50%) scale(1); }
    100%   { opacity: 0; transform: translate(-50%, -50%) scale(1); }
}

/* ---- Medal / streak callout (HUD2030 §C) — visual voice for the announcer's
   double-kill / spree lines. Sits ABOVE the frag banner so "DOUBLE KILL" +
   "FRAGGED X" stack as one broadcast moment. FragLayer.showMedal drives it. ---- */
#medal-callout {
    position: absolute;
    top: 13%; left: 50%;
    z-index: 21;
    transform: translate(-50%, -50%) scale(0.98);
    font-family: var(--x30-font-display);
    font-weight: 700;
    font-style: italic;
    font-size: clamp(15px, 2.2vh, 18px);
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--x30-feedback-gold);
    text-shadow: var(--x30-emit-gold), 0 1px 2px rgba(0, 0, 0, 0.8);
    opacity: 0;
    pointer-events: none;
    white-space: nowrap;
}
#medal-callout.medal-show { animation: medal-pop 1800ms linear both; }
@keyframes medal-pop {
    0%    { opacity: 0; transform: translate(-50%, calc(-50% + 6px)) scale(0.98); }
    15.5% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
    92.2% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
    100%  { opacity: 0; }
}

/* ---- Pickup toast ("+ FLAK CANNON") — the weapon-acquire cue. Low-center so it
   reads under the crosshair without fighting the top-right vitals or the center frag
   banner. Accent green owns acquisition (gold is reserved for kills — color law);
   Inter 600 uppercase to match the killfeed/label voice. FragLayer.showPickupToast
   drives it (adds .pickup-toast-show). ---- */
#pickup-toast {
    position: absolute;
    top: 62%; left: 50%;
    z-index: 21;
    transform: translate(-50%, -50%) scale(0.98);
    font-family: var(--x30-font-ui);
    font-weight: 600;
    font-size: clamp(13px, 1.9vh, 16px);
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    color: var(--x30-ink);
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
    opacity: 0;
    pointer-events: none;
    white-space: nowrap;
}
#pickup-toast .pt-plus { color: var(--x30-accent); text-shadow: var(--x30-emit-accent); }
#pickup-toast .pt-name { color: var(--x30-ink); }
#pickup-toast.pickup-toast-show { animation: pickup-toast-pop 2000ms linear both; }
@keyframes pickup-toast-pop {                      /* 2000ms: in ~280 / hold / out ~180 */
    0%    { opacity: 0; transform: translate(-50%, calc(-50% + 6px)) scale(0.98); }
    14%   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
    91%   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
    100%  { opacity: 0; }
}

/* directional damage arc canvas */
#damage-arc {
    position: fixed;
    inset: 0;
    width: 100%; height: 100%;
    pointer-events: none;
    z-index: 20;
}

/* dead-state dimming */
body.player-dead #crosshair, body.player-dead #weapon-panel { opacity: 0.18; }
body.player-dead #crosshair { transform: translate(-50%, -50%) scale(0.7); }

/* ============================================================================
   ENTRY / MAIN MENU (z40) — the loading screen (DESIGN 4b)
   ============================================================================ */

#entry-overlay {
    position: fixed;
    inset: 0;
    z-index: 40;
    display: flex;
    flex-direction: column;
    padding: calc(var(--sp-5) + env(safe-area-inset-top)) calc(var(--sp-5) + env(safe-area-inset-right)) calc(var(--sp-5) + env(safe-area-inset-bottom)) calc(var(--sp-5) + env(safe-area-inset-left));
    /* BIT 10/4: no backdrop-blur during the heaviest asset+shader load; the
       gradient scrim is bumped up to compensate. */
    background:
        radial-gradient(1100px 520px at 80% 8%, var(--x30-violet-tint), transparent 60%),
        radial-gradient(circle at 50% 42%, rgba(5, 7, 8, 0.55) 0%, rgba(5, 7, 8, 0.84) 60%, rgba(5, 7, 8, 0.95) 100%);
    transition: opacity 220ms var(--ease-exit), visibility 220ms var(--ease-exit);
}
#entry-overlay:not(.is-visible) {
    visibility: hidden;
    opacity: 0;
    pointer-events: none;
}

/* ---- Menu background hook (Part C). A full-bleed art layer BEHIND the menu content
   (the overlay's own radial scrim still sits under this via #entry-overlay background).
   --menu-bg is `none` today (transparent → the existing scrim shows through); set the
   var (see :root) to drop art. The ::after scrim darkens whatever art loads so the
   plates/title stay legible. ---- */
.entry-bg {
    position: absolute;
    inset: 0;
    z-index: 0;
    background-image: var(--menu-bg);
    background-size: cover;
    background-position: center;
    pointer-events: none;
}
.entry-bg::after {
    content: "";
    position: absolute;
    inset: 0;
    /* legibility scrim over any art: darker at edges, a touch of clearing at center */
    background:
        linear-gradient(180deg, rgba(5,7,8,0.78) 0%, rgba(5,7,8,0.62) 45%, rgba(5,7,8,0.88) 100%),
        radial-gradient(circle at 50% 42%, rgba(5,7,8,0.30) 0%, rgba(5,7,8,0.78) 100%);
}
/* keep the menu content above the art layer */
.entry-header, .entry-content { position: relative; z-index: 1; }

/* header row: logo + wordmark left, ping/build right */
.entry-header {
    display: flex;
    align-items: center;
    gap: 12px;
}
.entry-logo { width: 52px; height: 52px; display: block; }
.entry-wordmark {
    font-family: var(--font-display);
    font-weight: 600;
    font-size: 20px;
    letter-spacing: var(--x30-track-display);
    text-transform: uppercase;
    color: var(--x30-ink-hi);
}
.entry-meta {
    margin-left: auto;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 6px;
    font-family: var(--font-ui);
    font-size: 11px;
    color: var(--x30-ink-mid);
}
.entry-metaline { display: flex; align-items: baseline; gap: 10px; }
.entry-ping { letter-spacing: 0.06em; color: var(--x30-ink); }
.entry-build { font-size: 11px; color: var(--x30-ink-low); opacity: 1; }

/* uplink LED — the ONLY loading surface. Amber pulsing while connecting; flips to a
   steady green tick + "READY" when the triple gate opens (JS sets data-ready). */
.entry-uplink {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-family: var(--x30-font-ui);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    color: var(--x30-warn);
    transition: color 200ms ease;
}
.uplink-led {
    width: 6px; height: 6px;
    border-radius: 50%;
    background: var(--x30-warn);
    box-shadow: none;
    animation: uplink-pulse 1.1s ease-in-out infinite;
}
@keyframes uplink-pulse { 0%,100% { opacity: 0.4; } 50% { opacity: 1; } }
.entry-uplink[data-ready="true"] { color: var(--x30-accent); }
.entry-uplink[data-ready="true"] .uplink-led {
    background: var(--x30-accent);
    box-shadow: none;
    animation: none;
}

/* ---- Fractional progress readout (Part B / RETRO revival). True DSEG7 LED:
   live "NN.NN" digits stacked over the dim "888.88" hardware ghost + a small "%".
   Amber while loading, green when READY, red dashes when the uplink is lost.
   Fixed width so digits never jitter. ---- */
.entry-seg {
    display: inline-flex;
    align-items: baseline;
    gap: 4px;
    --seg-color: var(--seg-amber);
    --seg-glow: var(--seg-glow-amber);
}
.entry-seg[data-state="ready"] { --seg-color: var(--seg-green); --seg-glow: var(--seg-glow-green); }
.entry-seg[data-state="lost"]  { --seg-color: var(--seg-red);   --seg-glow: var(--seg-glow-red); }
.seg-stack {
    position: relative;
    display: inline-block;
    font-family: var(--seg-font);
    font-weight: 700;
    font-size: 14px;   /* base = the LARGE integer digits (gas-pump split size) */
    line-height: 1;
    letter-spacing: 0.04em;
    /* Fixed width locked so 1→3 digit swings and the "--" disconnect state never
       shift the layout (ProgressReadout writes per-frame; no layout thrash).
       DSEG7 cells are wide — reserve the full "888.88". */
    width: 4.4em;
    text-align: right;
    /* baseline-align the large int + small frac so they read as one meter */
    white-space: nowrap;
}
/* split-size sub-spans: integer part LARGE (base size), fractional part SMALL */
.seg-int { font-size: 1em; }
.seg-frac { font-size: 0.72em; }
/* the "888.88" unlit-segment ghost: absolutely stacked UNDER the live digits,
   right-aligned so int/frac columns overlay the live glyphs exactly */
.seg-ghost {
    display: block;
    position: absolute;
    inset: 0;
    text-align: right;
    color: var(--seg-ghost);
    text-shadow: none;
    user-select: none;
}
.seg-live {
    position: relative;
    display: block;
    text-align: right;
    color: var(--seg-color);
    text-shadow: var(--seg-glow);
}
.seg-live .seg-frac,
.seg-live .seg-int { vertical-align: baseline; }
.seg-pct {
    font-family: var(--x30-font-num);
    font-size: 10px;
    font-weight: 600;
    color: var(--seg-color);
    opacity: 1;
    letter-spacing: 0.02em;
}

/* ---- Fake intrusion-log terminal feed (Part D). Bottom-left of #entry-overlay: a
   small monospace panel, green-on-dark phosphor (distinct from the amber uplink LED
   but within the design language). ~4-6 visible lines; older lines dim toward the top
   (JS grades opacity) and new lines slide in from the bottom. Green "ACCESS GRANTED"
   climax on READY, red "UPLINK LOST" on disconnect (tone classes below). aria-hidden
   theater — the accessible status stays #entry-status. ---- */
.hack-feed {
    position: absolute;
    left: calc(var(--sp-5) + env(safe-area-inset-left));
    bottom: calc(var(--sp-5) + env(safe-area-inset-bottom));
    z-index: 1;
    width: min(340px, 42vw);
    padding: 10px 14px 11px;
    background: var(--x30-surface-1);
    border-left: 0;
    border-radius: var(--x30-r-2);
    box-shadow: var(--x30-el-1);
    pointer-events: none;
    font-family: var(--x30-font-ui);
    color: var(--x30-ink-mid);
    overflow: hidden;
}
.hack-feed-head {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 5px;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    color: var(--x30-ink-mid);
}
.hack-dot {
    width: 6px; height: 6px;
    border-radius: 50%;
    background: var(--x30-accent);
    box-shadow: none;
    animation: uplink-pulse 1.1s ease-in-out infinite;
}
/* blinking ellipsis retired — terminal-state rules below now target a hidden node */
.hack-ellipsis { display: none; }
.hack-feed.hack-done .hack-ellipsis span,
.hack-feed.hack-lost .hack-ellipsis span { animation: none; opacity: 1; }
.hack-feed.hack-done .hack-feed-head { color: var(--x30-accent); }
.hack-feed.hack-done .hack-dot { animation: none; opacity: 1; }
.hack-feed.hack-lost .hack-feed-head { color: var(--x30-threat); }
.hack-feed.hack-lost .hack-dot { animation: none; background: var(--x30-threat); box-shadow: none; }
.hack-feed-lines {
    display: flex;
    flex-direction: column;
    gap: 2px;
    /* height reserves ~6 lines; overflow hidden so scroll-up reads as a terminal */
    max-height: 96px;
    overflow: hidden;
}
.hack-line {
    font-size: 11px;
    line-height: 1.3;
    letter-spacing: 0;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    color: var(--x30-ink-mid);
    text-shadow: none;
    animation: hack-line-in var(--x30-t-base) var(--x30-ease-out) both;
}
@keyframes hack-line-in {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
}
/* tone variants (JS adds the class). ok = accent-dim; warn = warn; granted = accent
   climax (live-data emit, ≤8px — allowed); lost = threat. */
.hack-line.hack-ok { color: var(--x30-accent-dim); text-shadow: none; }
.hack-line.hack-warn { color: var(--x30-warn); text-shadow: none; }
.hack-line.hack-granted {
    color: var(--x30-accent);
    font-weight: 600;
    letter-spacing: 0.02em;
    text-shadow: var(--x30-emit-accent);
}
.hack-line.hack-lost {
    color: var(--x30-threat);
    font-weight: 600;
    letter-spacing: 0.02em;
    text-shadow: var(--x30-emit-threat);
}
/* reduced motion: no slide-in / scroll animation — lines still swap on cadence */
.hack-line.no-anim { animation: none; }
@media (prefers-reduced-motion: reduce) {
    .hack-line { animation: none !important; }
    .hack-dot { animation: none !important; }
    .hack-ellipsis span { animation: none !important; opacity: 1 !important; }
}

/* ---- Splash ticker (Part D): one single cycling intrusion line near the corner
   segment readout. Green phosphor, sits just above the splash-seg. ---- */
.splash-hack {
    position: absolute;
    right: calc(20px + env(safe-area-inset-right));
    bottom: calc(46px + env(safe-area-inset-bottom));
    max-width: min(60vw, 360px);
    font-family: var(--x30-font-ui);
    font-size: 11px;
    letter-spacing: 0;
    font-variant-numeric: tabular-nums;
    text-align: right;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    color: var(--x30-ink-low);
    text-shadow: none;
    opacity: 1;
    pointer-events: none;
    animation: hack-line-in var(--x30-t-base) var(--x30-ease-out) both;
}
.splash-hack.hack-ok { color: var(--x30-accent-dim); }
.splash-hack.hack-warn { color: var(--x30-warn); }
.splash-hack.hack-granted { color: var(--x30-accent); font-weight: 600; text-shadow: var(--x30-emit-accent); }
.splash-hack.hack-lost { color: var(--x30-threat); font-weight: 600; text-shadow: var(--x30-emit-threat); }
.splash-hack.no-anim { animation: none; }

.entry-content {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 18px;
    width: 100%;
}

/* ---- Title lockup: emblem-less (logo lives in the header) kicker · wordmark · rule.
   Chakra 700 upright, pure ink; the ONE gradient hairline sits under it. ---- */
.entry-lockup {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 8px;
    margin-bottom: 4px;
}
.lockup-kicker {
    font-family: var(--x30-font-ui);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--x30-accent-dim); /* accent justified: "LIVE" = online status */
}
.lockup-word {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 700;
    font-style: normal;
    font-size: clamp(40px, 6.5vw, 68px);
    line-height: 0.95;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--x30-ink);
    text-shadow: none;
}
/* the ONE brand gradient on the menu screen (grad budget: 1x/screen, hairline) */
.lockup-rule {
    width: 180px;
    max-width: 50vw;
    height: 1px;
    background: var(--x30-grad-brand);
    box-shadow: none;
    transition: opacity 160ms ease;
}
/* Brand-gradient budget: at most one per screen. With a modal open the modal's
   card strip carries it, so the lockup rule yields. */
body:has(#howto-modal:not(.howto-closed)) .lockup-rule,
body:has(.info-modal:not(.info-closed)) .lockup-rule { opacity: 0; }

/* compact callsign field above the plates */
.callsign-field { width: min(420px, 100%); }
.callsign-field .field-label { margin-top: 0; }

/* ---- Vertical plate menu (HUD2030: transparent rows, hairline separators,
   selection = accent-tint wash + 2px accent edge — zero glow). ---- */
.menu-plates {
    display: flex;
    flex-direction: column;
    gap: 0;
    width: min(420px, 100%);
}
.menu-plate + .menu-plate { border-top: 1px solid var(--x30-hairline); }
.menu-plate {
    position: relative;
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    min-height: 56px;
    padding: 0 18px;
    background: transparent;
    border: 0;
    box-shadow: none;
    clip-path: none;
    border-radius: var(--x30-r-2);
    color: var(--x30-ink-hi);
    text-align: left;
    cursor: pointer;
    overflow: hidden;
    transition: background-color var(--x30-t-fast) ease, color var(--x30-t-fast) ease;
}
/* the single state stroke — 2px left edge, color only (no width anim, no glow) */
.plate-edge {
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: var(--x30-edge);
    background: transparent;
    border-radius: var(--x30-r-2) 0 0 var(--x30-r-2);
    transition: background-color var(--x30-t-fast) ease;
}
.plate-body { display: flex; flex-direction: column; gap: 1px; flex: 1; padding-left: 6px; }
.plate-label {
    font-family: var(--font-display);
    font-weight: 600;
    font-style: normal;
    font-size: 18px;
    line-height: 1;
    letter-spacing: var(--x30-track-display);
    text-transform: uppercase;
    color: var(--x30-ink-hi);
}
.plate-sub {
    font-family: var(--x30-font-ui);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    color: var(--x30-ink-low);
}
/* chevron — 1.5px stroke glyph (mask-tinted; matches the icon system) */
.plate-chevron {
    width: 14px; height: 14px;
    flex: none;
    opacity: 0;
    background-color: var(--x30-accent);
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M5 3l4 4-4 4' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M5 3l4 4-4 4' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
    transform: none;
    transition: opacity var(--x30-t-fast) ease, transform var(--x30-t-fast) var(--x30-ease-out);
}
/* SELECTED state (hover / keyboard): tint wash + accent edge, zero glow */
.menu-plate.is-selected,
.menu-plate:hover {
    background: linear-gradient(0deg, var(--x30-accent-tint), var(--x30-accent-tint)), var(--x30-surface-1);
    box-shadow: none;
    transform: none;
}
.menu-plate.is-selected .plate-edge,
.menu-plate:hover .plate-edge { background: var(--x30-accent); }
.menu-plate.is-selected .plate-label,
.menu-plate:hover .plate-label { color: var(--x30-ink); }
.menu-plate.is-selected .plate-sub,
.menu-plate:hover .plate-sub { color: var(--x30-ink-mid); }
.menu-plate.is-selected .plate-chevron,
.menu-plate:hover .plate-chevron {
    opacity: 1;
    transform: translateX(2px);
}
.menu-plate:focus-visible { outline: none; }
.menu-plate:active { transform: scale(.995); }

/* PLAY plate keeps id #enter-arena — primary reads via a permanent accent edge +
   accent label (no gradient: the grad budget is spent on the lockup rule). */
#enter-arena.menu-plate .plate-edge { background: var(--x30-accent); }
#enter-arena.menu-plate .plate-label { color: var(--x30-accent); }
/* PLAY micro-states while the gate is still closed */
#enter-arena.is-arming { cursor: wait; }
#enter-arena.is-arming .plate-label { color: var(--x30-warn); }
#enter-arena.is-arming .plate-label::after {
    content: "";
    animation: arming-dots 1s steps(4, end) infinite;
}
@keyframes arming-dots { 0% { content: ""; } 25% { content: "."; } 50% { content: ".."; } 75% { content: "..."; } }
#enter-arena.is-ready-cta .plate-label { color: var(--x30-accent); text-shadow: var(--x30-emit-accent); animation: none; }
/* .enter-unlock replaces the menu-rise reveal animation on this plate (higher
   specificity), which was holding [data-reveal]'s opacity at 1 via its `both`
   fill — so pin opacity here or the plate vanishes the moment the gate opens. */
#enter-arena.enter-unlock { animation: enter-unlock 420ms var(--ease-snap); opacity: 1; }
@keyframes enter-unlock {
    0%   { transform: none; }
    35%  { transform: scale(1.01); }
    100% { transform: none; }
}

@keyframes card-in { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }

/* ---- Staggered rise-in reveal (body.menu-reveal, set when the splash lifts). Each
   [data-reveal] element rises with an index-based delay. prefers-reduced-motion
   collapses these to instant (see the a11y block). ---- */
[data-reveal] { opacity: 0; }
body.menu-reveal [data-reveal] {
    animation: menu-rise var(--x30-t-base) var(--x30-ease-out) both;
}
.entry-lockup[data-reveal]      { animation-delay: 0ms; }
.callsign-field[data-reveal]    { animation-delay: 30ms; }
.menu-plate[data-reveal]:nth-of-type(1) { animation-delay: 60ms; }
.menu-plate[data-reveal]:nth-of-type(2) { animation-delay: 90ms; }
.menu-plate[data-reveal]:nth-of-type(3) { animation-delay: 120ms; }
.menu-plate[data-reveal]:nth-of-type(4) { animation-delay: 150ms; }
.menu-plate[data-reveal]:nth-of-type(5) { animation-delay: 180ms; }
.menu-actions[data-reveal]      { animation-delay: 210ms; }
@keyframes menu-rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* 1px brand-gradient top hairline — the modal screen's single gradient */
.card-strip {
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 1px;
    background: var(--x30-grad-brand);
}

.entry-kicker, .menu-kicker {
    font-family: var(--x30-font-ui);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    color: var(--x30-ink-mid);
}

.field-label {
    display: block;
    margin: 20px 0 6px;
    font-family: var(--x30-font-ui);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    color: var(--x30-ink-mid);
}

.callsign {
    width: 100%;
    height: 48px;
    padding: 0 14px;
    background: var(--x30-surface-3);
    border: 1px solid var(--x30-hairline-strong);
    border-radius: var(--x30-r-2);
    color: var(--x30-ink);
    font-family: var(--font-display);
    font-size: 16px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    outline: none;
    transition: border-color 120ms ease, box-shadow 120ms ease;
}
.callsign::placeholder { color: var(--x30-ink-low); opacity: 1; }
.callsign:focus { border-color: var(--x30-accent); box-shadow: none; }

/* PLAY plate label (id #enter-arena is now .menu-plate above; the JS-driven text
   lives in this span). Kept as a class since the plate styling handles the rest. */
.enter-label { position: relative; z-index: 2; }

/* #entry-status now lives in the header uplink (.entry-uplink) — its font sizing is
   handled there; this keeps the aria-live label neutral if unwrapped. */
#entry-status { letter-spacing: 0.04em; }

.menu-actions {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    width: min(420px, 100%);
}

/* wallet plate (DESIGN 4b + SPL parameterized) — secondary shape */
.btn-wallet {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: 44px;
    padding: 0 16px;
    background: transparent;
    border: 1px solid var(--x30-hairline-strong);
    border-radius: var(--x30-r-2);
    color: var(--x30-ink-hi);
    font-family: var(--font-display);
    font-size: 13px;
    font-weight: 600;
    letter-spacing: var(--x30-track-display);
    text-transform: uppercase;
    cursor: pointer;
    transition: border-color 120ms ease, color 120ms ease, background-color 120ms ease;
}
.btn-wallet:hover { border-color: var(--x30-accent); color: var(--x30-ink); background: var(--x30-surface-1); }
.btn-wallet.is-pending { border-color: var(--x30-warn); color: var(--x30-warn); }
.wallet-tick {
    width: 6px; height: 6px;
    border-radius: var(--x30-r-pill);
    background: var(--x30-accent);
    box-shadow: none;
}
.wallet-sym { color: var(--x30-ink-mid); font-size: 12px; }

/* ghost button (HOW TO PLAY) */
.btn-ghost {
    height: 44px;
    padding: 0 12px;
    background: transparent;
    border: 0;
    color: var(--x30-ink-mid);
    font-family: var(--font-display);
    font-size: 13px;
    font-weight: 600;
    letter-spacing: var(--x30-track-display);
    text-transform: uppercase;
    cursor: pointer;
    transition: color 120ms ease;
}
.btn-ghost:hover { color: var(--x30-ink); text-decoration: underline; text-underline-offset: 4px; }
/* BIT 11: visible keyboard focus for the ghost button (Tab nav). */
.btn-ghost:focus-visible {
    outline: 2px solid var(--x30-accent);
    outline-offset: 3px;
    color: var(--x30-ink);
    text-decoration: none;
}

.key-hints {
    font-family: var(--font-ui);
    font-size: 11px;
    letter-spacing: 0.04em;
    color: var(--x30-ink-low);
    text-align: center;
}

/* ============================================================================
   GHOST MODALS (z50) — HOW TO PLAY + ISSUANCE / WHITEPAPER / ROADMAP share chrome
   ============================================================================ */
#howto-modal, .info-modal {
    position: fixed;
    inset: 0;
    z-index: 50;
    display: grid;
    place-items: center;
    padding: var(--sp-5);
    /* PERF: no full-screen backdrop-filter — darker scrim compensates */
    background: rgba(5, 7, 8, 0.80);
    transition: opacity var(--x30-t-base) var(--x30-ease-out), visibility var(--x30-t-base);
}
#howto-modal.howto-closed, .info-modal.info-closed {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition-duration: var(--x30-t-exit);
}
#howto-modal[hidden], .info-modal[hidden] { display: none; }
.howto-panel {
    position: relative;
    width: min(460px, 100%);
    max-height: min(80vh, 640px);
    overflow-y: auto;
    padding: 32px 28px 28px;
    background: var(--x30-surface-2);
    box-shadow: var(--x30-el-2);
    clip-path: none;
    border-radius: var(--x30-r-3);
    scrollbar-width: thin;
    scrollbar-color: var(--bg-3) transparent;
}
.howto-panel h2 {
    margin: 6px 0 4px;
    font-family: var(--font-display);
    font-weight: 600;
    font-style: normal;
    font-size: 24px;
    line-height: 1;
    letter-spacing: var(--x30-track-display);
    text-transform: uppercase;
    color: var(--x30-ink-hi);
    padding-bottom: 12px;
    border-bottom: 1px solid var(--x30-hairline);
}

/* ---- Info-modal content (ISSUANCE / WHITEPAPER / ROADMAP) ---- */
.token-plate {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 16px;
    padding: 12px 14px;
    background: var(--x30-surface-3);
    border-radius: var(--x30-r-2);
    clip-path: none;
    box-shadow: none;
}
.token-tick {
    width: 6px; height: 6px;
    border-radius: 50%;
    background: var(--x30-accent);
    box-shadow: none;
}
.token-sym {
    font-family: var(--font-display);
    font-weight: 600;
    font-style: normal;
    font-size: 20px;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--x30-ink);
}
.token-note {
    margin-left: auto;
    font-family: var(--font-ui);
    font-size: 10px;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    color: var(--x30-ink-low);
}
.info-rows { list-style: none; margin: 16px 0 0; padding: 0; }
.info-rows li {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 12px;
    padding: 11px 0;
    border-bottom: 1px solid var(--x30-hairline);
}
.info-rows li span {
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    color: var(--x30-ink-mid);
}
.info-rows li b {
    font-family: var(--x30-font-display);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--x30-ink-hi);
}
.info-note {
    margin: 18px 0 0;
    font-family: var(--font-ui);
    font-size: 14px;
    line-height: 1.55;
    color: var(--x30-ink-mid);
    max-width: 62ch;
}
.info-section { margin-top: 18px; }
.info-section h3 {
    margin: 0 0 4px;
    font-family: var(--x30-font-ui);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    color: var(--x30-ink-mid);
}
.info-section p {
    margin: 0;
    font-family: var(--font-ui);
    font-size: 14px;
    line-height: 1.55;
    color: var(--x30-ink-mid);
    max-width: 62ch;
}
/* roadmap phase list */
.roadmap-list { list-style: none; margin: 18px 0 0; padding: 0; }
.roadmap-list li {
    position: relative;
    padding: 12px 0 12px 16px;
    border-left: 2px solid var(--x30-hairline-strong);
    margin-left: 4px;
}
.roadmap-list li + li { margin-top: 2px; }
.roadmap-list .phase-tag {
    display: block;
    font-family: var(--x30-font-ui);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    color: var(--x30-ink-low);
}
.roadmap-list .phase-name {
    display: block;
    margin-top: 2px;
    font-family: var(--font-display);
    font-weight: 600;
    font-style: normal;
    font-size: 16px;
    letter-spacing: var(--x30-track-display);
    text-transform: uppercase;
    color: var(--x30-ink-hi);
}
.roadmap-list .phase-line {
    display: block;
    margin-top: 3px;
    font-family: var(--font-ui);
    font-size: 12px;
    line-height: 1.45;
    color: var(--x30-ink-mid);
}
.roadmap-list li.is-current {
    border-left-color: var(--x30-accent);
    box-shadow: none;
}
.roadmap-list li.is-current .phase-tag { color: var(--x30-accent); }
.howto-list { list-style: none; margin: 16px 0 0; padding: 0; }
.howto-list li {
    display: flex;
    justify-content: space-between;
    padding: 10px 0;
    border-bottom: 1px solid var(--x30-hairline);
    font-family: var(--font-ui);
    font-size: 14px;
    color: var(--x30-ink-mid);
}
/* BIT 11: keycap chrome — key names read as physical keys, not bold README text. */
.howto-list b {
    display: inline-block;
    padding: 2px 7px;
    margin-right: 4px;
    background: var(--x30-surface-3);
    border: 1px solid var(--x30-hairline-strong);
    border-bottom-width: 1px;
    border-radius: var(--x30-r-1);
    color: var(--x30-ink-hi);
    font-family: var(--font-display);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.06em;
}
.howto-touch { display: none; }

.panel-close {
    position: absolute;
    top: 14px; right: 12px;
    width: 44px; height: 44px;
    background: transparent;
    border: 0;
    color: var(--x30-ink-low);
    font-size: 18px;
    cursor: pointer;
    transition: color 120ms ease;
}
.panel-close:hover { color: var(--x30-ink); }

/* ============================================================================
   SETTINGS panel (z50) — DESIGN 4f
   ============================================================================ */
#settings-menu, #dev-inspector {
    position: fixed;
    z-index: 50;
    color: var(--x30-ink-hi);
    background: var(--x30-surface-2);
    box-shadow: var(--x30-el-2);
    transition: opacity 160ms var(--ease-snap), transform 160ms var(--ease-snap), visibility 160ms var(--ease-snap);
}

/* full-screen scrim behind the settings card while the pause menu is open
   (DESIGN 4f: scrim-heavy + blur). Driven by body.menu-open (already toggled by
   Simulator._openSettings/_closeSettings), so it never clips against the card's
   clip-path. z49 = just under the z50 card. Must live INSIDE #game-shell: the
   shell is position:fixed and forms its own stacking context, so a scrim hung
   off body would paint over the z50 card (its z-index can't escape the shell). */
body.menu-open #game-shell::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 49;
    background: rgba(5, 7, 8, 0.78);
    pointer-events: none;
}

#settings-menu {
    top: 50%; left: 50%;
    width: min(480px, calc(100vw - 32px));
    max-height: 80vh;
    overflow-y: auto;
    padding: 32px 28px 28px;
    clip-path: none;
    border-radius: var(--x30-r-3);
    transform: translate(-50%, -50%);
    /* BIT 12: custom thin scrollbar so the OS default gray bar doesn't ruin the
       notch card. Card background is opaque, so no backdrop-blur needed. */
    scrollbar-width: thin;
    scrollbar-color: var(--bg-3) transparent;
}
#settings-menu::-webkit-scrollbar { width: 6px; }
#settings-menu::-webkit-scrollbar-thumb { background: var(--bg-3); }

/* Always-reachable close (✕). The panel scrolls on small screens and the bottom
   RESUME button can fall off-screen, so this sticks to the panel's top-right and
   stays tappable at any scroll position. 44px touch target. */
.settings-close {
    position: sticky;
    top: 0;
    float: right;
    z-index: 4;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    margin: -10px -8px 0 0;
    padding: 0;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--x30-ink-mid);
    font-size: 30px;
    line-height: 1;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: color 120ms var(--ease-snap), background 120ms var(--ease-snap), transform 120ms var(--ease-snap);
}
.settings-close:hover { background: var(--x30-surface-3); color: var(--x30-ink); }
.settings-close:active { transform: scale(.92); }
.settings-close:focus-visible { outline: 2px solid var(--x30-accent); outline-offset: 2px; }

#settings-menu.settings-closed {
    visibility: hidden;
    opacity: 0;
    transform: translate(-50%, -46%);
    pointer-events: none;
}

#settings-title {
    margin: 6px 0 20px;
    font-family: var(--font-display);
    font-weight: 600;
    font-style: normal;
    font-size: 24px;
    line-height: 1;
    letter-spacing: var(--x30-track-display);
    text-transform: uppercase;
    color: var(--x30-ink-hi);
    padding-bottom: 12px;
    border-bottom: 1px solid var(--x30-hairline);
}

.settings-group {
    margin: 8px 0 6px;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--x30-hairline);
    font-family: var(--font-ui);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    color: var(--x30-ink-mid);
}

.settings-row {
    display: flex;
    flex-direction: column;
    gap: 10px;
    min-height: 48px;
    margin-bottom: 18px;
    padding-top: 8px;
}
.settings-row label {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-family: var(--x30-font-ui);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    color: var(--x30-ink-mid);
}
.settings-row label span {
    font-family: var(--x30-font-num);
    font-weight: 600;
    font-size: 18px;
    color: var(--x30-ink);
    font-variant-numeric: tabular-nums;
}

/* ---- slider (DESIGN 4f / BIT 12): 4px track drawn on the runnable-track with a
   --fill-driven gradient (fill tracks the value), 28px hit area, bone thumb + ink
   outline, hover/active/focus feedback. JS sets --fill = value%. ---- */
.settings-row input[type="range"] {
    appearance: none;
    -webkit-appearance: none;
    width: 100%;
    height: 28px;                 /* hit area; visual track stays 4px */
    background: transparent;
    outline: none;
    cursor: pointer;
    --fill: 50%;
}
.settings-row input[type="range"]::-webkit-slider-runnable-track {
    height: 2px;
    background: linear-gradient(var(--x30-accent), var(--x30-accent)) 0 0 / var(--fill) 100% no-repeat,
                var(--x30-surface-3);
}
.settings-row input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 14px; height: 14px;
    margin-top: -6px;             /* (2 - 14) / 2 */
    border: 0;
    border-radius: 50%;
    background: var(--x30-ink);
    cursor: pointer;
    transition: transform 120ms var(--ease-snap), box-shadow 120ms var(--ease-snap);
}
.settings-row input[type="range"]:hover::-webkit-slider-thumb,
.settings-row input[type="range"]:active::-webkit-slider-thumb { transform: scale(1.15); }
.settings-row input[type="range"]:focus-visible::-webkit-slider-thumb {
    box-shadow: 0 0 0 2px var(--x30-surface-2), 0 0 0 4px var(--x30-accent);
}
/* Firefox: native progress layer, no JS needed there */
.settings-row input[type="range"]::-moz-range-track { height: 2px; background: var(--x30-surface-3); }
.settings-row input[type="range"]::-moz-range-progress {
    height: 2px;
    background: var(--x30-accent);
}
.settings-row input[type="range"]::-moz-range-thumb {
    box-sizing: border-box;
    width: 14px; height: 14px;
    border: 0;
    border-radius: 50%;
    background: var(--x30-ink);
    cursor: pointer;
}

/* BIT 12: FOV readout unit */
#fov-val::after { content: "°"; margin-left: 1px; font-size: 13px; color: var(--x30-ink-low); }

/* toggle checkbox (DESIGN 4f) */
.settings-toggle-row label { justify-content: space-between; }
.settings-row input[type="checkbox"] {
    appearance: none;
    -webkit-appearance: none;
    width: 52px; height: 28px;
    border-radius: var(--r-full);
    background: var(--x30-surface-3);
    position: relative;
    cursor: pointer;
    transition: background 120ms ease;
}
.settings-row input[type="checkbox"]::after {
    content: "";
    position: absolute;
    top: 3px; left: 3px;
    width: 22px; height: 22px;
    border-radius: 50%;
    background: var(--x30-ink-low);
    transition: transform 120ms var(--ease-snap), background 120ms ease;
}
/* checked: accent as the knob dot, not a fill */
.settings-row input[type="checkbox"]:checked { background: var(--x30-surface-3); }
.settings-row input[type="checkbox"]:checked::after { transform: translateX(24px); background: var(--x30-accent); }

/* primary DONE / RESUME button + PLAY-shaped secondary reuse */
#resume-game {
    width: 100%;
    height: 56px;
    margin-top: 4px;
    border: 0;
    border-radius: var(--x30-r-2);
    clip-path: none;
    background: var(--x30-accent-tint);
    box-shadow: inset var(--x30-edge) 0 0 var(--x30-accent);   /* 2px accent left edge */
    color: var(--x30-accent);
    font-family: var(--font-display);
    font-size: 16px;
    font-weight: 600;
    letter-spacing: var(--x30-track-display);
    text-transform: uppercase;
    cursor: pointer;
    transition: background-color 120ms var(--ease-snap), transform 120ms var(--ease-snap);
}
#resume-game:hover { background: rgba(20, 241, 149, 0.14); box-shadow: inset var(--x30-edge) 0 0 var(--x30-accent); filter: none; }
#resume-game:active { transform: translateY(1px) scale(.985); }

/* ---- Dev inspector (dev-only behavior; markup ships) ---- */
#dev-inspector {
    top: calc(var(--sp-5) + env(safe-area-inset-top));
    left: calc(var(--sp-5) + env(safe-area-inset-left));
    width: 292px;
    padding: 20px;
    box-shadow: var(--edge-hairline);
    border-top: 2px solid var(--x30-threat);
    border-radius: var(--r-1);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}
#dev-inspector.dev-closed {
    visibility: hidden;
    opacity: 0;
    transform: translateX(-12px);
    pointer-events: none;
}
.dev-title {
    margin: 0 0 17px;
    color: var(--x30-threat);
    font-family: var(--font-display);
    font-size: 16px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}
.dev-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 10px;
}
.dev-row label { color: var(--x30-ink-mid); font-size: 12px; }
.dev-row input, .dev-row select {
    width: 112px;
    min-height: 30px;
    padding: 4px 7px;
    border: 1px solid var(--line);
    border-radius: var(--r-1);
    outline: none;
    background: var(--bg-2);
    color: var(--bone);
    font-size: 12px;
}

#settings-menu, #dev-inspector { touch-action: auto; }

/* ============================================================================
   FRAG ARENA — TOUCH DESIGN TOKENS v1.0 (single source of truth)
   Smoked glass / machined edge / one-red discipline / physical press.
   ============================================================================ */
:root {
    /* --- surface: smoked glass (alpha base only; NEVER backdrop-filter) --- */
    --tc-glass-hi: rgba(24, 30, 40, 0.78);
    --tc-glass-lo: rgba(11, 14, 20, 0.66);
    --tc-sheen: radial-gradient(120% 90% at 50% 0%, rgba(255, 255, 255, 0.10), rgba(255, 255, 255, 0) 55%);

    /* --- strokes: bright outer rim + dark inner seam = "double stroke" --- */
    --tc-rim: rgba(255, 255, 255, 0.16);
    --tc-rim-strong: rgba(255, 255, 255, 0.28);
    --tc-edge-light: inset 0 1px 0 rgba(255, 255, 255, 0.14);
    --tc-inner-stroke: inset 0 0 0 1px rgba(8, 11, 16, 0.55);
    --tc-drop: 0 6px 18px rgba(0, 0, 0, 0.45);

    /* --- ink & accent discipline (one red family owns fire + danger) --- */
    --tc-ink: var(--x30-ink);
    --tc-ink-dim: var(--x30-ink-mid);
    --tc-accent-fire: var(--x30-threat);   /* one red everywhere */
    --tc-accent-self: var(--x30-ally);
    --tc-accent-util: var(--x30-warn);

    /* --- motion --- */
    --tc-t-press: 90ms;
    --tc-t-state: 160ms;
    --tc-ease-press: cubic-bezier(0.30, 0.00, 0.20, 1.00);
    --tc-ease-out: cubic-bezier(0.22, 1.00, 0.36, 1.00);

    /* --- icon system: 24px grid / stroke 2 / round caps, tinted via mask --- */
    --tc-icon-fire: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M22 12h-4M6 12H2M12 6V2M12 22v-4'/%3E%3C/svg%3E");
    --tc-icon-aim: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3Cpath d='M3 7V5a2 2 0 0 1 2-2h2'/%3E%3Cpath d='M17 3h2a2 2 0 0 1 2 2v2'/%3E%3Cpath d='M21 17v2a2 2 0 0 1-2 2h-2'/%3E%3Cpath d='M7 21H5a2 2 0 0 1-2-2v-2'/%3E%3C/svg%3E");
    --tc-icon-jump: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m17 11-5-5-5 5'/%3E%3Cpath d='m17 18-5-5-5 5'/%3E%3C/svg%3E");
    --tc-icon-reload: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8'/%3E%3Cpath d='M21 3v5h-5'/%3E%3C/svg%3E");
    --tc-icon-switch: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 3 4 7l4 4'/%3E%3Cpath d='M4 7h16'/%3E%3Cpath d='m16 21 4-4-4-4'/%3E%3Cpath d='M20 17H4'/%3E%3C/svg%3E");
    --tc-icon-throw: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='14.8' r='6.9'/%3E%3Cpath d='M8.5 8.6V7h5v1.6'/%3E%3Cpath d='M13.5 7h1.8'/%3E%3Ccircle cx='16.8' cy='7' r='1.5'/%3E%3C/svg%3E");
    --tc-icon-gear: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z'/%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3C/svg%3E");
}

/* ============================================================================
   TOUCH CONTROLS (z30) — DESIGN 3.2 / 4d. Restyle only; ids preserved.
   ============================================================================ */
#touch-controls, #touch-controls * {
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
    -webkit-tap-highlight-color: transparent;
}
#touch-controls {
    position: fixed;
    inset: 0;
    z-index: 30;
    transition: opacity 160ms ease;
}
/* WHY modal-open AND screen-open ARE HERE.
   #game-shell is position:fixed, which creates a STACKING CONTEXT — so every z-index
   inside it (the wallet modal's 50, the screens' 60) is scoped to that subtree, while
   #touch-controls sits at z-index 30 in the ROOT context as a direct child of body.
   30 therefore beats the entire shell, and the touch zones paint over a modal that is
   perfectly visible. Mid-match that made the wallet panel untappable on a phone: you
   could see the field and every tap landed on #touch-move-zone instead.
   Raising #game-shell above 30 would fix the modal and bury the touch controls under
   the HUD, so the layer stands down instead — the same thing menu-open already does
   for the settings panel. */
body:not(.arena-entered) #touch-controls,
body.menu-open #touch-controls,
body.modal-open #touch-controls,
body.screen-open #touch-controls,
body.player-dead #touch-controls {
    opacity: 0;
    pointer-events: none;
}

#touch-move-zone, #touch-look-zone { position: absolute; top: 0; bottom: 0; width: 50%; }
#touch-move-zone { left: 0; }
#touch-look-zone { right: 0; }

/* floating joystick — smoked-glass puck. No backdrop-filter: the WebGL canvas
   repaints every frame, so blur re-samples it per frame. Glass is faked with
   alpha base + sheen + dual stroke + inner falloff. */
#touch-joy-base {
    position: absolute;
    z-index: 31;
    display: none;
    width: 120px; height: 120px;
    margin: -60px 0 0 -60px;
    border: 1.5px solid var(--tc-rim);
    border-radius: 50%;
    background:
        var(--tc-sheen),
        linear-gradient(165deg, var(--tc-glass-hi), var(--tc-glass-lo));
    box-shadow:
        var(--tc-edge-light),
        var(--tc-inner-stroke),
        inset 0 -12px 20px rgba(0, 0, 0, 0.30),
        var(--tc-drop);
    pointer-events: none;
}
#touch-joy-base.active { display: block; }
/* machined-dial detail: travel guide ring + dead-zone dot (paint-only, free) */
#touch-joy-base::before {
    content: '';
    position: absolute;
    inset: 16px;
    border: 1px solid rgba(255, 255, 255, 0.07);
    border-radius: 50%;
}
#touch-joy-base::after {
    content: '';
    position: absolute;
    top: 50%; left: 50%;
    width: 4px; height: 4px;
    margin: -2px 0 0 -2px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.20);
}
#touch-joy-knob {
    position: absolute;
    top: 50%; left: 50%;
    width: 52px; height: 52px;
    border: 1px solid var(--tc-rim-strong);
    border-radius: 50%;
    background:
        radial-gradient(circle at 50% 30%, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0) 60%),
        linear-gradient(165deg, rgba(36, 43, 56, 0.94), rgba(17, 21, 29, 0.90));
    box-shadow:
        var(--tc-edge-light),
        0 0 0 1px rgba(8, 11, 16, 0.50),
        0 4px 12px rgba(0, 0, 0, 0.50),
        0 0 12px rgba(127, 233, 255, 0.20);
    transform: translate(-50%, -50%);
}

/* thumb buttons — smoked-glass pucks. Visual weight ladder (size / opacity /
   rim strength): FIRE 96px .96 > secondaries 60-64px .88 > GEAR 48px .62.
   Icons live in ::before (SVG mask tinted by currentColor); the legacy unicode
   glyph is hidden via font-size:0. No backdrop-filter (WebGL repaint cost). */
#touch-fire, #touch-aim, #touch-jump, #touch-reload, #touch-switch, #touch-throw, #touch-gear {
    position: absolute;
    z-index: 32;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1.5px solid var(--tc-rim);
    border-radius: 50%;
    background:
        var(--tc-sheen),
        linear-gradient(165deg, var(--tc-glass-hi), var(--tc-glass-lo));
    color: var(--tc-ink);
    font-size: 0;               /* legacy unicode glyph hidden; icon lives in ::before */
    box-shadow:
        var(--tc-edge-light),
        var(--tc-inner-stroke),
        inset 0 -10px 18px rgba(0, 0, 0, 0.28),
        var(--tc-drop);
    opacity: 0.88;
    transition:
        transform var(--tc-t-press) var(--tc-ease-press),
        box-shadow var(--tc-t-press) var(--tc-ease-press),
        border-color var(--tc-t-state) var(--tc-ease-out),
        opacity var(--tc-t-state) var(--tc-ease-out),
        color var(--tc-t-state) var(--tc-ease-out);
}
/* icon layer — SVG mask tinted by currentColor: state recolors are free */
#touch-fire::before, #touch-aim::before, #touch-jump::before, #touch-reload::before,
#touch-switch::before, #touch-throw::before, #touch-gear::before {
    content: '';
    position: absolute;
    inset: 0;
    background-color: currentColor;
    -webkit-mask-image: var(--tc-icon);
    -webkit-mask-repeat: no-repeat;
    -webkit-mask-position: center;
    -webkit-mask-size: var(--tc-icon-size, 44%);
            mask: var(--tc-icon) no-repeat center / var(--tc-icon-size, 44%);
}
#touch-fire   { --tc-icon: var(--tc-icon-fire);   --tc-icon-size: 40%; }
#touch-aim    { --tc-icon: var(--tc-icon-aim); }
#touch-jump   { --tc-icon: var(--tc-icon-jump); }
#touch-reload { --tc-icon: var(--tc-icon-reload); }
#touch-switch { --tc-icon: var(--tc-icon-switch); }
#touch-throw  { --tc-icon: var(--tc-icon-throw); }
#touch-gear   { --tc-icon: var(--tc-icon-gear);   --tc-icon-size: 46%; }

/* :active — physical press: sink in 90ms, steady 1px accent ring, no glow spam */
#touch-aim.active, #touch-jump.active, #touch-reload.active, #touch-switch.active, #touch-gear.active {
    border-color: var(--tc-accent-self);
    color: #ffffff;
    box-shadow:
        inset 0 2px 8px rgba(0, 0, 0, 0.42),
        var(--tc-inner-stroke),
        0 0 0 1px rgba(127, 233, 255, 0.30),
        0 2px 8px rgba(0, 0, 0, 0.40);
    opacity: 1;
    transform: scale(0.90);
}

/* FIRE — primary action (96px, heaviest weight). Red rim + red-tinted plate;
   the icon stays accent-red at rest, white on press. */
#touch-fire {
    right: calc(28px + env(safe-area-inset-right));
    bottom: calc(72px + env(safe-area-inset-bottom));
    width: 96px; height: 96px;
    border-color: rgba(255, 59, 70, 0.60);
    background:
        radial-gradient(120% 90% at 50% 0%, rgba(255, 59, 70, 0.14), rgba(255, 59, 70, 0) 55%),
        linear-gradient(165deg, rgba(46, 22, 24, 0.80), rgba(20, 12, 14, 0.70));
    color: var(--tc-accent-fire);
    opacity: 0.96;
}
#touch-fire.active {
    border-color: var(--x30-threat);
    color: #ffffff;
    box-shadow:
        inset 0 2px 10px rgba(60, 8, 6, 0.55),
        var(--tc-inner-stroke),
        0 0 0 1px rgba(255, 59, 70, 0.45),
        0 2px 10px rgba(0, 0, 0, 0.45);
    opacity: 1;
    transform: scale(0.90);
}
/* thumb-arc secondaries — rects unchanged from the prior map:
   fire x[28..124] y[72..168] / reload x[44..104] y[178..238] /
   jump x[140..204] y[168..232] / switch x[118..178] y[254..314] /
   throw x[210..270] y[168..228] / gear top-right corner */
#touch-reload {
    right: calc(44px + env(safe-area-inset-right));
    bottom: calc(178px + env(safe-area-inset-bottom));
    width: 60px; height: 60px;
}
#touch-jump {
    right: calc(140px + env(safe-area-inset-right));
    bottom: calc(168px + env(safe-area-inset-bottom));
    width: 64px; height: 64px;
}
#touch-switch {
    right: calc(118px + env(safe-area-inset-right));
    bottom: calc(254px + env(safe-area-inset-bottom));
    width: 60px; height: 60px;
}
/* AIM (ADS) — now in the base selector + given a rect. x[140..200] y[72..132]:
   16px clear of fire (x<=124), 36px clear of jump (y>=168). */
#touch-aim {
    right: calc(140px + env(safe-area-inset-right));
    bottom: calc(72px + env(safe-area-inset-bottom));
    width: 60px; height: 60px;
}
/* ADS selected — persistent steady ring (needs JS to toggle .is-selected;
   the .active press state above already fires on touch regardless). */
#touch-aim.is-selected {
    border-color: var(--tc-accent-self);
    color: var(--tc-accent-self);
    opacity: 1;
    box-shadow:
        var(--tc-edge-light),
        var(--tc-inner-stroke),
        0 0 0 2px rgba(127, 233, 255, 0.35),
        var(--tc-drop);
}
/* THROW — frag grenade, amber ordnance tint */
#touch-throw {
    right: calc(210px + env(safe-area-inset-right));
    bottom: calc(168px + env(safe-area-inset-bottom));
    width: 60px; height: 60px;
    border-color: rgba(255, 184, 77, 0.45);
    color: var(--tc-accent-util);
}
#touch-throw.active {
    border-color: var(--tc-accent-util);
    color: #ffffff;
    box-shadow:
        inset 0 2px 8px rgba(0, 0, 0, 0.42),
        var(--tc-inner-stroke),
        0 0 0 1px rgba(255, 184, 77, 0.35),
        0 2px 8px rgba(0, 0, 0, 0.40);
    opacity: 1;
    transform: scale(0.90);
}
/* GEAR — quietest rung of the ladder */
#touch-gear {
    top: calc(12px + env(safe-area-inset-top));
    right: calc(12px + env(safe-area-inset-right));
    width: 48px; height: 48px;
    opacity: 0.62;
}

/* --- STATE SYSTEM: cooldown ring + disabled ------------------------------- */
/* COOLDOWN — reload / grenade recharge. JS hook:
   btn.classList.add('is-cooling'); btn.style.setProperty('--cd', remaining/total); */
@property --cd { syntax: '<number>'; initial-value: 0; inherits: false; }

#touch-reload.is-cooling, #touch-throw.is-cooling { color: var(--tc-ink-dim); }
#touch-reload.is-cooling::after, #touch-throw.is-cooling::after {
    content: '';
    position: absolute;
    inset: -5px;
    border-radius: 50%;
    background: conic-gradient(var(--_cd-accent, var(--tc-accent-self)) calc(var(--cd, 0) * 1turn), rgba(255, 255, 255, 0.07) 0);
    -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 3.5px), #000 calc(100% - 3px));
            mask: radial-gradient(farthest-side, transparent calc(100% - 3.5px), #000 calc(100% - 3px));
}
#touch-throw.is-cooling::after { --_cd-accent: var(--tc-accent-util); }

/* DISABLED — no ammo / no grenade: desaturated, silent, non-interactive */
#touch-controls .is-disabled {
    opacity: 0.30;
    filter: saturate(0);
    pointer-events: none;
}

/* ============================================================================
   RESPONSIVE — DESIGN 6. Pointer-coarse governs control scheme; width governs
   sizing.
   ============================================================================ */

/* mobile HUD sizing: compact clusters, feed shrinks, key hints hidden */
@media (max-width: 767px), (pointer: coarse) {
    #top-hud {
        top: calc(var(--sp-3) + env(safe-area-inset-top));
        left: calc(var(--sp-4) + env(safe-area-inset-left));
        right: calc(56px + env(safe-area-inset-right));
    }
    .brand-mark { width: 27px; height: 27px; }
    .brand-name { display: none; }
    .match-strip { min-height: 26px; padding: 0 9px; font-size: 11px; }
    /* the centered scoreboard shares the top row with the match-strip on
       narrow screens — drop it below the strip (strip height + gap) */
    #tdm-scoreboard { top: calc(10px + 34px + env(safe-area-inset-top)); }

    /* ------------------------------------------------------------------ */
    /* SLICK-AF MOBILE LAYOUT — vital readouts live in the bottom-CENTER   */
    /* safe corridor, flanking the crosshair. Both thumb zones (left move, */
    /* right fire-cluster) stay unobstructed. Flat plates, data emits      */
    /* light (chrome diet). See overlap math in the note at end of block.  */
    /* ------------------------------------------------------------------ */

    /* Halo-CE TOP layout on mobile too: weapon/ammo + grenades stack in a real
       flex column anchored top-RIGHT, just below the strip and the top-right
       gear button (#touch-gear ends ~60px down, so 64px clears it). The bottom
       of the screen is now fully free for the thumb zones. */
    #hud-right-group {
        position: absolute;
        top: calc(64px + env(safe-area-inset-top));
        right: calc(var(--sp-4) + env(safe-area-inset-right));
        bottom: auto;
        left: auto;
        display: flex;
        flex-direction: column;
        align-items: flex-end;
        gap: 8px;
    }

    /* HP — top-LEFT, just below the strip. Flat scrim plate, teal accent on the
       (now outer) left edge; only the number + fill glow. */
    #health-panel {
        position: absolute;
        top: calc(64px + env(safe-area-inset-top));
        left: calc(var(--sp-4) + env(safe-area-inset-left));
        right: auto;
        bottom: auto;
        min-width: 0;
        padding: 7px 15px 8px 12px;
        clip-path: none;
        background: var(--hud-scrim, var(--scrim));
        border: none;
        border-left: var(--x30-edge) solid var(--hud-vitals, var(--sol-teal));
        box-shadow: 0 3px 12px rgba(0, 0, 0, 0.45);
        pointer-events: none;
    }
    .health-readout { justify-content: flex-start; }
    #health-value {
        font-family: var(--font-numeral, var(--font-hud));
        font-size: 42px;
        font-variant-numeric: tabular-nums;
    }
    .health-unit { display: none; }
    .health-track { width: 92px; height: 6px; margin-top: 5px; margin-left: 0; }
    .hud-skull { width: 24px; height: 24px; }

    /* WEAPON / AMMO — bottom-center-RIGHT, left-aligned toward the crosshair.
       Pure DISPLAY (no pointer-events; reload/switch are touch buttons).
       Chrome diet: kill the Halo glow box-shadow + corner brackets + icon
       drop-shadow; keep a single self-cyan accent on the crosshair-facing
       (left) edge. Flat scrim plate. */
    #weapon-panel {
        position: relative;
        top: auto;
        left: auto;
        right: auto;
        bottom: auto;
        min-width: 0;
        display: flex;
        flex-direction: row;
        align-items: center;
        gap: 10px;
        padding: 7px 13px 8px 15px;
        text-align: left;
        clip-path: none;
        background: var(--hud-scrim, var(--scrim));
        border: none;
        border-left: var(--x30-edge) solid var(--hud-self, var(--halo-cyan-deep));
        box-shadow: 0 3px 12px rgba(0, 0, 0, 0.45);
        pointer-events: none;
    }
    #weapon-panel::before,
    #weapon-panel::after { display: none; }
    #weapon-icon {
        width: 52px; height: 28px;
        background-color: var(--x30-ally-dim);
        filter: none;
    }
    .weapon-readout { align-items: flex-start; }
    #weapon-name {
        display: block;
        font-size: 12px;
        letter-spacing: 0.12em;
        color: var(--x30-ink-mid);
        text-shadow: none;
    }
    #ammo-counter { margin-top: 0; gap: 4px; }
    #magazine-ammo {
        font-family: var(--font-numeral, var(--font-hud));
        font-size: 46px;
        color: #ffffff;
        font-variant-numeric: tabular-nums;
    }
    .ammo-separator, #reserve-ammo {
        font-family: var(--font-numeral, var(--font-hud));
        font-size: 20px;
        font-variant-numeric: tabular-nums;
    }
    /* keep the .is-reloading reveal (base #reload-state / .is-reloading rules
       still apply); just tighten it for the compact center plate */
    #reload-state { min-height: 11px; margin-top: 1px; font-size: 9px; }

    /* colour law: when health is critical, red must OWN the peripheral read —
       dim the calm cyan weapon accent so it can't out-shout the red. */
    body.low-health #weapon-panel { border-left-color: rgba(127, 233, 255, 0.35); }
    body.low-health #weapon-icon,
    body.low-health #weapon-name { opacity: 0.6; }

    /* GRENADE — stacks just ABOVE the ammo plate inside the top-right column.
       Flat plate, amber utility accent, chrome diet (no clip/glow). */
    #grenade-panel {
        position: relative;
        top: auto;
        left: auto;
        right: auto;
        bottom: auto;
        min-width: 0;
        padding: 5px 12px 5px 10px;
        clip-path: none;
        background: var(--hud-scrim, var(--scrim));
        border: none;
        border-left: var(--x30-edge) solid var(--hud-util, #ffb84d);
        box-shadow: 0 3px 12px rgba(0, 0, 0, 0.45);
        pointer-events: none;
    }
    #grenade-icon { color: var(--x30-warn); text-shadow: none; }
    #grenade-count {
        font-family: var(--font-numeral, var(--font-hud));
        font-variant-numeric: tabular-nums;
    }

    /* ------------------------------------------------------------------ */
    /* OVERLAP CHECK (thumb clearance). x = px from the screen's RIGHT     */
    /* edge; y = px from the BOTTOM edge. safe-area insets shift every     */
    /* element equally, so they cancel and are omitted below.             */
    /*                                                                    */
    /* Right thumb-cluster (buttons, from right edge):                    */
    /*   fire   x[28..124]  y[72..168]                                    */
    /*   reload x[44..104]  y[178..238]                                   */
    /*   jump   x[140..204] y[168..232]                                   */
    /*   throw  x[210..270] y[168..228]                                   */
    /*   switch x[118..178] y[254..314]                                   */
    /*   -> whole cluster lives at x ≤ 270 (right half only), y ≥ 72.     */
    /*                                                                    */
    /* Readouts anchor to CENTER and grow LEFTWARD (right-anchored):      */
    /*   ammo/grenade right edge = 50% - 4  (just left of crosshair)      */
    /*   HP right edge          = 50% - 132                              */
    /* Their nearest-to-cluster point is the ammo/grenade RIGHT edge at   */
    /* abs-x = center-4, i.e. x_from_right = screen - (center-4)          */
    /*        = center + 4  (since center = screen/2).                    */
    /* No overlap requires center+4 > 270  ->  center > 266  ->           */
    /* screen width > 532px. The narrowest supported landscape phone is   */
    /* ~568px CSS px (iPhone SE landscape); there center=284, so the ammo */
    /* right edge sits at x=288 from the right — 18px clear of the        */
    /* cluster's x≤270. Wider phones only widen the gap (640->54px,       */
    /* 844->156px). Verified rects (568px, W_ammo≈120, W_hp≈100):         */
    /*   AMMO abs-x[160..280]  clusterLeft abs-x=298  -> gap 18px  OK     */
    /*   HP   abs-x[ 52..152]  (well left of center)  -> no cluster  OK   */
    /* Vertically the readouts sit at bottom y[16..~70] (ammo) and        */
    /* y[62..~96] (grenade); but they never enter the cluster's x-band    */
    /* (x≤270 is the RIGHT side; readouts are center/left), so the y      */
    /* overlap with fire (y≥72) is moot. HP & ammo & grenade collide with */
    /* NONE of fire/reload/switch/jump/throw on any supported width.      */
    /*                                                                    */
    /* Left MOVE zone = left screen half; the joystick is FLOATING        */
    /* (spawns at the touch point, pointer-events:none when idle) — the   */
    /* HP plate in the lower-left-of-center is the conventional mobile-   */
    /* shooter vitals slot and does not block the move thumb.             */
    /* ------------------------------------------------------------------ */

    /* crosshair is now an inline SVG sized in user units; the spread-true parts
       already scale with viewport height (JS), so no fixed px override here —
       just a slight global shrink so the base ticks aren't chunky on phones */
    #crosshair { transform: translate(-50%, -50%) scale(0.875); }

    /* kill feed drops BELOW the top-right vitals stack (grenade+weapon) so it
       no longer shares the corner the ammo plate now occupies. */
    #kill-feed { top: calc(206px + env(safe-area-inset-top)); gap: 4px; max-width: 210px; } /* clear grenade plate (group bottom ~195) */
    .kill-feed-row { height: 24px; padding: 0 8px; font-size: 12px; }
    .kill-feed-row .kf-weapon { display: none; }
    #kill-feed .kill-feed-row:nth-child(n+4) { display: none; }

    #frag-banner { font-size: 22px; }

    /* === AAA type & plate pass (override; later in the same media query) === */
    #health-panel, #weapon-panel, #grenade-panel {
        background: var(--x30-surface-0);
        box-shadow: var(--x30-el-1);
        border-radius: var(--x30-r-2);
    }
    #health-value {
        font-weight: 600;
        letter-spacing: -0.01em;
        text-shadow: 0 1px 2px rgba(0, 0, 0, 0.60);
    }
    #magazine-ammo {
        font-weight: 600;
        letter-spacing: -0.01em;
        text-shadow: 0 1px 2px rgba(0, 0, 0, 0.60);
    }
    .ammo-separator, #reserve-ammo { font-weight: 500; color: var(--x30-ink-mid); }
    #weapon-name { font-weight: 600; font-size: 10px; letter-spacing: 0.20em; }
    #reload-state { font-weight: 600; letter-spacing: 0.28em; }
    #grenade-count { font-weight: 700; }
    .match-strip { font-weight: 600; letter-spacing: 0.08em; }
    /* crosshair: base dual ink halo (styles line ~329) already handles bright
       skies — no weaker mobile override (HUD2030 Surface D §2). */
}

/* menu reflow on mobile (DESIGN 4b mobile) */
@media (max-width: 767px) {
    #entry-overlay { padding-left: calc(var(--sp-4) + env(safe-area-inset-left)); padding-right: calc(var(--sp-4) + env(safe-area-inset-right)); }
    .entry-wordmark { font-size: 16px; }
    .entry-logo { width: 44px; height: 44px; }
    .entry-content { gap: 14px; }
    .callsign-field, .menu-plates, .menu-actions { width: 100%; }
    .plate-label { font-size: 17px; }
    #resume-game { height: 52px; font-size: 18px; }
    .menu-actions { flex-wrap: wrap; }
    #settings-title { font-size: 26px; }
    #combat-title { font-size: clamp(26px, 8vw, 40px); }

    /* MENU intrusion feed: on phones the full-width centered plates + footer fill the
       column, leaving no clear lower-left gutter — so the menu feed is HIDDEN here to
       avoid colliding with the plates/actions/footer. The accessible status stays in
       the header (#entry-status), and the SPLASH ticker (below) keeps the flavor on
       the boot screen. (Desktop keeps the full panel.) */
    .hack-feed { display: none; }
    .splash-hack { font-size: 10px; bottom: calc(44px + env(safe-area-inset-bottom)); }
}

/* touch → show the touch how-to variant */
@media (pointer: coarse) {
    .howto-desktop { display: none; }
    .howto-touch { display: block; }
    .key-hints { display: none; } /* WASD hints are meaningless on touch */
}

/* hide player-count first on very narrow screens */
@media (max-width: 460px) {
    #player-count, .status-divider { display: none; }
}

/* Short landscape (phones held sideways): the vertical plate menu is taller than
   the viewport, so its lower plates + the wallet/how-to actions were falling
   below the fold with no way to reach them. Make the overlay SCROLL and top-align
   its content so every control stays reachable; drop the decorative title lockup
   to reclaim vertical space and tighten the plate rhythm. */
@media (max-height: 560px) and (pointer: coarse) {
    #entry-overlay {
        overflow-y: auto;
        overflow-x: hidden;
        -webkit-overflow-scrolling: touch;
    }
    .entry-content {
        flex: 1 0 auto;
        justify-content: flex-start;
        gap: 10px;
        padding-bottom: calc(12px + env(safe-area-inset-bottom));
    }
    .entry-lockup { display: none; }
    .menu-plates { gap: 8px; }
    .menu-plate { min-height: 46px; }
    .menu-actions { margin-top: 2px; }
}

/* very short landscape phones: trim vertical spacing */
@media (max-height: 400px) {
    .key-hints { display: none; }
    .entry-content { gap: 8px; }
    .lockup-word { font-size: clamp(28px, 5vh, 44px); }
    .menu-plate { min-height: 48px; }
    .plate-label { font-size: 17px; }
}

/* ============================================================================
   SPLASH (z60) — DESIGN 4a / 5.4. Logo draw-in over --bg-0; auto-advances.
   ============================================================================ */
#splash {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: radial-gradient(circle at 50% 46%, #090C10 0%, var(--x30-void) 70%);
    transition: opacity 220ms var(--ease-exit), transform 220ms var(--ease-exit);
}
#splash.splash-out { opacity: 0; transform: scale(1.04); pointer-events: none; }
/* BIT 13: dedicated leaving state that releases input immediately during fade. */
#splash.is-leaving { pointer-events: none; animation: splash-out 0.22s ease-out forwards; }
@keyframes splash-out { to { opacity: 0; visibility: hidden; } }

/* ---- Console-style partner boot cards (Part A). One card is `.is-active` at a time;
   cards crossfade (~250ms). The stage holds them stacked so the fade is a true
   cross-dissolve, not a layout jump. ---- */
.splash-stage {
    position: relative;
    width: min(92vw, 560px);
    /* fixed height so the crossfade never reflows the card around it. Raised
       340->392 when the Solana card gained the Toly mark and the ident moved onto a
       card of its own: cards are absolutely positioned and centred, so a taller
       stage only ADDS clearance — it cannot shift the shorter cards' composition.
       392->432 when Toly moved to the head of the Solana card and grew. */
    height: 432px;
    display: grid;
    place-items: center;
}
.splash-card {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 26px;
    text-align: center;
    opacity: 0;
    visibility: hidden;
    transform: scale(0.985);
    /* exits leave faster than entrances (crossfade tail) */
    transition: opacity var(--x30-t-exit) var(--x30-ease-out), transform var(--x30-t-exit) var(--x30-ease-out), visibility 0s var(--x30-t-exit);
}
.splash-card.is-active {
    opacity: 1;
    visibility: visible;
    transform: scale(1);
    transition: opacity var(--x30-t-base) var(--x30-ease-out), transform var(--x30-t-base) var(--x30-ease-out);
}
.splash-kicker {
    font-family: var(--x30-font-ui);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.24em;
    text-transform: uppercase;
    color: var(--x30-ink-low);
}
.splash-logos {
    display: flex;
    align-items: center;
    justify-content: center;
}
.splash-logo {
    width: min(40vw, 320px);
    height: auto;
    max-height: 120px;
    display: block;
    object-fit: contain;
}
/* mark-only logos (tensor / pump pill) sit beside a styled wordmark */
.splash-logo-mark {
    width: auto;
    height: clamp(48px, 9vw, 84px);
    max-height: 84px;
}
.splash-pair-item {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 16px;
}
.splash-wordmark-txt {
    font-family: var(--font-display);
    font-weight: 600;
    font-size: clamp(28px, 5vw, 52px);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--x30-ink);
}
/* paired cards: Metaplex + Helius, and Tensor + Magic Eden. Side by side; stack
   on narrow screens. */
.splash-logos-pair {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: clamp(28px, 6vw, 72px);
    flex-wrap: wrap;
}
/* Two FULL wordmarks on one card (Metaplex + Helius) — a bare .splash-logo is
   min(40vw, 320px), so two-up would want 80vw and overflow the stage. Constrained
   only in the paired context; the single-logo cards are untouched. */
.splash-logos-pair .splash-logo {
    width: min(30vw, 236px);
    max-height: 84px;
}

/* TOLY rides the Solana card. Height-driven, not width-driven: the stage's
   VERTICAL budget is what actually runs out (hard 392px, less on phones), and a
   width-driven mark blows through it on a narrow tall viewport. The Solana
   wordmark above him is capped tighter here than elsewhere so the pair fits
   without growing the stage again. */
.splash-toly {
    width: auto;
    /* LEADS the card now, so he carries it: ~33% taller than when he sat under the
       wordmark. Still height-driven, not width-driven — the stage's VERTICAL budget
       is the one that runs out (432px, less on phones), and a width-driven mark
       blows through it on a narrow tall viewport. */
    height: clamp(158px, 32vh, 262px);
    max-width: 72vw;
    display: block;
    object-fit: contain;
    filter: drop-shadow(0 10px 26px rgba(0, 0, 0, 0.6));
}
/* tighter gap + a smaller wordmark than the other cards, so the taller mark and its
   caption both fit without growing the stage again */
.splash-card[data-card="solana"] { gap: 12px; }
.splash-card[data-card="solana"] .splash-logo { max-height: 68px; }

/* GATE card prompt + sub-caption */
.splash-gate {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
}
.splash-prompt {
    font-family: var(--font-display);
    font-size: 14px;
    font-weight: 600;
    letter-spacing: 0.28em;
    text-transform: uppercase;
    color: var(--x30-ink);
    /* sanctioned loop: "waiting for input" breath, not a hard blink */
    animation: x30-breathe 1.6s ease-in-out infinite;
}
.splash-subcap {
    font-family: var(--font-ui);
    font-size: 11px;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    color: var(--x30-ink-low);
}
@keyframes x30-breathe { 0%,100% { opacity: 1; } 50% { opacity: 0.45; } }

/* small % echo in the splash corner (Part B / RETRO). DSEG7 LED digits over the
   "888.88" ghost + a 2px accent progress hairline under the digits (width =
   load %, JS sets --seg-fill, throttled to whole-percent changes — not a
   per-frame style write). */
.splash-seg {
    position: absolute;
    right: calc(20px + env(safe-area-inset-right));
    bottom: calc(20px + env(safe-area-inset-bottom));
    display: inline-flex;
    align-items: baseline;
    gap: 3px;
    font-family: var(--seg-font);
    font-weight: 700;
    letter-spacing: 0.04em;
    color: var(--seg-amber);
    opacity: 1;
}
/* 2px meter under the digits: hairline track + accent fill */
.splash-seg::after {
    content: "";
    position: absolute;
    left: 0; right: 0; bottom: -6px;
    height: 2px;
    background: var(--x30-hairline);
}
.splash-seg::before {
    content: "";
    position: absolute;
    left: 0; bottom: -6px;
    height: 2px;
    width: var(--seg-fill, 0%);
    max-width: 100%;
    background: var(--x30-accent);
    z-index: 1;
}
.splash-seg-live {
    position: relative;
    display: inline-block;
    width: 4.4em;               /* reserve the full DSEG7 "888.88" (matches header) */
    text-align: right;
    font-size: 13px;            /* base = LARGE integer digits */
    line-height: 1;
    letter-spacing: 0.04em;
    white-space: nowrap;
}
.splash-seg-live > .seg-int,
.splash-seg-live > .seg-frac {
    position: relative;
    text-shadow: var(--seg-glow-amber);
}
/* the unlit-segment ghost rides the shared .seg-ghost rule (absolute overlay) */
.splash-seg-pct {
    font-family: var(--x30-font-num);
    font-size: 9px;
    font-weight: 600;
    letter-spacing: 0.02em;
}

.splash-foot {
    position: absolute;
    bottom: calc(24px + env(safe-area-inset-bottom));
    left: 50%;
    transform: translateX(-50%);
    font-family: var(--font-ui);
    font-size: 11px;
    letter-spacing: 0.06em;
    color: var(--x30-ink-low);
    opacity: 1;
    white-space: nowrap;
}

@media (max-width: 767px) {
    .splash-stage { width: 92vw; height: 372px; }
    .splash-logo { width: min(62vw, 280px); max-height: 96px; }
    .splash-logos-pair { flex-direction: column; gap: 28px; }
    .splash-logo-mark { height: clamp(40px, 12vw, 64px); }
    /* stacked pair: each wordmark may use the width again, but the two of them
       plus the kicker share the shorter stage — so cap their height */
    .splash-logos-pair .splash-logo { width: min(56vw, 230px); max-height: 62px; }
    .splash-card[data-card="solana"] .splash-logo { max-height: 56px; }
    .splash-toly { height: clamp(132px, 27vh, 210px); max-width: 82vw; }
    /* mark + wordmark pairs must be allowed to WRAP and shrink. "PUMP.FUN" sets
       wider than "TENSOR" at the same size, and at 390px the pill + wordmark row
       ran ~48px past the 92vw stage (measured) — it bled out of the card rather
       than scrolling the page, so it never showed up as an overflow. */
    .splash-pair-item { flex-wrap: wrap; gap: 10px; max-width: 100%; }
    .splash-wordmark-txt { font-size: clamp(20px, 6.4vw, 30px); }
}

/* LANDSCAPE PHONES — the stage, the foot and the seg readout share a very short
   viewport. Everything trades size for clearance: without this the Toly card and
   the stacked Metaplex/Helius pair both overflow a ~360px-tall window. The pair
   goes back to a ROW here, because height is the scarce axis, not width. */
@media (max-height: 560px) {
    .splash-stage { height: min(300px, 74vh); }
    .splash-logo { max-height: 56px; }
    .splash-logos-pair { flex-direction: row; gap: clamp(18px, 5vw, 40px); }
    .splash-logos-pair .splash-logo { width: min(34vw, 190px); max-height: 46px; }
    .splash-card[data-card="solana"] { gap: 10px; }
    .splash-card[data-card="solana"] .splash-logo { max-height: 40px; }
    .splash-toly { height: clamp(92px, 40vh, 156px); max-width: 56vw; }
    .splash-pair-item { flex-wrap: nowrap; gap: 10px; max-width: 100%; }
    .splash-wordmark-txt { font-size: clamp(18px, 3.4vw, 26px); }
}

/* ============================================================================
   ACCESSIBILITY — reduced motion (DESIGN 5). Replace movement with opacity; kill
   strobes/sheens/shake.
   ============================================================================ */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        scroll-behavior: auto !important;
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
    /* staggered reveal collapses to instant-visible */
    [data-reveal], body.menu-reveal [data-reveal] { opacity: 1 !important; animation: none !important; }
    .uplink-led, #enter-arena.is-ready-cta .plate-label, #enter-arena.is-arming .plate-label::after { animation: none !important; }
    /* GATE prompt: no blink; the card still holds until tap (JS-driven) and cards
       still cycle — only the crossfade + blink are suppressed. */
    .splash-prompt { opacity: 1 !important; animation: none !important; }
    #splash, #splash * { animation-duration: 0.01ms !important; animation-delay: 0s !important; }
    .splash-card { transition: none !important; }
    body.low-health #health-value, body.low-health .hud-skull { animation: none; }
    #weapon-panel.is-reloading #reload-state,
    #weapon-panel.is-empty #magazine-ammo { animation: none; }
    body.player-hit #damage-flash { opacity: 0.2; }
    /* keep the low-HP danger read as a steady (non-strobing) tint */
    body.low-health #low-hp-vignette, #low-hp-vignette.active { animation: none; opacity: 0.7; }
    #combat-state:not(.combat-state-hidden) { animation: none; }
    #enter-arena.enter-unlock { animation: none; }
    /* feed/announce surfaces: opacity-only, no translate; JS timers still clear */
    .kill-feed-row { transition-property: opacity; transform: none; }
    #frag-banner.fb-in, #frag-banner.frag-banner-show,
    #medal-callout.medal-show { animation-name: none; opacity: 1; }
}

/* Floating overhead nametags. Subtle white callsigns above remote players;
   positioned per-frame by CharacterModel (world head-point projected to screen).
   #nametags fills the #game-shell (position: relative) and never eats input. */
#nametags {
    position: absolute;
    inset: 0;
    pointer-events: none;
    overflow: hidden;
}
.nametag {
    position: absolute;
    transform: translate(-50%, -100%) translateY(-6px);
    color: rgba(255, 255, 255, 0.82);
    font-size: 11px;
    font-family: 'Chakra Petch', monospace;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-shadow: 0 1px 4px rgba(0,0,0,0.85), 0 0 10px rgba(0,0,0,0.4);
    white-space: nowrap;
    display: none;
    user-select: none;
}

/* ADS aim button (touch): styling now lives in the main TOUCH CONTROLS block
   (base selector + #touch-aim rect + .active/.is-selected states) under the
   v1.0 design tokens. Old standalone block removed to avoid overriding it. */


/* ============================================================================
   TOUCH CONTROL LAYOUT v2 — ergonomics pass (geometry contract).
   Thumb-rest origin = fire center (bottom-right). Core keys (fire/aim/reload/
   jump) inside the reach arc; switch/throw on the outer stretch ring (low-freq).
   Three layouts: base(landscape) / portrait / short-landscape. Portrait keeps
   every key RIGHT of screen centre (clear of the floating joystick). All rects
   derive from the contract vars — change one size, nothing overlaps.
   ============================================================================ */
#touch-controls {
    --tc-r: calc(20px + env(safe-area-inset-right));
    --tc-b: calc(20px + env(safe-area-inset-bottom));
    --tc-fire: 88px;
    --tc-aim: 58px;
    --tc-sec: 54px;   /* jump / reload / switch / throw */
    --tc-gear: 46px;
    --tc-gap: 16px;
}
#touch-fire   { width: var(--tc-fire); height: var(--tc-fire); }
#touch-aim    { width: var(--tc-aim);  height: var(--tc-aim); }
#touch-jump, #touch-reload, #touch-switch, #touch-throw { width: var(--tc-sec); height: var(--tc-sec); }
#touch-gear   { width: var(--tc-gear); height: var(--tc-gear); }

/* ---- BASE = LANDSCAPE. 3-column arc; wide so it spreads sideways, not up. ---
   col1(x≈r): fire + reload | col2: aim + jump | col3(inboard): switch + throw */
#touch-fire   { right: var(--tc-r); bottom: var(--tc-b); top: auto; left: auto; }
#touch-reload { right: calc(var(--tc-r) + (var(--tc-fire) - var(--tc-sec)) / 2); bottom: calc(var(--tc-b) + var(--tc-fire) + var(--tc-gap)); top: auto; left: auto; }
#touch-aim    { right: calc(var(--tc-r) + var(--tc-fire) + var(--tc-gap)); bottom: var(--tc-b); top: auto; left: auto; }
#touch-jump   { right: calc(var(--tc-r) + var(--tc-fire) + var(--tc-gap) + (var(--tc-aim) - var(--tc-sec)) / 2); bottom: calc(var(--tc-b) + var(--tc-aim) + var(--tc-gap)); top: auto; left: auto; }
#touch-switch { right: calc(var(--tc-r) + var(--tc-fire) + var(--tc-aim) + var(--tc-gap) * 2); bottom: var(--tc-b); top: auto; left: auto; }
#touch-throw  { right: calc(var(--tc-r) + var(--tc-fire) + var(--tc-aim) + var(--tc-gap) * 2 + (var(--tc-aim) - var(--tc-sec)) / 2); bottom: calc(var(--tc-b) + var(--tc-sec) + var(--tc-gap)); top: auto; left: auto; }
#touch-gear   { top: calc(12px + env(safe-area-inset-top)); right: calc(12px + env(safe-area-inset-right)); bottom: auto; left: auto; }

/* ---- PORTRAIT (tall, narrow) — RETRO DE-CLUTTER pass. Smaller contract + a
        thinner shape: big FIRE bottom-right (primary thumb), AIM beside it,
        JUMP above-left, and reload/switch/throw as ONE compact 46px column
        stacked over fire. Cluster top ≈ 310px from the bottom (was ~504px).
        Every hit target stays ≥ 44px; the --tc-b floor clears the bottom-center
        LED strip (see the portrait HUD block at the end of this file). -------- */
@media (orientation: portrait) {
    #touch-controls { --tc-fire: 80px; --tc-aim: 52px; --tc-sec: 46px; --tc-gap: 12px; --tc-b: calc(56px + env(safe-area-inset-bottom)); }
    /* primary pair: fire (corner) + aim inboard of it */
    #touch-fire   { right: var(--tc-r); bottom: var(--tc-b); }
    #touch-aim    { right: calc(var(--tc-r) + var(--tc-fire) + var(--tc-gap)); bottom: var(--tc-b); }
    /* jump above-left of fire (over the aim column — the natural thumb roll) */
    #touch-jump   { right: calc(var(--tc-r) + var(--tc-fire) + var(--tc-gap)); bottom: calc(var(--tc-b) + var(--tc-aim) + var(--tc-gap)); }
    /* compact small-button column over fire: reload / switch / throw (46px) */
    #touch-reload { right: calc(var(--tc-r) + (var(--tc-fire) - var(--tc-sec)) / 2); bottom: calc(var(--tc-b) + var(--tc-fire) + var(--tc-gap)); }
    #touch-switch { right: calc(var(--tc-r) + (var(--tc-fire) - var(--tc-sec)) / 2); bottom: calc(var(--tc-b) + var(--tc-fire) + var(--tc-sec) + var(--tc-gap) * 2); }
    #touch-throw  { right: calc(var(--tc-r) + (var(--tc-fire) - var(--tc-sec)) / 2); bottom: calc(var(--tc-b) + var(--tc-fire) + var(--tc-sec) * 2 + var(--tc-gap) * 3); }
}

/* ---- SHORT LANDSCAPE (height ≤ 460, coarse = any phone held sideways).
        Shrink the contract; the base 3-col arc recomputes compact. ---------- */
@media (max-height: 460px) and (pointer: coarse) {
    #touch-controls {
        --tc-fire: 76px;
        --tc-aim: 52px;
        --tc-sec: 48px;
        --tc-gap: 14px;
        --tc-b: calc(16px + env(safe-area-inset-bottom));
    }
}

/* ===========================================================================
   TDM (Team Deathmatch) HUD — team scoreboard strip + MATCH_END winner banner.
   Colors: RED (team 0) vs BLUE (team 1), matching CharacterModel's team tint.
   =========================================================================== */
:root {
    --tdm-red: var(--x30-threat);
    --tdm-blue: var(--x30-ally);
}

/* the centered top scoreboard: RED n · M:SS · n BLUE */
#tdm-scoreboard {
    position: absolute;
    top: 10px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    gap: 18px;
    padding: 4px 10px;
    border-radius: 0;
    background: none;
    border: none;
    font-family: var(--x30-font-num);
    pointer-events: none;
    z-index: 6;
    line-height: 1;
}
#tdm-scoreboard .tdm-team {
    position: relative;
    display: inline-flex;
    align-items: baseline;
    gap: 7px;
    padding-bottom: 5px;
}
/* the 2px team-color underline — team color lives HERE, not in the text */
#tdm-scoreboard .tdm-team::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: 0;
    height: 2px; border-radius: 1px; background: currentColor; opacity: 0.55;
}
#tdm-scoreboard .tdm-tag {
    font-family: var(--x30-font-ui);
    font-size: var(--x30-size-micro);
    font-weight: 600;
    letter-spacing: var(--x30-track-label);
    opacity: 1;
    color: var(--x30-ink-mid);
}
#tdm-scoreboard .tdm-num {
    font-size: var(--x30-size-timer);
    font-weight: 600;
    min-width: 26px;
    color: var(--x30-ink);
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
    text-align: center;
}
/* .tdm-side-red/.tdm-side-blue paint ONLY the underline (currentColor) —
   tag/num override color above. */
#tdm-scoreboard .tdm-side-red { color: var(--tdm-red); }
#tdm-scoreboard .tdm-side-blue { color: var(--tdm-blue); }
#tdm-scoreboard .tdm-timer {
    font-size: var(--x30-size-timer);
    font-weight: 600;
    color: var(--x30-ink);
    letter-spacing: 0.04em;
    padding: 0 4px;
    opacity: 1;
    font-variant-numeric: tabular-nums;
}
/* own-team highlight: the local player's side turns its RED/BLUE tag into a FILLED,
   team-colored "· YOU" pill so there is never a doubt which side you are on — readable
   at a glance mid-fight, not a faint 9px afterthought. */
#tdm-scoreboard[data-myteam="red"] .tdm-side-red,
#tdm-scoreboard[data-myteam="blue"] .tdm-side-blue {
    text-shadow: none;
}
#tdm-scoreboard[data-myteam="red"] .tdm-side-red::after,
#tdm-scoreboard[data-myteam="blue"] .tdm-side-blue::after { opacity: 1; height: 2px; }
#tdm-scoreboard[data-myteam="red"] .tdm-side-red .tdm-tag,
#tdm-scoreboard[data-myteam="blue"] .tdm-side-blue .tdm-tag {
    display: inline-block;
    color: #fff;
    padding: 2px 8px;
    border-radius: 999px;
    font-weight: 700;
    line-height: 1;
    animation: myteam-pulse 1.8s ease-in-out infinite;
}
#tdm-scoreboard[data-myteam="red"] .tdm-side-red .tdm-tag {
    background: var(--tdm-red); box-shadow: 0 0 12px -1px var(--tdm-red);
}
#tdm-scoreboard[data-myteam="blue"] .tdm-side-blue .tdm-tag {
    background: var(--tdm-blue); box-shadow: 0 0 12px -1px var(--tdm-blue);
}
#tdm-scoreboard[data-myteam="red"] .tdm-side-red .tdm-tag::after,
#tdm-scoreboard[data-myteam="blue"] .tdm-side-blue .tdm-tag::after {
    content: " · YOU";
    font-size: 9px;
    letter-spacing: var(--x30-track-label);
    color: rgba(255, 255, 255, 0.9);
}
/* one gentle breath so the eye catches the badge on spawn, then it settles */
@keyframes myteam-pulse {
    0%, 100% { filter: brightness(1); }
    50% { filter: brightness(1.28); }
}
/* dim the strip a touch during the intermission (the banner takes over) */
#tdm-scoreboard.match-over { opacity: 0.5; }

/* FFA: no teams — neutralize the red/blue team coloring so the two slots read as a plain
   personal-vs-leader frag readout (JS relabels the tags YOU / TOP). */
#tdm-scoreboard[data-mode="ffa"] .tdm-side-red,
#tdm-scoreboard[data-mode="ffa"] .tdm-side-blue { color: var(--x30-ink); }
#tdm-scoreboard[data-mode="ffa"] .tdm-team::after { background: var(--x30-hairline-strong); }
#tdm-scoreboard[data-mode="ffa"] .tdm-tag::after { content: none !important; }
/* SUDDEN DEATH overtime: the clock (which reads "OT") goes steady threat-red + emit —
   no strobe (loop law: only low-HP + connecting may loop). */
#tdm-scoreboard.sudden-death .tdm-timer {
    color: var(--tdm-red);
    animation: none;
    text-shadow: var(--x30-emit-threat);
}
@keyframes sudden-death-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }

/* MATCH_END winner banner (centered overlay during the ~15s intermission) */
#tdm-banner {
    position: absolute;
    top: 32%;
    left: 50%;
    transform: translate(-50%, -50%) scale(0.98);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding: 24px 48px;
    border-radius: var(--x30-r-3);
    background: var(--x30-surface-2);
    border: none;
    box-shadow: var(--x30-el-2);
    text-align: center;
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--x30-t-slow) var(--x30-ease-out),
                transform var(--x30-t-slow) var(--x30-ease-out);
    pointer-events: none;
    z-index: 40;
}
#tdm-banner.is-visible {
    opacity: 1;
    visibility: visible;
    transform: translate(-50%, -50%) scale(1);
}
#tdm-banner-title {
    font-family: var(--x30-font-display);
    font-size: 44px;
    font-weight: 700;
    letter-spacing: var(--x30-track-display);
    color: var(--x30-ink);
    padding-bottom: 10px;
    position: relative;
}
#tdm-banner-title::after {
    content: "";
    position: absolute;
    left: 50%; bottom: 0;
    width: 64px; height: 2px;
    transform: translateX(-50%);
    background: var(--x30-hairline-strong);
}
#tdm-banner-title.tdm-red::after  { background: var(--x30-threat); }
#tdm-banner-title.tdm-blue::after { background: var(--x30-ally); }
#tdm-banner-title.tdm-win::after  { background: var(--x30-feedback-gold); }
#tdm-banner-title.tdm-red, #tdm-banner-title.tdm-blue, #tdm-banner-title.tdm-win {
    color: var(--x30-ink);
    text-shadow: none; /* team color = underline, never fill */
}
#tdm-banner-score {
    font-family: var(--x30-font-num);
    font-weight: 600;
    font-size: 26px;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.08em;
    color: var(--x30-ink-mid);
}

/* ============================================================================
   RETRO PASS — seven-segment readouts + CRT chrome (applies over everything
   above; tokens live in the RETRO LAYER block at the top of this file).
   Pattern: every numeric readout becomes a .seg-wrap — the live digits stacked
   (inline-grid, same cell) over a dim .seg-under ghost of 8s, so unlit segments
   read like real hardware. DSEG7 carries NUMERALS ONLY; labels stay Chakra/Inter.
   ============================================================================ */

/* ---- the reusable segment-readout pattern ---- */
.seg-wrap {
    position: relative;
    display: inline-grid;
    white-space: nowrap;
}
.seg-wrap > span {
    grid-area: 1 / 1;
    text-align: right;
}
.seg-wrap .seg-under {
    color: var(--seg-ghost) !important;
    text-shadow: none !important;
    animation: none !important;
    user-select: none;
    pointer-events: none;
}

/* every in-game numeral goes DSEG7 (ghosts inherit via .seg-wrap font) */
#health-value, #armor-value, #magazine-ammo, #reserve-ammo, #grenade-count,
#tdm-timer, #tdm-score-red, #tdm-score-blue,
.seg-wrap .seg-under {
    font-family: var(--seg-font);
    font-weight: 700;
    font-variant-numeric: normal; /* DSEG7 is fixed-cell already */
    letter-spacing: 0.04em;
}

/* sizes: DSEG7 cells are ~1.6x wider than Barlow Condensed — step each numeral
   down so the plates keep their footprint. Size lives on the WRAPPER; live
   digits + ghost both run at 1em so the two layers can never drift apart. */
.seg-health { font-size: 30px; line-height: 1; }
.seg-armor { font-size: 13px; }
.seg-mag { font-size: 28px; line-height: 1; }
.seg-reserve { font-size: 13px; }
.seg-grenade { font-size: 16px; }
#tdm-scoreboard .tdm-timer, #tdm-scoreboard .tdm-num { font-size: 17px; }
#health-value, #armor-value, #magazine-ammo, #reserve-ammo, #grenade-count,
#tdm-timer, #tdm-score-red, #tdm-score-blue { font-size: 1em; line-height: inherit; }
.ammo-separator { font-family: var(--x30-font-num); font-size: 13px; }

/* ---- phosphor color law: green = healthy/positive, amber = neutral, red =
   critical (reuses --x30-threat). Glow is cheap text-shadow, live data only. -- */
#health-value { color: var(--seg-green); text-shadow: var(--seg-glow-green); }
body.mid-health #health-value { color: var(--seg-amber); text-shadow: var(--seg-glow-amber); }
/* low-health keeps the existing threat color + hp-glow breathing loop (base rule) */
body.overheal #health-value { text-shadow: var(--x30-emit-gold); }
#armor-value { color: var(--seg-cyan); text-shadow: var(--seg-glow-cyan); }
#magazine-ammo { color: var(--seg-green); text-shadow: var(--seg-glow-green); }
#weapon-panel.is-low-ammo #magazine-ammo { color: var(--seg-amber); text-shadow: var(--seg-glow-amber); }
#weapon-panel.is-empty #magazine-ammo { color: var(--seg-red); text-shadow: var(--seg-glow-red); }
#weapon-panel.is-reloading #magazine-ammo { opacity: 0.35; } /* unchanged contract */
#reserve-ammo { color: rgba(255, 176, 46, 0.65); text-shadow: none; }
#grenade-count { color: var(--seg-amber); text-shadow: var(--seg-glow-amber); }
#grenade-panel.is-empty #grenade-count,
#grenade-panel[data-charges="0"] #grenade-count { color: var(--x30-ink-low); text-shadow: none; }
/* scoreboard digits: amber neutral; team identity stays in the 2px underline */
#tdm-timer, #tdm-score-red, #tdm-score-blue { color: var(--seg-amber); text-shadow: var(--seg-glow-amber); }
/* sudden death: the clock reads "OT" in threat red (the wrapper rule recolors
   only the wrapper now — restate on the live digits) */
#tdm-scoreboard.sudden-death #tdm-timer { color: var(--seg-red); text-shadow: var(--seg-glow-red); }

/* ---- CRT chrome: scanlines layered over the existing plate fills + a 1px
   top-edge bevel highlight. background-image rides ON TOP of the shorthand
   background colors set earlier (later rule, same specificity). ---- */
#health-panel, #weapon-panel, #grenade-panel, .match-strip, #udamage-badge,
.kill-feed-row, #tdm-banner, .howto-panel, #settings-menu, .hack-feed {
    background-image: var(--crt-scanlines);
}
#health-panel, #weapon-panel, #grenade-panel {
    box-shadow: var(--x30-el-1), var(--crt-bevel);
}
.match-strip { box-shadow: var(--x30-el-1), var(--crt-bevel); }

/* faint in-game vignette (paint-only; ::before stays behind the positioned
   HUD children). Gated on arena-entered so it never doubles the menu scrim. */
body.arena-entered #arena-hud::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(ellipse at center, transparent 62%, rgba(5, 7, 8, 0.16) 100%);
}

/* menu + splash read as the same machine: full-surface scanlines over the glass
   (above the content, authentic CRT) + a very subtle phosphor flicker. */
#entry-overlay::after,
#splash::after,
#map-change::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 3;
    pointer-events: none;
    background: var(--crt-scanlines);
    opacity: 0.5;
    animation: crt-flicker 6s ease-in-out infinite;
}
@keyframes crt-flicker { 0%, 100% { opacity: 0.5; } 50% { opacity: 0.38; } }

/* ---- menu: PLAY is the hero plate ---- */
#enter-arena.menu-plate { min-height: 74px; }
#enter-arena.menu-plate .plate-label { font-size: 24px; }

/* ---- NOW PLAYING line (under the PLAY plate; MenuControls fills it from the
   /mapinfo poll). Hidden until the extended payload actually arrives. ---- */
#now-playing {
    display: none;
    padding: 10px 18px 12px 26px;
    border-top: 1px solid var(--x30-hairline);
    font-family: var(--x30-font-ui);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}
#now-playing[data-live="true"] { display: block; }
#now-playing .np-kicker { color: var(--x30-ink-low); text-shadow: none; }
#now-playing .np-now { color: var(--seg-green); text-shadow: var(--seg-glow-green); }
#now-playing .np-next { margin-top: 3px; color: var(--x30-ink-mid); text-shadow: none; }

/* ---- MAP-CHANGE interstitial (Overwatch-style rotation; z above everything).
   Spinner = three staggered-pulse DSEG7 "8" cells — pure CSS, no JS ticks. ---- */
#map-change {
    position: fixed;
    inset: 0;
    z-index: 70;
    display: none;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 20px;
    background: radial-gradient(circle at 50% 46%, #090C10 0%, var(--x30-void) 70%);
}
#map-change.mc-visible { display: flex; }

/* ---- rotation interstitial: LOADING vs READY (see #map-change in index.html) ----
   The next-map block and the DROP IN plate exist only in the READY state; the
   loading spinner/segments only in LOADING. One overlay, two honest states, instead
   of a loading screen that silently turned into a live match. ---- */
#map-change .mc-next,
#map-change .mc-drop { display: none; }
#map-change.mc-ready .mc-next { display: block; text-align: center; }
#map-change.mc-ready .mc-drop { display: inline-flex; }
#map-change.mc-ready .mc-seg,
#map-change.mc-ready .mc-sub { display: none; }
#map-change.mc-ready .mc-title { opacity: 0.6; font-size: 0.8em; letter-spacing: 0.28em; }

#map-change .mc-next-label {
    font-size: 12px;
    letter-spacing: 0.3em;
    color: var(--x30-ink-low);
    margin-bottom: 6px;
}
#map-change .mc-map-name {
    font-family: var(--font-display, inherit);
    font-size: clamp(28px, 6vw, 54px);
    line-height: 1.05;
    letter-spacing: 0.04em;
    color: var(--x30-ink);
}
#map-change .mc-mode-name {
    margin-top: 4px;
    font-size: 14px;
    letter-spacing: 0.22em;
    color: var(--x30-accent);
}
#map-change .mc-drop {
    flex-direction: column;
    align-items: center;
    gap: 2px;
    margin-top: 8px;
    padding: 16px 44px;
    border: none;
    border-radius: var(--x30-r-2, 6px);
    background: var(--x30-accent);
    color: var(--x30-void, #05070A);
    cursor: pointer;
    font-family: var(--font-display, inherit);
    box-shadow: var(--x30-el-2, 0 6px 24px rgba(0, 0, 0, 0.5));
    transition: transform 120ms var(--ease-snap, ease), filter 120ms ease;
    /* the card sits over a live scene — make sure the plate is the click target */
    pointer-events: auto;
    z-index: 4;
}
#map-change .mc-drop:hover { filter: brightness(1.08); }
#map-change .mc-drop:active { transform: scale(0.97); }
#map-change .mc-drop:focus-visible { outline: 3px solid var(--x30-ink); outline-offset: 3px; }
#map-change .mc-drop-label { font-size: 26px; letter-spacing: 0.1em; }
#map-change .mc-drop-hint { font-size: 11px; letter-spacing: 0.2em; opacity: 0.72; }
@media (prefers-reduced-motion: reduce) {
    #map-change .mc-drop { transition: none; }
}
.mc-title {
    font-family: var(--x30-font-display);
    font-weight: 700;
    font-size: 22px;
    letter-spacing: 0.3em;
    text-indent: 0.3em;
    text-transform: uppercase;
    color: var(--x30-ink);
}
.mc-seg {
    display: flex;
    gap: 8px;
    font-family: var(--seg-font);
    font-weight: 700;
    font-size: 38px;
    line-height: 1;
    color: var(--seg-amber);
}
.mc-seg span {
    text-shadow: var(--seg-glow-amber);
    animation: mc-pulse 0.9s ease-in-out infinite;
}
.mc-seg span:nth-child(2) { animation-delay: 0.15s; }
.mc-seg span:nth-child(3) { animation-delay: 0.3s; }
@keyframes mc-pulse { 0%, 100% { opacity: 0.12; } 50% { opacity: 1; } }
.mc-sub {
    font-family: var(--x30-font-ui);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    color: var(--x30-ink-mid);
}

/* ============================================================================
   PORTRAIT DE-CLUTTER (workstream B) — the three HUD plates collapse into ONE
   chromeless bottom-center LED strip (segment HP | segment ammo | grenade pips)
   sitting just above the thumb zones; the top keeps only the timer/score.
   Geometry: strip bottom 20px, digits ~26px tall → strip top ≈ 66px; the touch
   cluster floor is --tc-b = 56px on the RIGHT side only (fire x 20..100 from the
   right; the strip's right edge stays left of it on every ≥360px portrait).
   ============================================================================ */
@media (orientation: portrait) and (pointer: coarse) {
    /* top row: timer/score only — brand + connection strip go quiet */
    .brand-lockup, .match-strip { display: none; }
    #tdm-scoreboard { top: calc(8px + env(safe-area-inset-top)); }

    /* plates → chromeless phosphor (the digits ARE the plate) */
    #health-panel, #weapon-panel, #grenade-panel {
        background: none;
        border-left: 0;
        box-shadow: none;
        padding: 0;
        min-width: 0;
    }

    /* HP: right-aligned against the strip's center-left slot */
    #health-panel {
        top: auto;
        left: auto;
        right: calc(50% + 44px);
        bottom: calc(20px + env(safe-area-inset-bottom));
    }
    #health-panel .health-track,
    #health-panel .hud-skull,
    #health-panel .health-unit { display: none !important; }
    .seg-health { font-size: 26px; }
    #armor-row { margin-top: 2px; justify-content: flex-end; }
    #armor-row .armor-track { display: none; }
    .seg-armor { font-size: 11px; }

    /* ammo (then pips) flow rightward from center — row-reverse puts the DOM's
       weapon panel first, grenade pips second */
    #hud-right-group {
        top: auto;
        right: auto;
        left: calc(50% - 32px);
        bottom: calc(20px + env(safe-area-inset-bottom));
        flex-direction: row-reverse;
        align-items: center;
        gap: 14px;
    }
    #weapon-icon, #weapon-name { display: none; }
    .seg-mag { font-size: 26px; }
    .seg-reserve, .ammo-separator { font-size: 12px; }
    #reload-state { min-height: 10px; margin-top: 0; font-size: 8px; letter-spacing: 0.2em; }

    /* grenades: tiny pips only (count + icon add nothing at this size) */
    #grenade-icon, .seg-grenade { display: none; }
    .grenade-pips .pip { width: 6px; height: 12px; }

    /* kill feed climbs back up now the top-right corner is free */
    #kill-feed { top: calc(64px + env(safe-area-inset-top)); }
}

/* ---- reduced motion: kill the retro loops (flicker / spinner) outright ---- */
@media (prefers-reduced-motion: reduce) {
    #entry-overlay::after, #splash::after, #map-change::after { animation: none !important; }
    .mc-seg span { animation: none !important; opacity: 1 !important; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   IDENTITY GATE (#whoami) — "who is playing?", between the boot reel and the
   menu. Sits at z58: ABOVE the menu it is holding back, BELOW the splash
   (z60) that is fading out over it, so the reel dissolves onto this screen
   instead of cutting to it.

   Colour law: the accent is the only interactive colour, so BOTH plates use
   it for hover/focus — the human/agent difference is carried by the glyph and
   the mono treatment of the path, not by a second accent. The violet hairline
   on the agent plate is ambient identity (allowed), never a state.
   ═══════════════════════════════════════════════════════════════════════════ */
#whoami {
    position: fixed;
    inset: 0;
    z-index: 58;
    display: none;
    align-items: center;
    justify-content: center;
    padding: max(20px, env(safe-area-inset-top)) 20px max(20px, env(safe-area-inset-bottom));
    background:
        radial-gradient(120% 90% at 50% 0%, rgba(20, 241, 149, 0.06), transparent 60%),
        var(--x30-void);
}
#whoami.is-open { display: flex; }
/* the gate owns the screen while open: nothing behind it should be reachable */
body.whoami-open #entry-overlay { pointer-events: none; }

.whoami-frame {
    width: min(680px, 100%);
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 14px;
    animation: whoami-rise 260ms cubic-bezier(0.2, 0.7, 0.3, 1) both;
}
@keyframes whoami-rise {
    from { opacity: 0; transform: translateY(10px); }
    to   { opacity: 1; transform: none; }
}

.whoami-kicker {
    font-family: var(--font-display);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--x30-accent);
    opacity: 0.85;
}
.whoami-title {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 700;
    font-size: clamp(28px, 6.4vw, 46px);
    line-height: 1;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--x30-ink);
}
.whoami-sub {
    margin: 0;
    max-width: 46ch;
    font-family: var(--x30-font-ui);
    font-size: 14px;
    line-height: 1.5;
    color: var(--x30-ink-mid);
}

.whoami-choices {
    width: 100%;
    display: grid;
    grid-template-columns: 1fr;
    gap: 12px;
    margin-top: 8px;
}
@media (min-width: 640px) {
    .whoami-choices { grid-template-columns: 1fr 1fr; }
}

.whoami-plate {
    position: relative;
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 18px 16px;
    text-align: left;
    background: var(--x30-surface-1);
    border: 1px solid var(--x30-hairline);
    border-radius: var(--x30-r-2);
    color: var(--x30-ink-hi);
    cursor: pointer;
    transition: background-color var(--x30-t-fast) ease,
                border-color var(--x30-t-fast) ease,
                transform var(--x30-t-fast) ease;
}
/* ambient identity stripe — the agent door reads as the other kind of thing */
.whoami-plate-agent { border-left: 2px solid var(--x30-violet); }

.whoami-plate:hover,
.whoami-plate:focus-visible {
    background: linear-gradient(0deg, var(--x30-accent-tint), var(--x30-accent-tint)), var(--x30-surface-1);
    border-color: var(--x30-accent-dim);
    outline: none;
    transform: translateY(-1px);
}
.whoami-plate:active { transform: translateY(0); }

.whoami-glyph {
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    color: var(--x30-accent);
}
.whoami-glyph svg { width: 100%; height: 100%; display: block; }
.whoami-plate-agent .whoami-glyph { color: var(--x30-ally); }

.whoami-body { display: flex; flex-direction: column; gap: 3px; flex: 1; min-width: 0; }
.whoami-label {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: 17px;
    line-height: 1;
    letter-spacing: var(--x30-track-display);
    text-transform: uppercase;
}
.whoami-desc {
    font-family: var(--x30-font-ui);
    font-size: 12.5px;
    line-height: 1.35;
    color: var(--x30-ink-mid);
}
.whoami-desc code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12px;
    color: var(--x30-ally);
}

.whoami-chevron {
    flex: 0 0 auto;
    width: 7px; height: 7px;
    border-right: 2px solid var(--x30-ink-low);
    border-top: 2px solid var(--x30-ink-low);
    transform: rotate(45deg);
    transition: border-color var(--x30-t-fast) ease, transform var(--x30-t-fast) ease;
}
.whoami-plate:hover .whoami-chevron,
.whoami-plate:focus-visible .whoami-chevron {
    border-color: var(--x30-accent);
    transform: rotate(45deg) translate(1px, -1px);
}

.whoami-foot {
    margin: 6px 0 0;
    max-width: 52ch;
    font-family: var(--x30-font-ui);
    font-size: 11.5px;
    line-height: 1.5;
    color: var(--x30-ink-low);
}
.whoami-foot strong { color: var(--x30-ink-mid); font-weight: 600; }

@media (prefers-reduced-motion: reduce) {
    .whoami-frame { animation: none; }
    .whoami-plate:hover, .whoami-plate:focus-visible { transform: none; }
}


/* ============================================================================
   POST-MATCH OVERLAY (#match-end) + swap-card additions.
   Painted by client/graphics/MatchEndOverlay.js. Reveal is staged: the overlay
   adds .stage-0 ... .stage-5 over the first 10s of the intermission and the rules
   below opt each block in, so the beats are declarative rather than scripted.
   ============================================================================ */
#match-end {
    position: fixed; inset: 0; z-index: 60;
    display: none; flex-direction: column; align-items: center; justify-content: center;
    gap: clamp(6px, 1.2vh, 14px);
    padding: max(16px, env(safe-area-inset-top)) 16px max(16px, env(safe-area-inset-bottom));
    background: radial-gradient(ellipse at 50% 42%, rgba(4,7,11,0.72) 0%, rgba(2,4,7,0.93) 68%);
    backdrop-filter: blur(2px);
    font-family: var(--x30-font-ui, 'Chakra Petch', system-ui, sans-serif);
    color: var(--x30-ink, #d7e2ee);
    pointer-events: none;
}
#match-end.is-visible { display: flex; }

/* Every block starts hidden and is opted in by its stage class, so a block whose
   stage has not arrived occupies no space and cannot flash. */
#match-end .me-head, #match-end .me-board, #match-end #me-stats,
#match-end #me-nemesis, #match-end #me-badges,
#match-end #me-session, #match-end #me-next {
    opacity: 0; transform: translateY(6px);
    transition: opacity 320ms ease, transform 320ms ease;
}
#match-end.stage-1 .me-head,
#match-end.stage-2 .me-board,
#match-end.stage-3 #me-stats, #match-end.stage-3 #me-nemesis,
#match-end.stage-4 #me-badges,
#match-end.stage-5 #me-session, #match-end.stage-5 #me-next {
    opacity: 1; transform: none;
}
/* once a stage has passed, later stages must not re-hide earlier blocks */
#match-end.stage-2 .me-head, #match-end.stage-3 .me-head, #match-end.stage-4 .me-head, #match-end.stage-5 .me-head,
#match-end.stage-3 .me-board, #match-end.stage-4 .me-board, #match-end.stage-5 .me-board,
#match-end.stage-4 #me-stats, #match-end.stage-5 #me-stats,
#match-end.stage-4 #me-nemesis, #match-end.stage-5 #me-nemesis,
#match-end.stage-5 #me-badges { opacity: 1; transform: none; }

#match-end .me-title {
    font-family: var(--x30-font-display, var(--x30-font-display, 'Teko', sans-serif));
    font-size: clamp(38px, 7.2vw, 84px); line-height: 0.92; letter-spacing: 0.06em;
    text-transform: uppercase; color: #fff;
}
#match-end[data-result="win"]  .me-title { color: #fff; text-shadow: 0 0 24px rgba(0,229,255,0.28); }
#match-end[data-result="loss"] .me-title { color: #9fb0c2; }
#match-end[data-result="draw"] .me-title { color: #c8d3e0; }
#match-end .me-head::after {
    content: ''; display: block; height: 2px; margin: 8px auto 0; width: 132px;
    background: linear-gradient(90deg, transparent, var(--x30-feedback-gold, #e8b23a), transparent);
}
#match-end[data-result="loss"] .me-head::after {
    background: linear-gradient(90deg, transparent, var(--tdm-red, #ff4d4d), transparent);
}
#match-end .me-score {
    font-family: var(--x30-font-display, 'Teko', sans-serif); font-size: clamp(22px, 3.4vw, 40px);
    letter-spacing: 0.1em; color: var(--x30-ink-hi, #eef4fb);
}
#match-end .me-sub {
    font-size: clamp(10px, 1.1vw, 12px); letter-spacing: 0.22em; text-transform: uppercase;
    color: var(--x30-ink-low, #7d8b9b);
}

/* ---- ranking table ---- */
#match-end .me-board {
    width: min(680px, 94vw);
    border: 1px solid rgba(120,150,180,0.16);
    background: rgba(6,10,15,0.62);
}
#match-end .me-cols, #match-end .me-row {
    display: grid; grid-template-columns: 34px 1fr 44px 44px 52px;
    align-items: center; gap: 8px; padding: 5px 12px;
}
#match-end .me-cols {
    font-size: 9px; letter-spacing: 0.2em; color: var(--x30-ink-low, #6f7d8d);
    border-bottom: 1px solid rgba(120,150,180,0.14);
}
#match-end .me-row {
    font-size: clamp(12px, 1.3vw, 15px); letter-spacing: 0.04em;
    border-bottom: 1px solid rgba(120,150,180,0.06);
}
#match-end .me-row:last-child { border-bottom: 0; }
#match-end .me-row .me-k, #match-end .me-row .me-d, #match-end .me-row .me-net,
#match-end .me-cols .me-k, #match-end .me-cols .me-d, #match-end .me-cols .me-net {
    text-align: right; font-variant-numeric: tabular-nums;
}
#match-end .me-row .me-rank { color: var(--x30-ink-low, #7d8b9b); font-variant-numeric: tabular-nums; }
#match-end .me-row .me-name { display: flex; align-items: center; gap: 7px; min-width: 0; }
#match-end .me-row .me-net { color: var(--x30-ink-low, #8b98a8); }
#match-end .me-row[data-team="red"]  .me-rank { color: var(--tdm-red, #ff4d4d); }
#match-end .me-row[data-team="blue"] .me-rank { color: var(--tdm-blue, #4db8ff); }
/* your own row has to be findable in well under a second */
#match-end .me-row.is-me {
    background: rgba(0,229,255,0.10);
    box-shadow: inset 2px 0 0 var(--x30-accent, #00e5ff);
}
#match-end .me-row.is-me .me-name { color: #fff; }
/* Silicon is labelled, and a human who placed behind bots is told their human rank
   rather than having it hidden — this game is about learning to beat the agents. */
#match-end .me-agent {
    font-size: 8px; letter-spacing: 0.16em; padding: 1px 4px; border-radius: 2px;
    color: #0b0d10; background: var(--x30-feedback-gold, #ffb000); flex: 0 0 auto;
}
#match-end .me-human {
    font-size: 8px; letter-spacing: 0.16em; color: var(--x30-ink-low, #7d8b9b);
    border: 1px solid rgba(120,150,180,0.28); padding: 1px 4px; border-radius: 2px; flex: 0 0 auto;
}

/* ---- personal stats ---- */
#match-end .me-stats { display: flex; gap: clamp(10px, 2.4vw, 30px); align-items: flex-end; }
#match-end .me-stat { display: flex; flex-direction: column; align-items: center; gap: 2px; }
#match-end .me-stat-v {
    font-family: var(--x30-font-display, 'Teko', sans-serif); font-size: clamp(20px, 2.8vw, 32px); line-height: 1;
    color: var(--x30-ink-hi, #eef4fb); font-variant-numeric: tabular-nums;
}
#match-end .me-stat-l {
    font-size: 8px; letter-spacing: 0.18em; color: var(--x30-ink-low, #6f7d8d); text-transform: uppercase;
}

#match-end .me-nemesis {
    display: flex; align-items: center; gap: 9px; flex-wrap: wrap; justify-content: center;
    font-size: clamp(10px, 1.1vw, 12px); letter-spacing: 0.1em;
}
#match-end .me-nem-label { color: var(--tdm-red, #ff4d4d); letter-spacing: 0.2em; font-size: 9px; }
#match-end .me-nem-name { color: #fff; display: inline-flex; align-items: center; gap: 6px; }
#match-end .me-nem-detail { color: var(--x30-ink-low, #7d8b9b); }

/* ---- accolades: at most three, so a badge still means something ---- */
#match-end .me-badges { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }
#match-end .me-badge {
    display: flex; flex-direction: column; gap: 2px; padding: 7px 13px;
    border: 1px solid rgba(232,178,58,0.34); background: rgba(232,178,58,0.06);
    min-width: 150px;
}
#match-end .me-badge.is-mine { border-color: var(--x30-feedback-gold, #e8b23a); background: rgba(232,178,58,0.13); }
#match-end .me-badge[data-kind="carrier"] { border-color: rgba(160,180,200,0.34); background: rgba(160,180,200,0.06); }
#match-end .me-badge-label { font-size: 8px; letter-spacing: 0.2em; color: var(--x30-feedback-gold, #e8b23a); }
#match-end .me-badge[data-kind="carrier"] .me-badge-label { color: #b9c7d6; }
#match-end .me-badge-who {
    font-family: var(--x30-font-display, 'Teko', sans-serif); font-size: clamp(15px, 1.9vw, 21px); line-height: 1;
    color: #fff; display: inline-flex; align-items: center; gap: 6px;
}
#match-end .me-badge-detail { font-size: 9px; letter-spacing: 0.12em; color: var(--x30-ink-low, #7d8b9b); }

#match-end .me-session, #match-end .me-next {
    font-size: clamp(9px, 1vw, 11px); letter-spacing: 0.2em; text-transform: uppercase;
    color: var(--x30-ink-low, #7d8b9b);
}
#match-end .me-next { color: var(--x30-accent, #00e5ff); }
#match-end .me-clock {
    margin-top: 2px; font-size: 9px; letter-spacing: 0.24em; text-transform: uppercase;
    color: var(--x30-ink-low, #5f6c7b);
}

/* The combat HUD is unmounted for the intermission: a crosshair, an ammo card and a
   health plate over a match you can no longer affect is noise, and it was competing
   with the only thing worth reading. */
body.match-ended #crosshair,
body.match-ended #health-panel,
body.match-ended #weapon-panel,
body.match-ended #grenade-panel,
body.match-ended #hack-feed,
body.match-ended #objective-hud,
body.match-ended #tdm-scoreboard,
body.match-ended #tdm-banner { opacity: 0; transition: opacity 260ms ease; pointer-events: none; }

/* Running session record on the swap card. */
.mc-session {
    margin-top: 12px; font-size: 10px; letter-spacing: 0.2em; text-transform: uppercase;
    color: var(--x30-ink-low, #7d8b9b);
}

@media (prefers-reduced-motion: reduce) {
    #match-end .me-head, #match-end .me-board, #match-end #me-stats,
    #match-end #me-nemesis, #match-end #me-badges,
    #match-end #me-session, #match-end #me-next { transition: none; }
}

/* ── LINK WALLET (read-only) ──────────────────────────────────────────────────
   Paste field inside the ghost-modal chrome. Deliberately plain: this is not a
   wallet-connect button and should not look like one — nothing here signs. */
.wallet-row { display: flex; gap: 8px; margin: 14px 0 6px; }
.wallet-row input {
    flex: 1 1 auto; min-width: 0;
    background: rgba(0,0,0,.45);
    border: 1px solid rgba(255,255,255,.18);
    color: #e8ecf3;
    font: 13px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
    letter-spacing: .02em;
    padding: 10px 12px; border-radius: 6px;
}
.wallet-row input:focus { outline: none; border-color: rgba(240,169,60,.75); }
.wallet-row input::placeholder { color: #6d7789; }
.wallet-row button {
    flex: 0 0 auto;
    background: rgba(240,169,60,.14);
    border: 1px solid rgba(240,169,60,.55);
    color: #f0a93c;
    font: 700 12px/1 ui-sans-serif, system-ui, sans-serif;
    letter-spacing: .12em;
    padding: 0 18px; border-radius: 6px; cursor: pointer;
}
.wallet-row button:hover { background: rgba(240,169,60,.24); }
#wallet-holdings li b { color: #f0a93c; }

/* ── LOADOUT PANEL ────────────────────────────────────────────────────────────
   Two columns: a live 3D figure wearing the armour the linked wallet holds, and
   the weapon/Cloth lists. The lists deliberately show LOCKED rows as well as
   owned ones — Season 1 removed weapon pickups from the arena floor, so a player
   who owns nothing needs to see what exists and what linking would unlock.
   Collapses to one column on narrow screens; the figure keeps a fixed aspect so
   it never squashes. */
.loadout-panel { max-width: 860px; }
.loadout-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 22px;
    margin-top: 18px;
    align-items: start;
}
.loadout-figure {
    position: relative;
    aspect-ratio: 3 / 4;
    min-height: 260px;
    border: 1px solid var(--x30-hairline);
    border-radius: var(--r-1);
    /* faint floor-up wash so the figure is not floating in flat black */
    background:
        radial-gradient(ellipse 70% 45% at 50% 92%, rgba(255, 176, 46, 0.16), transparent 70%),
        linear-gradient(180deg, rgba(7, 9, 12, 0.35), rgba(7, 9, 12, 0.75));
    overflow: hidden;
}
#loadout-canvas {
    display: block;
    width: 100%;
    height: 100%;
    /* the camera has attachControl, so the canvas owns the drag gesture */
    touch-action: none;
    outline: none;
}
.loadout-hint {
    position: absolute;
    left: 0; right: 0; bottom: 8px;
    text-align: center;
    pointer-events: none;
    font-family: var(--font-ui);
    font-size: 10px;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    color: var(--x30-ink-mid);
    opacity: 0.65;
}
.loadout-h3 {
    margin: 0 0 2px;
    font-family: var(--x30-font-display);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--x30-ink-mid);
}
.loadout-h3 + .loadout-rows { margin-top: 4px; }
.loadout-rows { margin-bottom: 18px; }
.loadout-rows li { padding: 9px 0; }

/* State colouring carries the whole message, so it must survive a greyscale
   screenshot too — hence the differing weight/opacity, not colour alone. */
.loadout-rows li[data-state="owned"] b { color: var(--sol-teal); }
.loadout-rows li[data-state="issued"] b { color: var(--x30-ink-hi); opacity: 0.85; }
.loadout-rows li[data-state="locked"] span,
.loadout-rows li[data-state="locked"] b {
    opacity: 0.42;
    font-weight: 500;
}
.loadout-rows li[data-state="locked"] b::before {
    content: "⌁ ";
    letter-spacing: 0;
}

@media (max-width: 720px) {
    .loadout-grid { grid-template-columns: minmax(0, 1fr); }
    .loadout-figure { aspect-ratio: 4 / 3; min-height: 220px; }
}

/* ── IN-MATCH CHAT ────────────────────────────────────────────────────────────
   Bottom-left, above the touch controls' left thumb zone. pointer-events stays
   NONE on the log so chat can never swallow a click meant for the arena; only
   the input bar takes events, and only while it is open. */
#chat {
    position: fixed;
    left: 14px;
    bottom: 96px;
    z-index: 40;
    width: min(46ch, 52vw);
    display: flex;
    flex-direction: column;
    gap: 6px;
    pointer-events: none;
}
#chat-log {
    display: flex;
    flex-direction: column;
    gap: 2px;
    max-height: 30vh;
    overflow: hidden;
    pointer-events: none;
}
.chat-line {
    font-family: var(--font-ui);
    font-size: 13px;
    line-height: 1.35;
    /* the arena behind chat is arbitrary brightness — the shadow is what keeps it
       readable over a white wall as well as a dark corridor */
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.95), 0 0 6px rgba(0, 0, 0, 0.7);
    word-break: break-word;
    transition: opacity 900ms ease;
}
.chat-line.chat-stale { opacity: 0.38; }
.chat-who { color: var(--bone); font-weight: 700; margin-right: 6px; }
.chat-text { color: var(--bone); }
.chat-tag { color: var(--sol-teal); font-weight: 700; margin-right: 5px; }
.chat-line[data-scope="team"] .chat-who { color: var(--sol-teal); }

#chat-bar {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px;
    background: var(--scrim-heavy);
    border: 1px solid var(--x30-hairline);
    border-radius: var(--r-1);
    pointer-events: auto;
}
#chat-bar.chat-hidden { display: none; }
#chat-scope {
    flex: 0 0 auto;
    font-family: var(--x30-font-display);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.1em;
    padding: 2px 6px;
    border-radius: 2px;
    color: var(--ink);
    background: var(--bone);
}
#chat-scope[data-scope="team"] { background: var(--sol-teal); }
#chat-input {
    flex: 1 1 auto;
    min-width: 0;
    background: transparent;
    border: 0;
    outline: none;
    color: var(--bone);
    font-family: var(--font-ui);
    font-size: 13px;
}
.chat-help {
    flex: 0 0 auto;
    font-family: var(--font-ui);
    font-size: 9px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--x30-ink-mid);
    opacity: 0.6;
}
@media (max-width: 720px) {
    /* phones: the touch sticks own the bottom corners, so sit chat higher and
       drop the keyboard hint (there are no ALT/ESC keys to press) */
    #chat { width: 78vw; bottom: 132px; }
    .chat-help { display: none; }
}
/* bridged lines (Telegram / pump.fun) — visually distinct from arena chatter so a
   voice from outside the match is never mistaken for a player standing next to you */
.chat-line[data-source] .chat-src { color: var(--sol-purple); }
.chat-line[data-source] .chat-text { color: var(--bone-dim); }


/* ── EQUIP: armour finish picker ───────────────────────────────────────────────
   A set is one finish, so these are radio-like: exactly one is pressed. The swatch
   carries the identity — the Solana one is the only gradient on the page, which is
   the point of that finish. */
.finish-picker { display:flex; flex-wrap:wrap; gap:6px; margin:6px 0 14px; }
.finish-btn {
    display:flex; align-items:center; gap:7px;
    padding:7px 11px; cursor:pointer;
    background:transparent; color:var(--x30-ink-mid);
    border:1px solid var(--x30-hairline); border-radius:var(--r-1);
    font-family:var(--font-ui); font-size:11px; letter-spacing:.06em; text-transform:uppercase;
    transition:border-color .14s ease, color .14s ease;
}
.finish-btn:hover { border-color:var(--x30-ink-mid); color:var(--x30-ink-hi); }
.finish-btn:focus-visible { outline:2px solid var(--sol-teal); outline-offset:2px; }
.finish-btn[aria-pressed="true"] { border-color:var(--sol-teal); color:var(--x30-ink-hi); }
.finish-btn b { font-weight:600; }
.finish-btn .fstat { font-family:var(--x30-font-display); font-size:10px; opacity:.62; }
.finish-btn .fsw {
    width:13px; height:13px; border-radius:2px; flex:0 0 auto;
    box-shadow:inset 0 0 0 1px rgba(0,0,0,.4);
}
.finish-btn[data-finish="gold"]   .fsw { background:#E3B23C; }
.finish-btn[data-finish="silver"] .fsw { background:#C6CDD6; }
.finish-btn[data-finish="ebony"]  .fsw { background:#22252B; }
.finish-btn[data-finish="solana"] .fsw { background:linear-gradient(160deg,#14F195,#9945FF); }
.finish-none { font-family:var(--font-ui); font-size:12px; color:var(--x30-ink-mid); margin:2px 0 10px; }

@media (prefers-reduced-motion: reduce) { .finish-btn { transition:none; } }




/* ══ SCREENS ═══════════════════════════════════════════════════════════════════
   Routed destinations (#/character, #/issuance, #/codex) that REPLACE the menu.

   The distinction from .info-modal above is deliberate and visual, not just
   structural: a modal is a lit panel on a dark scrim, floating and dismissible.
   A screen is opaque, edge to edge, and has a back affordance instead of a ✕ —
   it should feel like somewhere you went, not something that opened on top of
   what you were doing. */
#menu-screens { position: fixed; inset: 0; z-index: 60; pointer-events: none; }

.menu-screen {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    pointer-events: auto;
    background: var(--x30-void);
    /* Faint brand wash from the top-left so a full-bleed dark surface still has a
       light source, rather than reading as a void with text on it. */
    background-image:
        radial-gradient(120% 80% at 0% 0%, var(--x30-violet-tint), transparent 60%),
        radial-gradient(90% 70% at 100% 100%, var(--x30-accent-tint), transparent 55%);
    opacity: 1;
    transition: opacity var(--x30-t-base) var(--x30-ease-out), transform var(--x30-t-base) var(--x30-ease-out);
}
.menu-screen.screen-closed {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    /* Slides down on exit — the menu is conceptually "above", so leaving a screen
       drops it away rather than fading it in place. */
    transform: translateY(12px);
    transition-duration: var(--x30-t-exit);
}
.menu-screen:focus { outline: none; }

/* While a screen is up the menu behind it must not be reachable by tab or by a
   stray click landing in a gap. */
body.screen-open #entry-overlay { pointer-events: none; }

.screen-head {
    display: flex;
    align-items: center;
    gap: 20px;
    flex: 0 0 auto;
    padding: 22px clamp(20px, 5vw, 56px) 18px;
    border-bottom: 1px solid var(--x30-hairline);
}
.screen-heading h2 {
    margin: 2px 0 0;
    font-family: var(--font-display);
    font-weight: 600;
    font-size: clamp(22px, 3.4vw, 34px);
    line-height: 1;
    letter-spacing: var(--x30-track-display);
    text-transform: uppercase;
    color: var(--x30-ink-hi);
}
.screen-back {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 9px 14px;
    background: var(--x30-surface-3);
    border: 1px solid var(--x30-hairline-strong);
    border-radius: var(--x30-r-2);
    color: var(--x30-ink-mid);
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    cursor: pointer;
    transition: color var(--x30-t-fast), border-color var(--x30-t-fast);
}
.screen-back:hover, .screen-back:focus-visible {
    color: var(--x30-accent);
    border-color: var(--x30-accent-dim);
}
.screen-back-glyph { font-size: 14px; line-height: 1; }
.screen-state {
    margin-left: auto;
    font-family: var(--font-ui);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    color: var(--x30-accent);
}
.screen-body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: clamp(18px, 3vw, 32px) clamp(20px, 5vw, 56px) 40px;
    scrollbar-width: thin;
    scrollbar-color: var(--x30-surface-3) transparent;
}

/* ---- ISSUANCE ------------------------------------------------------------- */
/* A DELIBERATE four-column layout, not auto-fit. auto-fit resolved to three columns
   at desktop width, so the two `span 2` cards each left a third of the row empty and
   the whole screen sat in its left two-thirds. The composition is fixed instead:

     row 1  [ CURRENT BLOCK        ][ REWARD ][ MINED ]
     row 2  [ LEDGER — full width                    ]

   The ledger is full-bleed because a holder table is a wide object; cramping it into
   half the row was what pushed it into a second column of dead space. */
.blood-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 14px;
    max-width: 1280px;
}
.blood-card-current { grid-column: span 2; }
.blood-card-ledger { grid-column: 1 / -1; }
@media (max-width: 1080px) {
    .blood-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .blood-card-current { grid-column: span 2; }
}
@media (max-width: 640px) {
    .blood-grid { grid-template-columns: minmax(0, 1fr); }
    .blood-card-current { grid-column: span 1; }
}

.blood-card {
    padding: 18px 18px 16px;
    background: var(--x30-surface-1);
    border: 1px solid var(--x30-hairline);
    border-radius: var(--x30-r-3);
}
.blood-card-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    /* The narrow cards cannot fit "BLOCK REWARD" and "5,000 BLOOD" on one line, and
       letting the VALUE wrap split it as "5,000 / BLOOD". Wrapping the head instead
       drops the whole value onto its own line, intact. */
    flex-wrap: wrap;
}
.blood-card-title {
    font-family: var(--font-ui);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    color: var(--x30-ink-mid);
}
.blood-big {
    /* Tabular so a ticking height does not shuffle the layout every ten minutes. */
    font-family: var(--font-display);
    font-variant-numeric: tabular-nums;
    font-size: clamp(20px, 2.6vw, 28px);
    font-weight: 600;
    letter-spacing: 0.02em;
    line-height: 1.05;
    white-space: nowrap;
    color: var(--x30-ink);
}
.blood-sub {
    font-family: var(--font-ui);
    font-size: 10px;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    color: var(--x30-ink-mid);
}
.blood-sub b { color: var(--x30-ink-hi); }

.blood-bar {
    position: relative;
    height: 6px;
    margin: 14px 0 8px;
    background: var(--x30-surface-3);
    border-radius: 3px;
    overflow: hidden;
}
.blood-bar-fill {
    height: 100%;
    width: 0;
    border-radius: 3px;
    background: var(--x30-grad-brand);
    transition: width var(--x30-t-base) var(--x30-ease-out);
}
/* The open block reads as ally-cyan rather than brand-green: it is a clock, not a
   quantity, and it should not look like more supply being mined. */
.blood-bar-fill.blood-window { background: var(--x30-ally); box-shadow: var(--x30-emit-ally); }

.blood-card-foot {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    font-family: var(--font-ui);
    font-variant-numeric: tabular-nums;
    font-size: 10px;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    color: var(--x30-ink-mid);
}
.blood-card-foot b { color: var(--x30-ink-hi); }
.blood-rows { max-height: 210px; overflow-y: auto; scrollbar-width: thin; }
.blood-rows li b { font-variant-numeric: tabular-nums; }
.rows-empty {
    display: block;
    padding: 12px 0;
    border: 0;
    font-family: var(--font-ui);
    font-size: 11px;
    letter-spacing: 0.04em;
    color: var(--x30-ink-low);
}

/* ---- ARMORY --------------------------------------------------------------- */
.armory-filters { display: flex; flex-direction: column; gap: 8px; margin-bottom: 20px; }
.filter-row { display: flex; flex-wrap: wrap; gap: 6px; }
.filter-row-sub[hidden] { display: none; }

.filter-chip {
    padding: 8px 14px;
    background: transparent;
    border: 1px solid var(--x30-hairline-strong);
    border-radius: var(--x30-r-2);
    color: var(--x30-ink-mid);
    font-family: var(--font-ui);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    cursor: pointer;
    transition: color var(--x30-t-fast), border-color var(--x30-t-fast), background var(--x30-t-fast);
}
.filter-chip:hover { color: var(--x30-ink-hi); border-color: var(--x30-hairline-strong); }
.filter-chip.is-active {
    background: var(--x30-accent-tint);
    border-color: var(--x30-accent-dim);
    color: var(--x30-accent);
}
/* The finish row is a refinement OF the armour choice, so it reads as subordinate
   rather than as a second peer row of buttons. */
.filter-row-sub .filter-chip { font-size: 9px; padding: 6px 11px; }

.armory-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 14px;
    max-width: 1280px;
}
.armory-card {
    display: flex;
    flex-direction: column;
    padding: 12px;
    background: var(--x30-surface-1);
    border: 1px solid var(--x30-hairline);
    border-radius: var(--x30-r-3);
}
/* Rarity is a left edge, not a card-wide tint: the art is the subject and a coloured
   wash behind it would fight the metal finishes these pieces are sold on. */
.armory-card[data-rarity="legendary"] { border-left: 2px solid var(--x30-feedback-gold); }
.armory-card[data-rarity="rare"] { border-left: 2px solid var(--x30-ally); }
.armory-card[data-rarity="uncommon"] { border-left: 2px solid var(--x30-accent-dim); }
.armory-card[data-rarity="common"] { border-left: 2px solid var(--x30-hairline-strong); }
.armory-card.is-owned { background: var(--x30-accent-tint); }

.armory-art {
    position: relative;
    aspect-ratio: 1 / 1;
    background: var(--x30-void);
    border-radius: var(--x30-r-2);
    overflow: hidden;
}
.armory-art img, .armory-art video { width: 100%; height: 100%; object-fit: contain; display: block; }
.armory-owned {
    position: absolute;
    top: 8px; left: 8px;
    padding: 3px 7px;
    background: var(--x30-accent);
    border-radius: 3px;
    color: var(--x30-void);
    font-family: var(--font-ui);
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.14em;
}
.armory-card-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin-top: 12px;
}
.armory-card-head h4 {
    margin: 0;
    font-family: var(--font-display);
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--x30-ink-hi);
}
.armory-rarity {
    flex: 0 0 auto;
    font-family: var(--font-ui);
    font-size: 9px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--x30-ink-low);
}
.armory-meta {
    margin-top: 4px;
    font-family: var(--font-ui);
    font-size: 10px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--x30-ink-mid);
}
.armory-buy { display: flex; gap: 6px; margin-top: 12px; }
.armory-buy-btn {
    flex: 1 1 0;
    padding: 8px 6px;
    background: var(--x30-surface-3);
    border: 1px solid var(--x30-hairline-strong);
    border-radius: var(--x30-r-2);
    color: var(--x30-ink-mid);
    font-family: var(--font-ui);
    font-size: 9px;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-align: center;
    text-decoration: none;
    transition: color var(--x30-t-fast), border-color var(--x30-t-fast);
}
.armory-buy-btn:hover { color: var(--x30-accent); border-color: var(--x30-accent-dim); }
.armory-supply {
    margin-top: 8px;
    font-family: var(--font-ui);
    font-variant-numeric: tabular-nums;
    font-size: 9px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--x30-ink-low);
}

/* ---- FRAGBENCH ------------------------------------------------------------ */
/* The endpoint is shown as selectable text, not behind a copy button: an agent that
   reached this page by reading it needs the string to be IN the document. */
.bench-endpoint {
    margin-top: 14px;
    padding: 10px 12px;
    background: var(--x30-void);
    border: 1px solid var(--x30-hairline);
    border-radius: var(--x30-r-2);
}
.bench-endpoint-label {
    display: block;
    margin-bottom: 4px;
    font-family: var(--font-ui);
    font-size: 9px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--x30-ink-low);
}
.bench-endpoint code {
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    font-size: 12px;
    color: var(--x30-accent);
    user-select: all;
    word-break: break-all;
}
/* A button styled as one of the link chips needs the resets an <a> does not. */
button.armory-buy-btn { cursor: pointer; font-family: var(--font-ui); }
button.armory-buy-btn:disabled { opacity: 0.45; cursor: default; }
button.armory-buy-btn:disabled:hover { color: var(--x30-ink-mid); border-color: var(--x30-hairline-strong); }

.whoami-foot-hatch { font-size: 11px; opacity: 0.75; }
.whoami-foot-hatch a { color: var(--x30-accent); text-decoration: none; }
.whoami-foot-hatch a:hover { text-decoration: underline; }

/* ---- CODEX ---------------------------------------------------------------- */
.codex-grid {
    display: grid;
    grid-template-columns: minmax(200px, 260px) minmax(0, 1fr);
    gap: clamp(16px, 3vw, 36px);
    max-width: 1100px;
    align-items: start;
}
@media (max-width: 720px) { .codex-grid { grid-template-columns: 1fr; } }

.codex-list { display: flex; flex-direction: column; gap: 2px; }
.codex-cat {
    margin: 16px 0 6px;
    font-family: var(--font-ui);
    font-size: 9px;
    font-weight: 600;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--x30-ink-low);
}
.codex-cat:first-child { margin-top: 0; }

.codex-item {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 10px 12px;
    /* The active marker is a left edge rather than a fill: a list of filled rows
       reads as a set of buttons, and this is a table of contents. */
    border: 0;
    border-left: 2px solid transparent;
    background: transparent;
    text-align: left;
    cursor: pointer;
    transition: background var(--x30-t-fast), border-color var(--x30-t-fast);
}
.codex-item:hover { background: var(--x30-surface-1); }
.codex-item.is-active {
    background: var(--x30-accent-tint);
    border-left-color: var(--x30-accent);
}
.codex-item-title {
    font-family: var(--font-display);
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--x30-ink-mid);
}
.codex-item.is-active .codex-item-title { color: var(--x30-ink-hi); }
.codex-item-kicker {
    font-family: var(--font-ui);
    font-size: 9px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--x30-ink-low);
}

.codex-reader {
    padding: clamp(18px, 2.4vw, 28px);
    background: var(--x30-surface-1);
    border: 1px solid var(--x30-hairline);
    border-radius: var(--x30-r-3);
    /* Hold a constant height across entries. Without it the panel shrink-wraps its
       prose, so a two-paragraph entry leaves most of the screen empty and switching
       entries resizes the card under the cursor. */
    min-height: min(62vh, 560px);
    display: flex;
    flex-direction: column;
}
.codex-reader-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    padding-bottom: 14px;
    border-bottom: 1px solid var(--x30-hairline);
}
.codex-reader-head h3 {
    margin: 4px 0 0;
    font-family: var(--font-display);
    font-size: clamp(18px, 2.4vw, 24px);
    font-weight: 600;
    letter-spacing: var(--x30-track-display);
    text-transform: uppercase;
    color: var(--x30-ink-hi);
}
.codex-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    margin-top: 14px;
    padding-right: 6px;
    scrollbar-width: thin;
    scrollbar-color: var(--x30-surface-3) transparent;
}
.codex-body p {
    margin: 14px 0 0;
    /* Longer measure and looser leading than the modals used: this is the one
       surface in the product meant to be READ rather than scanned. */
    max-width: 68ch;
    font-family: var(--font-ui);
    font-size: 14px;
    line-height: 1.72;
    color: var(--x30-ink-mid);
}
.codex-body p:first-child { margin-top: 0; color: var(--x30-ink-hi); }

.codex-narrate {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    flex: 0 0 auto;
    padding: 9px 15px;
    background: var(--x30-surface-3);
    border: 1px solid var(--x30-hairline-strong);
    border-radius: var(--x30-r-2);
    color: var(--x30-ink-mid);
    font-family: var(--font-ui);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: var(--x30-track-label);
    text-transform: uppercase;
    cursor: pointer;
    transition: color var(--x30-t-fast), border-color var(--x30-t-fast);
}
.codex-narrate:hover, .codex-narrate:focus-visible {
    color: var(--x30-accent);
    border-color: var(--x30-accent-dim);
}
/* Triangle at rest, twin bars while speaking — the control states what it will do
   next, so one button covers play and stop without a label change. */
.codex-narrate-glyph {
    width: 0; height: 0;
    border-left: 8px solid currentColor;
    border-top: 5px solid transparent;
    border-bottom: 5px solid transparent;
}
.codex-narrate.is-playing { color: var(--x30-accent); border-color: var(--x30-accent-dim); }
.codex-narrate.is-playing .codex-narrate-glyph {
    width: 8px; height: 10px;
    border: 0;
    background: linear-gradient(to right, currentColor 0 3px, transparent 3px 5px, currentColor 5px 8px);
}
.codex-narrate.is-playing .codex-narrate-label::after {
    content: 'ING…';
}

@media (prefers-reduced-motion: reduce) {
    .menu-screen, .blood-bar-fill { transition: none; }
}

/* ══ MARKET HUD ════════════════════════════════════════════════════════════════
   Marketcap, pressure gauge and whale alerts. From the designer round robin in
   _work/hud-roundrobin/ — brief was "retro, almost steampunk, like the logo, a bit
   Warhammer, aggressive and almost arcane", with the hard constraint that the
   CURRENT retro look is improved rather than replaced.

   So: no new colour system, no new type. It extends the seven-segment instrument
   language the HUD already speaks — DSEG7 numerals over a dim ghost readout, the
   way an unlit LED segment sits behind a lit one on real hardware — and adds the
   housing that makes it read as a mounted instrument rather than a floating div:
   an inset top ridge, a darker bottom lip, and a hairline frame. */

#market-hud {
    position: absolute;
    top: 14px;
    right: 14px;
    z-index: 8;
    pointer-events: none;
}

#mcap-panel {
    width: 210px;
    padding: 8px 10px 7px;
    background: var(--x30-surface-0);
    border: 1px solid var(--x30-hairline);
    border-radius: var(--x30-r-2);
    /* The housing. A 1px light ridge along the top inside edge and a darker lip along
       the bottom is the whole trick that turns a rectangle into a machined face-plate —
       it is how a real bezel catches light, and it costs nothing to draw. */
    box-shadow:
        inset 0 1px 0 rgba(244, 247, 249, 0.08),
        inset 0 -1px 0 rgba(0, 0, 0, 0.55),
        var(--x30-el-1);
    transition: opacity 180ms var(--x30-ease-out), border-color 150ms linear;
}

.mcap-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
}
.mcap-label {
    font-family: var(--font-ui);
    font-size: 8px;
    font-weight: 600;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--x30-ink-mid);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.mcap-delta {
    font-family: var(--font-ui);
    font-size: 9px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.08em;
    white-space: nowrap;
}
/* COLOUR LAW: a fall is AMBER, not the combat red. Red on this HUD means "you are in
   danger" and must never be spent on a chart — a player reading red has to be able to
   trust it without a glance. */
.mcap-delta[data-dir="up"] { color: var(--x30-accent); }
.mcap-delta[data-dir="down"] { color: var(--x30-warn); }

.mcap-readout {
    display: flex;
    align-items: baseline;
    gap: 4px;
    margin-top: 3px;
}
.mcap-unit {
    font-family: var(--font-ui);
    font-size: 10px;
    font-weight: 700;
    color: var(--x30-ink-mid);
}
/* seg-wrap / seg-under is the HUD's existing ghost-digit idiom (see the grenade and
   ammo counters); the mcap field is simply a wider instance of it. */
.seg-mcap {
    position: relative;
    display: inline-block;
    font-family: 'DSEG7', var(--font-display), monospace;
    font-weight: 700;
    font-size: 15px;
    letter-spacing: 0.02em;
    line-height: 1.1;
}
.seg-mcap .seg-under {
    position: absolute;
    inset: 0;
    color: rgba(244, 247, 249, 0.05);
    pointer-events: none;
}
.seg-mcap > span:not(.seg-under) {
    position: relative;
    color: var(--x30-ink);
}
#mcap-panel[data-state="live"] .seg-mcap > span:not(.seg-under) { text-shadow: var(--x30-emit-ally); }
/* NO DATA is a legible state, not a blank one — dashes, dimmed, exactly what an
   instrument with no signal shows. We never render an invented number. */
#mcap-panel[data-state="nodata"] .seg-mcap > span:not(.seg-under) { color: var(--x30-ink-low); }
#mcap-panel[data-state="nodata"] .mcap-label { color: var(--x30-ink-low); }

/* A single 150ms frame flash on a macro move. Nothing pulses on a normal tick — an
   element that blinks constantly is one the eye learns to discard. */
#mcap-panel[data-flash="up"] { border-color: var(--x30-accent); }
#mcap-panel[data-flash="down"] { border-color: var(--x30-warn); }

.pressure-row { margin-top: 6px; }
.pressure-gauge {
    display: flex;
    gap: 1px;
    height: 4px;
}
/* Ten discrete blocks, not a continuous bar: a segmented gauge reads as an instrument,
   and it stops a one-pixel drift from looking like movement. */
.gauge-block {
    flex: 1 1 0;
    height: 100%;
    background: var(--x30-surface-3);
    transition: background 200ms linear;
}
.gauge-block[data-s="buy"] { background: var(--x30-accent); }
.gauge-block[data-s="sell"] { background: var(--x30-warn); }
.gauge-block[data-s="off"] { background: rgba(244, 247, 249, 0.06); }

.pressure-legend {
    display: flex;
    justify-content: space-between;
    margin-top: 3px;
    font-family: var(--font-ui);
    font-size: 7.5px;
    font-weight: 600;
    letter-spacing: 0.12em;
}
.p-buy { color: var(--x30-accent); }
.p-sell { color: var(--x30-warn); }

/* ---- TIER III whale alert -------------------------------------------------- */
#whale-alert-banner {
    position: absolute;
    top: 2%;
    left: 50%;
    z-index: 9;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px 16px;
    background: var(--x30-surface-1);
    border: 1px solid var(--x30-feedback-gold);
    border-radius: var(--x30-r-2);
    box-shadow: var(--x30-emit-gold), inset 0 1px 0 rgba(255, 194, 75, 0.18);
    font-family: var(--font-ui);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--x30-feedback-gold);
    white-space: nowrap;
    pointer-events: none;
    /* Hidden by default and never display:none — transform/opacity animate on the
       compositor, and this fires mid-firefight where a layout pass is not free. */
    opacity: 0;
    transform: translateX(-50%) translateY(-8px);
    transition: opacity 160ms var(--x30-ease-out), transform 160ms var(--x30-ease-out);
}
#whale-alert-banner[data-on="1"] { opacity: 1; transform: translateX(-50%) translateY(0); }
.whale-sigil { font-size: 11px; }
.whale-amount {
    font-family: 'DSEG7', var(--font-display), monospace;
    font-size: 13px;
    letter-spacing: 0.02em;
}
.whale-wallet { color: rgba(255, 194, 75, 0.6); letter-spacing: 0.1em; }

/* ---- COMBAT SUPPRESSION ---------------------------------------------------- */
/* The market yields to the firefight. Every pixel of this competes with an enemy
   silhouette, and no marketcap is worth a death. body.in-combat is set in Simulator
   for 3.5s after taking damage or landing a hit — there was no in-combat signal in
   this codebase before, only a death flag, so the window is defined there. */
body.in-combat #mcap-panel {
    opacity: 0.2;
    border-color: transparent;
    box-shadow: none;
}
body.in-combat #whale-alert-banner[data-on="1"] {
    opacity: 0.3;
    transform: translateX(-50%) translateY(-4px) scale(0.85);
    box-shadow: none;
}

@media (prefers-reduced-motion: reduce) {
    #mcap-panel, #whale-alert-banner, .gauge-block { transition: none; }
}

/* Phones have no room for a 210px instrument beside the vitals, and the crosshair
   margin is far tighter. The readout survives; the gauge legend does not. */
@media (max-width: 720px) {
    #market-hud { top: 8px; right: 8px; }
    #mcap-panel { width: 150px; padding: 6px 8px 5px; }
    .seg-mcap { font-size: 12px; }
    .pressure-legend { display: none; }
    #whale-alert-banner { font-size: 9px; padding: 5px 10px; gap: 7px; }
}

/* ══ SNIPER SCOPE ══════════════════════════════════════════════════════════════
   From the round robin in _work/sniper-roundrobin/. Zero assets: the entire optic
   is two radial gradients, four divs and a masked gradient pair for the mil ticks.

   The load-bearing decision is OCCLUSION rather than vignette. Outside the tube is
   not dimmed, it is solid --x30-void — a real scope's field is a tunnel and losing
   your peripheral vision is what you PAY for the magnification. A darkened-but-
   visible surround would hand the player the zoom for free, which is exactly the
   videogame-scope-as-sticker the brief rejected. */

#scope-viewport {
    position: absolute;
    inset: 0;
    z-index: 12;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    /* Composited: the whole optic animates on transform + opacity only. */
    transform: scale(1.15);
    will-change: transform, opacity;
    /* No transition — Simulator drives this per-frame off the ADS ramp, so a CSS
       transition here would fight the JS and lag the iris behind the FOV. */
}
#scope-viewport.is-on { visibility: visible; }

/* ---- the tube ---- */
/* Percentages, not vh: on a short landscape phone a vh-sized tube overflows the
   frame entirely and the player sees nothing but housing. min() keeps the optic
   inside the SHORT axis whatever the aspect. */
.scope-mask {
    position: absolute;
    inset: 0;
    background: radial-gradient(circle at center,
        transparent 0,
        transparent calc(min(50vw, 50vh) * 0.62),
        /* a whisper of coating bloom right at the glass edge */
        rgba(20, 241, 149, 0.045) calc(min(50vw, 50vh) * 0.63),
        /* the inner bevel of the tube, then solid housing */
        rgba(5, 7, 8, 0.90) calc(min(50vw, 50vh) * 0.66),
        var(--x30-void) calc(min(50vw, 50vh) * 0.70),
        var(--x30-void) 100%);
}

/* ---- the eye box ---- */
/* The crescent that creeps in when the cheek weld drifts. Oversized and inset so it
   can travel without ever exposing a bare corner of the frame. */
.scope-shadow {
    position: absolute;
    inset: -14%;
    background: radial-gradient(circle at center,
        transparent 0,
        transparent calc(min(50vw, 50vh) * 0.50),
        rgba(5, 7, 8, 0.95) calc(min(50vw, 50vh) * 0.64),
        var(--x30-void) calc(min(50vw, 50vh) * 0.78));
    transform: translate3d(0, 0, 0);
    will-change: transform;
}

/* ---- the reticle ---- */
/* Etched, not projected: ink white with a dark halo so it survives against both a
   bright skybox and a black doorway. It is NOT the accent green — a reticle is not
   an interactive element, and green here would read as UI floating in the world. */
.scope-reticle {
    position: absolute;
    top: 50%;
    left: 50%;
    width: min(100vw, 100vh);
    height: min(100vw, 100vh);
    transform: translate3d(-50%, -50%, 0);
}
.ret-line {
    position: absolute;
    background: var(--x30-ink);
    box-shadow: 0 0 1px var(--x30-void);
    opacity: 0.85;
}
/* A 3px gap at the centre. The crosshair must not cover the thing you are aiming at
   — at 22 FOV a head is only a few px across and a solid cross hides it. */
.ret-v-top    { left: 50%; top: 0;    width: 1.5px; height: calc(50% - 3px); transform: translateX(-50%); }
.ret-v-bottom { left: 50%; bottom: 0; width: 1.5px; height: calc(50% - 3px); transform: translateX(-50%); }
.ret-h-left   { top: 50%; left: 0;    height: 1.5px; width: calc(50% - 3px); transform: translateY(-50%); }
.ret-h-right  { top: 50%; right: 0;   height: 1.5px; width: calc(50% - 3px); transform: translateY(-50%); }

/* Mil ticks: two repeating gradients, masked to an annulus so the marks exist along
   the crosshair arms and nowhere else. Cheap, and it reads as a ranging scale
   rather than as decoration. */
.scope-reticle::after {
    content: "";
    position: absolute;
    inset: 34%;
    background-image:
        repeating-linear-gradient(0deg, transparent 0 18px, var(--x30-ink) 18px 19.5px),
        repeating-linear-gradient(90deg, transparent 0 18px, var(--x30-ink) 18px 19.5px);
    -webkit-mask: radial-gradient(circle at center, transparent 0 20px, #000 21px 96px, transparent 97px);
    mask: radial-gradient(circle at center, transparent 0 20px, #000 21px 96px, transparent 97px);
    opacity: 0.5;
}

/* The crosshair the rest of the game draws is redundant inside an optic and sits at
   a different scale — two crosshairs at once is the clearest possible tell that the
   scope is a sticker. */
body.scoped #crosshair { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
    /* The eye-box drift is the one part of this that is pure motion. The tube, the
       occlusion and the reticle all stay — they are information, not animation. */
    .scope-shadow { transform: none !important; }
}
