/* ============================================================================
   UT99 Codex — interface tokens, boot screens, and Botpack.ChallengeHUD.

   TWO SURFACES. They are different subsystems in the shipped game and they are
   different subsystems here.

     UWindow (the menu) is opaque metal with hard nine-slice bevels, and it is
     a design surface: it has skins.
       UWindow/UWindowWindow.uc:1331-1361  DrawUpBevel — nine segments
       UMenu/UMenuOptionsClientWindow.uc:45 calls it for a client area
       UMenu/UMenuGoldLookAndFeel.uc:1037-1045 sizes every region at 2px
       UWindow/UWindowRootWindow.uc:36 makes the skin a config string,
       defaulted at UMenu/UMenuRootWindow.uc:118 and exposed as GuiSkinCombo
       at UMenu/UMenuVideoClientWindow.uc:407.
     Its tokens are --menu-*, under [data-skin]. It is never translucent in a
     classic skin: `Opacity` does not occur anywhere under UWindow/ or UMenu/.

     The HUD is not a design surface. It is a port of Botpack/ChallengeHUD.uc
     using the shipped Botpack.Icons sprites at the shipped coordinates. Its
     tokens are --ut-*, and the numbers below are quoted from the script.

   THE HUD'S COORDINATE SPACE. ChallengeHUD.uc:312 fixes the entire layout to
   `Scale = (HUDScale * Canvas.ClipX)/1280.0`, so --ut-px is one UT unit and
   every literal here is the literal in the script. StatScale (:471) and
   WeapScale (:679) get derived tokens from the same two user settings.

   THE HUD'S BLENDING. `Canvas.DrawColor` multiplies the texel and
   `Canvas.Style` decides how it lands. Reproduced as
     background-blend-mode: multiply   texel * DrawColor
     mask-image: <same sprite>         the FLAGS=2 masked alpha
     mix-blend-mode: screen            STY_Translucent is src + dst*(1-src),
                                       which is the `screen` blend exactly
   and ChallengeHUD.uc:317-322's own escape hatch — Opacity == 16 draws
   STY_Normal at SolidHUDColor — is wired to the same setting, not reinvented.
   ========================================================================= */

