/* ============================================================================
   Facing Worlds — global chrome + HUD
   ----------------------------------------------------------------------------
   The HUD is authored here rather than inline in JS. Everything under the `ut-`
   prefix is built by src/game/hud/hud-root.js, which only ever writes
   text, `data-*` and `is-*` classes — the entire look lives in this file.

   Visual target: the UT2003/2004 status bar, checked against a full-size retail
   screenshot rather than recalled (see the header of hud-root.js for exactly
   which images and what they show). The chassis of that HUD is:

     * chamfered dark plates with a bright metal outline, one at each bottom
       corner — health left, ammo right;
     * a round recessed disc on the outboard end of each plate carrying the
       glyph, half-sunk into the plate's silhouette;
     * LARGE white squared numerals inside the plates, with a hue glow behind
       them. The number is the dominant element on the screen;
     * a weapon bar of small slots along the bottom centre, the held slot lit;
     * small chips in the top corners.

   Kept from UT99 because neither later HUD has anything as good: the PAPER DOLL,
   a glowing team-colour figure of the armoured soldier, standing beside the
   health plate and carrying the same damage band as the numeral.

   Everything is tinted one hue at a time (gold when there are no teams;
   html[data-team] swaps the bar to red or blue). No UT art or fonts are used or
   shipped: the doll and every glyph are hand-authored inline SVG, and the type
   is a generic squared grotesque with a full system fallback stack.
   ========================================================================== */

/* Prevent text selection and blue overlay */
html,
body {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
  -webkit-user-drag: none;
}

/* Prevent selection on all elements */
* {
  -webkit-user-select: none !important;
  -moz-user-select: none !important;
  -ms-user-select: none !important;
  user-select: none !important;
  -webkit-touch-callout: none !important;
  -webkit-tap-highlight-color: transparent !important;
  -webkit-user-drag: none !important;
}

/* Remove focus outlines and blue highlights */
*:focus {
  outline: none !important;
  -webkit-tap-highlight-color: transparent !important;
}

button,
div,
span,
a,
input,
textarea,
select {
  -webkit-tap-highlight-color: transparent !important;
  outline: none !important;
}

/* Allow text selection only for links */
a {
  -webkit-user-select: text !important;
  -moz-user-select: text !important;
  -ms-user-select: text !important;
  user-select: text !important;
}

/* ============================================================================
   Design tokens
   ========================================================================== */

:root {
  /* UT99's HUD, matched against a retail screenshot rather than interpreted.
     Google Fonts is injected by hud-root.js; only the small caption text uses
     it now, because the numerals are drawn as seven-segment shapes. */
  --ut-font-ui: "Saira Condensed", "Arial Narrow", "Roboto Condensed",
    "DIN Condensed", "Haettenschweiler", Impact, sans-serif;
  --ut-font-num: var(--ut-font-ui);

  /* --- accent -------------------------------------------------------------
     UT99's HUD is BLUE by default, not gold. The panels are flat translucent
     navy with a bright blue edge; the numerals are pale periwinkle; the glyphs
     are saturated blue. The one warm colour anywhere is the yellow bracket
     around the held weapon slot, which is why it reads instantly. */
  --ut-hue: #b9b9ff;
  --ut-hue-deep: #4a4aff;
  --ut-glow: rgba(90, 90, 255, 0.75);
  --ut-glow-weak: rgba(90, 90, 255, 0.28);
  --ut-fill: rgba(40, 40, 200, 0.26);

  /* --- panel chassis -------------------------------------------------------
     FLAT, not bevelled. UT99 panels are a translucent wash with a single bright
     1px edge — you can see the map through them. The chamfered, gradient-lit
     plates this replaces are the UT2003/2004 idiom and were the main reason the
     old bar read as the later games. */
  --ut-plate-top: rgba(18, 20, 78, 0.5);
  --ut-plate-bot: rgba(10, 12, 52, 0.5);
  --ut-edge-top: rgba(96, 100, 255, 0.85);
  --ut-edge-bot: rgba(96, 100, 255, 0.85);

  /* Seven-segment: lit bars, and the dim ghost bars a real segment display
     shows for the segments that are off. */
  --ut-seg-on: #c3c3ff;
  --ut-seg-off: rgba(90, 95, 200, 0.16);
  --ut-seg-glow: rgba(140, 140, 255, 0.55);

  /* The active weapon slot's bracket. The only warm colour on the HUD. */
  --ut-bracket: #ffd21e;

  --ut-warn: #ffa023;
  --ut-warn-glow: rgba(255, 150, 25, 0.9);
  --ut-crit: #ff4436;
  --ut-crit-glow: rgba(255, 56, 44, 0.95);

  --ut-ink: rgba(200, 205, 255, 0.5);
}

html[data-team="red"] {
  --ut-hue: #ff6b56;
  --ut-hue-deep: #e02414;
  --ut-glow: rgba(255, 66, 46, 0.85);
  --ut-glow-weak: rgba(255, 66, 46, 0.32);
  --ut-fill: rgba(220, 34, 20, 0.32);
  --ut-plate-top: rgba(96, 42, 40, 0.82);
  --ut-edge-top: rgba(255, 176, 162, 0.7);
  --ut-edge-bot: rgba(126, 52, 44, 0.6);
}

html[data-team="blue"] {
  --ut-hue: #7cb6ff;
  --ut-hue-deep: #2b6ff0;
  --ut-glow: rgba(70, 140, 255, 0.85);
  --ut-glow-weak: rgba(70, 140, 255, 0.32);
  --ut-fill: rgba(30, 70, 220, 0.32);
}

/* ============================================================================
   HUD layer
   ========================================================================== */

.ut-hud {
  position: fixed;
  inset: 0;
  z-index: 900;
  pointer-events: none;
  font-family: var(--ut-font-ui);
  transition: opacity 200ms ease-out;
}

.ut-hud * {
  box-sizing: border-box;
}

/* While dead the bar stays on screen but steps back, so the death card is the
   only thing with contrast. */
.ut-hud.is-dead .ut-bar {
  opacity: 0.3;
  transition: opacity 160ms ease-out;
}

/* ---- the chamfered plate ---------------------------------------------------
   The outline is the OUTER element's background showing through a 2px padding
   ring. Both boxes are cut with the same polygon, so the diagonal keeps its
   stroke — a clipped `border` would leave the corner open. Every plate on
   screen (meters, chips, weapon slots, scoreboard, dialog) is this one object.
   -------------------------------------------------------------------------- */

.ut-panel {
  --cut: 11px;
  --clip: polygon(
    var(--cut) 0,
    100% 0,
    100% calc(100% - var(--cut)),
    calc(100% - var(--cut)) 100%,
    0 100%,
    0 var(--cut)
  );
  position: relative;
  padding: 2px;
  clip-path: var(--clip);
  background: linear-gradient(160deg, var(--ut-edge-top), var(--ut-edge-bot));
}

.ut-panel__in {
  position: relative;
  height: 100%;
  clip-path: var(--clip);
  background: linear-gradient(
    170deg,
    var(--ut-plate-top) 0%,
    rgba(14, 21, 38, 0.88) 52%,
    var(--ut-plate-bot) 100%
  );
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16),
    inset 0 -18px 24px -18px rgba(0, 0, 0, 0.9);
}

/* Mirror the chamfer on the right-hand side of the screen so the two bays lean
   toward each other instead of both leaning the same way. */
.ut-bay--right .ut-panel,
.ut-hint {
  --clip: polygon(
    0 0,
    calc(100% - var(--cut)) 0,
    100% var(--cut),
    100% 100%,
    var(--cut) 100%,
    0 calc(100% - var(--cut))
  );
}

/* ---- numerals --------------------------------------------------------------
   Tabular figures plus a reserved min-width: the plate never resizes as the
   value gains or loses a digit.
   -------------------------------------------------------------------------- */

.ut-num {
  font-family: var(--ut-font-num);
  font-weight: 700;
  font-size: var(--ut-num-size, 34px);
  line-height: 0.94;
  color: #fff;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  letter-spacing: 0.005em;
  min-width: calc(var(--ut-num-slots, 3) * 0.6em);
  text-align: right;
  text-shadow: 0 0 10px var(--ut-glow), 0 0 30px var(--ut-glow),
    0 2px 3px rgba(0, 0, 0, 0.9);
}

.ut-num.is-text {
  color: rgba(255, 255, 255, 0.55);
}

/* ---- glyph disc ------------------------------------------------------------
   The round recessed cap on the outboard end of a meter.
   -------------------------------------------------------------------------- */

.ut-disc {
  flex: 0 0 auto;
  width: var(--ut-disc-size, 46px);
  height: var(--ut-disc-size, 46px);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: radial-gradient(
    circle at 34% 26%,
    rgba(104, 132, 184, 0.7) 0%,
    rgba(24, 34, 56, 0.92) 58%,
    rgba(5, 8, 16, 0.95) 100%
  );
  box-shadow: 0 0 0 2px var(--ut-edge-top), inset 0 2px 5px rgba(255, 255, 255, 0.14),
    inset 0 -4px 8px rgba(0, 0, 0, 0.7), 0 0 16px var(--ut-glow-weak);
}

.ut-disc__glyph {
  width: 54%;
  height: 54%;
  color: var(--ut-hue);
  filter: drop-shadow(0 0 6px var(--ut-glow));
}

.ut-disc__glyph svg {
  width: 100%;
  height: 100%;
  display: block;
  fill: currentColor;
}

/* ============================================================================
   Bottom bar
   ========================================================================== */

.ut-bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 0 20px 16px;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
}

.ut-bay {
  display: flex;
  align-items: flex-end;
  gap: 12px;
}

.ut-bay--right {
  flex-direction: column;
  align-items: flex-end;
  gap: 5px;
}

/* ---- meters (armour, health, ammo) ---------------------------------------- */

.ut-meter {
  display: flex;
  align-items: center;
  gap: 9px;
}

.ut-panel--meter {
  --cut: 12px;
}

.ut-panel--meter .ut-panel__in {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: 4px 15px 5px;
}

.ut-meter--health,
.ut-meter--ammo {
  --ut-num-size: 46px;
  --ut-disc-size: 48px;
}

.ut-meter--armor {
  --ut-num-size: 24px;
  --ut-disc-size: 28px;
}

.ut-meter--armor .ut-panel {
  --cut: 8px;
}

.ut-meter--armor .ut-panel__in {
  padding: 2px 11px 3px;
}

/* Health and ammo bands. `data-level` is written by hud-root.js. */
.ut-meter--health[data-level="warn"] {
  --ut-glow: var(--ut-warn-glow);
  --ut-hue: var(--ut-warn);
}

.ut-meter--health[data-level="crit"],
.ut-meter--ammo[data-level="crit"] {
  --ut-glow: var(--ut-crit-glow);
  --ut-hue: var(--ut-crit);
}

.ut-meter--health[data-level="crit"] .ut-num {
  animation: ut-pulse 900ms ease-in-out infinite;
}

@keyframes ut-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.62;
  }
}

/* No armour on the server yet. A real, dimmed 0 is honest; a number that never
   moves would not be. */
/* ---- paper doll ------------------------------------------------------------
   Geometry, material and behaviour are all in the UT99 pass at the bottom of
   this file: the doll is `Icons.Man` drawn at 128S x 256S on the top-right
   corner. Nothing is left here — it used to be a glowing 66px figure in the
   bottom-left bay that recoloured with health and blinked when hit, and
   ChallengeHUD does none of those things.
   -------------------------------------------------------------------------- */

/* ---- weapon name ---------------------------------------------------------- */

.ut-weaponname {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--ut-hue);
  padding-right: 4px;
  text-shadow: 0 0 10px var(--ut-glow), 0 1px 3px rgba(0, 0, 0, 0.95);
}

/* Key hint, top right. Flat rather than plated — it is instruction, not state. */
.ut-hint {
  position: absolute;
  top: 15px;
  right: 16px;
  --cut: 7px;
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 4px 10px 4px 5px;
  clip-path: var(--clip);
  background: rgba(6, 9, 16, 0.55);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.24em;
  color: var(--ut-ink);
}

.ut-hint kbd {
  font: inherit;
  padding: 2px 6px 1px;
  color: rgba(255, 255, 255, 0.72);
  background: rgba(255, 255, 255, 0.09);
  border: 1px solid rgba(255, 255, 255, 0.2);
  letter-spacing: 0.14em;
}

/* ============================================================================
   Damage vignette + death card
   ========================================================================== */

.ut-vignette {
  position: absolute;
  inset: 0;
  opacity: 0;
  /* ChallengeHUD draws NO screen wash — SetDamage only parks red minus tiles
     on the doll (.ut-doll__hits), and those are the real feedback. What is
     kept here is the ENGINE's ClientFlash, which is a flat additive fog over
     the whole view, so this must be a flat low-alpha fill: an edge vignette is
     something UT99 never draws. */
  background: rgba(190, 12, 6, 0.16);
  transition: opacity 320ms ease-out;
}

