/* ---------------------------------------------------------------------------
   hud.css — everything a player looks at DURING a match.

   ONE FILE, ONE BAND. The old HUD was five unrelated islands: three black boxes
   pinned to three different top offsets, a pill, and a button. Nothing shared a
   baseline, a corner radius or a type scale, so nothing could be guaranteed not
   to touch anything else, and it measurably did: the clock overlapped the invite
   chip by 4px at every desktop size, and the prestige banner landed on the score,
   the clock, the chip and the standings at once on a 320px phone.

   The fix is structural, not a nudge. Score, clock and standings now hang off a
   single band under the top bar. Anything that must sit lower gets its own row
   with a stated height. Positions come out of that grid, so a collision becomes
   a thing you can only get by editing the grid.

   Kenny picked the direction from three mockups (2026-08-07): a broadcast band,
   with the standings and the invite code drawn WITHOUT panels, so they read as
   part of the frame rather than as windows floating on it.

   Two constraints under everything here:

   1. It floats over a MOVING arena. A white blob past 130px of radius sweeps the
      corners, because the camera keeps you centred. Every scrim is set against
      white, not against the menu background. No backdrop-filter: six blurred
      panels over a canvas repainting 30 times a second costs frames.
   2. It is read in under a second, mid-fight. The score and the clock are the two
      biggest things on screen after the blobs. Everything else is deliberately
      quieter than they are, and nothing that you read once keeps its pixels.
   --------------------------------------------------------------------------- */

:root {
  --hud-top: 54px;        /* the top bar's height: the band's ceiling */
  --hud-band: 74px;       /* the band's own height */
  --hud-gut: 22px;        /* the side gutter every row shares */
  --hud-row2: 4px;        /* the gap under the band, where phones open a second row */
  /* The four anchors every fixed box in here hangs off. The notch is folded in ONCE,
     so no rule below has to remember it and no rule can forget it. */
  --hud-y1: calc(var(--hud-top) + env(safe-area-inset-top, 0px));
  --hud-y2: calc(var(--hud-y1) + var(--hud-band) + var(--hud-row2));
  --hud-l: calc(var(--hud-gut) + env(safe-area-inset-left, 0px));
  --hud-r: calc(var(--hud-gut) + env(safe-area-inset-right, 0px));
}

/* ---- The scrim ------------------------------------------------------------
   One gradient, painted once, fading out at the bottom. A gradient rather than a
   panel is the whole point of the direction: the chrome dissolves into the arena
   instead of sitting on top of it in a box. */
#hudBand {
  position: fixed; z-index: 4; left: 0; right: 0; display: none;
  top: var(--hud-y1); height: calc(var(--hud-band) + 46px); pointer-events: none;
  background: linear-gradient(180deg, rgba(6,9,20,.90) 0%, rgba(6,9,20,.62) 52%, rgba(6,9,20,0) 100%);
}
body.playing #hudBand { display: block; }

/* Everything in the band shares one ceiling and one gutter.
   NOTHING in it takes a pointer. On a phone the strip lies directly over the arena
   you aim across, and an aim drag that starts on the score or the clock was being
   swallowed by a readout - the only things in the HUD a finger may land on are the
   two actual buttons (#quitBtn, #roomChip). */
#hud, #timer, #scoreboard {
  position: fixed; z-index: 5; color: #fff; pointer-events: none; top: var(--hud-y1);
  /* min-height, not height. The score block gained a third line and a fixed 74px
     band clipped it. The band's SCRIM (#hudBand) is still a fixed height, so the
     look is unchanged; only the box that must hold three lines can grow. */
  min-height: var(--hud-band);
  display: flex; flex-direction: column; justify-content: center;
}

