/* HUD GEOMETRY. The only file that decides where anything on the field HUD is
 * and how big it is. Colour, texture, border and type live in hud-theme.css.
 *
 * This replaces hud-cod.css, hud-waw.css, hud-responsive.css and hud-field.css,
 * which between them carried 291 `!important` declarations and four
 * contradictory opinions about the position of every readout. The v0.14 audit's
 * finding was that this made every responsive change unpredictable, and it was
 * right: at 320px the loadout action bar laid out 90px outside its own track
 * and no amount of reading the sheets explained why.
 *
 * RULES FOR THIS FILE
 *   1. Geometry only. If a declaration is a colour, a shadow, a font or a
 *      border, it belongs in hud-theme.css.
 *   2. No `!important`. If a rule needs it, a sheet that should not own
 *      geometry still does — fix that instead.
 *   3. Size decides structure. `html.is-touch` may move a control because a
 *      thumb is not a cursor; it may never decide whether something fits.
 *
 * INFORMATION TIERS (v0.14 audit §6)
 *   A — always visible: objective line, enemy count, vitals, ammunition
 *   B — contextual: objective detail, perks, stowed weapons, squad, kill feed
 *   C — pause and map only: ledger, statistics, full inventory
 * Tier B is dropped as the viewport gets smaller, in that order.
 *
 * BUDGETS
 *   Persistent HUD <= 14% of a desktop viewport, <= 11% of a touch viewport.
 *   Centre-safe rectangle x 20-80%, y 18-72% belongs to the player.
 * Both are enforced by scripts/check_layout_matrix.py.
 */

:root {
  /* Player-adjustable; set on <html> by Settings. */
  --hud-scale: 1;
  --hud-sub-scale: 1;
  --hud-sub-plate: .78;
  --hud-safe: 0px;
  --hud-opacity: .85;

  /* Every HUD size is expressed in this unit, so the scale slider is one
     multiply rather than forty independent media-query edits. */
  --u: calc(var(--hud-scale) * 1px);

  --sa-t: env(safe-area-inset-top, 0px);
  --sa-r: env(safe-area-inset-right, 0px);
  --sa-b: env(safe-area-inset-bottom, 0px);
  --sa-l: env(safe-area-inset-left, 0px);

  --hud-l: calc(var(--sa-l) + var(--hud-safe) + 26px);
  --hud-r: calc(var(--sa-r) + var(--hud-safe) + 26px);
  --hud-t: calc(var(--sa-t) + var(--hud-safe) + 16px);
  --hud-b: calc(var(--sa-b) + var(--hud-safe) + 20px);

  --mm: 132px;                            /* minimap, quoted by four things */
  --tr-w: max(var(--mm), 124px);          /* width of the top-right cluster */
  --vitals-w: 186px;
}

/* ---------------------------------------------------------------- the layer */
/* Above the weather. The post-processing stack runs z-index 38-45 — dread
   wash, blood, the multiply grain, the vignette, chroma, film grain, threat
   and damage vignettes, heal flash and the explosion flash. The vignette alone
   reaches 94% black at the corners, which is exactly and only where the HUD
   lives; underneath it, no plate or type size can rescue a readout. The
   overlays are a lens effect on the world, not on the instruments. */
#hud { position: fixed; inset: 0; z-index: 56; pointer-events: none; display: none; }
#hud.active { display: block; opacity: var(--hud-opacity); }
#box-cycle { z-index: 59; }

/* -------------------------------------------------------------- the clusters */
#hud .hud-cluster {
  position: absolute;
  display: flex;
  flex-direction: column;
  gap: calc(7 * var(--u));
  pointer-events: none;
  z-index: 2;
}
/* Children are laid out by the cluster, never by themselves. This is what
   makes two readouts in one corner unable to overlap. */
#hud .hud-cluster > * {
  position: static;
  inset: auto;
  margin: 0;
  float: none;
  max-width: 100%;
}
#hud .hud-cluster > *:empty { display: none; }

#hud-tl { left: var(--hud-l); top: var(--hud-t); align-items: flex-start; }
#hud-tr { right: var(--hud-r); top: var(--hud-t); align-items: flex-end; width: var(--tr-w); }
#hud-bl { left: var(--hud-l); bottom: var(--hud-b); align-items: flex-start; justify-content: flex-end; }
#hud-br { right: var(--hud-r); bottom: var(--hud-b); align-items: flex-end; justify-content: flex-end; }