.ut-vignette.is-on {
  opacity: 1;
  transition: opacity 40ms ease-out;
}

.ut-death {
  position: absolute;
  top: 42%;
  left: 50%;
  transform: translate(-50%, -50%) scale(0.94);
  width: min(420px, 76vw);
  text-align: center;
  opacity: 0;
  visibility: hidden;
  /* A soft dark ground under the card. Without it the two red rules and the
     RESPAWNING caption disappear whenever you die facing the lit side of the
     asteroid, which is most of the time. */
  background: radial-gradient(
    ellipse at center,
    rgba(0, 0, 0, 0.66) 0%,
    rgba(0, 0, 0, 0.42) 55%,
    rgba(0, 0, 0, 0) 78%
  );
  padding: 34px 40px;
  transition: opacity 200ms ease-out, transform 200ms ease-out,
    visibility 0s linear 200ms;
}

.ut-death.is-on {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, -50%) scale(1);
  transition: opacity 200ms ease-out, transform 200ms ease-out;
}

.ut-death__rule {
  height: 2px;
  background: linear-gradient(
    90deg,
    transparent,
    var(--ut-crit) 22%,
    var(--ut-crit) 78%,
    transparent
  );
  box-shadow: 0 0 14px var(--ut-crit-glow);
}

.ut-death__title {
  font-size: clamp(34px, 6vw, 62px);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #fff;
  padding: 10px 0 2px;
  text-shadow: 0 0 18px var(--ut-crit-glow), 0 0 52px var(--ut-crit-glow),
    0 2px 4px rgba(0, 0, 0, 0.9);
}

/* It used to read RESPAWNING, which was a STATUS — a thing happening to you, and
   half-lit is exactly right for one. Since Task 11 the server waits for the player
   (UT99's PlayerPawn.Dead binds Fire to ServerReStartPlayer), so this line is the
   only INSTRUCTION on the screen and the only thing that ends the wait. Measured on
   the death screen against the lit side of the asteroid: at 0.5 alpha and 12px it is
   legible but reads as a caption under the title rather than as something to do. */
.ut-death__sub {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.42em;
  color: rgba(255, 255, 255, 0.82);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
  padding-bottom: 12px;
}

/* ============================================================================
   Crosshair + hitmarker (first-person-weapon.js)
   ----------------------------------------------------------------------------
   Four thin ticks around an empty centre with a single centre pip. The gap is
   driven from JS (weapon bloom); the pip is this file's ::after, so no JS change
   was needed to add it.
   ========================================================================== */

/* Botpack.CHair1, the stock default (Engine.HUD Crosshair=0). Drawn at
   15 * CrosshairColor = 15 * (0,16,0) = (0,240,0) GREEN, STY_Translucent, with
   NO outline, NO glow and NO bloom — and it does not move when you fire. The
   box is sized per frame by first-person-weapon.js to 64 * XScale px, XScale
   being the integer max(1, int(0.1 + ClipX/640)), so the 64-unit art lands on
   whole pixels the way the nearest-neighbour blit does. */
.ut-crosshair {
  position: fixed;
  top: 50%;
  left: 50%;
  width: 64px;
  height: 64px;
  margin: -32px 0 0 -32px;
  z-index: 950;
  pointer-events: none;
  opacity: 1;
  color: rgb(0, 240, 0);
}

.ut-crosshair svg {
  display: block;
  width: 100%;
  height: 100%;
}

.ut-crosshair svg rect {
  fill: currentColor;
}

/* NOT A SOURCE BEHAVIOUR — kept deliberately, and annotated the way the reload
   blink and the TAB hint are. UT99 gives hit confirmation through the victim's
   pain sound over a LAN-latency link; this build is a browser peer-to-peer game
   where that sound may never arrive, so a 80 ms white X at the crosshair is the
   cheapest honest substitute. Nothing in ChallengeHUD draws it. */
.ut-hitmarker {
  position: fixed;
  top: 50%;
  left: 50%;
  width: 0;
  height: 0;
  z-index: 951;
  pointer-events: none;
  opacity: 0;
}

.ut-hitmarker i {
  position: absolute;
  top: 0;
  left: 0;
  width: 22px;
  height: 3px;
  background: #fff;
  box-shadow: 0 0 8px var(--ut-glow), 0 0 2px rgba(0, 0, 0, 0.9);
}

.ut-hitmarker i:first-child {
  transform: translate(-50%, -50%) rotate(45deg);
}

.ut-hitmarker i:last-child {
  transform: translate(-50%, -50%) rotate(-45deg);
}

/* ---- kill flash: REMOVED ---------------------------------------------------
   DrawFragCount's only feedback on a frag is the 3 s gold sawtooth on the frag
   box and its gold glow blob, both of which are implemented. ChallengeHUD has
   no screen-edge pulse of any kind, so there is no .ut-killflash any more.
   -------------------------------------------------------------------------- */

/* ============================================================================
   Message type (spec §1.10 / §1.11)
   ----------------------------------------------------------------------------
   UT99 has no condensed display face and no tracking anywhere in the message
   path: MyFonts.GetSmallFont / GetBigFont / GetHugeFont return LadderFonts,
   which are plain bitmap sans, and the size is chosen by ClipX alone.

     small  (message rail)   SmallFont / UTLadder10 / 14 / 16
     big    (LocalMessages)  16 / 18 / 20 / 22
     huge   (MultiKill 2+)   16 / 20 / 22 / 30

   YL is the line box the source advances by when it stacks messages, so it is
   the same token the CTF banner offsets itself with.
   ========================================================================== */
:root {
  --ut-msg-font: Arial, Helvetica, Tahoma, "DejaVu Sans", sans-serif;
  --ut-font-small: 10px;
  --ut-font-big: 16px;
  --ut-font-huge: 16px;
  --ut-yl-big: 19px;
}
@media (min-width: 640px) {
  :root {
    --ut-font-big: 18px;
    --ut-font-huge: 20px;
    --ut-yl-big: 22px;
  }
}
@media (min-width: 800px) {
  :root {
    --ut-font-small: 14px;
    --ut-font-big: 20px;
    --ut-font-huge: 22px;
    --ut-yl-big: 24px;
  }
}
@media (min-width: 1024px) {
  :root {
    --ut-font-small: 16px;
    --ut-font-big: 22px;
    --ut-font-huge: 30px;
    --ut-yl-big: 26px;
  }
}

/* ============================================================================
   Multi-kill / spree announcements (first-person-weapon.js)
   ----------------------------------------------------------------------------
   LocalMessages at YPos/768 * ClipY. MultiKillMessage sits one line BELOW the
   0.2552 H line and is RED; KillingSpreeMessage is a CriticalEventLowPlus on
   the 0.2552 H line itself in (0,128,255). bFadeMessage means the colour is
   multiplied by remaining/Lifetime — a straight linear ramp to black over the
   whole 3 s. Nothing arrives, nothing punches in, nothing glows.
   ========================================================================== */

.ut-announce {
  position: fixed;
  left: 0;
  right: 0;
  z-index: 960;
  pointer-events: none;
  opacity: 0;
  text-align: center;
  white-space: nowrap;
  font-family: var(--ut-msg-font);
  font-weight: 400;
  font-style: normal;
  letter-spacing: normal;
  text-transform: none;
  text-shadow: none;
}

.ut-announce[data-size="big"] {
  font-size: var(--ut-font-big);
  line-height: var(--ut-yl-big);
}

.ut-announce[data-size="huge"] {
  font-size: var(--ut-font-huge);
  line-height: calc(var(--ut-font-huge) * 1.18);
}

.ut-announce--multikill {
  top: calc(25.52% + var(--ut-yl-big));
  color: rgb(255, 0, 0);
}

.ut-announce--spree {
  top: 25.52%;
  color: rgb(0, 128, 255);
}

.ut-announce.is-on {
  animation: ut-msg-fade 3s linear forwards;
}

/* ---- pointer-lock prompt ---------------------------------------------------
   "CLICK TO PLAY", shown whenever the pointer is not locked — on load and after
   every Escape. Pointer lock has to be asked for inside a user gesture, so that
   dead moment is unavoidable and every browser shooter puts the same sign in it.

   pointer-events: none is load-bearing, not tidiness: the click that dismisses
   this has to reach the canvas underneath, because the canvas click handler
   (src/game/engine/input.js) is what actually calls requestPointerLock. If the
   prompt swallowed the click there would be nothing to swallow it FOR.

   Not UT99 art — UT99 has no such state — so it is deliberately plain, in the
   HUD's own message face rather than the heavy condensed plate lettering, and
   sits below centre so it never covers the crosshair.
   -------------------------------------------------------------------------- */
.ut-lock-prompt {
  position: fixed;
  left: 0;
  right: 0;
  top: 62%;
  z-index: 970;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition:
    opacity 0.18s linear,
    visibility 0s linear 0.18s;
  text-align: center;
  font-family: var(--ut-msg-font);
  color: #fff;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.9);
}

.ut-lock-prompt.is-visible {
  opacity: 1;
  visibility: visible;
  transition:
    opacity 0.18s linear,
    visibility 0s linear;
}

.ut-lock-prompt b {
  display: block;
  font-size: calc(var(--ut-font-big) * 1.5);
  line-height: 1.2;
  letter-spacing: 0.12em;
  font-weight: 700;
}

.ut-lock-prompt span {
  display: block;
  margin-top: 0.5em;
  font-size: var(--ut-font-big);
  line-height: var(--ut-yl-big);
  opacity: 0.72;
}