/* ---- Score (band left) ---------------------------------------------------- */
#hud { left: var(--hud-l); }
#hud b { color: #ffd15c; }
.hud-score { display: flex; align-items: baseline; gap: 11px; }
.hs-label { font-size: 10px; letter-spacing: .22em; text-transform: uppercase; color: #8f9dc4; font-weight: 700; }
/* The one number the whole match is about. It was 14px, the same size as the word
   beside it and smaller than the sign-in button in the top bar. */
.hud-score b { font-size: 40px; line-height: 1; font-weight: 800; font-variant-numeric: tabular-nums; text-shadow: 0 2px 14px rgba(0,0,0,.85); }
/* Your total, under the score you are carrying: the scoreboard answers "where am I
   against everyone else", this answers "where am I".

   REFERENCE, NOT A READOUT. 18px was a mistake: it read as a second score, so you
   had to decide which number you were looking at, and that is exactly the cost the
   carried score cannot afford mid-fight. At 12px against the score's 40px there is
   no contest and the glance lands where it should.

   min-width and the ellipsis matter as much as the size: at 2,551 total this line
   was pushing the block off the left edge of the window. */
.hs-total {
  flex-basis: 100%; min-width: 0; margin-top: 2px;
  /* An explicit line box. Without it the flex line sized to the baseline and the
     bold digits overhung their own parent by 6px, which `overflow: hidden` then
     sliced off - the gate caught it at three viewport sizes. */
  font-size: 12px; line-height: 1.6; font-weight: 600; letter-spacing: .04em;
  color: #7e8cae; font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 8px rgba(0,0,0,.85);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* font-size: inherit IS THE FIX, and it was the whole bug. `.hud-score b` sets 40px,
   and the total's digits are a <b> inside .hud-score, so they were rendering at 40px
   too - the same size as the carried score. That is why the total competed with it
   and why it ran off the left edge, and no amount of tuning .hs-total's own
   font-size could win while the child overrode it. */
.hs-total b { font-size: inherit; color: #9fb0c8; font-weight: 700; }
.hud-score { flex-wrap: wrap; }

/* ---- Clock (band centre) --------------------------------------------------
   No panel. The digits carry themselves at 40px, and the rail under them gives
   the same fact a second way: how much of the match is gone, readable without
   parsing a number at all. */
#timer {
  left: 50%; transform: translateX(-50%); align-items: center; min-width: 190px;
  font-variant-numeric: tabular-nums; font-size: 40px; font-weight: 800;
  letter-spacing: .02em; text-align: center; text-shadow: 0 2px 14px rgba(0,0,0,.85);
}
#timer::after {
  content: ""; display: block; width: 190px; height: 3px; margin-top: 8px;
  border-radius: 2px; background: rgba(255,255,255,.16);
  /* --spent is written by render.js each tick: 0 at the whistle, 1 at full time. */
  background-image: linear-gradient(90deg, #7fe3ff, #ffd15c);
  background-repeat: no-repeat;
  background-size: calc(var(--spent, 0) * 100%) 100%;
}
/* Between matches the clock stops being a clock and becomes a sentence, so it is
   set at a size a sentence can be read at, and the rail has nothing to say. */
#timer.intermission { font-size: 15px; font-weight: 700; letter-spacing: .2px; color: #7dffa6; }
#timer.intermission::after { display: none; }

/* ---- Standings (band right) -----------------------------------------------
   Panel-free, per the Cockpit mockup. The soft bottom fade that used to live here
   is GONE, and losing it is the fix rather than a saving: it existed because the
   board ran ten rows deep and the last of them sat on bare arena, and the board is
   now capped at the top three plus your own row (js/hud-paint.js). The row it faded
   out is now YOURS, the one row here that must never be the hard one to read. */
#scoreboard {
  right: var(--hud-r); height: auto; min-width: 210px;
  justify-content: flex-start; padding: 2px 0 0; text-align: right;
}
#scoreboard .hud-title {
  font-weight: 700; color: #8f9dc4; letter-spacing: .22em; font-size: 10px;
  text-transform: uppercase; margin-bottom: 9px; text-shadow: 0 1px 6px rgba(0,0,0,.9);
}
#scoreboard .row {
  justify-content: flex-end; gap: 14px; padding: 3px 0; font-size: 14px; font-weight: 600;
  color: #e6ebff; text-shadow: 0 2px 9px rgba(0,0,0,.95), 0 0 2px rgba(0,0,0,.85);
}
#scoreboard .row b { color: #ffd15c; font-variant-numeric: tabular-nums; min-width: 52px; }
/* Desktop reads the board; the one-line placement below is the phone's whole panel. */
#scoreboard .sb-rank { display: none; }
/* You, marked by a rule rather than by colour alone. */
#scoreboard .row.me { border-right: 2px solid #7fe3ff; padding-right: 8px; margin-right: -10px; }
/* Where YOU are, in one line, and on a phone it is the whole panel (see below). It
   replaces the empty state, which is what this panel printed for entire matches: the
   rows came off state.standings, which is prestige-locked and stays empty until
   somebody banks 1,000, so the largest text object on the HUD was a sentence about a
   board that had not started yet. */