#hud .hud-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: calc(12 * var(--u));
}
#hud .hud-row .hud-key { flex: 0 0 auto; }
#hud .hud-key { display: block; white-space: nowrap; }

/* ------------------------------------------------- top-left: the objective */
/* Wide enough that the objective VERB — second information band, 20px since
   the v0.21 audit — stays on one line for a normal order. A card sized to
   17px type turned every second objective into a two-line block, and the card
   grows downward into the centre-safe rectangle when it does. */
#hud #mission {
  display: block;
  width: min(344px, 34vw);
  padding: calc(10 * var(--u)) calc(14 * var(--u)) calc(11 * var(--u));
}
#hud #mission-kicker { }
#hud #mission-title { margin-top: calc(5 * var(--u)); }
#hud #mission-detail { margin-top: calc(4 * var(--u)); }
#hud #mission-meta {
  display: none;
  gap: calc(12 * var(--u));
  justify-content: space-between;
  margin-top: calc(7 * var(--u));
  padding-top: calc(6 * var(--u));
}
#hud #mission-meta:has(> span:not(:empty)) { display: flex; }
#hud #mission-meta > span:empty { display: none; }
/* TIER B: the description and the progress line are what the player reads
   once. Four seconds after an objective changes the card keeps only its verb
   and its distance, and expands again on the next change. */
#hud #mission.lean #mission-kicker,
#hud #mission.lean #mission-detail { display: none; }
#hud #mission.lean { padding-block: calc(7 * var(--u)) calc(8 * var(--u)); }
#hud #mission.lean #mission-title { margin-top: 0; }
#hud #mission.lean #mission-meta { margin-top: calc(4 * var(--u)); padding-top: calc(4 * var(--u)); }

/* Side objectives — urns, the Electro-Grid, a Relay Surge. Tier B: a row per
   live objective under the order card, empty (and so hidden) otherwise. */
#hud #side-ops { display: flex; flex-direction: column; gap: calc(4 * var(--u)); width: min(260px, 28vw); }
#hud #side-ops .so { padding: calc(5 * var(--u)) calc(10 * var(--u)) calc(6 * var(--u)) calc(11 * var(--u)); }
#hud #side-ops .so b,
#hud #side-ops .so span { display: block; }
#hud #side-ops .so span { margin-top: calc(2 * var(--u)); }
#hud #side-ops .bar { height: calc(3 * var(--u)); margin-top: calc(4 * var(--u)); }
#hud #side-ops .bar i { display: block; height: 100%; width: 0; transition: width .25s; }

/* Boss health. Top centre, narrow enough to clear the order card and the map
   on the left and right; only laid out while a boss is alive. */
#hud #boss-bar {
  position: absolute; left: 50%; top: var(--hud-t);
  width: min(420px, 36vw);
  transform: translate(-50%, -8px);
  opacity: 0; pointer-events: none;
  transition: opacity .3s, transform .3s;
}
#hud #boss-bar.show { opacity: 1; transform: translate(-50%, 0); }
#hud #boss-bar .bb-name { margin-bottom: calc(5 * var(--u)); text-align: center; }
#hud #boss-bar .bb-track { position: relative; height: calc(9 * var(--u)); }
#hud #boss-bar .bb-fill { position: absolute; inset: 0 auto 0 0; width: 100%; transition: width .18s; }
#hud #boss-bar .bb-armor { position: absolute; left: 0; top: 0; height: calc(3 * var(--u)); width: 0; transition: width .18s; }

/* ---------------------------------------- top-right: map, enemies, squad */
#hud #hud-fps {
  display: none;
  padding: 0;
  line-height: 1;
  pointer-events: none;
}
#hud #hud-fps.on { display: block; }
#pause-fps {
  position: absolute;
  top: calc(var(--sa-t) + 10px);
  right: calc(var(--sa-r) + 12px);
  z-index: 3;
  pointer-events: none;
  line-height: 1;
  display: none;
}
#pause-fps.on { display: block; }
#hud #minimap {
  display: block;
  width: var(--mm); height: var(--mm);
  pointer-events: auto;
}
#hud #minimap.off { display: none; }
#hud #zcount {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: calc(9 * var(--u));
  width: 100%;
  padding: calc(6 * var(--u)) calc(9 * var(--u)) calc(7 * var(--u));
  text-align: right;
}
#hud #squad-box { display: flex; flex-direction: column; align-items: flex-end; gap: calc(3 * var(--u)); }
#hud .squad-row {
  max-width: min(168px, 32vw);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#hud .squad-row .squad-name { display: inline; }