/* DrawColor * remaining / Lifetime, and nothing else. */
@keyframes ut-msg-fade {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

/* ============================================================================
   Message rail (hud-root.js, fed by kill-notification.js)
   ----------------------------------------------------------------------------
   ChallengeHUD's four-line message area: SetPos(6, 2 + YL*line), clip width
   768S - 10 (60 vw), 3 s LocalMessage lifetime, left aligned, no animation on
   the lines themselves. Only the colour separates the kinds — DeathMessagePlus
   (255,0,0), SayMessagePlus (0,255,0), StringMessagePlus white.

   Behind them is DrawSpeechArea's backdrop: HUDColor * MessageFadeTime, i.e. a
   very faint tinted strip about 77 S tall with a hairline lower edge, fading in
   at +8/s (1/8 s) and out at -2/s (1/2 s).
   ========================================================================== */

.ut-msgs {
  position: absolute;
  left: 6px;
  top: 2px;
  width: 60vw;
  font-family: var(--ut-msg-font);
  font-size: var(--ut-font-small);
  line-height: 1.25;
  font-weight: 400;
  pointer-events: none;
}

.ut-msgs__back {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 6vw;
  background: rgba(var(--ut-rgb, 0, 120, 239), 0.13);
  /* Every HUD texture is STY_Translucent: dst = src + dst*(1-src). The strip
     must LIFT the tower geometry behind it, never multiply it down, so it
     screens like every other tile. The hairline lower edge is a screened 1px
     strip (inset shadow) rather than a border, so the blended box keeps its
     exact 6vw height. */
  mix-blend-mode: screen;
  box-shadow: inset 0 -1px 0 rgba(var(--ut-rgb, 0, 120, 239), 0.22);
  opacity: 0;
  transition: opacity 500ms linear;
}

.ut-msgs.is-on .ut-msgs__back {
  opacity: 1;
  transition: opacity 125ms linear;
}

.ut-msgs__list {
  position: relative;
}

.ut-msgs__line {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  letter-spacing: normal;
  text-transform: none;
  text-shadow: none;
}

.ut-msgs__line--death {
  color: rgb(255, 0, 0);
}

.ut-msgs__line--chat {
  color: rgb(0, 255, 0);
}

.ut-msgs__line--pickup {
  color: #fff;
}

/* ============================================================================
   Centre message + match-end card (hud-root.js)
   ----------------------------------------------------------------------------
   The CriticalEventPlus line at 0.2552 H: (0,128,255) for kills, captures,
   returns and sprees, red where the class overrides it, big font, same linear
   3 s fade as the announcements.
   ========================================================================== */

.ut-center {
  position: absolute;
  left: 0;
  right: 0;
  top: 25.52%;
  text-align: center;
  white-space: nowrap;
  pointer-events: none;
  font-family: var(--ut-msg-font);
  font-size: var(--ut-font-big);
  line-height: var(--ut-yl-big);
  opacity: 0;
}

.ut-center[data-tone="team"] {
  color: rgb(0, 128, 255);
}

.ut-center[data-tone="red"] {
  color: rgb(255, 0, 0);
}

.ut-center.is-on {
  animation: ut-msg-fade 3s linear forwards;
}

/* The standing final score, two lines under the winner announcement. It holds
   until the server's match-reset, which the 3 s message cannot. */
.ut-matchend {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(25.52% + 2 * var(--ut-yl-big));
  text-align: center;
  white-space: nowrap;
  pointer-events: none;
  font-family: var(--ut-msg-font);
  font-size: var(--ut-font-big);
  line-height: var(--ut-yl-big);
  color: #fff;
  display: none;
}

.ut-hud.is-matchover .ut-matchend {
  display: block;
}

/* ============================================================================
   CTF carry banners (CTFMessage2, bottom centre)
   ----------------------------------------------------------------------------
   switch 0 "You have the flag, return to base!" is (255,255,0) at
   H - 2*YL - 0.0833H; switch 1 "The enemy has your flag, recover it!" is
   (255,0,0) at H - 3*YL - 0.0833H — one line ABOVE it. Lifetime is 1 s and
   ChallengeCTFHUD.Timer re-sends the message every second while the condition
   holds, with bFadeMessage: full brightness on the second, fading linearly to
   black before the next one. Hence the 1 s looping ramp rather than a blink.

   The container's bottom edge is 0.0833H + one YL up, which puts the lower
   (yellow) line's TOP at exactly H - 0.0833H - 2*YL.
   ========================================================================== */

.ut-ctfmsg {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(8.33vh + var(--ut-yl-big));
  display: flex;
  flex-direction: column;
  align-items: center;
  pointer-events: none;
  font-family: var(--ut-msg-font);
  font-size: var(--ut-font-big);
  line-height: var(--ut-yl-big);
  white-space: nowrap;
}

/* visibility, not display: the enemy line must keep its line box so the yellow
   line stays on the bottom row whether or not the red one is showing. */
.ut-ctfmsg__line {
  visibility: hidden;
  opacity: 0;
}

.ut-ctfmsg__line.is-on {
  visibility: visible;
  animation: ut-ctf-pulse 1s linear infinite;
}

.ut-ctfmsg__line--mine {
  color: rgb(255, 255, 0);
}

.ut-ctfmsg__line--enemy {
  color: rgb(255, 0, 0);
}

@keyframes ut-ctf-pulse {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

/* ============================================================================
   Scoreboard (highscore-display.js) — held on TAB
   ========================================================================== */

.ut-scoreboard {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(640px, 88vw);
  opacity: 0;
  visibility: hidden;
  transition: opacity 90ms ease-out, visibility 0s linear 90ms;
}

.ut-scoreboard.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity 90ms ease-out;
}

/* The scoreboard is the same plate as the meters, one size up. */
.ut-scoreboard__inner {
  --cut: 22px;
  --clip: polygon(
    var(--cut) 0,
    100% 0,
    100% calc(100% - var(--cut)),
    calc(100% - var(--cut)) 100%,
    0 100%,
    0 var(--cut)
  );
  padding: 2px;
  clip-path: var(--clip);
  background: linear-gradient(160deg, var(--ut-edge-top), var(--ut-edge-bot));
}

.ut-scoreboard__body {
  clip-path: var(--clip);
  padding: 16px 22px 20px;
  background: linear-gradient(
    170deg,
    rgba(20, 28, 46, 0.94) 0%,
    rgba(6, 9, 17, 0.96) 100%
  );
}

.ut-scoreboard__title {
  font-size: 27px;
  font-weight: 700;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  text-align: center;
  color: #fff;
  text-shadow: 0 0 14px var(--ut-glow), 0 0 40px var(--ut-glow);
  padding-bottom: 12px;
}

.ut-scoreboard__head {
  display: flex;
  justify-content: space-between;
  padding: 4px 8px 4px;
  background: var(--ut-fill);
  border-top: 1px solid var(--ut-hue);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.28em;
  color: rgba(255, 255, 255, 0.66);
}

.ut-scoreboard__list {
  display: flex;
  flex-direction: column;
}

.ut-row {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 7px 8px 6px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.07);
}

.ut-row.is-local {
  background: linear-gradient(90deg, var(--ut-fill), rgba(255, 255, 255, 0.02));
  box-shadow: inset 2px 0 0 var(--ut-hue);
}

.ut-row__rank {
  width: 22px;
  flex: 0 0 22px;
  font-size: 13px;
  font-weight: 700;
  color: rgba(255, 255, 255, 0.35);
}

.ut-row__name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 18px;
  font-weight: 600;
  letter-spacing: 0.05em;
  color: rgba(255, 255, 255, 0.86);
}

.ut-row.is-local .ut-row__name {
  color: #fff;
  text-shadow: 0 0 10px var(--ut-glow);
}

.ut-row__frags {
  font-family: var(--ut-font-num);
  font-size: 21px;
  font-weight: 700;
  color: var(--ut-hue);
  text-shadow: 0 0 10px var(--ut-glow);
  font-variant-numeric: tabular-nums;
}

.ut-scoreboard__empty {
  padding: 22px 4px;
  text-align: center;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.26em;
  color: rgba(255, 255, 255, 0.32);
}

/* ============================================================================
   Network status chip (built by src/game/network/network.js)
   ----------------------------------------------------------------------------
   That file is outside this partition and pins itself top-right with inline
   styles — which is now the key hint's corner. Move it to the top centre and
   flatten it. Overriding here rather than editing network.js keeps this round
   inside its partition.
   ========================================================================== */

#net-status {
  top: 10px !important;
  right: auto !important;
  left: 50% !important;
  transform: translateX(-50%);
  font-family: var(--ut-font-ui) !important;
  font-size: 10px !important;
  font-weight: 700 !important;
  letter-spacing: 0.3em !important;
  border-radius: 0 !important;
  border-width: 0 0 1px 0 !important;
  background: rgba(0, 0, 0, 0.55) !important;
  padding: 5px 16px 4px !important;
}

/* ============================================================================
   Name changer (name-changer.js)
   ========================================================================== */

.ut-modal {
  position: fixed;
  inset: 0;
  z-index: 1200;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.72);
  opacity: 0;
  visibility: hidden;
  transition: opacity 140ms ease-out, visibility 0s linear 140ms;
}

.ut-modal.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity 140ms ease-out;
}

.ut-modal__dialog {
  width: min(400px, 88vw);
}

.ut-modal__inner {
  --cut: 18px;
  --clip: polygon(
    var(--cut) 0,
    100% 0,
    100% calc(100% - var(--cut)),
    calc(100% - var(--cut)) 100%,
    0 100%,
    0 var(--cut)
  );
  padding: 2px;
  clip-path: var(--clip);
  background: linear-gradient(160deg, var(--ut-edge-top), var(--ut-edge-bot));
}

.ut-modal__body {
  clip-path: var(--clip);
  padding: 20px 22px 22px;
  background: linear-gradient(
    170deg,
    rgba(20, 28, 46, 0.96) 0%,
    rgba(6, 9, 17, 0.97) 100%
  );
}

.ut-modal__title {
  font-family: var(--ut-font-ui);
  font-size: 19px;
  font-weight: 700;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  text-align: center;
  color: #fff;
  text-shadow: 0 0 12px var(--ut-glow);
  margin-bottom: 14px;
}

.ut-modal__input {
  width: 100%;
  padding: 10px 12px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid var(--ut-edge-top);
  border-radius: 0;
  color: #fff;
  font-family: var(--ut-font-ui);
  font-size: 17px;
  letter-spacing: 0.06em;
}

.ut-modal__buttons {
  display: flex;
  gap: 8px;
  margin-top: 12px;
}

.ut-btn {
  flex: 1 1 0;
  padding: 9px 10px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 0;
  color: rgba(255, 255, 255, 0.8);
  font-family: var(--ut-font-ui);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  cursor: pointer;
}

.ut-btn--primary {
  background: var(--ut-fill);
  border-color: var(--ut-hue);
  color: #fff;
}

.ut-modal__hint {
  margin-top: 10px;
  text-align: center;
  font-family: var(--ut-font-ui);
  font-size: 10px;
  letter-spacing: 0.2em;
  color: rgba(255, 255, 255, 0.35);
}

/* ============================================================================
   Credits
   ----------------------------------------------------------------------------
   Bottom-centre caption, lifted to sit ABOVE the weapon bar rather than behind
   it. Quiet enough to read as a caption on the bar, not as HUD state.
   ========================================================================== */

#credits-container {
  position: fixed;
  bottom: 62px;
  left: 50%;
  transform: translateX(-50%);
  width: min(420px, 42vw);
  text-align: center;
  color: rgba(255, 255, 255, 0.4);
  font-family: var(--ut-font-ui);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.06em;
  line-height: 1.5;
  z-index: 1000;
  /* The block sits over the middle of the viewport, so it must not eat the
     look-controls drag. Only the links themselves take the pointer. */
  pointer-events: none;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
}

#credits-container a {
  pointer-events: auto;
}

.credit-line {
  margin-bottom: 2px;
}

.credit-instructions {
  font-style: normal;
  margin-bottom: 7px;
  /* These are the only controls the player is ever told about, and at 0.3 they
     were unreadable over the lit side of the asteroid. */
  color: rgba(255, 255, 255, 0.55);
}

/* index.html carries a desktop line and a touch line; exactly one of them is the
   truth on any given device, so show only that one. The touch line describes the
   touch path in src/game/engine/input.js; the media query that picks it is the same
   one that lifts the ammo bay clear of the FIRE button further down. */
.credit-instructions--touch {
  display: none;
}

@media (hover: none) and (pointer: coarse) {
  .credit-instructions--desktop {
    display: none;
  }

  .credit-instructions--touch {
    display: block;
  }
}

.credit-highlight {
  color: rgba(255, 255, 255, 0.55);
}

.credit-link {
  margin-top: 6px;
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.credit-link a {
  color: rgba(99, 205, 255, 0.75);
  text-decoration: none;
  border-bottom: 1px solid rgba(99, 205, 255, 0.3);
}

.credit-link a:hover {
  color: #63cdff;
}

.credit-author {
  color: rgba(255, 255, 255, 0.7);
  text-decoration: none;
  border-bottom: 1px solid rgba(255, 255, 255, 0.25);
}

.credit-author:hover {
  color: #ffffff;
  border-bottom-color: #ffffff;
}

/* ============================================================================
   Narrow / short viewports
   ========================================================================== */

@media (max-width: 980px) {
  .ut-bar {
    padding: 0 14px 12px;
  }

  .ut-meter--health,
  .ut-meter--ammo {
    --ut-num-size: 36px;
    --ut-disc-size: 38px;
  }

  .ut-meter--armor {
    --ut-num-size: 19px;
    --ut-disc-size: 23px;
  }

  .ut-doll {
    width: 46px;
  }

  .ut-weaponname {
    font-size: 11px;
    letter-spacing: 0.24em;
  }

  .ut-wslot {
    width: 36px;
    height: 26px;
  }

  .ut-wslot__icon {
    width: 21px;
  }

  .ut-chip {
    --ut-num-size: 21px;
  }

  #credits-container {
    width: min(340px, 76vw);
    bottom: 56px;
    font-size: 10px;
  }
}

@media (max-width: 620px) {
  /* On a phone the doll and the weapon bar are the first things to go — health
     and ammo are the two readouts you actually act on. The armour meter stays:
     it is small, and it is the only thing that will report a pickup. */
  .ut-doll,
  .ut-weapons,
  .ut-hint {
    display: none;
  }

  .ut-meter--health,
  .ut-meter--ammo {
    --ut-num-size: 30px;
    --ut-disc-size: 32px;
  }

  /* Credits move out from under the centre and stack over the health bay, so
     they cannot land on the ammo block once it lifts for the fire button. */
  #credits-container {
    left: 12px;
    right: auto;
    transform: none;
    text-align: left;
    width: min(240px, 62vw);
    bottom: 108px;
  }
}

/* first-person-weapon.js parks an 80px round FIRE button at bottom:20 right:20 on
   touch devices, exactly where the ammo bay sits. Lift the bay clear of it
   rather than fight for the corner. */
@media (hover: none) and (pointer: coarse) {
  .ut-bay--right {
    margin-bottom: 100px;
  }
}

@media (max-height: 560px) {
  .ut-meter--health,
  .ut-meter--ammo {
    --ut-num-size: 28px;
    --ut-disc-size: 30px;
  }

  .ut-doll {
    width: 40px;
  }

  #credits-container {
    display: none;
  }
}

/* ============================================================================
   UT99 pass
   ----------------------------------------------------------------------------
   Everything below matches the retail UT99 HUD against a real screenshot, and
   supersedes the UT2003/2004-styled rules above where the two disagree. The
   differences that matter, in order of how much they change the read:

     1. Numerals are SEVEN-SEGMENT, with the unlit bars faintly visible.
     2. Panels are FLAT translucent washes with one bright edge — no chamfer,
        no gradient, no bevel. You can see the map through them.
     3. Armour sits ABOVE health in ONE panel, top right, glyph to the RIGHT of
        each number, with the paper doll standing beside the panel.
     4. The bottom is a single full-width strip carrying the weapon slots and
        the ammo readout, not three separate corner bays.
     5. The held weapon slot wears a YELLOW CORNER BRACKET. It is the only warm
        colour on the HUD.
   ========================================================================== */