#scoreboard .sb-rank { font-size: 13px; font-weight: 600; color: #b9c3e2; margin-bottom: 8px; text-shadow: 0 2px 9px rgba(0,0,0,.95), 0 0 2px rgba(0,0,0,.85); }
#scoreboard .sb-rank b { color: #ffd15c; font-size: 17px; font-variant-numeric: tabular-nums; }

/* ---- Invite code (under the clock) ----------------------------------------
   Also panel-free. The code is the part you read aloud to a friend and it used to
   be the quietest thing in a sentence ("Room WXYZ · click to copy"), so it is set
   in mono with the letters apart and the label demoted. The Copy affordance waits
   for a hover instead of taking permanent space; touch has no hover, so there it
   is always on. */
#roomChip {
  position: fixed; z-index: 6; display: none; align-items: center; gap: 10px;
  top: var(--hud-y2); left: 50%; transform: translateX(-50%);
  background: none; border: 0; padding: 4px 8px; border-radius: 999px; cursor: pointer;
  text-shadow: 0 2px 9px rgba(0,0,0,.95);
}
.rc-label { font-size: 10px; letter-spacing: .18em; text-transform: uppercase; color: #8fb4ff; font-weight: 700; }
.rc-code { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 15px; font-weight: 700; letter-spacing: .18em; color: #dbe7ff; }
.rc-copy {
  font-size: 10px; letter-spacing: .08em; font-weight: 700; color: #b9d2ff; opacity: 0;
  background: rgba(120,175,255,.2); border-radius: 999px; padding: 4px 9px;
  transition: opacity var(--t-quick) var(--e-standard);
}
#roomChip:hover .rc-copy, #roomChip:focus-visible .rc-copy, #roomChip.copied .rc-copy { opacity: 1; }
@media (hover: none) { .rc-copy { opacity: 1; } }
#roomChip.copied .rc-copy { color: #052; background: #7dffa6; }

/* ---- Resources (low centre) -----------------------------------------------
   Dashes and weapon cooldowns are the only things here you act ON rather than
   read, so they sit near where your hands and your eye already are instead of in
   a paragraph at the top of the screen. They used to be two sentences and two
   labels inside the score panel, permanently, for information you learn once. */
#resBar {
  position: fixed; z-index: 6; display: none; left: 50%; bottom: 26px;
  transform: translateX(-50%); align-items: center; gap: 20px; pointer-events: none;
  padding: 11px 20px; border-radius: 16px;
  background: rgba(6,9,20,.72); border: 1px solid rgba(255,255,255,.10);
  box-shadow: 0 10px 30px rgba(0,0,0,.45);
}
body.playing #resBar { display: flex; }
.res-sep { width: 1px; height: 26px; background: rgba(255,255,255,.13); }

/* "Have I got a dash to escape with" is the most time-critical read on the HUD.
   A flat track, not a hatch: the diagonal stripe that briefly lived here read as
   noise on a bar you glance at mid-fight, and the partly-filled shape already
   separates empty from full without it (Kenny, 2026-08-07). */
#dashCharges { display: flex; gap: 5px; }
.dashPip { width: 34px; height: 8px; border-radius: 4px; overflow: hidden; position: relative; background: rgba(255,255,255,.16); }
/* No width transition: render.js rewrites the level every frame with a transform,
   and an easing curve here showed a part-full pip at the instant the charge landed. */
.dashPip .pipFill {
  position: absolute; left: 0; top: 0; bottom: 0; width: 0%; border-radius: 4px;
  background: #5cff8f; box-shadow: 0 0 10px rgba(92,255,143,.45);
  transition: background .15s linear, opacity .15s linear;
}

.wpn-meters { display: flex; gap: 12px; }
.wpn {
  position: relative; width: 38px; height: 38px; border-radius: 11px; overflow: hidden;
  display: grid; place-items: center; background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.12);
}
.wpn-ico { font-size: 17px; line-height: 1; z-index: 1; }
/* The cooldown fills the KEY, bottom up, instead of a separate bar beside it: one
   object per weapon, so what is charging and what it charges are the same shape. */