#hud #killfeed {
  display: flex; flex-direction: column; align-items: flex-end;
  gap: calc(4 * var(--u));
  max-width: min(300px, 34vw);
}
#hud .kfeed { padding: 4px 10px; }

/* In the top-right column, not a fixed overlay — at ~1126px a magic
   `top: mm + 58` sat on the killfeed / standing-order stack. pointer-events
   stay on so the look-zone (z 55, under #hud at 56) does not eat the tap. */
#map-toggle {
  position: static;
  z-index: auto;
  display: none;
  min-width: 44px; min-height: 44px;
  padding: 6px 11px;
  pointer-events: auto;
  align-self: flex-end;
}
#hud.active #map-toggle { display: block; }
/* Bare "MINIMAP" read as a stray label, not a control. Surface the key the
   way the world prompts do ("HOLD F ..."); touch keeps the plain tap chip. */
#map-toggle::after {
  content: "  M";
  opacity: .55;
  letter-spacing: .04em;
}
html.is-touch #map-toggle::after { content: none; }

/* ------------------------------------------------------ bottom-left: vitals */
#hud #hp-box,
#hud #points-box,
#hud #round-box,
#hud #stam-box {
  display: block;
  width: calc(var(--vitals-w) * var(--hud-scale));
  max-width: 46vw;
  padding: calc(7 * var(--u)) calc(10 * var(--u)) calc(8 * var(--u));
}
#hud #hp-bar { height: calc(11 * var(--u)); margin-top: calc(5 * var(--u)); overflow: hidden; }
#hud #hp-fill { height: 100%; }
#hud #hp-num { position: static; }
#hud #stam-bar { height: calc(6 * var(--u)); margin-top: calc(4 * var(--u)); overflow: hidden; }
#hud #stam-fill { height: 100%; }
#hud #round-tally { display: block; margin-top: calc(4 * var(--u)); }
#hud #round-num { margin-top: calc(2 * var(--u)); }
#hud #kills-box { display: none; }        /* kills belong to the pause screen */
#hud #perks { display: flex; flex-direction: row; gap: calc(5 * var(--u)); }
#hud .perk-chip { width: calc(32 * var(--u)); height: calc(32 * var(--u)); padding: 0; }
#hud .perk-chip img { display: block; width: 100%; height: 100%; }

/* ---------------------------------------------------- bottom-right: weapon */
#hud #ammo-box {
  display: block;
  min-width: calc(158 * var(--u));
  padding: calc(7 * var(--u)) calc(12 * var(--u)) calc(8 * var(--u));
  text-align: right;
}
#hud #wpn-head {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: calc(8 * var(--u));
  margin: 0 0 calc(3 * var(--u));
}
#hud #wpn-plate {
  width: calc(42 * var(--u));
  height: calc(28 * var(--u));
  object-fit: contain;
  object-position: right center;
  opacity: .88;
  filter: drop-shadow(0 2px 4px rgba(0,0,0,.55));
  flex: 0 0 auto;
}
#hud #wpn-plate.pap {
  filter: drop-shadow(0 0 6px rgba(120,220,255,.45)) saturate(1.2);
}
#hud #wpn-name {
  margin: 0;
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}
#hud #ammo-line {
  display: flex; align-items: baseline; justify-content: flex-end;
  gap: calc(7 * var(--u));
  height: auto;
}
#hud #ammo-res { margin: 0; }
#hud #ammo-key {
  display: flex; justify-content: flex-end;
  gap: calc(14 * var(--u));
  margin-top: calc(3 * var(--u));
}
#hud #nade-pips { margin-top: calc(3 * var(--u)); }
#hud #nade-box {
  display: flex; align-items: center; justify-content: flex-end;
  gap: calc(7 * var(--u));
  padding: calc(5 * var(--u)) calc(10 * var(--u)) calc(6 * var(--u));
}
#hud #nade-icon { width: calc(18 * var(--u)); height: calc(18 * var(--u)); }
#hud #nade-box .hud-key { order: -1; }
#hud #alt-wpns { display: flex; flex-direction: column; align-items: flex-end; gap: calc(3 * var(--u)); }