/* --- the rendering model: one continuous unit ------------------------------
   ChallengeHUD.HUDSetup, verbatim:

     Scale     = HUDScale * ClipX / 1280      (HUDScale 1.0)
     StatScale = Scale * StatusScale          (= Scale)
     WeapScale = Scale * WeaponScale          (= 0.8 * Scale)

   so every length on this HUD is `k * S` with S = ClipX / 1280. S is
   CONTINUOUS — there is no integer HudScale ladder to step at 960 / 1600 /
   2240 — and it depends on WIDTH only: vertical positions are `ClipY - k*S`,
   i.e. bottom-anchored in vw, never vh. 1 S = 0.078125vw, so a 128 S tile is
   10vw at every resolution: 192px at 1920 wide, 64px at 640. */
:root {
  --ut-s: 0.078125vw;
  /* The old 640-space base unit, kept for the rules that still count in base
     pixels. It is exactly 2 S, and now scales with the window continuously
     instead of jumping a whole step at each breakpoint. */
  --ut-u: calc(2 * var(--ut-s));
}

/* --- HUD colour ------------------------------------------------------------
   Every HUD texture is GREYSCALE; DrawColor tints it and STY_Translucent
   composites it as `dst = src + dst*(1 - src)` — a SCREEN blend, which is why
   black texels are invisible and nothing on this HUD ever darkens the world.

     DM / no team   HUDColor = FavoriteHUDColor(0,0,16) * 15.9  ->   0,  0,254
     team 1 blue    0.9375 * TeamColor[1] = 0.9375*(0,128,255)  ->   0,120,239
     team 0 red     0.9375 * TeamColor[0]                       -> 239,  0,  0

   Blue team is NOT pure blue, it is azure; only the DM default is pure blue.
   SolidHUDColor is the same TeamColor unscaled, and it is what the held weapon
   icon and the team score digits use.

   The alphas are the atlas luminances: fill 66/255 = .26, grid 84/255 = .33,
   3-texel border 124/255 = .49, digits 127/255 = .50. Where one is painted
   OVER another in CSS (the grid and the border sit on the fill) the token
   carries the alpha that COMPOSITES to the luminance, a = (target - .26)/.74,
   because in the original the texel simply holds that value instead of
   layering on top of the fill. */
.ut-hud {
  --ut-rgb: 0, 0, 254; /* HUDColor */
  --ut-solid-rgb: 0, 0, 255; /* SolidHUDColor / TeamColor */

  --ut-fill: rgba(var(--ut-rgb), 0.26); /* lum 66 */
  --ut-grid: rgba(var(--ut-rgb), 0.095); /* -> lum 84 over the fill */
  --ut-border: rgba(var(--ut-rgb), 0.311); /* -> lum 124 over the fill */
  --ut-fill-flat: var(--ut-fill);

  /* Glyph faces, likewise pre-composited over the 0.26 fill. */
  --ut-glyph: rgba(var(--ut-rgb), 0.595); /* lum .70 front face */
  --ut-glyph-hi: rgba(var(--ut-rgb), 0.865); /* lum .90 lit face */
  --ut-glyph-lo: rgba(var(--ut-rgb), 0.257); /* lum .45 shaded face */
  --ut-glyph-edge: rgba(var(--ut-rgb), 0.932); /* lum .95 highlight */
  --ut-glyph-rim: rgba(var(--ut-rgb), 0.324); /* lum .50 outline */

  /* BigNumbers are drawn WhiteColor + translucent: a 50% white stroke with a
     36% rim down its right and bottom edge. For a pure-white source, alpha
     compositing and the screen blend give the same result, so the digits need
     no blend mode of their own. */
  --ut-seg-on: rgba(255, 255, 255, 0.5);
  --ut-seg-rim: rgba(255, 255, 255, 0.36);
  /* There ARE no unlit segments — the atlas is masked and index 0 is
     transparent. The token stays defined only so the older rules that still
     reference it resolve to "nothing drawn". */
  --ut-seg-off: transparent;
  --ut-seg-glow: transparent;

  /* SetDamage marks the doll RedColor whenever the HUD colour is blue-ish
     (G > 100 or B > 100), which covers DM and blue team. */
  --ut-dash-rgb: 255, 0, 0;
}

html[data-team="blue"] .ut-hud {
  --ut-rgb: 0, 120, 239;
  --ut-solid-rgb: 0, 128, 255;
}

html[data-team="red"] .ut-hud {
  --ut-rgb: 239, 0, 0;
  --ut-solid-rgb: 255, 0, 0;
  /* the else branch of SetDamage: (White - HUDColor) with R forced to 255 */
  --ut-dash-rgb: 255, 255, 255;
}

/* --- the box: every status tile is this one material -----------------------
   HudElements1's four 128x64 boxes (skull / cross / shield / bullets) and the
   doll share one background: a flat fill, a 2-texel grid on a 17-texel pitch,
   and a 3-texel border. Measured on the atlas:

     fill    lum 66  (.26)
     grid    lum 84  (.33)  2 texels, pitch 17, first line at x 12-13, y 14-15
     border  lum 124 (.49)  3 texels, all round

   No gradient, no bevel, no rounding, no glow. `mix-blend-mode: screen` is the
   STY_Translucent composite; over the black sky it is identical to painting
   the same alpha, and over anything bright it is the reason the HUD never
   darkens what is behind it. */
.ut-tile {
  position: absolute;
  mix-blend-mode: screen;
  border-radius: 0;
  background-color: var(--ut-fill);
  background-image: repeating-linear-gradient(
      to right,
      transparent 0 calc(12 * var(--ut-s)),
      var(--ut-grid) calc(12 * var(--ut-s)) calc(14 * var(--ut-s)),
      transparent calc(14 * var(--ut-s)) calc(17 * var(--ut-s))
    ),
    repeating-linear-gradient(
      to bottom,
      transparent 0 calc(14 * var(--ut-s)),
      var(--ut-grid) calc(14 * var(--ut-s)) calc(16 * var(--ut-s)),
      transparent calc(16 * var(--ut-s)) calc(17 * var(--ut-s))
    );
  box-shadow: inset 0 0 0 calc(3 * var(--ut-s)) var(--ut-border);
}

/* --- flat panels ---------------------------------------------------------- */
.ut-panel,
.ut-panel__in {
  clip-path: none !important;
  border-radius: 0 !important;
  box-shadow: none !important;
}
.ut-panel {
  background: var(--ut-edge-top) !important;
  padding: 1px !important;
}
.ut-panel__in {
  background: linear-gradient(var(--ut-plate-top), var(--ut-plate-bot)) !important;
}

/* --- BigNumbers (HudElements1 rows 0..63) ----------------------------------
   Pixel dump of the 0 / 1 / 8 cells:

     cell      25 x 64 texels
     glyph     cols 0..21, rows 0..35 — at the TOP of the cell; rows 36..63 are
               empty, so the ink stops 36S below the DrawBigNum origin
     stroke    5 texels core + a 1-texel darker rim on the RIGHT and BOTTOM
               (lum 127 core, 93 rim) = 6 texels of visible bar
     verticals cols 0..4 (+rim 5) and 15..19 (+rim 20)
     joints    every corner is broken by a 1-2 texel diagonal gap, and both
               ends of every bar are 45-degree mitres
     digit 1   cols 15..21 only — right-aligned in its cell

   Nothing else is in the texture: the UNLIT SEGMENTS ARE NOT DRAWN. There are
   no ghost bars and a blank leading cell draws nothing at all.

   Kerning (DrawDigit / DrawBigNum, Step = 16 * UpScale):

     CurX -= (d == 1 ? 0.625 : 0.25) * Step      -> margin-left -10S / -4S
     DrawTile 25S wide                            (Canvas advances CurX by 25S)
     CurX += 7 * UpScale                          -> margin-right +7S

   so the pitch is 25 - 4 + 7 = 28 S, and a "1" pulls only itself 6 S left. */
.ut-num {
  display: flex;
  align-items: flex-start;
  gap: 0;
  font-size: 0;
  line-height: 0;
  text-shadow: none;
}

.ut-seg {
  position: relative;
  flex: 0 0 auto;
  width: calc(25 * var(--ut-s));
  height: calc(64 * var(--ut-s));
  margin-left: calc(-4 * var(--ut-s));
  margin-right: calc(7 * var(--ut-s));
}
.ut-seg.is-one {
  margin-left: calc(-10 * var(--ut-s));
}

/* A blank cell is not drawn and does not advance: DrawBigNum accounts for the
   leading blanks by moving the field start, which .ut-num's padding does. */
.ut-seg.is-blank {
  display: none;
}

/* Unlit segments do not exist in the atlas. Only `.is-on` is painted. */
.ut-seg i {
  display: none;
  position: absolute;
  background: none;
  box-shadow: none;
}
.ut-seg i.is-on {
  display: block;
}

/* Bars are 6 texels thick including the rim, mitred 3 texels (half the
   thickness) at both ends, and pulled back 0.8 texels at every joint so the
   corners keep the dark diagonal notch the atlas has. */
/* The selectors carry `i` so they outrank the `.ut-seg i` reset above, which
   is what switches the unlit segments off. */
.ut-seg i.ut-seg__a,
.ut-seg i.ut-seg__d,
.ut-seg i.ut-seg__g {
  left: calc(0.8 * var(--ut-s));
  width: calc(19.4 * var(--ut-s));
  height: calc(6 * var(--ut-s));
  background: linear-gradient(
    to bottom,
    var(--ut-seg-on) 0 calc(5 * var(--ut-s)),
    var(--ut-seg-rim) calc(5 * var(--ut-s)) 100%
  );
  clip-path: polygon(
    0 50%,
    calc(3 * var(--ut-s)) 0,
    calc(100% - 3 * var(--ut-s)) 0,
    100% 50%,
    calc(100% - 3 * var(--ut-s)) 100%,
    calc(3 * var(--ut-s)) 100%
  );
}
.ut-seg i.ut-seg__a { top: 0; }
.ut-seg i.ut-seg__g { top: calc(15 * var(--ut-s)); }
.ut-seg i.ut-seg__d { top: calc(30 * var(--ut-s)); }

.ut-seg i.ut-seg__b,
.ut-seg i.ut-seg__c,
.ut-seg i.ut-seg__e,
.ut-seg i.ut-seg__f {
  width: calc(6 * var(--ut-s));
  height: calc(13.4 * var(--ut-s));
  background: linear-gradient(
    to right,
    var(--ut-seg-on) 0 calc(5 * var(--ut-s)),
    var(--ut-seg-rim) calc(5 * var(--ut-s)) 100%
  );
  clip-path: polygon(
    50% 0,
    100% calc(3 * var(--ut-s)),
    100% calc(100% - 3 * var(--ut-s)),
    50% 100%,
    0 calc(100% - 3 * var(--ut-s)),
    0 calc(3 * var(--ut-s))
  );
}
.ut-seg i.ut-seg__f { left: 0; top: calc(3.8 * var(--ut-s)); }
.ut-seg i.ut-seg__b { left: calc(15 * var(--ut-s)); top: calc(3.8 * var(--ut-s)); }
.ut-seg i.ut-seg__e { left: 0; top: calc(18.8 * var(--ut-s)); }
.ut-seg i.ut-seg__c { left: calc(15 * var(--ut-s)); top: calc(18.8 * var(--ut-s)); }

.ut-meter--ammo.is-reloading .ut-seg i.is-on { animation: ut-seg-blink 0.42s steps(1) infinite; }
@keyframes ut-seg-blink { 50% { background: var(--ut-seg-off); box-shadow: none; } }

/* --- top right: armour over health, doll on the corner ---------------------
   ChallengeHUD.DrawStatus, in S:

     doll   Icons.Man (0,0,128,256) at (W - 128S, 0), 128 x 256
     shield HudElements1 (0,192,128,64) at (W - 128S - 140S, 0),  128 x 64
     cross  HudElements1 (128,128,128,64) at the same X, Y = 64S, 128 x 64
     digits DrawBigNum(value, X + 4S, Y + 16S)

   So: two tiles stacked FLUSH — no gap, no divider, no shared frame; each
   carries its own 3-texel border, so the seam reads as a double rule — and a
   12 S gap between them and the doll. The glyph is part of the tile art and
   lives in the tile's right 30%; the digits are a separate pass over it. */
.ut-vitals-bay {
  position: absolute;
  top: 0;
  right: 0;
  width: calc(268 * var(--ut-s)); /* 20.9375vw: doll + gap + box */
  height: calc(256 * var(--ut-s));
  pointer-events: none;
}