.wpn-bar { position: absolute; inset: 0; }
.wpn-fill { position: absolute; left: 0; right: 0; bottom: 0; height: 100%; transform-origin: bottom center; opacity: .30; }
.wpn.sword .wpn-fill { background: #e6ecf7; }
.wpn.gun   .wpn-fill { background: #ff7a7a; }
/* Ready is carried by the RING and the icon glow, not by an inset shadow: an inset
   ring is painted over by the fill, so it would vanish at exactly 100%, which is
   the only moment it means anything. */
.wpn.ready { border-color: rgba(255,255,255,.75); box-shadow: 0 0 12px rgba(255,255,255,.2); }
.wpn.ready .wpn-fill { opacity: .5; }
.wpn.ready .wpn-ico { filter: drop-shadow(0 0 5px rgba(255,255,255,.6)); }

/* The controls, said once. public/js/hud.js retires this a few seconds into your
   first match and never shows it again, because a hint you have already read is
   just something in the way. */
/* Under the cluster, out of the flow. Inline, it made the cluster sit off-centre
   while it was up and jump sideways the moment it retired, which is a lot of
   movement to spend on something leaving quietly. */
.res-hint {
  position: absolute; top: 100%; left: 50%; transform: translateX(-50%);
  margin-top: 9px; font-size: 11px; color: #9fabd0; white-space: nowrap;
  text-shadow: 0 2px 8px rgba(0,0,0,.9); transition: opacity .5s ease;
}
#resBar.hint-done .res-hint { opacity: 0; }
/* The keys called out in the hint read as keys. Q especially: it is the only one
   here that is not already obvious from the buttons beside it, and a plain letter
   in a sentence is not discoverable, which is exactly what Kenny found. */
.res-hint b { background: rgba(159,171,208,.16); color: #dbe6f5; font-weight: 700; border-radius: 3px; padding: 0 4px; margin: 0 1px; }

/* ---- Leave ----------------------------------------------------------------
   The only visible way out of a match, and on a phone there was NO way out at all:
   this rule read `body.playing:not(.touch-device)`, so the button was display:none
   at every touch size and the remaining exits were the Esc key (js/input.js) and
   waiting for the match to end. A phone has no Esc key.

   Desktop keeps it bottom-left, where the pointer lives. Touch moves it to the head
   of the strip, the one corner neither thumb is holding down, inset for the notch:
   a control you cannot see is the bug being fixed. */
#quitBtn {
  position: fixed; left: var(--hud-l); bottom: 22px; z-index: 7; display: none;
  padding: 11px 17px; min-height: 44px; border: 1px solid rgba(255,255,255,.12);
  border-radius: 12px; background: rgba(6,9,20,.78); color: #cfd6e6;
  font-size: 13px; font-weight: 600; cursor: pointer;
}
#quitBtn:hover { background: rgba(6,9,20,.92); color: #fff; }
body.playing #quitBtn { display: block; }
body.touch-device #quitBtn {
  top: calc(var(--hud-y1) + 5px); bottom: auto; left: var(--hud-l);
  min-width: 44px; min-height: 44px; padding: 0 12px; line-height: 42px; background: rgba(6,9,20,.86);
}

/* ---- Phones ---------------------------------------------------------------
   THE CENTRE COLUMN IS THE ARENA. The camera keeps your blob in the middle of the
   screen, so anything in the middle column stands between you and the thing you are
   steering. In landscape it measured 47px of clear sky above your own blob and 117px
   below, against 422px sideways: the HUD sat nearer to you than the walls did.

   So the two ends of the strip carry everything and the middle carries nothing:

     strip   Leave  score  |  (empty)  |  clock
     row 2   placement     |           |  invite code
     row 3   dash pips     |           |

   Keyed on a coarse pointer and a short screen as well as on width, because the
   layout is about hands and not about pixels: 844x390 and 740x360 are phones held
   sideways, they are WIDER than 760, and they were both getting the desktop band.

   The resource cluster comes off the bottom on a phone too. Low centre is correct
   on a mouse, where the pointer lives there; on a touch device it is the middle of
   the zone the twin sticks occupy, so it would sit on the controls it reports on. */
@media (max-width: 760px), (pointer: coarse) and (max-height: 500px) {
  :root { --hud-band: 56px; --hud-gut: 10px; --hud-row2: 8px; }

  /* The scrim is itself an opaque box, and a full-width one was the FIRST thing on
     the sight line up. Masked out across the middle, so it still backs the score
     and the clock and stops existing over your own blob. */
  #hudBand {
    -webkit-mask-image: linear-gradient(90deg, #000 24%, transparent 40% 60%, #000 76%);
    mask-image: linear-gradient(90deg, #000 24%, transparent 40% 60%, #000 76%);
  }

  .hud-score { flex-direction: column; align-items: flex-start; gap: 1px; }
  .hud-score b { font-size: 26px; }
  .hs-total { font-size: 11px; }
  .hs-label { font-size: 9px; letter-spacing: .18em; }
  /* After the Leave button, not under it: one strip read left to right.
     76px was the button's width and nothing else, so with the button measuring
     74.5px the score started 1.5px past its edge and the two read as one object.
     90px is the button plus a real gap, which is what stops SCORE looking like
     part of the control next to it. */
  #hud { left: calc(var(--hud-l) + 90px); }

  /* Clock to the right gutter. It was dead centre, which is where you are. */
  #timer { left: auto; right: var(--hud-r); transform: none; align-items: flex-end; text-align: right; font-size: 26px; min-width: 108px; }
  #timer::after { width: 108px; margin-top: 5px; height: 2px; }
  #timer.intermission { font-size: 12px; }

  /* Row 2 right, under the clock. The code is read once, at the start of a match. */
  #roomChip { top: var(--hud-y2); left: auto; right: var(--hud-r); transform: none; min-height: 44px; padding: 0; }
  #roomChip .rc-label, #roomChip .rc-copy { display: none; }
  #roomChip .rc-code { font-size: 14px; letter-spacing: .14em; }

  /* Row 2 left: your placement, and nothing else. Four named rows is three lines of
     wrapped text at 320px and it landed on the dash pips underneath. */
  #scoreboard { top: var(--hud-y2); left: var(--hud-l); right: auto; min-width: 0; max-width: 40vw; padding: 0; text-align: left; }
  #scoreboard .hud-title, #scoreboard .row { display: none; }
  #scoreboard .sb-rank { display: block; font-size: 12px; margin: 0; }
  #scoreboard .sb-rank b { font-size: 15px; }

  /* Row 3 left: bare pips. Panel, separator and both weapon meters are gone - the
     meters said exactly what #swingBtn and #gunBtn already draw as a pie and a ready
     ring (app.css), so the box was a second copy of a readout your thumb rests on.
     Each pip gets its own shadow now that the thing behind it may be a white blob. */
  #resBar {
    left: var(--hud-l); right: auto; bottom: auto; transform: none; top: calc(var(--hud-y2) + 24px);
    gap: 0; padding: 0; border: 0; background: none; box-shadow: none;
  }
  .res-sep, .wpn-meters { display: none; }
  #dashCharges { gap: 4px; }
  .dashPip { width: 21px; height: 8px; box-shadow: 0 1px 5px rgba(0,0,0,.95); }
  .res-hint { display: none; } /* no room; How to Play carries it on a phone */
}

/* Landscape phones have ~330px of height for a HUD, a kill feed and two thumbs. */
@media (max-height: 430px) {
  :root { --hud-band: 48px; }
  .hud-score b { font-size: 22px; }
  .hs-total { font-size: 10px; }
  #timer { font-size: 22px; }
  #timer::after { margin-top: 3px; }
  #quitBtn { bottom: 10px; }
}

/* ---- The prestige banner --------------------------------------------------
   Ranking up is the best thing that happens in a match, and it was landing on top
   of the score, the clock, the invite code and the standings at once: 4 separate
   collisions at 320x568, measured. It was anchored to `top: 16%`, which on an
   800px window is 128px, which is the middle of the chrome.

   Anchored to the band's bottom edge instead, so the gap it sits in is a stated
   number rather than a fraction of whatever window it lands in. On short and
   narrow screens there is no such gap, so it comes off the bottom instead, where
   the only thing to clear is the Leave button. */
#prestige { top: calc(var(--hud-y2) + 58px); }

/* Tall and narrow: the clear middle, between row 2 and the kill feed. */
@media (max-width: 760px) and (min-height: 561px) {
  #prestige { top: auto; bottom: 220px; }
}

/* Short: there is no clear middle at all. Every horizontal band is claimed, so it
   takes the empty LEFT of the arena instead. The kill feed owns the right, row 2
   owns the top, and this is the only quiet rectangle left on a landscape phone. */
@media (max-height: 560px) {
  #prestige { top: auto; bottom: 74px; left: 26%; }
  .pr-num { font-size: 34px; }
  .pr-star { font-size: 22px; }
  .pr-tier { font-size: 13px; letter-spacing: 3px; }
}