/* ------------------------------------------------- centre: the player's own */
/* Nothing persistent may enter x 20-80%, y 18-72%. What is allowed here is
   transient, and the crosshair, which is the point. */
#hud #xhair { position: absolute; left: 50%; top: 50%; width: 0; height: 0; --sp: 6px; }
#hud #xhair i { position: absolute; display: block; }
#hud #xhair .t { width: 2px; height: 9px; left: -1px; bottom: var(--sp); }
#hud #xhair .b { width: 2px; height: 9px; left: -1px; top: var(--sp); }
#hud #xhair .l { height: 2px; width: 9px; right: var(--sp); top: -1px; }
#hud #xhair .r { height: 2px; width: 9px; left: var(--sp); top: -1px; }
#hud #xhair .dot { display: none; }
#hud #hitmark { position: absolute; left: 50%; top: 50%; width: 0; height: 0; }
#hud #hitmark i { position: absolute; width: 3px; height: 14px; }
#hud #hitmark .h1 { transform: translate(-11px, -19px) rotate(-45deg); }
#hud #hitmark .h2 { transform: translate(8px, -19px) rotate(45deg); }
#hud #hitmark .h3 { transform: translate(-11px, 5px) rotate(45deg); }
#hud #hitmark .h4 { transform: translate(8px, 5px) rotate(-45deg); }
#hud #hitmark.kill i { background: #e7b873; box-shadow: 0 0 8px rgba(231,184,115,.8); }
#hud #hitmark.pop { animation: hitPop .12s ease-out; }
@keyframes hitPop { from { transform: scale(1.4); } to { transform: scale(1); } }

/* Notices temporarily occupy the objective slot; the aiming lane stays clear. */
#hud #banner { display: none; width: min(344px, 34vw); padding: calc(8 * var(--u)) calc(12 * var(--u)); }
#hud.has-notice #banner { display: block; }
#hud.has-notice #mission { display: none; }
#hud #banner-sub { margin-top: calc(5 * var(--u)); }
#hud #banner-sub:empty { display: none; }
#hud #objective-marker { position: absolute; left: 50%; top: 50%; min-width: 56px; display: none; transform: translate(-50%, -50%); }
#hud #objective-marker.show { display: block; }
#hud #objective-marker:before { display: block; width: 11px; height: 11px; margin: 0 auto 7px; }
/* Downed-mate marker: a column (arrow, name, metres) so the edge variant can
   drop the arrow and let the name plate carry the direction on its own. */
#hud #mate-down { position: absolute; left: 50%; top: 50%; display: none; flex-direction: column; align-items: center; gap: calc(3 * var(--u)); min-width: 72px; transform: translate(-50%, -50%); pointer-events: none; }
#hud #mate-down.show { display: flex; }
#hud #mate-down .md-arrow { display: block; width: 0; height: 0; border: 7px solid transparent; border-bottom: none; border-top: 10px solid currentColor; }
#hud #mate-down.edge .md-arrow { display: none; }
#hud #mastery-callout {
  position: absolute; left: 50%; bottom: 31%;
  max-width: min(72vw, 560px);
  padding: 6px 12px;
  transform: translate(-50%, 0);
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  text-align: center;
}
#hud #lowhp { position: absolute; inset: 0; }
#hud #revive-bar { position: absolute; left: 50%; bottom: 25%; width: min(290px, 76vw); transform: translateX(-50%); display: none; text-align: center; }
#hud #revive-bar .lbl { margin-bottom: 5px; }
#hud #revive-track { height: 5px; overflow: hidden; }
#hud #revive-fill { height: 100%; width: 0; }
#hud #reload-tip { display: none; }       /* the ammo block carries the state */
#hud #round-splat { position: absolute; inset: 0; }

/* Power-up pills: top-centre, wrapping, never reaching the centre band. */
#hud #pills {
  position: absolute; left: 50%; top: var(--hud-t);
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: calc(8 * var(--u));
  max-width: min(46vw, 560px);
  transform: translateX(-50%);
}
#hud .pill { min-width: 0; padding: calc(5 * var(--u)) calc(10 * var(--u)) calc(6 * var(--u)); }
#hud .pill .bar { height: 1px; }
#hud .ppop { position: absolute; }