.ut-vital {
  position: absolute;
  right: calc(140 * var(--ut-s)); /* 10.9375vw */
  width: calc(128 * var(--ut-s)); /* 10vw */
  height: calc(64 * var(--ut-s)); /* 5vw */
  mix-blend-mode: screen;
}
.ut-vital--armor { top: 0; }
.ut-vital--health { top: calc(64 * var(--ut-s)); }

/* The tile art — box plus glyph — is its own layer because below 50 health it
   ramps and the digits over it do not ramp with it. */
.ut-vital__art {
  inset: 0;
}

.ut-vital__glyph {
  position: absolute;
}
.ut-vital__glyph svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}
/* Health cross: pixel-dumped from HudElements1 (128,128,128,64) at ink
   threshold 140 — the ink runs cols 79..122 / rows 9..52 of the tile, i.e. a
   44 x 44 isometric plus at (79S, 9S). The stem is cols 93..106 (14 texels)
   running the full 44 rows; the band crosses the whole 44 and drops ~5 texels
   from the left arm to the right one, which is the whole of its perspective. */
.ut-vital--health .ut-vital__glyph {
  left: calc(79 * var(--ut-s));
  top: calc(9 * var(--ut-s));
  width: calc(44 * var(--ut-s));
  height: calc(44 * var(--ut-s));
}
/* Armour shield: atlas cols 84..123, rows 196..250 -> 40 x 55 at (84S, 4S). */
.ut-vital--armor .ut-vital__glyph {
  left: calc(84 * var(--ut-s));
  top: calc(4 * var(--ut-s));
  width: calc(40 * var(--ut-s));
  height: calc(55 * var(--ut-s));
}
.ut-vital__glyph .ut-glyph-face { fill: var(--ut-glyph); stroke: none; }
.ut-vital__glyph .ut-glyph-lit { fill: var(--ut-glyph-hi); stroke: none; }
.ut-vital__glyph .ut-glyph-dark { fill: var(--ut-glyph-lo); stroke: none; }
.ut-vital__glyph .ut-glyph-hi { fill: var(--ut-glyph-edge); stroke: none; }
/* The shield's interior is the box fill showing through (atlas lum 66..110
   against a 66 fill), so only its rim is painted. */
.ut-vital__glyph .ut-glyph-rim {
  fill: none;
  stroke: var(--ut-glyph-rim);
  stroke-width: 2;
}

/* Digits: origin (tile + 4S, tile + 16S), glyph 36S tall from there. */
.ut-vital .ut-num {
  position: absolute;
  left: calc(4 * var(--ut-s));
  top: calc(16 * var(--ut-s));
}

/* --- Health < 50 -----------------------------------------------------------
   TutIconBlink is a 0 -> 0.5 s sawtooth. With H1 = 1.5*TutIconBlink and
   H2 = 1 - H1:

     cross tile = White*H2 + (HUDColor - White)*H1. The second term clamps to
     black for every HUD colour, so the TILE turns WHITE and its brightness
     ramps 100% -> 25% across each half second, then snaps back.
     digits     = White*(H2^2 + H1^2): 100% -> 53% at t=0.25 -> 62.5% at t=0.5,
     then snap. They dim and recover; they never switch off and never change
     hue. Health >= 50 animates nothing, and 25 is not a threshold at all. */
.ut-vital--health.is-low {
  --ut-rgb: 255, 255, 255;
  /* A custom property's value is substituted where it is DECLARED, so the
     derived tokens above are already frozen to the HUD colour by the time they
     inherit down here — re-pointing --ut-rgb alone changes nothing. Each
     derived token has to be restated at the same alpha in white. */
  --ut-fill: rgba(255, 255, 255, 0.26);
  --ut-fill-flat: rgba(255, 255, 255, 0.26);
  --ut-grid: rgba(255, 255, 255, 0.095);
  --ut-border: rgba(255, 255, 255, 0.311);
  --ut-glyph: rgba(255, 255, 255, 0.595);
  --ut-glyph-hi: rgba(255, 255, 255, 0.865);
  --ut-glyph-lo: rgba(255, 255, 255, 0.257);
  --ut-glyph-edge: rgba(255, 255, 255, 0.932);
  --ut-glyph-rim: rgba(255, 255, 255, 0.324);
}
.ut-vital--health.is-low .ut-vital__art {
  animation: ut-low-tile 0.5s linear infinite;
}
@keyframes ut-low-tile {
  from { opacity: 1; }
  to { opacity: 0.25; }
}
.ut-vital--health.is-low .ut-num {
  animation: ut-low-digits 0.5s linear infinite;
}
@keyframes ut-low-digits {
  0% { opacity: 1; }
  50% { opacity: 0.53; }
  100% { opacity: 0.625; }
}

/* --- the paper doll (Icons.Man, 128 x 256) ---------------------------------
   Fill lum 90 (.35) under a 4-texel-pitch wireframe grid at lum 138 (.54) and
   a lum 255 outline. No glow, no health tint, no hit blink — the only thing
   that ever changes on it is the chest plate brightening with armour and the
   red damage markers. The grid's alpha is pre-composited over the fill the
   same way the box grid is: (.54 - .35) / .65 = .292. */