:root {
  color-scheme: dark;

  /* The tint, shared. The one thing both surfaces may touch, because it is the
     player's and not the designer's: Botpack/ChallengeHUD.uc:105
     `var globalconfig color FavoriteHUDColor` — see ui-settings.js. */
  --tint-rgb: 0 0 254;
  --tint: rgb(var(--tint-rgb));
  --tint-soft: rgb(var(--tint-rgb) / .26);
  --tint-edge: color-mix(in oklab, rgb(var(--tint-rgb)) 62%, #fff);

  /* Type, shared. System stacks only; first paint beats a bespoke face. */
  --font-display: Bahnschrift, "DIN Alternate", "Roboto Condensed", "Arial Narrow", sans-serif;
  --font-readout: "Cascadia Mono", "JetBrains Mono", ui-monospace, Consolas, monospace;

  font-family: var(--font-display);
  font-synthesis-weight: none;
}

/* ========================================================= MENU SKIN tokens */
/* Opaque. Beveled. --menu-blur stays 0 in every classic skin on purpose.
   Values are verbatim pixels from the shipped look-and-feel textures, read at
   the BevelUp* regions UMenuGoldLookAndFeel.uc:1037-1045 declares:
   row 16 is the outline, row 17 the highlight, rows 20-29 the metal body. */

[data-skin="blue"], [data-skin="classic"] {   /* models/UMenu/Texture/BlueMisc.png */
  --menu-metal: linear-gradient(#5b7fb4, #698dc2);
  --menu-metal-flat: #6084b9;
  --menu-metal-hot: linear-gradient(#698dc2, #7599ce);
  --menu-edge-light: #bcd6ff;
  --menu-edge-dark: #0d3166;
  /* BlueClientArea.png is a 128x1 ramp #454265 -> #cac7ea stretched across the
     client. Its light half is used, because black is the shipped text colour
     (UWindow/UWindowDialogControl.uc:158 TextColor=(R=0,G=0,B=0)) and the dark
     end of that ramp cannot carry it. */
  --menu-client: linear-gradient(100deg, #8481a4, #cac7ea);
  --menu-chrome: #05295e;         /* BlueMenuTL.png row 0 */
  --menu-hair: rgb(13 49 102 / .34);
}

[data-skin="metal"] {                          /* models/UMenu/Texture/MetalMisc.png */
  --menu-metal: linear-gradient(#70825b, #80926b);
  --menu-metal-flat: #72845d;
  --menu-metal-hot: linear-gradient(#80926b, #8fa17a);
  --menu-edge-light: #cbddb6;
  --menu-edge-dark: #24360f;
  --menu-client: linear-gradient(100deg, #7e9069, #c4d6af);  /* MetalClientArea.png */
  --menu-chrome: #372100;                      /* MetalMenuTL.png row 0 */
  --menu-hair: rgb(36 54 15 / .34);
}

[data-skin="gold"] {                           /* models/UMenu/Texture/Misc.png */
  --menu-metal: linear-gradient(#656fda, #7c8af5);
  --menu-metal-flat: #6874e0;
  --menu-metal-hot: linear-gradient(#7c8af5, #8f9cf9);
  --menu-edge-light: #c5d2ff;
  --menu-edge-dark: #27284c;
  /* Gold ships no separately extracted client ramp; derived from its own metal
     body so the skin stays internally consistent. */
  --menu-client: linear-gradient(100deg, #8f9cf9, #d5dbff);
  --menu-chrome: #27284c;
  --menu-hair: rgb(39 40 76 / .34);
}

[data-skin="blue"], [data-skin="classic"], [data-skin="metal"], [data-skin="gold"] {
  --menu-chrome-ink: #fff;        /* UMenuGoldLookAndFeel.uc:1033 */
  --menu-ink: #000;               /* UWindowDialogControl.uc:158 */
  --menu-ink-dim: #33333f;
  --menu-ink-off: #606060;        /* UMenuBlueLookAndFeel.uc:177-179, disabled */
  --menu-frame-w: 2px;
  --menu-bevel: conic-gradient(from 45deg, var(--menu-edge-dark) 0 50%, var(--menu-edge-light) 0);
  --menu-bevel-in: conic-gradient(from 45deg, var(--menu-edge-light) 0 50%, var(--menu-edge-dark) 0);
  --menu-radius: 0;
  --menu-blur: 0px;
  --menu-scrim: rgb(2 5 12 / .9);
  --menu-focus: #ffef7a;
  --menu-accent: var(--menu-chrome);
  --menu-pane-pad: 0px;
  --menu-field: #fff;
  --menu-field-ink: #000;
}

/* No windows, no title bars, no chrome. Structure and type do the work and the
   arena is the backdrop — so this skin, and only this skin, is translucent. */
[data-skin="modern"] {
  --menu-metal: rgb(255 255 255 / .05);
  --menu-metal-flat: rgb(255 255 255 / .05);
  --menu-metal-hot: rgb(255 255 255 / .1);
  --menu-edge-light: transparent;
  --menu-edge-dark: transparent;
  --menu-client: transparent;
  --menu-chrome: transparent;
  --menu-chrome-ink: #f2f4f8;
  --menu-ink: #eef1f6;
  --menu-ink-dim: #98a2b0;
  --menu-ink-off: #5d6672;
  --menu-hair: rgb(255 255 255 / .13);
  --menu-frame-w: 1px;
  --menu-bevel: linear-gradient(var(--menu-hair), var(--menu-hair));
  --menu-bevel-in: var(--menu-bevel);
  --menu-radius: 2px;
  --menu-blur: 22px;
  --menu-scrim: rgb(6 8 13 / .74);
  --menu-focus: var(--tint-edge);
  --menu-accent: var(--tint-edge);
  --menu-pane-pad: clamp(24px, 5vw, 72px);
  --menu-field: transparent;
  --menu-field-ink: var(--menu-ink);
}

/* ------------------------------------------------------------- primitives  */
* { box-sizing: border-box; }
html, body, #game { width: 100%; height: 100%; margin: 0; overflow: hidden; }
body { background: #05070b; color: var(--menu-ink); }
#game { display: block; background: #05070b; }
:focus-visible { outline: 2px solid var(--menu-focus); outline-offset: 2px; }

/* ============================================================ boot screens */
/* Menu surfaces, so they follow the skin. */
.loading {
  position: fixed; inset: 0; z-index: 10;
  display: flex; align-items: center; justify-content: center;
  /* The first-run browser boundary is intentionally anonymous. Do not let the
     already-loaded map, its billboards, or the menu skin identify the game
     before the player presses Play. */
  background: #05070b;
  text-align: center;
}
.loading { flex-direction: column; gap: 18px; }
.loading[hidden], .hud[hidden], .death-overlay[hidden] { display: none; }
/* main.js hides each DOM HUD node the shipped ChallengeHUD bytecode draws for
   itself (DUPLICATED_BY_CHALLENGE_HUD). The `hidden` attribute's UA rule loses
   to ANY authored `display`, and `.mode-hud { display: flex }` defeated it --
   MEASURED 2026-08-01: the DOM probe read `hidden: true` while the screenshot
   showed the banner still on screen. This makes the hide list unconditional. */
.hud [hidden] { display: none !important; }

.loading p { margin: 0; color: #98a2b0; font: 400 13px/1.5 var(--font-readout); }
.startup-gate { z-index: 11; }
.resume-capture-hint {
  position: fixed; inset: 0; z-index: 9;
  display: grid; place-items: center; width: 100%; height: 100%;
  appearance: none; margin: 0; padding: 0; border: 0;
  background: rgb(0 0 0 / .22); color: #fff; cursor: crosshair;
  font: 700 13px/1 var(--font-readout); letter-spacing: .08em;
  text-transform: uppercase; text-shadow: 0 1px 2px #000;
}
.resume-capture-hint::before {
  content: ""; position: absolute; left: 50%; top: 50%; width: 54px; height: 54px;
  transform: translate(-50%, -50%); border: 1px solid rgb(255 255 255 / .62);
  border-radius: 50%; box-shadow: 0 0 28px rgb(255 255 255 / .12), inset 0 0 18px rgb(0 0 0 / .65);
}
.resume-capture-hint::after {
  content: ""; position: absolute; left: 50%; top: 50%; width: 8px; height: 8px;
  transform: translate(-50%, -50%); border: 1px solid #fff; border-radius: 50%;
}
.resume-capture-hint > span { transform: translateY(54px); }
.resume-capture-hint:hover { background: rgb(0 0 0 / .16); }
.resume-capture-hint:focus-visible { outline: 2px solid #fff; outline-offset: -4px; }
.resume-capture-hint[hidden] { display: none; }
.startup-play {
  width: clamp(132px, 18vw, 184px); aspect-ratio: 1; margin-top: 8px;
  display: grid; place-items: center; padding: 0;
  border: 2px solid rgb(255 255 255 / .54); border-radius: 50%;
  background: radial-gradient(circle at 38% 32%, rgb(255 255 255 / .18), transparent 34%),
    var(--menu-chrome);
  color: var(--menu-chrome-ink); cursor: pointer;
  box-shadow: 0 0 0 1px rgb(0 0 0 / .8), 0 18px 70px rgb(0 0 0 / .58),
    0 0 42px color-mix(in srgb, var(--menu-accent) 38%, transparent);
  transition: transform 120ms ease, filter 120ms ease, box-shadow 120ms ease;
  animation: startup-play-pulse 1.8s ease-in-out infinite;
}
.startup-play svg { width: 58%; height: 58%; margin-left: 8%; overflow: visible; }
.startup-play path { fill: currentColor; stroke: rgb(0 0 0 / .42); stroke-width: 2; stroke-linejoin: round; }
.startup-play:hover { transform: scale(1.055); filter: brightness(1.13); }
.startup-play:active { transform: scale(.97); }
.startup-play:disabled { cursor: wait; opacity: .72; animation: none; }
@keyframes startup-play-pulse {
  0%, 100% { box-shadow: 0 0 0 1px rgb(0 0 0 / .8), 0 18px 70px rgb(0 0 0 / .58),
    0 0 24px color-mix(in srgb, var(--menu-accent) 24%, transparent); }
  50% { box-shadow: 0 0 0 1px rgb(0 0 0 / .8), 0 18px 70px rgb(0 0 0 / .58),
    0 0 58px color-mix(in srgb, var(--menu-accent) 58%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .startup-play { animation: none; transition: none; }
}

/* ============================================== Botpack.ChallengeHUD (DOM)  */
.hud {
  position: fixed; inset: 0; z-index: 5; pointer-events: none;
  /* ChallengeHUD.uc:312 Scale, :471 StatScale, :679 WeapScale. main.js writes
     --hud-scale / --status-scale / --weapon-scale from the same three
     globalconfig floats declared at ChallengeHUD.uc:104. */
  --ut-px: calc(var(--hud-scale, 1) * 100vw / 1280);
  --ut-stat: calc(var(--status-scale, 1) * var(--ut-px));
  --ut-weap: calc(var(--weapon-scale, .8) * var(--ut-px));
  --ut-hud: rgb(0 0 254); --ut-solid: rgb(0 0 254); --ut-half: rgb(0 0 127);
  --ut-base: rgb(255 255 255); --ut-belt: rgb(255 255 0);
  --ut-gold: rgb(255 255 0); --ut-white: rgb(255 255 255); --ut-purple: rgb(255 0 255);
  color: var(--ut-white);
}

/* One sprite primitive. Every HUD element is a DrawTile, so every HUD element
   is this: a masked, multiplied, screened rectangle of an atlas. */
.ut-plate:before, .ut-frags:before, .ut-ammo:before,
.ut-slot:before, .ut-slot:after, .ut-slot-key:before, .ut-slot-ammo:before,
.crosshair:before, .ut-digit,
.doll-body, .doll-chest, .doll-thigh, .doll-boots, .doll-belt {
  background-image: var(--tex);
  background-size: var(--tex-size);
  background-position: var(--tex-pos);
  background-repeat: no-repeat;
  background-color: var(--tint);
  background-blend-mode: multiply;
  -webkit-mask-image: var(--tex);
  -webkit-mask-size: var(--tex-size);
  -webkit-mask-position: var(--tex-pos);
  -webkit-mask-repeat: no-repeat;
  mask-image: var(--tex);
  mask-size: var(--tex-size);
  mask-position: var(--tex-pos);
  mask-repeat: no-repeat;
}
.ut-plate:before, .ut-frags:before, .ut-ammo:before,
.ut-slot:before, .ut-slot:after, .ut-slot-key:before, .ut-slot-ammo:before, .crosshair:before {
  content: ""; position: absolute; inset: 0;
}
/* ChallengeHUD.uc:317-322 — at Opacity 16 the HUD is STY_Normal and there is
   no blend at all. Below that it is STY_Translucent, which is `screen`. */
.hud[data-style="translucent"] .ut-plate:before,
.hud[data-style="translucent"] .ut-frags:before,
.hud[data-style="translucent"] .ut-ammo:before,
.hud[data-style="translucent"] .ut-slot:before,
.hud[data-style="translucent"] .ut-slot:after,
.hud[data-style="translucent"] .ut-slot-key:before,
.hud[data-style="translucent"] .ut-slot-ammo:before,
.hud[data-style="translucent"] .crosshair:before,
.hud[data-style="translucent"] .ut-digit,
.hud[data-style="translucent"] .ut-doll i { mix-blend-mode: screen; }

/* --- DrawStatus: the doll, ChallengeHUD.uc:466-500 ----------------------- */
/* X = ClipX - 128*StatScale, Y = 0. Base body is src (0,0,128,256) of the
   256x256 atlas; the armour overlays are its right half. */
.ut-status { position: absolute; inset: 0; pointer-events: none; }
.ut-doll {
  position: absolute; right: 0; top: 0;
  width: calc(128 * var(--ut-stat)); height: calc(256 * var(--ut-stat));
  --tex: var(--tex-doll);
  --tex-size: calc(256 * var(--ut-stat)) calc(256 * var(--ut-stat));
}
.ut-doll i { position: absolute; left: 0; width: 100%; }
.doll-body {
  top: 0; height: 100%; --tex-pos: 0 0; --tint: var(--ut-hud);
}
.ut-doll[data-amplified] .doll-body { --tint: var(--ut-purple); }   /* :475-478 */
/* :486-497 — the overlay tint is HUDColor scaled by the armour fraction,
   which is a multiply toward black, so it is a mix with black. */
.doll-chest {
  top: 0; height: calc(64 * var(--ut-stat));
  --tex-pos: calc(-128 * var(--ut-stat)) 0;
  --tint: color-mix(in srgb, var(--ut-hud) calc(var(--chest, 0) * 100%), #000);
}
.doll-thigh {
  top: calc(64 * var(--ut-stat)); height: calc(64 * var(--ut-stat));
  --tex-pos: calc(-128 * var(--ut-stat)) calc(-64 * var(--ut-stat));
  --tint: color-mix(in srgb, var(--ut-hud) calc(var(--thigh, 0) * 100%), #000);
}
.doll-boots {
  top: calc(128 * var(--ut-stat)); height: calc(64 * var(--ut-stat)); display: none;
  --tex-pos: calc(-128 * var(--ut-stat)) calc(-128 * var(--ut-stat));
  --tint: var(--ut-hud);
}
.ut-doll[data-boots] .doll-boots { display: block; }
/* :480-485 — the belt is its own 128x256 texture, BaseColor with B forced 0. */
.doll-belt {
  top: 0; height: 100%; display: none;
  --tex: var(--tex-doll-belt);
  --tex-size: calc(128 * var(--ut-stat)) calc(256 * var(--ut-stat));
  --tex-pos: 0 0; --tint: var(--ut-belt);
}
.ut-doll[data-belt] .doll-belt { display: block; }

/* --- the four HudElements1 readouts -------------------------------------- */
.ut-plate, .ut-ammo, .ut-frags {
  position: absolute;
  width: calc(128 * var(--ut-px)); height: calc(64 * var(--ut-px));
  --tex: var(--tex-elements);
  --tex-size: calc(256 * var(--ut-px)) calc(256 * var(--ut-px));
  --tint: var(--ut-hud);
}
/* :557-580 armour at Y = 0, :525-556 health at Y = 64*Scale, both at
   X = ClipX - 128*StatScale - 140*Scale (the tile is 128*Scale wide). */
.ut-armor { top: 0; right: calc(128 * var(--ut-stat) + 12 * var(--ut-px)); --tex-pos: 0 calc(-192 * var(--ut-px)); }
.ut-health { top: calc(64 * var(--ut-px)); right: calc(128 * var(--ut-stat) + 12 * var(--ut-px)); --tex-pos: calc(-128 * var(--ut-px)) calc(-128 * var(--ut-px)); }
/* :596-603 ammo at X = ClipX - 128*Scale, Y = ClipY - 63.5*Scale. */
.ut-ammo { right: 0; bottom: calc(-0.5 * var(--ut-px)); --tex-pos: calc(-128 * var(--ut-px)) calc(-192 * var(--ut-px)); }
/* DrawFragCount leaves X at 0 when the weapon bar is shown. */
.ut-frags { left: 0; bottom: calc(-0.5 * var(--ut-px)); --tex-pos: 0 calc(-128 * var(--ut-px)); }

/* --- DrawBigNum / DrawDigit, ChallengeHUD.uc:336-403 --------------------- */
/* The host keeps main.js's text node for assistive technology at font-size 0,
   so `Show Frags`' visibility and `Show Weapons`' display still reach the
   sprites, which are its children. */
.ut-num {
  position: absolute; display: flex; align-items: flex-start;
  left: calc(4 * var(--ut-px)); top: calc(16 * var(--ut-px));
  font-size: 0; line-height: 0; white-space: nowrap; font-style: normal;
}
.ut-frags .ut-num { left: calc(40 * var(--ut-px)); }
.ut-lead { display: block; flex: none; width: calc(var(--w) * var(--ut-px)); height: 1px; }
.ut-digit {
  display: block; flex: none;
  width: calc(25 * var(--ut-px)); height: calc(64 * var(--ut-px));
  margin-left: calc(var(--adjust) * var(--ut-px));
  margin-right: calc(7 * var(--ut-px));
  --tex: var(--tex-elements);
  --tex-size: calc(256 * var(--ut-px)) calc(256 * var(--ut-px));
  --tex-pos: calc(-25 * var(--tile) * var(--ut-px)) calc(-1 * var(--row) * var(--ut-px));
  --tint: var(--ut-white);
}

/* --- DrawWeapons, ChallengeHUD.uc:673-825 -------------------------------- */
/* WeaponOffset (:682) is 0.1*HUDScale*WeaponScale*ClipX and a slot tile is
   128*WeapScale — algebraically the same number, so the bar is ten contiguous
   tiles centred on BaseX (:677). */
.ut-weapons {
  position: absolute; left: 50%; bottom: calc(-0.5 * var(--ut-weap));
  transform: translateX(-50%);
  font-size: 0; line-height: 0;
}
.ut-weapon-bar { display: flex; }
.ut-slot {
  position: relative; display: block; flex: none;
  width: calc(128 * var(--ut-weap)); height: calc(64 * var(--ut-weap));
  --tex: var(--tex-weapons);
  /* :770-771 the grid cell is 64x32 drawn into 128x64 — a 2x magnification. */
  --tex-size: calc(512 * var(--ut-weap)) calc(512 * var(--ut-weap));
  --tex-pos: calc(-2 * var(--grid-x, 0) * var(--ut-weap)) calc(-2 * var(--grid-y, 0) * var(--ut-weap));
  --tint: var(--ut-half);                        /* :765  0.5 * HUDColor */
}
.ut-slot[data-pending]:before { --tint: var(--ut-solid); }   /* :779 */
.ut-slot[data-icon]:before {                                 /* :710 DrawIcon(StatusIcon) */
  --tex: var(--icon);
  --tex-size: calc(128 * var(--ut-weap)) calc(64 * var(--ut-weap));
  --tex-pos: 0 0;
}
.ut-slot[data-selected]:before { --tint: var(--ut-solid); }
.ut-slot:after { display: none; }
.ut-slot[data-selected]:after {                              /* :722 selection frame */
  display: block;
  --tex: var(--tex-weapons);
  --tex-size: calc(256 * var(--ut-weap)) calc(256 * var(--ut-weap));
  --tex-pos: calc(-128 * var(--ut-weap)) calc(-64 * var(--ut-weap));
  --tint: var(--ut-gold);
}
.ut-slot-key, .ut-slot-ammo { position: absolute; display: none; }
.ut-slot[data-held] .ut-slot-key, .ut-slot[data-held] .ut-slot-ammo { display: block; }
/* :715-718 and :810-816 — the key numeral at 0.75 scale, GoldColor. */
.ut-slot-key {
  left: calc(4 * var(--ut-weap)); top: calc(4 * var(--ut-weap));
  width: calc(18.75 * var(--ut-weap)); height: calc(48 * var(--ut-weap));
  --tex: var(--tex-elements);
  --tex-size: calc(192 * var(--ut-weap)) calc(192 * var(--ut-weap));
  --tex-pos: calc(-18.75 * var(--tile, 0) * var(--ut-weap)) 0;
  --tint: var(--ut-gold);
}
/* :818-824 — src (64,64,128,8) squeezed into AmmoScale x 8, BaseColor. */
.ut-slot-ammo {
  left: calc(4 * var(--ut-weap)); top: calc(52 * var(--ut-weap));
  width: calc(var(--ammo, 0) * var(--ut-weap)); height: calc(8 * var(--ut-weap));
  --tex: var(--tex-elements);
  --tex-size: calc(2 * var(--ammo, 0) * var(--ut-weap)) calc(256 * var(--ut-weap));
  --tex-pos: calc(-0.5 * var(--ammo, 0) * var(--ut-weap)) calc(-64 * var(--ut-weap));
  --tint: var(--ut-base);
}

/* --- crosshair, ChallengeHUD.uc:1430-1483 -------------------------------- */
.crosshair {
  position: absolute; left: 50%; top: 50%;
  width: calc(64px * var(--crosshair-scale, 1)); height: calc(64px * var(--crosshair-scale, 1));
  transform: translate(-50%, -50%);
  --tex: var(--tex-chair1);
  --tex-size: 100% 100%;
  --tex-pos: 0 0;
  --tint: var(--crosshair-color, #00f000);       /* :1480  15 * CrosshairColor */
}
.hud[data-crosshair="1"] .crosshair { --tex: var(--tex-chair1); }
.hud[data-crosshair="2"] .crosshair { --tex: var(--tex-chair2); }
.hud[data-crosshair="3"] .crosshair { --tex: var(--tex-chair3); }
.hud[data-crosshair="4"] .crosshair { --tex: var(--tex-chair4); }
.hud[data-crosshair="5"] .crosshair { --tex: var(--tex-chair5); }
.hud[data-crosshair="6"] .crosshair { --tex: var(--tex-chair6); }
.hud[data-crosshair="7"] .crosshair { --tex: var(--tex-chair7); }
.hud[data-crosshair="8"] .crosshair { --tex: var(--tex-chair8); }
.hud[data-crosshair="9"] .crosshair { --tex: var(--tex-chair9); }
/* :1443-1447 the auto scale doubles past 640-wide steps. */
@media (min-width: 1280px) {
  .crosshair { width: calc(128px * var(--crosshair-scale, 1)); height: calc(128px * var(--crosshair-scale, 1)); }
}

/* --- text layers: synopsis, identify, messages --------------------------- */
/* These are DrawText in the original, so they are text here. The halo is the
   one addition: over a lit wall a translucent HUD washes out, and a scrim on
   the glyphs restores it without adding a plate the script never drew. */
.ut-synopsis, .target-identify, .message, .debug, .mode-hud, .speech-portrait span {
  text-shadow: 0 0 2px rgb(2 4 9 / .95), 0 0 7px rgb(2 4 9 / .8), 0 1px 3px rgb(2 4 9 / .9);
}
.ut-synopsis {
  position: absolute; left: calc(6 * var(--ut-px)); bottom: calc(72 * var(--ut-px));
  color: var(--ut-white);
}
.ut-synopsis em {
  font: 500 clamp(11px, calc(18 * var(--ut-px)), 20px)/1.3 var(--font-readout);
  font-style: normal; font-variant-numeric: tabular-nums; letter-spacing: .06em;
}
.mode-hud {
  position: absolute; left: 50%; top: calc(6 * var(--ut-px)); transform: translateX(-50%);
  display: flex; align-items: baseline; gap: 12px;
  max-width: min(860px, 94vw);
}
.mode-hud small {
  flex: none; color: var(--ut-gold); font: 600 9px/1 var(--font-readout);
  letter-spacing: .2em; text-transform: uppercase;
}
.mode-hud b {
  flex: none; color: var(--ut-white); font: 700 15px/1 var(--font-display);
  letter-spacing: .06em; text-transform: uppercase;
}
.mode-hud > span {
  min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
  color: #cdd4de; font: 400 11px/1 var(--font-readout);
}
.mode-teams { display: flex; gap: 6px; }
.mode-team {
  padding: 2px 6px; border-bottom: 2px solid currentColor;
  font: 600 11px/1 var(--font-readout); font-variant-numeric: tabular-nums;
}
.mode-team.team-0 { color: #ff7a6d; }
.mode-team.team-1 { color: #74b4ff; }
.mode-team.team-2 { color: #79e58c; }
.mode-team.team-3 { color: #ffdd6b; }

.message {
  position: absolute; left: 50%; top: 64px; transform: translateX(-50%);
  max-width: min(560px, 90vw); text-align: center;
  color: var(--ut-white); font: 500 12px/1.5 var(--font-readout);
}
.message:empty { display: none; }
.message .say { color: var(--ut-white); }
.message .teamsay { color: #79e58c; }
.message .crit { color: #ff8f7f; }
.message .pickup { color: var(--ut-gold); }

.target-identify {
  position: absolute; left: 50%; bottom: calc(128 * var(--ut-px)); transform: translateX(-50%);
  display: none; text-align: center;
}
.target-identify.visible { display: block; }
.target-identify span { display: block; color: var(--ut-white); font: 600 14px/1.2 var(--font-display); letter-spacing: .1em; }
.target-identify small { display: block; margin-top: 2px; color: var(--ut-gold); font: 400 11px var(--font-readout); font-variant-numeric: tabular-nums; }

.debug {
  position: absolute; right: 10px; top: calc(258 * var(--ut-stat)); text-align: right;
  color: #9aa6b6; font: 400 9px/1.35 var(--font-readout);
  font-variant-numeric: tabular-nums; opacity: .8;
}
.typing-prompt {
  position: absolute; left: 50%; bottom: calc(140 * var(--ut-px)); transform: translateX(-50%);
  display: none; color: var(--ut-gold); font: 500 12px var(--font-readout);
}
.typing-prompt.visible { display: block; }

/* --- FacePanel speech portrait, damage feedback -------------------------- */
.speech-portrait {
  position: absolute; left: 24px; bottom: calc(140 * var(--ut-px));
  display: none; width: calc(96 * var(--ut-px)); height: calc(96 * var(--ut-px));
}
.speech-portrait.visible { display: block; }
.speech-face-bg { position: absolute; inset: 0; background: rgb(2 5 12 / .55); border: 1px solid rgb(255 255 255 / .18); }
.speech-face { position: absolute; inset: 3px; background-size: cover; background-position: center; }
.speech-face-overlay { position: absolute; inset: 3px; background: rgb(0 0 0 / .22); }
.speech-portrait span {
  position: absolute; left: 0; right: 0; bottom: -16px; text-align: center;
  color: var(--ut-white); font: 600 10px var(--font-display); letter-spacing: .12em;
}
.damage-flash {
  position: absolute; inset: 0; opacity: 0; mix-blend-mode: screen;
  background: radial-gradient(circle, transparent 25%, rgb(213 20 20 / .34) 100%);
  transition: opacity 60ms linear;
}
.damage-flash[data-disabled="true"] { display: none; }
.damage-direction { position: absolute; inset: 0; z-index: 6; }
.dir-hit { position: absolute; display: none; width: 60px; height: 60px; opacity: 0; }
.dir-hit.n { left: 50%; top: 8%; transform: translateX(-50%); }
.dir-hit.ne { right: 24%; top: 18%; }
.dir-hit.e { right: 8%; top: 50%; transform: translateY(-50%); }
.dir-hit.se { right: 24%; bottom: 22%; }
.dir-hit.s { left: 50%; bottom: 12%; transform: translateX(-50%); }
.dir-hit.sw { left: 24%; bottom: 22%; }
.dir-hit.w { left: 8%; top: 50%; transform: translateY(-50%); }
.dir-hit.nw { left: 24%; top: 18%; }
.dir-hit:before {
  content: ""; display: block; width: 0; height: 0; margin: 0 auto;
  border-left: 20px solid transparent; border-right: 20px solid transparent;
  border-bottom: 30px solid #ff3524;
}
.dir-hit.active { display: block; animation: dir-fade 1s ease-out forwards; }
@keyframes dir-fade { from { opacity: .9; scale: 1; } to { opacity: 0; scale: .4; } }

/* --- guided redeemer camera (behaviour unchanged) ------------------------ */
.redeemer-hud { position: absolute; inset: 0; display: none; overflow: hidden; }
.hud[data-redeemer="guiding"] .redeemer-hud, .hud[data-redeemer="static"] .redeemer-hud { display: block; }
.hud[data-redeemer="guiding"] > .crosshair { display: none; }
.redeemer-readout {
  position: absolute; inset: 0; opacity: .72; mix-blend-mode: screen;
  background: url("../models/BotPack/Texture/Readout.png") left var(--redeemer-scroll, 0px)/128px 128px repeat-y;
}
.redeemer-reticle {
  position: absolute; left: 50%; top: 50%; width: min(40vw, 256px); aspect-ratio: 1;
  transform: translate(-50%, -50%);
  background: url("../models/BotPack/Texture/GuidedX.png") center/contain no-repeat;
}
.redeemer-static {
  position: absolute; inset: 0; display: none; image-rendering: pixelated;
  background: #888 url("../models/BotPack/Texture/Static_A00.png") center/256px 256px repeat;
}
.hud[data-redeemer="static"] .redeemer-static { display: block; }
.hud[data-redeemer="static"] .redeemer-readout,
.hud[data-redeemer="static"] .redeemer-reticle,
.hud[data-redeemer="static"] .redeemer-targets { display: none; }
.redeemer-targets { position: absolute; inset: 0; }
.redeemer-targets i {
  position: absolute; display: none; width: 24px; height: 35px;
  transform: translate(-50%, -50%);
  background: url("../models/BotPack/Texture/Crosshair6.png") top center/16px 16px no-repeat;
  color: #f33; font: 9px var(--font-readout);
}
.redeemer-targets i:after {
  content: attr(data-distance); position: absolute; left: 50%; top: 18px;
  transform: translateX(-50%); white-space: nowrap;
}

/* ============================================================ death screen */
.death-overlay {
  position: fixed; inset: 0; z-index: 7;
  display: flex; align-items: center; justify-content: center;
  background: var(--menu-scrim); backdrop-filter: blur(var(--menu-blur));
  pointer-events: auto;
}
.death-frame {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: clamp(24px, 5vw, 44px) clamp(32px, 6vw, 64px);
  background: var(--menu-client);
  border: var(--menu-frame-w) solid transparent;
  border-image-source: var(--menu-bevel); border-image-slice: 1;
  border-radius: var(--menu-radius);
}
[data-skin="modern"] .death-frame { border: 0; background: none; }
.death-frame span {
  color: var(--menu-ink); letter-spacing: .1em; text-transform: uppercase;
  font: 700 clamp(24px, 4vw, 46px)/1 var(--font-display);
}
.death-frame strong { color: var(--menu-ink-dim); font: 500 13px var(--font-readout); font-variant-numeric: tabular-nums; }
.death-frame button {
  margin-top: 12px; padding: 12px 30px;
  border: var(--menu-frame-w) solid transparent;
  border-image-source: var(--menu-bevel); border-image-slice: 1;
  border-radius: var(--menu-radius);
  background: var(--menu-metal); color: var(--menu-ink);
  font: 600 12px/1 var(--font-display); letter-spacing: .16em; text-transform: uppercase; cursor: pointer;
}
[data-skin="modern"] .death-frame button { border: 1px solid var(--tint-edge); background: var(--tint-soft); }
.death-frame small { color: var(--menu-ink-dim); font: 400 11px var(--font-readout); }

/* ================================================================ quality  */
:fullscreen #game { width: 100vw; height: 100vh; }
:fullscreen .hud, :fullscreen .uw-root { font-size: var(--fullscreen-font-size, 1em); }

@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
  .redeemer-reticle { image-rendering: auto; }
}

@media (prefers-reduced-motion: reduce) {
  .damage-flash { transition: none; }
  .damage-direction, .dir-hit { display: none; }
  .dir-hit.active { animation: none; }
}

@media (forced-colors: active) {
  .ut-doll, .ut-plate, .ut-ammo, .ut-frags, .ut-slot { forced-color-adjust: none; }
  .ut-num { font-size: 15px; color: CanvasText; background: Canvas; }
  .ut-digit, .ut-lead { display: none; }
  .mode-hud b, .ut-synopsis em, .message { color: CanvasText; }
  .hud { text-shadow: none; }
  .death-frame { border: 1px solid CanvasText; background: Canvas; color: CanvasText; }
}

/* Small viewports. ChallengeHUD.uc:450 drops the doll below ClipX 400 and
   :332 flags bLowRes below 400; the same two thresholds are honoured here,
   and the readouts fall back to their text so the numbers stay legible when
   one UT unit is under half a device pixel. */
@media (max-width: 800px) {
  .ut-doll { display: none; }
  .ut-armor, .ut-health { right: calc(6 * var(--ut-px)); }
  .mode-hud > span { display: none; }
}
@media (max-width: 400px) {
  .hud { --ut-px: calc(var(--hud-scale, 1) * 400px / 1280); }
  .ut-armor, .ut-health { right: 4px; }
}