/* ------------------------------------------ bottom-centre: prompt, subtitle */
/* Two layers that cannot meet: the prompt sits under the crosshair band, the
   subtitle rides the bottom of the frame, and the subtitle's width is bounded
   by the two bottom corner clusters rather than by a percentage of the window
   — a full-width band ran straight through the round tally at 1024px. */
#hud #prompt {
  position: absolute; left: 50%; right: auto;
  bottom: calc(23% + var(--sa-b));
  max-width: min(560px, 84vw);
  transform: translateX(-50%);
  text-align: center;
}
#hud #prompt-text { display: inline-block; max-width: 100%; padding: calc(10 * var(--u)) calc(16 * var(--u)); }
#hud #prompt-text .cost-no-line { display: block; margin-top: calc(4 * var(--u)); }
#hud #prompt-bar { width: 0; height: 3px; margin: 6px auto 0; }
#signal-subtitle {
  position: fixed; z-index: 49; left: 50%;
  bottom: calc(var(--sa-b) + 26px);
  width: max(220px, min(820px, calc(100vw - 2 * (var(--hud-l) + 200px))));
  padding: calc(8 * var(--u)) calc(16 * var(--u));
  transform: translateX(-50%);
  text-align: center;
  pointer-events: none;
}
#signal-subtitle:empty { padding: 0; }

/* =====================================================================
   TIERS BY SIZE. Structure follows the amount of room, never the input
   device. A 360px desktop window gets the compact layout too.
   ===================================================================== */

/* Tablet */
@media (min-width: 768px) and (max-width: 1200px) {
  :root { --mm: 118px; }
  #hud #mission, #hud #banner { width: min(320px, 30vw); }
  /* TIER B goes first. A tablet's centre-safe line is only ~140px down, and
     the description is the one line of this card the player reads once —
     `lean` already drops it four seconds after every objective change. */
  #hud #mission-detail { display: none; }
}

/* Phone width. Tier B starts going: the kill feed and the stowed-weapon strip
   are the first two readouts a small screen can do without, because the swap
   control and the ammo block already carry that state. */
@media (max-width: 767px) {
  :root {
    --mm: 96px;
    --vitals-w: 150px;
    --hud-l: calc(var(--sa-l) + var(--hud-safe) + 12px);
    --hud-r: calc(var(--sa-r) + var(--hud-safe) + 12px);
    --hud-t: calc(var(--sa-t) + var(--hud-safe) + 10px);
    --hud-b: calc(var(--sa-b) + var(--hud-safe) + 12px);
  }
  #hud #mission, #hud #banner { width: min(215px, 52vw); }
  #hud #killfeed, #hud #alt-wpns { display: none; }
  #hud .hud-cluster { gap: calc(3 * var(--u)); }
}

/* Short viewports — the landscape phone. Height is the constraint, so the
   stacks shed rows rather than shrinking type into the illegible band this
   whole system exists to eliminate. */
@media (max-height: 560px) {
  :root { --mm: 88px; }
  #hud #mission-detail, #hud #ammo-key, #hud #perks, #hud #side-ops .so span { display: none; }
  #hud #mission, #hud #side-ops { width: min(210px, 25vw); }
  #hud .hud-cluster { gap: calc(4 * var(--u)); }
  #hud #hp-box, #hud #points-box, #hud #round-box, #hud #stam-box {
    padding-block: calc(5 * var(--u)) calc(6 * var(--u));
  }
  #signal-subtitle { bottom: calc(var(--sa-b) + 10px); }
  #hud #prompt { bottom: calc(28% + var(--sa-b)); }
}