.ut-doll {
  position: absolute;
  top: 0;
  right: 0;
  width: calc(128 * var(--ut-s)); /* 10vw */
  height: calc(256 * var(--ut-s)); /* 20vw */
  mix-blend-mode: screen;
  filter: none;
  opacity: 1;
}
.ut-doll svg {
  display: block;
  width: 100%;
  height: 100%;
}
.ut-doll__fill { fill: rgba(var(--ut-rgb), 0.35); }
.ut-doll__grid { fill: url(#ut-doll-grid); }
#ut-doll-grid path {
  fill: none;
  stroke: rgba(var(--ut-rgb), 0.292);
  stroke-width: 1;
}
.ut-doll__edge {
  fill: none;
  stroke: rgba(var(--ut-rgb), 1);
  stroke-width: 2;
}
.ut-doll__panel { fill: rgba(var(--ut-rgb), 1); }
/* Chest overlay, drawn at HUDColor * Min(ChestAmount/100, 1). */
.ut-doll__plate {
  fill: rgba(var(--ut-rgb), 0.5);
  opacity: var(--ut-chest, 0);
}

/* Damage markers: the HudElements1 minus tile at HitPos, sized
   25*HitDamage x 64*HitDamage with HitDamage = Clamp(dmg*0.06, 2, 4), for one
   second, up to four at a time. Red over a blue-ish HUD; there is no screen
   vignette and no doll tint anywhere in ChallengeHUD. */
.ut-doll__hits {
  position: absolute;
  inset: 0;
}
.ut-dash {
  position: absolute;
  background: rgba(var(--ut-dash-rgb), 1);
  clip-path: polygon(0 50%, 20% 0, 80% 0, 100% 50%, 80% 100%, 20% 100%);
}

/* Number first, glyph to its right — the order in the original. */
.ut-vitals .ut-meter,
.ut-ammo-bay .ut-meter {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 9px;
  background: none;
  padding: 0;
}
.ut-vitals .ut-meter .ut-panel,
.ut-ammo-bay .ut-meter .ut-panel {
  background: none !important;
  padding: 0 !important;
}
.ut-vitals .ut-meter .ut-panel__in,
.ut-ammo-bay .ut-meter .ut-panel__in {
  background: none !important;
  padding: 0 !important;
}

.ut-disc {
  background: none !important;
  border: 0 !important;
  box-shadow: none !important;
  width: auto !important;
  height: auto !important;
}
.ut-disc__glyph svg { width: 26px; height: 26px; }
.ut-disc__glyph svg * { fill: var(--ut-hue-deep); stroke: var(--ut-hue-deep); }

/* --- bottom edge: frag box | ten weapon slots | ammo box --------------------
   ChallengeHUD.DrawFragCount / DrawAmmo / DrawWeapons, in the ONE unit the
   source uses: S = ClipX / 1280, continuous, width-only, = 0.078125vw. There is
   no strip and no integer HudScale ladder. Three separate islands sit on the
   bottom edge and the world shows through between them:

     frag tile   SetPos(0,            H - 63.5 S) 128 S x 64 S   -> 10vw x 5vw
     slot i      SetPos(0.1W + (i-1)*0.08W, H - 50.8 S) 102.4 x 51.2 S -> 8 x 4vw
     ammo tile   SetPos(W - 128 S,    H - 63.5 S) 128 S x 64 S   -> 10vw x 5vw

   so the row reads [frag 0..10vw][slot1 10..18] ... [slot10 82..90][ammo 90..100]
   and the two corner boxes stand 1vw taller than the slots and 0.4 S lower. Both
   overhang the bottom edge by 0.5 S: the tile's own 3-texel border is cut off
   there, which is why they look open at the bottom in the original.

   MATERIAL. Every HUD texture is GREYSCALE and DrawColor tints it, composited
   STY_Translucent (a screen blend). The alphas below are the measured texel
   luminances of the real atlas divided by 255, which is that blend's close
   approximation over a dark world:

     HudElements1 128x64 box (skull / bullets / cross / shield tiles)
       border 3 texels lum 124 = .486   fill lum 66 = .259
       grid   2 texels lum  84 = .329   pitch 17 texels, offset x 12 / y 14
     HUDWeapons 64x32 empty cell, drawn 2x and at 0.5 * HUDColor
       interior is a vertical ramp lum 102 -> 21, border 1 texel lum 122 -> 41,
       so the halved cell runs .20 at the top to .04 at the bottom: barely there
     UseAutoM 128x64, the HELD slot, drawn STY_Normal (Opacity 15 > 8) = OPAQUE
       border lum 122 = .478  number recess lum 96 = .376  fill lum 67 = .263
       grid lum 83 = .325  weapon silhouette lum 1 = BLACK
   -------------------------------------------------------------------------- */
.ut-bar {
  /* The two colours DrawWeapons actually uses. HUDColor is the opacity-scaled
     tint every translucent tile gets; SolidHUDColor is the unscaled team colour
     the OPAQUE held slot is drawn in. Defaults are the DM pair
     (FavoriteHUDColor * 15.9 = 0,0,254); the team rules below replace both.
     Lengths are in the shared `--ut-s` = S = ClipX / 1280 = 0.078125vw. */
  --ut99-hue: 0, 0, 254; /* HUDColor */

  /* SolidHUDColor pre-multiplied by each texel luminance, because the held slot
     is opaque: there is no alpha to do it with. */
  --ut99-sol-fill: #000043; /* 0,0,254 x .263 */
  --ut99-sol-grid: #000053; /* x .325 */
  --ut99-sol-panel: #000060; /* x .376 */
  --ut99-sol-line: #000079; /* x .478 */

  --ut99-gold: rgba(255, 255, 0, 0.584); /* GoldColor x the bracket's lum 149 */
  --ut99-gold-num: #7f7f00; /* GoldColor x the digit's lum 127, OPAQUE */

  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 5vw; /* 64 S: the corner boxes set it, the slots are shorter */
  display: block;
  padding: 0;
  margin: 0;
  background: none;
  border: 0;
  pointer-events: none;
}

/* ChallengeTeamHUD.HUDSetup: HUDColor = Opacity * 0.0625 * TeamColor[team],
   SolidHUDColor = TeamColor[team]. Blue is (0,128,255) AZURE, not pure blue. */
html[data-team="red"] .ut-bar {
  --ut99-hue: 239, 0, 0;
  --ut99-sol-fill: #430000;
  --ut99-sol-grid: #530000;
  --ut99-sol-panel: #600000;
  --ut99-sol-line: #7a0000;
}
html[data-team="blue"] .ut-bar {
  --ut99-hue: 0, 120, 239;
  --ut99-sol-fill: #002243;
  --ut99-sol-grid: #002a53;
  --ut99-sol-panel: #003060;
  --ut99-sol-line: #003d7a;
}

/* The 128x64 tile the frag and ammo boxes share with the health and armour
   boxes: flat fill, a 17-texel grid, a 3-texel border. No bevel, no gradient,
   no rounding. The grid is two single-line gradients tiled at the pitch so the
   phase stays exact at any width instead of breaking at the box edge. */
.ut-box {
  position: absolute;
  background-color: rgba(var(--ut99-hue), 0.259);
  background-image: linear-gradient(
      to right,
      rgba(var(--ut99-hue), 0.089) 0 calc(2 * var(--ut-s)),
      transparent calc(2 * var(--ut-s))
    ),
    linear-gradient(
      to bottom,
      rgba(var(--ut99-hue), 0.089) 0 calc(2 * var(--ut-s)),
      transparent calc(2 * var(--ut-s))
    );
  background-size: calc(17 * var(--ut-s)) 100%, 100% calc(17 * var(--ut-s));
  background-position: calc(12 * var(--ut-s)) 0, 0 calc(14 * var(--ut-s));
  background-repeat: repeat, repeat;
  /* 0.311, not 0.486: the border texel is lum 124 (.49) OUTRIGHT, and this
     shadow is painted OVER the .259 fill, so the alpha that composites to .49
     is (0.49 - 0.259) / 0.741 = 0.311 — the same number .ut-tile uses. */
  box-shadow: inset 0 0 0 calc(3 * var(--ut-s)) rgba(var(--ut99-hue), 0.311);
  border-radius: 0;
  /* STY_Translucent is dst + dst*(1-src): a SCREEN blend. Nothing on this HUD
     may darken the world, and these are the same four boxes as the cross and
     shield tiles two inches away, which already screen. */
  mix-blend-mode: screen;
}

/* --- the frag box (DrawFragCount) ------------------------------------------
   Drawn in DM *and* CTF: bHideFrags is false by default and ChallengeCTFHUD
   never overrides DrawFragCount. X is an uninitialised local int, i.e. 0. */
.ut-frags {
  left: 0;
  bottom: calc(-0.5 * var(--ut-s));
  width: 10vw; /* 128 S */
  height: 5vw; /* 64 S */
}

/* HudElements1 (0,128,128,64): the skull sits on the LEFT of the tile, ink bbox
   cols 8..39 rows 9..56, leaving cols 40..127 for the digits. */
.ut-frags__skull {
  position: absolute;
  left: calc(8 * var(--ut-s));
  top: calc(9 * var(--ut-s));
  width: calc(32 * var(--ut-s));
  height: calc(48 * var(--ut-s));
}

.ut-frags__skull svg,
.ut-ammo-bay__bullets svg {
  width: 100%;
  height: 100%;
  display: block;
}

/* The atlas is grey and HUDColor tints it, so every tone in the glyph is one
   hue at the texel's own luminance. */
.ut-frags__skull .g-body,
.ut-ammo-bay__bullets .g-body {
  fill: rgba(var(--ut99-hue), 0.55);
}
.ut-frags__skull .g-hi,
.ut-ammo-bay__bullets .g-hi {
  fill: rgba(var(--ut99-hue), 0.88);
}
.ut-frags__skull .g-mid,
.ut-ammo-bay__bullets .g-mid {
  fill: rgba(var(--ut99-hue), 0.72);
}
/* Sockets, nasal cavity and tooth gaps are DARKER than the tile fill, so under
   a screen blend they read as the world showing through: draw nothing. */
.ut-frags__skull .g-hole {
  fill: rgba(0, 0, 0, 0.35);
}

/* Score flash (DrawFragCount): for 3 s after ScoreTime the tile colour is
   Gold + (HUDColor - Gold) * frac(4t) — a 4 Hz sawtooth from gold back to the
   HUD colour — with a soft gold glow tile (HUDWeapons 0,128,256,128) drawn at
   256 S x 128 S centred on the box behind it. */
.ut-frags.is-scored {
  animation: ut-frag-score 3s linear 1;
}

@keyframes ut-frag-score {
  0%,
  8.33%,
  16.67%,
  25%,
  33.33%,
  41.67%,
  50%,
  58.33%,
  66.67%,
  75%,
  83.33%,
  91.67% {
    background-color: rgba(255, 255, 0, 0.55);
  }
  8.32%,
  16.66%,
  24.99%,
  33.32%,
  41.66%,
  49.99%,
  58.32%,
  66.66%,
  74.99%,
  83.32%,
  91.66%,
  100% {
    background-color: rgba(var(--ut99-hue), 0.259);
  }
}

/* The glow blob. The source draws it BEHIND the tile; at these alphas over a
   translucent tile the difference is not visible, so it is drawn on top and
   kept out of the hit-testing path. */
.ut-frags__flash {
  position: absolute;
  left: calc(-64 * var(--ut-s));
  top: calc(-32 * var(--ut-s));
  width: calc(256 * var(--ut-s));
  height: calc(128 * var(--ut-s));
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(
    ellipse at 50% 50%,
    rgba(255, 255, 0, 0.34) 0%,
    rgba(255, 255, 0, 0.16) 42%,
    rgba(255, 255, 0, 0) 72%
  );
}

.ut-frags.is-scored .ut-frags__flash {
  animation: ut-frag-glow 3s linear 1;
}

@keyframes ut-frag-glow {
  0%,
  95% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}

/* --- ten weapon slots (DrawWeapons) ----------------------------------------
   BaseX = 0.1 W, pitch WeaponOffset = 0.08 W, tile 128 x 64 at WeapScale 0.8:
   8vw x 4vw, edge to edge, no gap. All ten are always drawn. */
.ut-weapons {
  position: absolute;
  left: 10vw;
  bottom: calc(-0.4 * var(--ut-s));
  width: 80vw;
  height: 4vw; /* 51.2 S */
  display: flex;
  gap: 0;
  padding: 0;
  margin: 0;
  transform: none;
}

/* EMPTY and OWNED-BUT-NOT-HELD slots are the SAME cell: HUDWeapons
   ((i-1)%4*64, (i-1)/4*32, 64, 32) at 0.5 * HUDColor, translucent. The cell art
   is a vertical ramp with a 1-texel border and a ghost outline of that group's
   weapon, so at half strength it is a very dim box you can see the map through.
   One texel of the doubled cell is 0.125vw. */
.ut-wslot {
  position: relative;
  flex: 0 0 8vw;
  width: 8vw;
  height: 4vw;
  opacity: 1;
  border-radius: 0;
  background-color: transparent;
  background-image: linear-gradient(
      to bottom,
      rgba(var(--ut99-hue), 0.137) 0%,
      rgba(var(--ut99-hue), 0.041) 100%
    ),
    linear-gradient(
      to right,
      rgba(var(--ut99-hue), 0.03) 0 0.125vw,
      transparent 0.125vw
    ),
    linear-gradient(
      to bottom,
      rgba(var(--ut99-hue), 0.03) 0 0.125vw,
      transparent 0.125vw
    );
  background-size: 100% 100%, 1vw 100%, 100% 1vw;
  background-position: 0 0, 0.125vw 0, 0 0.875vw;
  background-repeat: no-repeat, repeat, repeat;
  /* the cell border: bright along the top, fading to a fifth of that at the
     bottom, which is what gives the empty bar its horizon line */
  box-shadow: inset 0 0 0 0.125vw rgba(var(--ut99-hue), 0.14),
    inset 0 0.125vw 0 0 rgba(var(--ut99-hue), 0.24);
  /* Translucent cell -> STY_Translucent -> screen, like every other tile. */
  mix-blend-mode: screen;
}

/* The left tenth of the cell is the recess the slot number sits in — a shade
   lighter than the box behind it in both the empty cell and the held tile. */
.ut-wslot::before {
  content: "";
  position: absolute;
  left: 0.125vw;
  top: 0.125vw;
  width: 1.25vw;
  height: calc(100% - 0.25vw);
  pointer-events: none;
  background: linear-gradient(
    to bottom,
    rgba(var(--ut99-hue), 0.08),
    rgba(var(--ut99-hue), 0)
  );
}

/* THE HELD SLOT. W.StatusIcon (UseAutoM) at SolidHUDColor, STY_Normal: the one
   OPAQUE thing on the HUD. Tinted grid box, lighter number recess, a black
   weapon silhouette and the black recess the ammo bar runs in. One texel of the
   128x64 tile is 0.0625vw here. */
.ut-wslot.is-active {
  background-color: #000;
  background-image: linear-gradient(
      to right,
      var(--ut99-sol-grid) 0 0.125vw,
      transparent 0.125vw
    ),
    linear-gradient(to bottom, var(--ut99-sol-grid) 0 0.125vw, transparent 0.125vw),
    linear-gradient(var(--ut99-sol-fill), var(--ut99-sol-fill));
  background-size: 1.0625vw 100%, 100% 1.0625vw, 100% 100%;
  background-position: 0.75vw 0, 0 0.875vw, 0 0;
  background-repeat: repeat, repeat, no-repeat;
  box-shadow: inset 0 0 0 0.1875vw var(--ut99-sol-line);
  /* W.StatusIcon is drawn STY_Normal — the one OPAQUE tile on the HUD, so it
     is also the one tile that does not screen. */
  mix-blend-mode: normal;
}

.ut-wslot.is-active::before {
  left: 0.1875vw;
  top: 0.1875vw;
  width: 1.25vw;
  height: 2.5vw;
  background: var(--ut99-sol-panel);
}

/* The recess the ammo bar sits in: black in the tile art, cols 3..98 rows
   50..60 of the 128x64 held tile. Only the held slot's art has one. */
.ut-wslot.is-active::after {
  content: "";
  position: absolute;
  left: 0.1875vw;
  top: 3.125vw;
  width: 5.9375vw;
  height: 0.6875vw;
  background: #000;
  pointer-events: none;
}

/* Slot number: HudElements1 digit i (0 for slot 10) at 0.75 * WeapScale * 25 x
   64, so a 15 S x 38.4 S cell whose glyph is 13.2 S x 21.6 S, at
   (slotX + 4 WeapScale, BaseY + 4 WeapScale) = (3.2 S, 3.2 S). GoldColor,
   STY_Normal -> OPAQUE gold at the glyph's own 50% luminance = (127,127,0).
   Drawn for every owned slot, the held one included. */
.ut-wslot__key {
  position: absolute;
  left: 0.25vw; /* 3.2 S */
  top: 0.25vw;
  z-index: 2;
  /* The digit is the SAME atlas cell the big numbers use, drawn at 0.75 of the
     weapon scale — so rather than restate the glyph, shrink the unit the shared
     `.ut-seg` model measures itself in: 0.6 S gives a 15 S x 38.4 S cell whose
     glyph is 13.2 S x 21.6 S, which is what DrawTile produces. */
  --ut-s: calc(0.6 * 0.078125vw);
  /* GoldColor x the glyph's own texels (core lum 127, rim 93), OPAQUE: this one
     tile is drawn STY_Normal, so it is the only flat colour on the bar. */
  --ut-seg-on: #7f7f00;
  --ut-seg-rim: #5d5d00;
  /* GoldColor STY_Normal: opaque, and its own blend so the cell's screen
     composite never reaches it. */
  mix-blend-mode: normal;
}

/* DrawTile, not DrawBigNum: the slot number is placed outright at
   (4 WeapScale, 4 WeapScale) with no kerning walk, so the shared cell's
   advance margins have to come off. */
.ut-wslot__key .ut-seg {
  margin: 0;
}

/* The weapon itself. Held: the SOLID BLACK silhouette of UseAutoM, ink bbox
   cols 40..108 rows 8..42 of the 128x64 tile. Not held: the same weapon as the
   1-texel ghost OUTLINE the HUDWeapons cell carries, at 0.5 * HUDColor. */
.ut-wslot__icon {
  position: absolute;
  left: 2.5vw; /* 40 texels x 0.0625vw */
  top: 0.5vw;
  width: 4.3125vw;
  height: 2.1875vw;
  z-index: 1;
  pointer-events: none;
}

.ut-wslot__icon svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

.ut-wslot__icon svg path {
  fill: none;
  stroke: rgba(var(--ut99-hue), 0.098);
  stroke-width: 1.4;
  vector-effect: non-scaling-stroke;
}

.ut-wslot.is-active .ut-wslot__icon svg path {
  fill: #000;
  stroke: var(--ut99-sol-line);
  stroke-width: 1;
}

/* Per-slot ammo bar: HudElements1 (64,64,128,8) drawn AmmoScale x 8 WeapScale
   at (slotX + 3.2 S, BaseY + 41.6 S) in BaseColor (white), so the texture keeps
   its own colour: a flat (255,236,0) band over the top quarter and an
   orange->yellow gradient (255,108,0) -> (255,244,0) under it. The full
   128-texel gradient is mapped onto the CURRENT width — it squashes, it does
   not clip — which is what a percentage-free linear-gradient already does.
   Max width is 88 * WeapScale = 70.4 S = 5.5vw. (Epic's FClamp caps the result
   at 88 *pixels*, so above 1600 px wide the real bar stops growing; the
   continuous 5.5vw is kept here for the same reason the scale is continuous.)
   Width is written by hud-root.js in vw. */
.ut-wslot__ammo {
  position: absolute;
  left: 0.25vw; /* 3.2 S */
  top: 3.25vw; /* 41.6 S */
  height: 0.5vw; /* 6.4 S */
  width: 0;
  max-width: 5.5vw; /* 70.4 S */
  z-index: 2;
  background-image: linear-gradient(to bottom, #ffec00 0 25%, transparent 25%),
    linear-gradient(to right, #ff6c00, #fff400);
  background-size: 100% 100%, 100% 100%;
}

/* Selection bracket: HUDWeapons (128,64,128,64) at slot size, GoldColor,
   translucent. Four corners and nothing else — 3 texels thick (0.1875vw),
   horizontal arms 12 texels (0.75vw), vertical arms 14 texels (0.875vw), at the
   tile's own luminance 149. */
.ut-wslot.is-active .ut-wslot__bracket {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  background-image: linear-gradient(var(--ut99-gold), var(--ut99-gold)),
    linear-gradient(var(--ut99-gold), var(--ut99-gold)),
    linear-gradient(var(--ut99-gold), var(--ut99-gold)),
    linear-gradient(var(--ut99-gold), var(--ut99-gold)),
    linear-gradient(var(--ut99-gold), var(--ut99-gold)),
    linear-gradient(var(--ut99-gold), var(--ut99-gold)),
    linear-gradient(var(--ut99-gold), var(--ut99-gold)),
    linear-gradient(var(--ut99-gold), var(--ut99-gold));
  background-size: 0.75vw 0.1875vw, 0.1875vw 0.875vw, 0.75vw 0.1875vw,
    0.1875vw 0.875vw, 0.75vw 0.1875vw, 0.1875vw 0.875vw, 0.75vw 0.1875vw,
    0.1875vw 0.875vw;
  background-position: 0 0, 0 0, 100% 0, 100% 0, 0 100%, 0 100%, 100% 100%,
    100% 100%;
  background-repeat: no-repeat;
  mix-blend-mode: normal;
}

.ut-wslot__bracket {
  display: none;
}

.ut-wslot.is-active .ut-wslot__bracket {
  display: block;
}

/* Not a source behaviour: this build's Enforcer has a client-side magazine, and
   a single-frame lift on the held cell is the cheapest honest way to show a
   shot landed. Kept small enough that it cannot be mistaken for the pending
   glow the original uses. */
.ut-wslot.is-fired {
  animation: ut-slot-fire 120ms ease-out 1;
}

@keyframes ut-slot-fire {
  0% {
    background-color: #3a3a3a;
  }
  100% {
    background-color: #000;
  }
}

/* --- the ammo box (DrawAmmo) ------------------------------------------------
   HudElements1 (128,192,128,64) at (W - 128 S, H - 63.5 S). The bullets glyph
   is part of the tile art, on the RIGHT: ink bbox cols 87..120 rows 8..57. */
.ut-ammo-bay {
  right: 0;
  bottom: calc(-0.5 * var(--ut-s));
  width: 10vw;
  height: 5vw;
  margin: 0;
  padding: 0;
  border: 0;
}

.ut-ammo-bay__bullets {
  position: absolute;
  left: calc(87 * var(--ut-s));
  top: calc(8 * var(--ut-s));
  width: calc(34 * var(--ut-s));
  height: calc(50 * var(--ut-s));
}

/* --- DrawBigNum inside the two corner boxes ---------------------------------
   Cell 25 S x 64 S with the glyph in its top-left 22 S x 36 S; rows 36..63 of
   the cell are empty, so the number's visual baseline is 36 S below the origin,
   not 64 S. Pitch is 25 - 4 + 7 = 28 S. The field is neither left- nor
   right-aligned: DrawBigNum steps CurX left by 16 S for every digit it is about
   to draw, so the start is 44 S - 16 S * ndigits from the origin (+28 S for one
   digit, +12 S for two, -4 S for three). */
.ut-num--frags,
.ut-num--ammo {
  position: absolute;
  top: calc(16 * var(--ut-s));
}

.ut-num--frags {
  left: calc(40 * var(--ut-s)); /* DrawBigNum(Score, X + 40 S, Y + 16 S) */
}
.ut-num--ammo {
  left: calc(4 * var(--ut-s)); /* DrawBigNum(AmmoAmount, X + 4 S, Y + 16 S) */
}

/* The field start — first tile = origin + (44 - 16n) S, so the number is
   neither left- nor right-aligned and each extra digit moves the start 16 S
   left and the end 12 S right — is the shared `.ut-num` model's: makeNumber
   writes the DrawBigNum Step walk as the field's own padding, and the cell
   carries the -4 S / -10 S kerning. Only the origin belongs here. */

/* Not a source behaviour either — UT99 has no reload — so it is the smallest
   thing that still reads: the digits hold at 0 and dim, they do not recolour. */
.ut-ammo-bay.is-reloading .ut-num--ammo {
  animation: ut-ammo-reload 0.42s steps(1) infinite;
}

@keyframes ut-ammo-reload {
  50% {
    opacity: 0.3;
  }
}

/* --- CTF: team score + flag icon, right edge, mid height (spec §1.8) --------
   ChallengeCTFHUD.DrawTeam, which OVERRIDES the base team HUD's icon+label
   version — there is no I_TeamR/I_TeamB tab in CTF. Per team index i:

     digits  DrawBigNum origin (W - 144 S, H - 336 S - 150 S * i)
     icon    DrawIcon at        (W -  70 S, H - 350 S - 150 S * i), 64 S square

   Team 0 is RED and team 1 is BLUE, so RED IS THE LOWER ROW. S = 0.078125vw:

     144 S = 11.25vw    336 S = 26.25vw    486 S = 37.96875vw
      70 S =  5.46875vw 350 S = 27.34375vw 500 S = 39.0625vw    64 S = 5vw

   Both members are placed against the viewport, so `.ut-flagrow` exists only to
   carry `data-flag` for the icon state.

   Hidden unless the document carries a team: network.js sets html[data-team]
   only when the server puts you on one, i.e. only in CTF, so DM never draws it.
   -------------------------------------------------------------------------- */
.ut-flags {
  display: none;
}

html[data-team="blue"] .ut-flags,
html[data-team="red"] .ut-flags {
  position: absolute;
  inset: 0;
  display: block;
}

.ut-flagrow {
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* STY_Translucent -> screen, on the row so the icon and its digits composite
     as one group (the same wrapper-level blend `.ut-wslot` and `.ut-frags`
     use); on the descendants it would screen the icon against itself. */
  mix-blend-mode: screen;
}

/* Icon rect: left edge at W-70S, 64S square, so its right margin is 6 S. */
.ut-flag {
  position: absolute;
  right: 0.46875vw;
  width: 5vw;
  height: 5vw;
}

.ut-flagrow--red .ut-flag {
  top: calc(100% - 27.34375vw);
}

.ut-flagrow--blue .ut-flag {
  top: calc(100% - 39.0625vw);
}

.ut-flag svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* Digit field: the DrawBigNum origin is the field's TOP-LEFT. Cell metrics and
   kerning are the shared `.ut-seg` model (25 S x 64 S, 28 S pitch); only the
   origin belongs here. */
.ut-num--flag {
  position: absolute;
  left: calc(100% - 11.25vw);
}

.ut-flagrow--red .ut-num--flag {
  top: calc(100% - 26.25vw);
}

.ut-flagrow--blue .ut-num--flag {
  top: calc(100% - 37.96875vw);
}

/* The score digits are drawn in TeamColor at FULL strength — (255,0,0) and
   (0,128,255), NOT the opacity-scaled HUDColor. The glyph's own texels are
   lum 127 core / 93 rim, so on screen the strokes are half the team colour with
   a slightly darker rim. Retinting is all these rows need: the stroke geometry,
   the mitres and the "unlit segments are not drawn" rule are the shared
   `.ut-seg` model's, and `--ut-seg-on` / `--ut-seg-rim` are its colour hooks. */
.ut-flagrow--blue {
  --ut-seg-on: rgba(0, 128, 255, 0.5);
  --ut-seg-rim: rgba(0, 128, 255, 0.36);
}

.ut-flagrow--red {
  --ut-seg-on: rgba(255, 0, 0, 0.5);
  --ut-seg-rim: rgba(255, 0, 0, 0.36);
}

/* --- flag icon states (I_Home / I_Capt / I_Down, spec §4.8) -----------------
   All three share the pole (lum 61 = .24, cap 99 = .39) and the cloth frame
   (lum 128 = .50). What changes is the interior: I_Home fills the cloth to
   lum 99, I_Capt and I_Down drop it to lum 61 and put a lum 128 figure on it.
   Colour is TeamColor[Flag.Team], translucent. No blink, no dimming — the state
   is carried by the artwork, which is the whole point of there being three
   textures.

   `carried` is the SERVER's word for bHeld; it is what network.js sends.
   -------------------------------------------------------------------------- */
.ut-flagrow--red .ut-flag {
  --ut-flag-rgb: 255, 0, 0;
}

.ut-flagrow--blue .ut-flag {
  --ut-flag-rgb: 0, 128, 255;
}

.ut-flag__pole {
  fill: rgba(var(--ut-flag-rgb), 0.24);
}
.ut-flag__cap {
  fill: rgba(var(--ut-flag-rgb), 0.39);
}
.ut-flag__cloth {
  fill: rgba(var(--ut-flag-rgb), 0.24);
}
.ut-flag__frame {
  fill: none;
  stroke: rgba(var(--ut-flag-rgb), 0.5);
  stroke-width: 1;
}
.ut-flag__post {
  fill: rgba(var(--ut-flag-rgb), 0.5);
}
.ut-flag__mark {
  fill: rgba(var(--ut-flag-rgb), 0.5);
  display: none;
}

.ut-flagrow[data-flag="home"] .ut-flag__cloth {
  fill: rgba(var(--ut-flag-rgb), 0.39);
}

.ut-flagrow[data-flag="carried"] .ut-flag__mark--capt {
  display: block;
}

.ut-flagrow[data-flag="dropped"] .ut-flag__mark--down {
  display: block;
}

/* The TAB hint keeps the flat idiom. UT99 has no such hint at all, but a browser
   game with no menu needs one. It must stay OUT of DrawSpeechArea's message
   band -- left 6S, top 2S, 768S wide (60vw) and ~77S (6vw) tall -- or it sits
   on kill-feed lines 2 and 3. It parks on the right instead, directly under the
   vitals bay (256S tall), which is the only other thing in that corner. */
.ut-hint {
  background: linear-gradient(var(--ut-plate-top), var(--ut-plate-bot));
  border: 1px solid var(--ut-edge-top);
  border-radius: 0;
  top: calc(256 * var(--ut-s) + 8px) !important;
  right: 6px !important;
  left: auto !important;
}


/* ==========================================================================
   Credits as a "?" in the corner. The block used to sit centred over the
   floor; UT99 draws nothing there, so it now collapses to one glyph tucked
   above the frag box and opens on click (touch) or hover (mouse).
   ========================================================================== */
#credits-container {
  left: 8px;
  right: auto;
  bottom: calc(5vw + 10px);
  transform: none;
  width: auto;
  max-width: min(360px, 70vw);
  text-align: left;
}

.credit-toggle {
  pointer-events: auto;
  cursor: pointer;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 1px solid rgba(var(--ut-rgb, 0, 120, 239), 0.6);
  background: rgba(var(--ut-rgb, 0, 120, 239), 0.26);
  color: rgba(255, 255, 255, 0.85);
  font: 700 13px/20px var(--ut-font-ui);
  text-align: center;
  mix-blend-mode: screen;
}

.credit-toggle:hover,
#credits-container.is-open .credit-toggle {
  background: rgba(var(--ut-rgb, 0, 120, 239), 0.6);
  color: #fff;
}

.credit-panel {
  display: none;
  margin: 0 0 6px;
  padding: 8px 10px;
  background: rgba(0, 0, 0, 0.55);
  border-left: 2px solid rgba(var(--ut-rgb, 0, 120, 239), 0.6);
}

#credits-container.is-open .credit-panel,
#credits-container:hover .credit-panel {
  display: block;
}