@media (max-height: 680px) { #hud #squad-box, #hud #alt-wpns { display: none; } }

/* A portrait phone has no gap between the order card and the enemy count, so
   the boss bar drops under the top row instead of sitting between them. */
@media (max-width: 480px) {
  #hud #boss-bar { top: calc(var(--hud-t) + 44px); width: 64vw; }
}

/* COMPACT — 480px or less in either axis. Tier A only.
 *
 * 11% of a 320x568 screen is 20,000 square pixels. That is one objective line,
 * one enemy count, one vitals block and one ammunition block, and nothing
 * else: the minimap and its toggle move to the pause screen, the objective
 * card loses everything but its verb and distance, and the vitals collapse
 * into a single row. Anything more is not a HUD on a screen this size, it is
 * a second thing to read while something is eating you. */
@media (max-width: 480px), (max-height: 430px) {
  :root {
    --mm: 0px; --vitals-w: 132px;
    /* What the right-hand end of the top row costs, so the objective on the
       left can be given the rest and nothing has to be measured by eye:
       the gutter, the enemy count, and the gutter between them. */
    --tr-reserve: calc(var(--hud-r) + 112px);
  }
  #hud #minimap,
  #hud #squad-box, #hud #killfeed, #hud #perks, #hud #alt-wpns,
  #hud #stam-box, #hud #nade-box { display: none; }
  /* Matching specificity: `#hud.active #map-toggle` outranks a bare
     `#map-toggle`, so hiding the dial without this left its button behind. */
  #hud.active #map-toggle { display: none; }

  /* The objective is ONE ROW: the verb, and how far away it is. Everything
     else about it is Tier B and lives in the pause screen's field ledger. */
  #hud #mission {
    display: flex; align-items: baseline;
    gap: calc(8 * var(--u));
    width: min(200px, calc(100vw - var(--hud-l) - var(--tr-reserve)));
    padding: calc(4 * var(--u)) calc(7 * var(--u)) calc(5 * var(--u));
  }
  #hud #banner { width: min(440px, calc(100vw - var(--hud-l) - var(--tr-reserve))); }
  #hud #mission-kicker, #hud #mission-detail, #hud #mission-progress, #hud #side-ops .so span { display: none; }
  #hud #side-ops { width: min(200px, calc(100vw - var(--hud-l) - var(--tr-reserve))); }
  #hud #side-ops .so { padding: calc(3 * var(--u)) calc(7 * var(--u)) calc(4 * var(--u)); }
  #hud #mission-title {
    margin-top: 0; min-width: 0;
    overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
  }
  #hud #mission-meta {
    display: flex; flex: 0 0 auto;
    margin-top: 0; padding-top: 0;
    border-top: 0;
  }

  /* Dim FPS stays in the top-right column above Enemies. Cap the column so
     the chip cannot grow into #ammo-box on 320px. Compact hides the dial. */
  #hud-tr { width: auto; max-width: min(108px, 32vw); }
  #hud #hud-fps.on {
    display: block;
    max-width: 100%;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }
  #hud #zcount { width: auto; min-width: 0; max-width: 100%; padding: calc(4 * var(--u)) calc(7 * var(--u)); }

  /* Vitals are ONE plate with three rows, not three plates with three
     shadows. Three stacked plates cost 40% more of a 320px screen than the
     numbers inside them do. */
  #hud-bl { gap: 0; }
  #hud #hp-box, #hud #points-box, #hud #round-box {
    width: calc(var(--vitals-w) * var(--hud-scale));
    padding: calc(3 * var(--u)) calc(7 * var(--u));
  }
  #hud #hp-box { padding-top: calc(5 * var(--u)); }
  #hud #round-box { padding-bottom: calc(5 * var(--u)); }
  #hud #hp-bar { height: calc(5 * var(--u)); margin-top: calc(2 * var(--u)); }
  #hud #round-tally { margin-top: calc(1 * var(--u)); }

  /* Weapon: name and count on one line, frags on the pad's own button. */
  #hud #ammo-box {
    display: flex; align-items: baseline; justify-content: flex-start;
    gap: calc(8 * var(--u));
    min-width: 0; width: min(166px, 52vw);
    padding: calc(3 * var(--u)) calc(7 * var(--u)) calc(4 * var(--u));
  }
  #hud #wpn-name { margin: 0; min-width: 0; flex: 1 1 auto; }
  #hud #ammo-line { flex: 0 0 auto; }
  #hud #ammo-key, #hud #nade-pips { display: none; }
  #hud #pills { max-width: 70vw; }
}

/* Compact portrait taller than an SE: the 220px-min plate at bottom+26px
   runs through HEALTH and POINTS (G28). Lift by size, not only is-touch.
   320x568 keeps the 152px touch rule so a two-line plate does not meet
   the 42% prompt. Landscape keeps the short-viewport 10px rule. */
@media (max-width: 480px) and (min-height: 600px) {
  #signal-subtitle,
  html.is-touch #signal-subtitle {
    width: min(88vw, calc(100vw - 24px));
    bottom: calc(var(--sa-b) + 168px);
  }
}

/* =====================================================================
   TOUCH. Modality moves controls because a thumb is not a cursor. It does
   not decide what fits — the size queries above already did that.
   ===================================================================== */
html.is-touch {
  --hud-l: calc(var(--sa-l) + var(--hud-safe) + 4px);
  --hud-r: calc(var(--sa-r) + var(--hud-safe) + 4px);
  --hud-t: calc(var(--sa-t) + var(--hud-safe) + 4px);
  --hud-b: calc(var(--sa-b) + var(--hud-safe) + 4px);
}
/* Pause owns the corner; FPS / minimap / enemies hug the same right edge under it. */
html.is-touch #hud-tr { right: calc(var(--hud-r) + 54px); }
/* Anchored from --mm like the base rule, so the button clears the dial and the
   enemy count on touch tablets too — a fixed 52px offset sat inside the 118px
   dial. Phones never see this: the compact tier hides the toggle entirely. */
html.is-touch #map-toggle { min-height: 44px; }
/* The button pad owns the bottom-right, so the weapon block takes the free
   strip inboard of the dial cluster. */
html.is-touch #hud-br {
  top: var(--hud-t); bottom: auto;
  right: calc(var(--hud-r) + 54px + var(--tr-w) + 14px);
}
html.is-touch #hud #ammo-box { min-width: calc(128 * var(--u)); }
/* Touch never shows keyboard hints, and the pad's grenade button already
   carries a live count badge — the #nade-box plate and the R/G key row are
   dead weight that pushed the weapon block into the centre-safe band on
   notched tablets. */
html.is-touch #hud #ammo-key,
html.is-touch #hud #nade-pips,
html.is-touch #hud #nade-box { display: none; }
html.is-touch #hud #prompt { bottom: calc(32% + var(--sa-b)); max-width: 62vw; }
/* The weapon block owns the top strip on touch, so the boss bar hangs under
   it instead of across the magazine count. */
html.is-touch #hud #boss-bar { top: calc(var(--hud-t) + 50px); width: min(300px, 34vw); }

/* Installed PWA: the status bar and home indicator are always chrome, not a
   collapsible Safari toolbar. Honour the insets even if JS has not tagged
   html.is-standalone yet (layout matrix blocks scripts). */
@media (display-mode: standalone), (display-mode: fullscreen) {
  html.is-touch {
    --hud-t: calc(var(--sa-t) + var(--hud-safe) + 4px);
    --hud-b: calc(var(--sa-b) + var(--hud-safe) + 6px);
    --hud-l: calc(var(--sa-l) + var(--hud-safe) + 4px);
    --hud-r: calc(var(--sa-r) + var(--hud-safe) + 4px);
  }
}
html.is-standalone.is-touch {
  --hud-t: calc(var(--sa-t) + var(--hud-safe) + 4px);
  --hud-b: calc(var(--sa-b) + var(--hud-safe) + 6px);
  --hud-l: calc(var(--sa-l) + var(--hud-safe) + 4px);
  --hud-r: calc(var(--sa-r) + var(--hud-safe) + 4px);
}

/* LEFT-HANDED. The pad mirrors to the left, so the vitals have to leave it —
   otherwise the interact button and the tray handle land on the health and
   points a player is trying to read while pressing them. Handedness is about
   which thumb does the work; the instruments follow it. */
html.is-touch.left-handed #hud-bl {
  left: auto; right: var(--hud-r);
  align-items: flex-end;
}
html.is-touch.left-handed #hud #hp-box,
html.is-touch.left-handed #hud #points-box,
html.is-touch.left-handed #hud #round-box,
html.is-touch.left-handed #hud #stam-box {
  border-left: 0;
  border-right: 3px solid currentColor;
}
/* The top strip deliberately does NOT mirror. The pause button, the dial and
   the weapon block are not under the thumb — only the bottom corner is — and
   moving the pause button to the left put it straight on the objective card. */

/* Compact touch: objective top-left, vitals left, ammo + threat + pause right.
   Ammo sits above the fire arc on portrait so the top band stays one line. */