/* Panel above, glyph below, so opening it never covers the frag box. */
#credits-container {
  display: flex;
  flex-direction: column-reverse;
  align-items: flex-start;
}

/* ==========================================================================
   ATLAS MODE — the HUD drawn from the original UT99 textures.
   GAME_CONFIG.HUD.ATLAS puts `.ut-hud--atlas` on the root; everything below
   is how ChallengeHUD.uc's DrawTile calls map onto CSS:

     DrawColor x texel luminance  ->  background-color masked by the atlas
                                      with mask-mode: luminance
     STY_Translucent              ->  mix-blend-mode: screen
     STY_Normal                   ->  mix-blend-mode: normal (the held slot
                                      and the gold slot numbers)
     one texel                    ->  1 S (0.078125vw) on the corner tiles,
                                      1.6 S in the weapon bar (64x32 cells
                                      drawn at 128x64), 0.6 S for slot numbers

   The atlases are greyscale, so luminance masking IS the engine's modulate.
   The SVG/CSS recreation below stays in the DOM and takes over again the
   moment the class is absent.
   ========================================================================== */
.ut-hud--atlas {
  --ut-atlas: url("assets/ut99/BotPack.Icons.HudElements1.png");
  --ut-wpn: url("assets/ut99/BotPack.Icons.HUDWeapons.png");
  --ut-man: url("assets/ut99/BotPack.Icons.Man.png");
}

/* A masked tile. Every atlas element is a ::before/::after carrying one of
   these; the parent keeps its position and size and loses its CSS art. */
.ut-hud--atlas .ut-seg::before,
.ut-hud--atlas .ut-vital__art::before,
.ut-hud--atlas .ut-frags::before,
.ut-hud--atlas .ut-ammo-bay::before,
.ut-hud--atlas .ut-wslot:not(.is-active)::before,
.ut-hud--atlas .ut-doll::before,
.ut-hud--atlas .ut-doll::after {
  content: "";
  position: absolute;
  inset: 0;
  width: auto;
  height: auto;
  pointer-events: none;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-mode: luminance;
  mask-mode: luminance;
}

/* --- digits: HudElements1 (d*25, 0, 25, 64), the cell IS 25S x 64S --------- */
.ut-hud--atlas .ut-seg > i { display: none !important; }
.ut-hud--atlas .ut-seg::before {
  --ut-d: 0;
  background: var(--ut-digit, #fff);
  -webkit-mask-image: var(--ut-atlas);
  mask-image: var(--ut-atlas);
  -webkit-mask-size: calc(256 * var(--ut-s)) calc(256 * var(--ut-s));
  mask-size: calc(256 * var(--ut-s)) calc(256 * var(--ut-s));
  -webkit-mask-position: calc(var(--ut-d) * -25 * var(--ut-s)) 0;
  mask-position: calc(var(--ut-d) * -25 * var(--ut-s)) 0;
}
.ut-hud--atlas .ut-seg[data-d="0"]::before { --ut-d: 0; }
.ut-hud--atlas .ut-seg[data-d="1"]::before { --ut-d: 1; }
.ut-hud--atlas .ut-seg[data-d="2"]::before { --ut-d: 2; }
.ut-hud--atlas .ut-seg[data-d="3"]::before { --ut-d: 3; }
.ut-hud--atlas .ut-seg[data-d="4"]::before { --ut-d: 4; }
.ut-hud--atlas .ut-seg[data-d="5"]::before { --ut-d: 5; }
.ut-hud--atlas .ut-seg[data-d="6"]::before { --ut-d: 6; }
.ut-hud--atlas .ut-seg[data-d="7"]::before { --ut-d: 7; }
.ut-hud--atlas .ut-seg[data-d="8"]::before { --ut-d: 8; }
.ut-hud--atlas .ut-seg[data-d="9"]::before { --ut-d: 9; }

/* DrawColor per readout: health and armour WhiteColor (DrawStatus), ammo
   WhiteColor (DrawAmmo), frags the current HUDColor (DrawFragCount), slot
   numbers GoldColor STY_Normal. */
.ut-hud--atlas .ut-num--frags .ut-seg::before { --ut-digit: rgb(var(--ut99-hue)); }
.ut-hud--atlas .ut-wslot__key .ut-seg::before { --ut-digit: #ffff00; }
.ut-hud--atlas .ut-num--flag .ut-seg::before { --ut-digit: rgb(var(--ut-solid-rgb, 255, 255, 255)); }
.ut-hud--atlas .ut-meter--ammo.is-reloading .ut-seg::before,
.ut-hud--atlas .ut-ammo-bay.is-reloading .ut-seg::before {
  animation: ut-atlas-blink 0.42s steps(1) infinite;
}
@keyframes ut-atlas-blink { 50% { opacity: 0; } }

/* --- the four 128x64 corner tiles, box and glyph in one draw ----------------- */
.ut-hud--atlas .ut-vital__art,
.ut-hud--atlas .ut-frags,
.ut-hud--atlas .ut-ammo-bay {
  background: none;
  box-shadow: none;
  border: 0;
}
.ut-hud--atlas .ut-vital__glyph,
.ut-hud--atlas .ut-frags__skull,
.ut-hud--atlas .ut-ammo-bay__bullets { display: none; }
.ut-hud--atlas .ut-vital__art::before,
.ut-hud--atlas .ut-frags::before,
.ut-hud--atlas .ut-ammo-bay::before {
  -webkit-mask-image: var(--ut-atlas);
  mask-image: var(--ut-atlas);
  -webkit-mask-size: calc(256 * var(--ut-s)) calc(256 * var(--ut-s));
  mask-size: calc(256 * var(--ut-s)) calc(256 * var(--ut-s));
  z-index: 0;
}
.ut-hud--atlas .ut-vital__art::before { background: rgb(var(--ut-rgb)); }
.ut-hud--atlas .ut-vital--health .ut-vital__art::before {
  -webkit-mask-position: calc(-128 * var(--ut-s)) calc(-128 * var(--ut-s));
  mask-position: calc(-128 * var(--ut-s)) calc(-128 * var(--ut-s));
}
.ut-hud--atlas .ut-vital--armor .ut-vital__art::before {
  -webkit-mask-position: 0 calc(-192 * var(--ut-s));
  mask-position: 0 calc(-192 * var(--ut-s));
}
.ut-hud--atlas .ut-frags::before {
  background: rgb(var(--ut99-hue));
  -webkit-mask-position: 0 calc(-128 * var(--ut-s));
  mask-position: 0 calc(-128 * var(--ut-s));
}
.ut-hud--atlas .ut-ammo-bay::before {
  background: rgb(var(--ut99-hue));
  -webkit-mask-position: calc(-128 * var(--ut-s)) calc(-192 * var(--ut-s));
  mask-position: calc(-128 * var(--ut-s)) calc(-192 * var(--ut-s));
}
/* Low health: DrawStatus lerps the tile colour toward white and the .is-low
   opacity ramp on .ut-vital__art already carries the pulse. */
.ut-hud--atlas .ut-vital--health.is-low .ut-vital__art::before { background: #fff; }
/* Children paint above the tile. */
.ut-hud--atlas .ut-vital .ut-num,
.ut-hud--atlas .ut-frags .ut-num,
.ut-hud--atlas .ut-frags__flash,
.ut-hud--atlas .ut-ammo-bay .ut-num { z-index: 1; }

/* --- weapon bar: HUDWeapons 64x32 cells at 2x, 0.5 * HUDColor, translucent --- */
.ut-hud--atlas .ut-wslot:not(.is-active) {
  background: none;
  box-shadow: none;
}
.ut-hud--atlas .ut-wslot:not(.is-active) .ut-wslot__icon { display: none; }
.ut-hud--atlas .ut-wslot:not(.is-active)::before {
  left: 0; top: 0;
  background: rgb(var(--ut99-hue));
  opacity: 0.5;
  -webkit-mask-image: var(--ut-wpn);
  mask-image: var(--ut-wpn);
  -webkit-mask-size: calc(409.6 * var(--ut-s)) calc(409.6 * var(--ut-s));
  mask-size: calc(409.6 * var(--ut-s)) calc(409.6 * var(--ut-s));
  -webkit-mask-position: calc(var(--ut-wx, 0) * -1.6 * var(--ut-s)) calc(var(--ut-wy, 0) * -1.6 * var(--ut-s));
  mask-position: calc(var(--ut-wx, 0) * -1.6 * var(--ut-s)) calc(var(--ut-wy, 0) * -1.6 * var(--ut-s));
}
.ut-hud--atlas .ut-wslot:nth-child(1)::before { --ut-wx: 0; --ut-wy: 0; }
.ut-hud--atlas .ut-wslot:nth-child(2)::before { --ut-wx: 64; --ut-wy: 0; }
.ut-hud--atlas .ut-wslot:nth-child(3)::before { --ut-wx: 128; --ut-wy: 0; }
.ut-hud--atlas .ut-wslot:nth-child(4)::before { --ut-wx: 192; --ut-wy: 0; }
.ut-hud--atlas .ut-wslot:nth-child(5)::before { --ut-wx: 0; --ut-wy: 32; }
.ut-hud--atlas .ut-wslot:nth-child(6)::before { --ut-wx: 64; --ut-wy: 32; }
.ut-hud--atlas .ut-wslot:nth-child(7)::before { --ut-wx: 128; --ut-wy: 32; }
.ut-hud--atlas .ut-wslot:nth-child(8)::before { --ut-wx: 192; --ut-wy: 32; }
.ut-hud--atlas .ut-wslot:nth-child(9)::before { --ut-wx: 0; --ut-wy: 64; }
.ut-hud--atlas .ut-wslot:nth-child(10)::before { --ut-wx: 64; --ut-wy: 64; }

/* --- status doll: Icons.Man (0,0,128,256) at HUDColor; chest plate
   (128,0,128,64) at HUDColor * ChestAmount over it ---------------------------- */
.ut-hud--atlas .ut-doll > svg { display: none; }
.ut-hud--atlas .ut-doll::before,
.ut-hud--atlas .ut-doll::after {
  background: rgb(var(--ut-rgb));
  -webkit-mask-image: var(--ut-man);
  mask-image: var(--ut-man);
  -webkit-mask-size: calc(256 * var(--ut-s)) calc(256 * var(--ut-s));
  mask-size: calc(256 * var(--ut-s)) calc(256 * var(--ut-s));
}
.ut-hud--atlas .ut-doll::before {
  -webkit-mask-position: 0 0;
  mask-position: 0 0;
}
.ut-hud--atlas .ut-doll::after {
  bottom: auto;
  height: calc(64 * var(--ut-s));
  opacity: var(--ut-chest, 0);
  -webkit-mask-position: calc(-128 * var(--ut-s)) 0;
  mask-position: calc(-128 * var(--ut-s)) 0;
}
.ut-hud--atlas .ut-doll__hits { z-index: 1; }

/* --- held slot: W.StatusIcon (UseAutoM, 128x64) at SolidHUDColor STY_Normal —
   the one opaque tile — then the HUDWeapons (128,64,128,64) highlight over it
   in GoldColor, translucent. The slot number and ammo bar stay as they are. */
.ut-hud--atlas {
  --ut-auto: url("assets/ut99/BotPack.Icons.UseAutoM.png");
  --ut-ihome: url("assets/ut99/BotPack.Icons.I_Home.png");
  --ut-icapt: url("assets/ut99/BotPack.Icons.I_Capt.png");
  --ut-idown: url("assets/ut99/BotPack.Icons.I_Down.png");
}
.ut-hud--atlas .ut-wslot.is-active {
  background: none;
  box-shadow: none;
}
.ut-hud--atlas .ut-wslot.is-active .ut-wslot__icon,
.ut-hud--atlas .ut-wslot.is-active .ut-wslot__bracket { display: none; }
.ut-hud--atlas .ut-wslot.is-active::before,
.ut-hud--atlas .ut-wslot.is-active::after {
  content: "";
  position: absolute;
  inset: 0;
  width: auto;
  height: auto;
  pointer-events: none;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-mode: luminance;
  mask-mode: luminance;
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
}
.ut-hud--atlas .ut-wslot.is-active::before {
  background: rgb(var(--ut-solid-rgb, 0, 0, 254));
  -webkit-mask-image: var(--ut-auto);
  mask-image: var(--ut-auto);
  mix-blend-mode: normal;
  z-index: 0;
}
.ut-hud--atlas .ut-wslot.is-active::after {
  top: 0;
  left: 0;
  background: #ffff00;
  -webkit-mask-image: var(--ut-wpn);
  mask-image: var(--ut-wpn);
  -webkit-mask-size: calc(409.6 * var(--ut-s)) calc(409.6 * var(--ut-s));
  mask-size: calc(409.6 * var(--ut-s)) calc(409.6 * var(--ut-s));
  -webkit-mask-position: calc(-128 * 1.6 * var(--ut-s)) calc(-64 * 1.6 * var(--ut-s));
  mask-position: calc(-128 * 1.6 * var(--ut-s)) calc(-64 * 1.6 * var(--ut-s));
  mix-blend-mode: screen;
  z-index: 2;
}
.ut-hud--atlas .ut-wslot.is-active .ut-wslot__key,
.ut-hud--atlas .ut-wslot.is-active .ut-wslot__ammo { z-index: 3; }

/* --- CTF flag state: I_Home / I_Capt / I_Down (32x32) at TeamColor,
   DrawIcon(Scale * 2) = 64 S, which is the 5vw box .ut-flag already is. */
.ut-hud--atlas .ut-flag > svg { display: none; }
.ut-hud--atlas .ut-flag::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: rgb(var(--ut-flag-rgb, 255, 255, 255));
  -webkit-mask-image: var(--ut-ihome);
  mask-image: var(--ut-ihome);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-mode: luminance;
  mask-mode: luminance;
  mix-blend-mode: screen;
}
.ut-hud--atlas .ut-flagrow[data-flag="carried"] .ut-flag::before {
  -webkit-mask-image: var(--ut-icapt);
  mask-image: var(--ut-icapt);
}
.ut-hud--atlas .ut-flagrow[data-flag="dropped"] .ut-flag::before {
  -webkit-mask-image: var(--ut-idown);
  mask-image: var(--ut-idown);
}

/* The game canvas. Fixed and full-viewport so the DOM HUD can overlay it at any size. */
#game {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none;
}