@media (max-width: 480px) {
  html.is-touch { --tr-reserve: calc(var(--hud-r) + 166px); }
  html.is-touch #hud-tl { left: var(--hud-l); top: var(--hud-t); }
  /* Ammo shares the top-right column under pause / enemies — never the fire
     arc and never the 18% centre-safe band (notched 430×932 needs this).
     Same top as #hud-tr put MAG on ENEMIES. Drop it under that stack. */
  html.is-touch #hud-br {
    left: auto;
    right: calc(var(--hud-r) + 54px);
    top: calc(var(--hud-t) + 48px);
    bottom: auto;
    align-items: flex-end;
  }
  html.is-touch #hud-br #ammo-box { text-align: right; width: min(128px, 30vw); padding: 2px 6px; }
  html.is-touch #hud #wpn-plate { display: none; }
  html.is-touch #hud #wpn-head { margin: 0; }
  html.is-touch.left-handed #hud-br {
    left: auto;
    right: calc(var(--hud-r) + 54px);
    top: calc(var(--hud-t) + 48px);
    bottom: auto;
    align-items: flex-end;
  }
  html.is-touch.left-handed #hud-br #ammo-box { text-align: right; }
  html.is-touch #signal-subtitle { left: 50%; width: 88vw; bottom: calc(var(--sa-b) + 152px); }
  html.is-touch #hud #prompt { bottom: calc(42% + var(--sa-b)); max-width: 84vw; }
  /* Under the order card and its side-op rows on the left, and the ammo
     column on the right. */
  html.is-touch #hud #boss-bar { top: calc(var(--hud-t) + 120px); width: 60vw; }
  html.is-touch #hud #mission {
    width: min(248px, calc(100vw - var(--hud-l) - var(--tr-reserve)));
  }
}

@media (max-width: 480px) and (min-height: 700px) {
  html.is-touch #hud #mission { align-items: flex-start; }
  html.is-touch #hud #mission-title {
    white-space: normal;
    line-height: 1.15;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}

/* Compact touch landscape: keep mission readable beside threat/ammo.
   Two-line wrap is landscape-only — portrait stacks ammo under mission and a
   taller card collides with #ammo-box (check_hud). */
@media (orientation: landscape) and (max-height: 460px) {
  html.is-touch #hud #mission {
    width: min(280px, calc(100vw - var(--hud-l) - var(--tr-reserve) - 8px));
    align-items: flex-start;
  }
  html.is-touch #hud #mission-title {
    font-size: calc(14 * var(--u));
    line-height: 1.15;
    white-space: normal;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  html.is-touch #hud #nade-box {
    min-height: 28px;
    padding-block: 4px;
  }
}

/* ------------------------------------------------------------- viewmodel */
/* inset:0 already sizes this to the viewport. An explicit 100vw/100vh took the
   LARGEST viewport iOS Safari will ever have — the one with the URL bar
   hidden — while the renderer and the muzzle flash are positioned from
   innerHeight, so the flash drifted off the barrel by the height of the bar. */
#vm { position: fixed; inset: 0; z-index: 15; pointer-events: none; display: none; overflow: hidden; }
#vm.on { display: block; }
#vm-img {
  position: absolute; left: 50%; top: 50%;
  width: clamp(720px, 74vw, 1180px);
  max-width: none; height: auto; display: block;
  transform-origin: 50% 68%;
  will-change: transform, opacity;
}
#vm-fallback { position: absolute; right: 6%; bottom: 4%; width: 46%; height: 26%; display: none; }
#vm.gun3d #vm-img, #vm.gun3d #vm-fallback { display: none; }
#muzzle { position: fixed; z-index: 23; pointer-events: none; width: 190px; height: 190px; display: none; }
#slash {
  position: fixed; left: 50%; top: 50%; z-index: 22; pointer-events: none;
  width: 150vmax; height: 26vmin; --d: 1;
  transform: translate(-50%, -50%) rotate(34deg) scaleX(.12);
}
#box-cycle {
  position: fixed; pointer-events: none; display: none;
  width: 210px;
  padding: 10px 12px 9px;
  transform: translate(-50%, -108%);
}
#box-cycle img { display: block; width: 100%; height: 88px; object-fit: contain; margin: 1px 0 -2px; }
#box-cycle-state, #box-cycle-name { display: block; }
#fade { position: fixed; inset: 0; z-index: 70; pointer-events: none; }
#vm-debug {
  position: fixed; left: 12px; bottom: 22%; z-index: 80;
  margin: 0; pointer-events: none; display: none;
}
