/* tank.tektology.com - the shared design system.
   Loaded FIRST by every page; each page's own stylesheet loads after and only
   adds what is genuinely local to it (the canvas, the minimap, the admin
   forms).

   Two hard constraints, both from the vhost CSP (nginx/csp-tank.conf):

     - style-src 'self' with no 'unsafe-inline'. No style attributes, no
       <style> blocks, and by house rule nothing sets el.style from JS either.
       Anything that needs a colour at runtime uses a class or an SVG `fill`.
     - font-src 'self'. No webfont CDN, so the type is the platform's own UI
       face and its own monospace. On Windows that is Segoe UI Variable and
       Cascadia Mono, on a Mac SF and SF Mono, on Android Roboto and Roboto
       Mono - all modern faces, and none of them a download.

   WHAT THIS IS SUPPOSED TO LOOK LIKE: the interface of a tank, not a web
   dashboard. The previous pass took the opposite lesson from the arcade build
   it replaced and stripped the game out along with the wide-tracked capitals -
   what was left was a competent admin panel that happened to have a Drive
   button on it. Three devices carry the look now:

     1. THE NOTCH. Every panel, button and field has its top-left and
        bottom-right corners cut off at 45 degrees, with the hairline carried
        round the diagonal. Nothing on a web page is shaped like that and
        everything on a combat display is. See "the notched box" below - it is
        one recipe, applied by hand, and worth reading before touching it.
     2. THE GROUND. The page does not sit on a flat near-black. It sits on a
        vignetted steel gradient with a 44px grid ruled over it, so a panel
        reads as a panel LAID ON something rather than as a slightly different
        shade of the same nothing.
     3. THE FIGURES ARE MONOSPACED. Every number that means something - a kill
        count, a percentage, a clock, a bay cost - is set in the platform's
        mono face at a tracked size. Prose stays in the UI face: this is an
        instrument reading dressed as one, not a terminal emulator.

   Capitals are back, but only where they were doing a job in the first place:
   the section eyebrow, the small furniture label above a figure, the HUD
   legends. Headings, buttons and body copy are sentence case at a normal
   track, because wide-tracked capitals on a HEADING is the 1980s cabinet tell
   and no amount of palette work outvotes it.

   Colour rule, inherited and kept: saturation is spent on the game, not on the
   furniture. The chrome is steel with one cyan accent. Amber means hot or
   nearly out, crimson means damage or death, green means healthy, gold means a
   built-in or a champion - and nothing decorative may use those four. The four
   TEAM colours belong to the game and appear nowhere in the chrome at all. */

/* ------------------------------------------------------------------ tokens */

:root {
  /* Surfaces, darkest to lightest. --bg and --bg-2 are the two ends of the
     page ground; the three --surface steps are what panels are made of. */
  --bg:         #07090d;
  --bg-2:       #0d1016;
  --surface:    #101620;
  --surface-2:  #151d29;
  --surface-3:  #1d2734;

  /* The hairline, in three weights. --line is the default panel edge,
     --line-hard is for something floating over the page, and --line-soft is
     for a divider INSIDE a panel, where a full-strength rule reads as a
     seam. */
  --line:       #223040;
  --line-soft:  rgba(140, 190, 225, 0.085);
  --line-hard:  #34485c;

  --fg:         #e6edf5;
  --fg-2:       #9aa9bb;
  --fg-3:       #64748a;

  /* ONE accent, and it is the interface's colour rather than a decoration:
     the primary button, the live edge, the selected tile, the focused field.
     Bright enough to carry a filled button and far enough off the blue and
     green TEAM fills that a cyan control never reads as a team. */
  --accent:      #35d6f0;
  --accent-hi:   #7fe9ff;
  --accent-dim:  rgba(53, 214, 240, 0.13);
  --accent-line: rgba(53, 214, 240, 0.45);
  --accent-ink:  #04161c;

  /* Reserved meanings. Do not use these for decoration.
     --warn is the amber: hot, reloading, nearly out of battery, a dropped
     flag. --gold is a lighter, warmer yellow and means a built-in gun or the
     outright leader - the two are deliberately distinguishable, because a
     Devastator's welded cannon is not a warning. */
  --gold:       #ffc861;
  --warn:       #ffab2e;
  --ok:         #4fd48a;
  --bad:        #ff4d5e;

  /* Corners, and there are only two left. The shape of a panel is the NOTCH
     now, and a soft radius fights it - so the four intermediate radii this
     file used to carry (--r-sm, --r, --r-lg and a 12px default) all went
     unused the moment the notch arrived and are gone rather than sitting here
     inviting somebody to round something off again.

     --r-xs is the 2px on the handful of things too small to notch: a nav link,
     a menu row, a list row. --r-pill is a real pill and is used only by things
     that genuinely are round - a status dot. */
  --r-xs:   2px;
  --r-pill: 999px;

  /* THE NOTCH.

     --cut is how much corner comes off. --notch is the shape, and
     --notch-lines are the two 45-degree hairlines that finish it.

     The shape has to be a clip-path: there is no border syntax for a cut
     corner. But a clip-path cuts the BORDER too, so an element with
     `border: 1px` and this clip has a hairline on its four straight edges and
     nothing at all across the two diagonals - which looks like a rendering
     fault rather than a bevel. --notch-lines is what fixes that: a pair of
     `--cut` square background tiles in the two cut corners, each carrying a
     one-pixel stripe laid exactly along the diagonal. 135deg is the axis that
     runs down-right, so its bands run along x + y = constant, which is the cut
     itself; the 50% stop lands the stripe on it at any --cut.

     `background-origin: border-box` is load bearing - without it the tile
     starts inside the border and the diagonal misses the corner by a pixel.

     THE PRICE, and it is why this is a recipe rather than a class on
     everything: a clip-path also clips the focus outline and the box-shadow.
     So every notched thing sets `outline: none` on focus and shows focus with
     its own edge instead, and nothing notched has a drop shadow. Separation
     from the page comes from the hairline and the grid behind it. */
  --cut: 11px;
  --notch: polygon(var(--cut) 0, 100% 0, 100% calc(100% - var(--cut)),
                   calc(100% - var(--cut)) 100%, 0 100%, 0 var(--cut));
  --notch-lines:
    linear-gradient(135deg, transparent calc(50% - 0.5px),
                    var(--panel-edge) calc(50% - 0.5px),
                    var(--panel-edge) calc(50% + 0.5px),
                    transparent calc(50% + 0.5px)),
    linear-gradient(135deg, transparent calc(50% - 0.5px),
                    var(--panel-edge) calc(50% - 0.5px),
                    var(--panel-edge) calc(50% + 0.5px),
                    transparent calc(50% + 0.5px));

  /* Defaults for the two properties every notched thing reads. A component
     that wants a different fill or a brighter edge sets THESE rather than
     `background` and `border-color`, so the diagonals follow it. */
  --panel-fill: var(--surface);
  --panel-edge: var(--line);

  /* There are no shadow tokens any more. A clip-path clips a box-shadow, so
     every floating thing on this site - the pop panels, the toast, the settings
     panel over the game - was paying for a shadow that was never drawn. What
     separates a floating panel from the page now is a --line-hard edge, a
     lighter fill, and the grid on the ground showing through nothing. */

  --ease: cubic-bezier(0.2, 0.7, 0.3, 1);
  --fast: 120ms var(--ease);
  --med:  180ms var(--ease);

  --mono: ui-monospace, "Cascadia Mono", "Segoe UI Mono", "SF Mono",
          "Roboto Mono", Menlo, Consolas, monospace;

  --bar-h:      54px;
  --bar-h-game: 46px;

  --gap-t: max(10px, env(safe-area-inset-top));
  --gap-b: max(10px, env(safe-area-inset-bottom));
  --gap-l: max(10px, env(safe-area-inset-left));
  --gap-r: max(10px, env(safe-area-inset-right));
}

* { box-sizing: border-box; }

/* The UA stylesheet's `[hidden] { display: none }` comes from the user-agent
   origin, so ANY author rule that sets `display` beats it. Every component
   here that something toggles with `.hidden = true` - the nav links, the pop
   panels, the player-list wrapper, the sign-in gate - also carries a display
   rule, so each one needed its own `[hidden]` override or it simply stayed on
   screen. That is a bug you rediscover one component at a time. One rule
   instead, and `el.hidden = true` means hidden everywhere. */
[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  color-scheme: dark;
}

/* THE GROUND. Four layers, front to back in source order: a cool bloom under
   the app bar, the vertical steel gradient, and the two rules of the grid.

   `background-attachment: fixed` keeps the grid still while the page scrolls,
   which is what makes it read as the surface the panels are lying on rather
   than as wallpaper travelling with them.

   The grid is deliberately near the floor of visibility - two hundredths of an
   alpha. It is meant to be felt at a glance and not looked at; at any more it
   is graph paper and it fights every hairline on the page. */
body {
  margin: 0;
  background-color: var(--bg);
  background-image:
    radial-gradient(ellipse 120% 80% at 50% 0%,
                    rgba(53, 214, 240, 0.05), transparent 70%),
    linear-gradient(180deg, var(--bg-2) 0%, var(--bg) 60%),
    repeating-linear-gradient(90deg, rgba(150, 200, 235, 0.021) 0 1px,
                              transparent 1px 44px),
    repeating-linear-gradient(0deg, rgba(150, 200, 235, 0.021) 0 1px,
                              transparent 1px 44px);
  background-attachment: fixed, fixed, fixed, fixed;
  color: var(--fg);
  font-family: system-ui, -apple-system, "Segoe UI Variable Text", "Segoe UI",
               Roboto, Helvetica, Arial, sans-serif;
  font-size: 15px;
  line-height: 1.55;
  -webkit-tap-highlight-color: transparent;
}

/* Numbers that change in place - clocks, scores, percentages - must not
   shuffle the layout sideways every time a digit changes width, and they are
   the readings rather than the prose, so they are the mono face too. */
.num,
.stat dd,
.hs-dots, .hs-time, .hs-rank,
.pscore {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}

h1, h2, h3 {
  margin: 0;
  font-weight: 600;
  letter-spacing: -0.011em;   /* negative, not positive - not a cabinet marquee */
  line-height: 1.25;
  color: var(--fg);
}

h1 { font-size: 27px; }
h2 { font-size: 18px; }
h3 { font-size: 15px; }

a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-hi); }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}

/* Capitals, and this is the whole of where they are allowed: a small label
   that marks a block as furniture rather than content. It is furniture BECAUSE
   it is set this way - that is the job. */
.eyebrow {
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-3);
}

.muted { color: var(--fg-2); }
.dim   { color: var(--fg-3); }
.small { font-size: 13px; }

/* --------------------------------------------------------- the notched box */

/* The recipe from the tokens above. Written out on each component that wants
   it rather than shared through one class, because CSS has no @extend and a
   real shared class would mean editing the markup of four pages and two JS
   builders - `.card`, `.btn` and `.input` each carry their own copy for that
   reason, and so do a dozen components in game.css. These two are the ones
   whose copy is identical. */
.pop,
.toast {
  background-color: var(--panel-fill);
  background-image: var(--notch-lines);
  background-repeat: no-repeat, no-repeat;
  background-position: top left, bottom right;
  background-size: var(--cut) var(--cut), var(--cut) var(--cut);
  background-origin: border-box;
  border: 1px solid var(--panel-edge);
  border-radius: 0;
  clip-path: var(--notch);
}

/* ------------------------------------------------------------------ appbar */

/* One bar, two skins. `.appbar` on the site pages is a normal sticky header
   the content flows under. `.appbar--game` floats over the WebGL canvas, is
   shorter, and dims itself out of the way while you are playing - see the
   idle rules further down.

   NOT notched: it spans the whole window, so there are no corners to cut. What
   it has instead is a one-pixel cyan lower edge at low opacity, which is the
   line the page hangs from. */
.appbar {
  position: sticky;
  top: 0;
  z-index: 40;
  background: linear-gradient(180deg, rgba(10, 13, 19, 0.93),
                                      rgba(10, 13, 19, 0.82));
  backdrop-filter: blur(14px) saturate(1.25);
  -webkit-backdrop-filter: blur(14px) saturate(1.25);
  border-bottom: 1px solid var(--line);
  box-shadow: 0 1px 0 rgba(53, 214, 240, 0.11);
}

.appbar-in {
  position: relative;         /* the pop panels anchor to their own wrapper, not this */
  display: flex;
  align-items: center;
  gap: 10px;
  height: var(--bar-h);
  max-width: 1160px;
  margin: 0 auto;
  padding: 0 max(16px, var(--gap-l)) 0 max(16px, var(--gap-r));
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  color: var(--fg);
  font-weight: 600;
  font-size: 15px;
  letter-spacing: 0.01em;
  flex-shrink: 0;
  padding: 4px;
  margin: -4px;
}

.brand:hover { color: var(--fg); }
.brand:hover .brand-mark { color: var(--accent-hi); }

.brand-mark {
  width: 22px;
  height: 22px;
  display: block;
  color: var(--accent);
  flex-shrink: 0;
  transition: color var(--fast);
}

/* The muzzle on the mark. Gold, which is one of its two sanctioned homes - the
   other is a built-in gun in the garage. */
.brand-mark .food { fill: var(--gold); }

.brand-name { white-space: nowrap; }

/* Pushes everything after it to the right edge. */
.appbar-spacer { flex: 1 1 auto; min-width: 0; }

/* --- links --- */

.nav {
  display: flex;
  align-items: center;
  gap: 2px;
  min-width: 0;
}

/* Square, not notched: at eleven pixels of padding a cut corner eats the
   label. What makes these read as part of the same system is the tracked
   lettering and the accent underline on the page you are on. */
.nav-link {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.03em;
  line-height: 1;
  padding: 9px 11px;
  border: 0;
  border-radius: var(--r-xs);
  background: transparent;
  color: var(--fg-2);
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--fast), background-color var(--fast);
}

.nav-link:hover { color: var(--fg); background: var(--surface-2); }

/* The page you are already on: a filled chip AND a lit bottom edge. The edge
   is what survives being folded into the mobile panel, where a chip alone next
   to the others is easy to lose. */
.nav-link[aria-current="page"] {
  color: var(--accent);
  background: var(--accent-dim);
  box-shadow: inset 0 -2px 0 var(--accent);
}

/* A pop's trigger while its panel is open. */
.nav-link[aria-expanded="true"] { color: var(--fg); background: var(--surface-3); }

.nav-ico {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
  display: block;
}

/* The little chevron on a pop trigger. Rotates when the panel opens. */
.caret {
  width: 9px;
  height: 9px;
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.7;
  transition: transform var(--fast);
}

[aria-expanded="true"] > .caret { transform: rotate(180deg); }

/* --- account button --- */

.who-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.02em;
  line-height: 1;
  padding: 5px 10px 5px 5px;
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  background: var(--surface);
  color: var(--fg);
  cursor: pointer;
  white-space: nowrap;
  transition: border-color var(--fast), background-color var(--fast);
}

.who-btn:hover,
.who-btn[aria-expanded="true"] {
  border-color: var(--accent-line);
  background: var(--surface-2);
}

/* Initial-in-a-box. Cheaper than an avatar service and it never 404s. Square
   with one cut corner - at 25px, one is all that reads. */
.who-dot {
  display: grid;
  place-items: center;
  width: 25px;
  height: 25px;
  flex-shrink: 0;
  background: var(--accent-dim);
  border: 1px solid var(--accent-line);
  color: var(--accent);
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  line-height: 1;
  clip-path: polygon(6px 0, 100% 0, 100% 100%, 0 100%, 0 6px);
}

.who-name {
  max-width: 11ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ------------------------------------------------------------- pop panels */

/* Every dropdown on the site: the leaderboard, the player list, the account
   menu, and the nav links themselves once the screen is too narrow to lay
   them out in a row. One wrapper, one panel, one set of behaviours in nav.js.

   NOTCHED, and therefore with no drop shadow - see the price in the tokens.
   What separates it from the page instead is a --line-hard edge and a lighter
   fill than a page panel gets. */

.pop-wrap { position: relative; display: inline-flex; }

.pop {
  --panel-fill: var(--surface-2);
  --panel-edge: var(--line-hard);
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  z-index: 50;
  min-width: 220px;
  max-width: min(92vw, 380px);
  max-height: min(70vh, 460px);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 7px;
}

/* Anchored to the right edge - for anything living on the right of the bar,
   which would otherwise run off the screen. */
.pop--right { left: auto; right: 0; }

.pop--wide { min-width: 310px; }

.pop-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding: 6px 9px 8px;
}

.pop-sep {
  height: 1px;
  margin: 6px -7px;
  background: var(--line-soft);
  border: 0;
}

.pop-note {
  margin: 0;
  padding: 4px 9px 8px;
  font-size: 12.5px;
  color: var(--fg-3);
}

/* A row inside a pop that does something. Anchors and buttons both. */
.pop-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  font: inherit;
  font-size: 14px;
  text-align: left;
  padding: 9px;
  border: 0;
  border-radius: var(--r-xs);
  background: transparent;
  color: var(--fg-2);
  cursor: pointer;
  transition: color var(--fast), background-color var(--fast);
}

.pop-item:hover { color: var(--fg); background: var(--surface-3); }
.pop-item .nav-ico { color: var(--fg-3); }
.pop-item:hover .nav-ico { color: var(--accent); }

/* The account card at the top of the account menu: who you are, and the career
   figures. */
.pop-me { padding: 10px 9px 12px; }

.pop-me-name {
  font-size: 15px;
  font-weight: 600;
  color: var(--fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pop-me-sub {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-3);
}

/* --------------------------------------------------------- figure readouts */

/* A row of instrument tiles: a small capitalised legend and a monospaced
   figure under it, with a bar down the left edge.

   The columns used to be three uneven fractions with a hand-tuned ratio, to
   stop the "Played" figure wrapping onto a second line and dragging its tile
   out of step. `auto-fit` with a floor does the same job without the magic
   numbers, and it does not break the moment a fourth or fifth figure is
   added - which is exactly what had happened by the time this was rewritten:
   four figures in three columns left a hole in the grid. */
.figs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
  gap: 5px;
  margin: 11px 0 0;
  padding: 0;
}

.fig {
  min-width: 0;
  padding: 8px 10px;
  background: var(--surface-3);
  border-left: 2px solid var(--line-hard);
}

.fig dt {
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.fig dd {
  margin: 2px 0 0;
  font-family: var(--mono);
  font-size: 15px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--fg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The figure that matters most in its group. ONE per group - two accents in a
   row of four is a row with no emphasis in it. */
.fig-accent { border-left-color: var(--accent); background: var(--accent-dim); }
.fig-accent dd { color: var(--accent); }

/* ------------------------------------------------------- game bar variant */

.appbar--game {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 40;
  border-bottom: 0;
  box-shadow: none;
  background: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  /* The bar frame must not eat a swipe meant for the board. The controls
     inside it opt back in. */
  pointer-events: none;
  transition: opacity var(--med);
}

.appbar--game .appbar-in {
  height: var(--bar-h-game);
  max-width: none;
  padding: 0 max(10px, var(--gap-r)) 0 max(10px, var(--gap-l));
  margin-top: var(--gap-t);
  gap: 8px;
}

.appbar--game .brand,
.appbar--game .nav-link,
.appbar--game .who-btn,
.appbar--game .pop,
.appbar--game .pop-wrap,
.appbar--game .bar-read { pointer-events: auto; }

/* Over a lit 3-D board a flat panel disappears; these need their own ground. */
.appbar--game .nav-link,
.appbar--game .bar-read {
  background: rgba(9, 12, 18, 0.7);
  border: 1px solid var(--line);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

.appbar--game .nav-link:hover,
.appbar--game .nav-link[aria-expanded="true"] {
  background: rgba(29, 39, 52, 0.9);
  border-color: var(--accent-line);
  color: var(--fg);
}

.appbar--game .who-btn {
  background: rgba(9, 12, 18, 0.7);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

.appbar--game .brand-name { display: none; }   /* the mark alone is the home link here */

/* Idle fade. nav.js sets data-idle="1" after a few seconds of nothing, and
   clears it on any input, on focus, or on Esc. Opacity only: the bar keeps its
   hit area so a deliberate tap or hover brings it straight back, which is what
   stops it becoming a thing you have to hunt for. */
.appbar--game[data-idle="1"] { opacity: 0.22; }
.appbar--game[data-idle="1"]:hover,
.appbar--game[data-idle="1"]:focus-within { opacity: 1; }

/* The live readout that sits in the game bar: the clock and the connection
   light. Notched at a smaller cut than a panel - it is a short strip and the
   full 11px eats into the first legend. */
.bar-read {
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 7px 12px;
  min-width: 0;
  clip-path: polygon(7px 0, 100% 0, 100% calc(100% - 7px),
                     calc(100% - 7px) 100%, 0 100%, 0 7px);
}

/* main.js owns this element's `hidden` - the figures belong to an account and
   stay off until the session comes back. The connection light beside it is not
   part of the list and stays either way. */
.bar-read .stats {
  display: flex;
  align-items: center;
  gap: 13px;
  margin: 0;
}

.read {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-size: 13px;
  line-height: 1;
}

.read dt {
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-3);
}

.read dd {
  margin: 0;
  font-family: var(--mono);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--fg);
}

/* The connection light. Was a line of text ("2 players", "connecting") sitting
   under the title; here it is a dot with the text as its tooltip and as
   screen-reader content, because on the game screen the words were noise nine
   times out of ten. Live and down both glow, because those are the two states
   worth catching out of the corner of an eye. */
.status {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin: 0;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--fg-3);
  white-space: nowrap;
}

.status::before {
  content: "";
  width: 6px;
  height: 6px;
  flex-shrink: 0;
  border-radius: var(--r-pill);
  background: var(--fg-3);
  transition: background-color var(--med), box-shadow var(--med);
}

.status[data-state="live"]::before {
  background: var(--ok);
  box-shadow: 0 0 7px rgba(79, 212, 138, 0.75);
}
.status[data-state="waiting"]::before { background: var(--warn); }
.status[data-state="down"]::before {
  background: var(--bad);
  box-shadow: 0 0 7px rgba(255, 77, 94, 0.7);
}

/* ------------------------------------------------------------ player list */

/* Shared by the Players pop on both game screens. */

.plist {
  margin: 0;
  padding: 3px;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.plist li {
  display: grid;
  grid-template-columns: 11px 1fr auto;
  align-items: center;
  gap: 10px;
  font-size: 13.5px;
  padding: 6px;
  border-radius: var(--r-xs);
}

.plist li.me { background: var(--accent-dim); box-shadow: inset 2px 0 0 var(--accent); }
.plist li.me .pname { color: var(--accent); font-weight: 600; }
.plist li.dead { opacity: 0.42; }

.swatch { width: 11px; height: 11px; flex-shrink: 0; display: block; }

.pname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.pscore {
  color: var(--fg-3);
  font-size: 12.5px;
  flex-shrink: 0;
}

/* ------------------------------------------------------------------ panels */

.page {
  max-width: 1160px;
  margin: 0 auto;
  padding: 30px max(16px, var(--gap-l)) 72px max(16px, var(--gap-r));
}

/* THE PANEL. The notched recipe, plus the accent tick - a short cyan hairline
   running down the inside of the left edge from the cut, which is the one mark
   that says "instrument" rather than "card". It is a third background layer
   rather than a pseudo-element, so both pseudo-elements stay free for the
   components that need them. */
.card {
  position: relative;
  padding: 20px;
  background-color: var(--panel-fill);
  background-image:
    linear-gradient(180deg, var(--accent) 0%, transparent 100%),
    var(--notch-lines);
  background-repeat: no-repeat, no-repeat, no-repeat;
  background-position: 0 var(--cut), top left, bottom right;
  background-size: 1px 38px, var(--cut) var(--cut), var(--cut) var(--cut);
  background-origin: border-box;
  border: 1px solid var(--panel-edge);
  border-radius: 0;
  clip-path: var(--notch);
}

.card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}

/* ---------------------------------------------------------------- buttons */

/* Notched, and therefore focused with its own edge rather than an outline -
   see the price in the tokens. The `outline: none` on `:focus-visible` is paid
   for by the inset double ring beside it, which is more visible than the UA
   ring rather than less. */
.btn {
  --panel-fill: var(--accent);
  --panel-edge: var(--accent);
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 1;
  padding: 12px 20px;
  background-color: var(--panel-fill);
  background-image: var(--notch-lines);
  background-repeat: no-repeat, no-repeat;
  background-position: top left, bottom right;
  background-size: var(--cut) var(--cut), var(--cut) var(--cut);
  background-origin: border-box;
  border: 1px solid var(--panel-edge);
  border-radius: 0;
  clip-path: var(--notch);
  color: var(--accent-ink);
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color var(--fast), border-color var(--fast),
              color var(--fast), filter var(--fast);
}

.btn:hover {
  --panel-fill: var(--accent-hi);
  --panel-edge: var(--accent-hi);
  color: var(--accent-ink);
}

.btn:active { filter: brightness(0.9); }

.btn:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--accent-ink),
              inset 0 0 0 3px var(--accent-hi);
}

.btn[disabled] { opacity: 0.45; cursor: default; filter: none; }

/* An explicit name for the default rank, so markup that says which one it
   wants reads the same as markup that relies on it. The settings panel over
   the game asks for this one by name. */
.btn-primary {
  --panel-fill: var(--accent);
  --panel-edge: var(--accent);
  color: var(--accent-ink);
}

/* Second rank: steel, not cyan. Two filled accent buttons side by side read as
   two primary actions and the eye has to stop and pick one. */
.btn-ghost {
  --panel-fill: var(--surface-2);
  --panel-edge: var(--line-hard);
  color: var(--fg);
}

.btn-ghost:hover {
  --panel-fill: var(--surface-3);
  --panel-edge: var(--accent-line);
  color: var(--fg);
}

.btn-ghost:focus-visible {
  box-shadow: inset 0 0 0 2px var(--surface), inset 0 0 0 3px var(--accent);
}

/* Third rank: no box at all until you point at it. */
.btn-quiet {
  --panel-fill: transparent;
  --panel-edge: transparent;
  color: var(--fg-2);
}

.btn-quiet:hover {
  --panel-fill: var(--surface-2);
  --panel-edge: var(--line);
  color: var(--fg);
}

.btn-quiet:focus-visible {
  box-shadow: inset 0 0 0 2px var(--bg), inset 0 0 0 3px var(--accent);
}

.btn-danger {
  --panel-fill: var(--bad);
  --panel-edge: var(--bad);
  color: #1a0508;
}

.btn-danger:hover {
  --panel-fill: #ff6f7d;
  --panel-edge: #ff6f7d;
  color: #1a0508;
}

.btn-sm    { font-size: 12.5px; padding: 8px 13px; --cut: 8px; }
.btn-lg    { font-size: 15.5px; padding: 15px 26px; --cut: 13px; }
.btn-block { display: flex; width: 100%; }

.btn-ico {
  width: 17px;
  height: 17px;
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* A text button that is not a box at all. */
.link {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  color: var(--accent);
  cursor: pointer;
  text-decoration: none;
}

.link:hover { color: var(--accent-hi); text-decoration: underline; }

/* ----------------------------------------------------------------- inputs */

.field { display: flex; flex-direction: column; gap: 6px; }

.lbl {
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-3);
}

/* Notched like everything else. An <input> CANNOT have pseudo-elements, so the
   diagonals have to come from a background-image pair rather than from a
   ::before - which is the reason that recipe exists in the first place.
   Focus is the accent border and the lit diagonals with it, since the clip
   would have eaten an outline. */
.input {
  --panel-fill: var(--surface-2);
  --panel-edge: var(--line);
  font: inherit;
  font-size: 15px;
  width: 100%;
  padding: 11px 13px;
  color: var(--fg);
  background-color: var(--panel-fill);
  background-image: var(--notch-lines);
  background-repeat: no-repeat, no-repeat;
  background-position: top left, bottom right;
  background-size: var(--cut) var(--cut), var(--cut) var(--cut);
  background-origin: border-box;
  border: 1px solid var(--panel-edge);
  border-radius: 0;
  clip-path: var(--notch);
  transition: border-color var(--fast), background-color var(--fast);
}

.input::placeholder { color: var(--fg-3); }
.input:hover { --panel-edge: var(--line-hard); }

.input:focus,
.input:focus-visible {
  outline: none;
  --panel-edge: var(--accent);
  --panel-fill: var(--surface);
}

/* The chevron is a THIRD background layer here, in front of the two diagonals,
   rather than the element's only background - which is what it was before the
   notch existed. It is a data: URI, which img-src allows; an inline background
   on the element would be a style attribute and the CSP would drop it. */
select.input {
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  padding-right: 34px;
  background-image:
    url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5 6 8l3.5-3.5' fill='none' stroke='%2364748a' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"),
    var(--notch-lines);
  background-repeat: no-repeat, no-repeat, no-repeat;
  background-position: right 11px center, top left, bottom right;
  background-size: 12px, var(--cut) var(--cut), var(--cut) var(--cut);
}

/* A rule with a word sitting in the gap. Drawn by the pseudo-elements so it
   works at any width with no fixed measurement. */
.or {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 2px 0;
  font-family: var(--mono);
  color: var(--fg-3);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.or::before,
.or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}

/* ------------------------------------------------------ switches and slider */

/* Both live HERE rather than in settings.css, because three places want them
   now: the controls page, the panel over the game, and the admin page's bot
   dials. The admin page used to draw its own out of a bare `accent-color` on
   the native control, which is where the one unstyled checkbox on the site
   came from. */

.switch {
  display: grid;
  grid-template-columns: 42px 1fr;
  gap: 12px;
  align-items: start;
  cursor: pointer;
}

/* The real checkbox stays in the DOM and keeps its keyboard behaviour and its
   accessible name; it is the painted track next to it that is visible. Moved
   off-screen rather than display:none, which would take it out of the tab
   order entirely. */
.switch input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.switch-track {
  position: relative;
  display: block;
  width: 42px;
  height: 22px;
  margin-top: 2px;
  background: var(--surface-3);
  border: 1px solid var(--line-hard);
  transition: background var(--fast), border-color var(--fast),
              box-shadow var(--fast);
}

.switch-knob {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 14px;
  height: 14px;
  background: var(--fg-3);
  transition: transform var(--med), background var(--fast);
}

.switch input:checked + .switch-track {
  background: var(--accent-dim);
  border-color: var(--accent);
  box-shadow: 0 0 9px rgba(53, 214, 240, 0.22);
}
.switch input:checked + .switch-track .switch-knob {
  background: var(--accent);
  transform: translateX(20px);
}
.switch input:focus-visible + .switch-track {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.switch-text { display: flex; flex-direction: column; gap: 2px; font-size: 14px; }
.switch-text b { font-weight: 600; color: var(--fg); }

/* --- the slider --- */

.slider-row {
  display: flex;
  align-items: center;
  gap: 14px;
}

.slider-out {
  min-width: 62px;
  text-align: right;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 15px;
  font-weight: 700;
  color: var(--accent);
}

.slider-ends {
  display: flex;
  justify-content: space-between;
  margin-top: 2px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--fg-3);
}

/* Styled from scratch on both engines. The two rule sets cannot be combined -
   a selector list containing one vendor pseudo-element that a browser does not
   recognise invalidates the WHOLE rule, so ::-webkit and ::-moz have to be
   written out separately even though the declarations are identical. */
/* 22px tall, not 26, and the focus ring sits 1px off it rather than 4.
   THE REASON IS THE FOCUS RING. An <input type=range> is a tall box with a
   thin track floating in the middle of it, so an outline drawn round the
   element is nowhere near the control it is marking - at 26px and a 4px offset
   it landed squarely on the "HEAVY / TWITCHY" labels below, which reads as a
   layout fault rather than as focus. Tight to the box, and the labels get
   their clearance back above. */
.slider {
  flex: 1;
  min-width: 0;
  height: 22px;
  margin: 0;
  padding: 0;
  background: transparent;
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
}

.slider:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.slider::-webkit-slider-runnable-track {
  height: 4px;
  background: var(--surface-3);
  border-top: 1px solid var(--line-hard);
}
.slider::-moz-range-track {
  height: 4px;
  background: var(--surface-3);
  border-top: 1px solid var(--line-hard);
}

/* A cut-cornered slab, not a dot: the same shape language as everything else
   on the page. */
.slider::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 13px;
  height: 18px;
  margin-top: -7px;              /* centres an 18px thumb on a 4px track */
  border: none;
  background: var(--accent);
  clip-path: polygon(4px 0, 100% 0, 100% calc(100% - 4px),
                     calc(100% - 4px) 100%, 0 100%, 0 4px);
}
.slider::-moz-range-thumb {
  width: 13px;
  height: 18px;
  border: none;
  border-radius: 0;
  background: var(--accent);
  clip-path: polygon(4px 0, 100% 0, 100% calc(100% - 4px),
                     calc(100% - 4px) 100%, 0 100%, 0 4px);
}

/* ------------------------------------------------------------ leaderboard */

/* One markup shape, three homes: the dashboard panel, the pop panel in the
   bar, and the start card on the game screen. The header row and the list rows
   share a column template, so a heading always sits over the figures it names.
   Change one, change both. */

.hiscores-head,
.hiscores-list li {
  display: grid;
  grid-template-columns: 26px 1fr 52px 62px;
  align-items: center;
  gap: 10px;
  font-size: 14px;
  padding: 7px 8px;
}

.hiscores-head {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-3);
  padding-bottom: 6px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 3px;
}

.hiscores-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.hiscores-list li:nth-child(even) { background: rgba(150, 200, 235, 0.026); }

.hs-rank { color: var(--fg-3); font-size: 12px; }
.hs-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hs-dots { font-weight: 700; text-align: right; color: var(--fg); }
.hs-time { color: var(--fg-3); font-size: 13px; text-align: right; }

.hiscores-head .hs-rank,
.hiscores-head .hs-dots,
.hiscores-head .hs-time { font-size: inherit; font-weight: inherit; color: inherit; }

/* The outright leader. Gold is a reserved colour and this is one of its two
   sanctioned homes. */
.hiscores-list li.champion {
  background: rgba(255, 200, 97, 0.07);
  box-shadow: inset 2px 0 0 var(--gold);
}
.hiscores-list li.champion .hs-rank,
.hiscores-list li.champion .hs-name,
.hiscores-list li.champion .hs-dots,
.hiscores-list li.champion .hs-time { color: var(--gold); }
.hiscores-list li.champion .hs-name { font-weight: 600; }

.hiscores-note { margin: 10px 0 0; font-size: 13px; color: var(--fg-3); }

/* Inside a pop the rows tighten up and lose the panel padding. */
.pop .hiscores-head,
.pop .hiscores-list li { font-size: 13.5px; padding: 6px 9px; }
.pop .hiscores-head { padding-bottom: 6px; }

/* ----------------------------------------------------- tournament banner */

/* THE TOURNAMENT BANNER (2026-09-13). One link, at the top of the page on
   home, Controls and admin, and at the top of the garage on /game/ - never
   over a running match. It points at /tournament/, and nav.js hides it once
   the tournament is over and swaps "Starts October 1st" for "On now" while it
   runs, both off /api/tournament.

   Gold edge and gold tag: the prize is a champion's, which is one of the two
   things the reserved gold is for (see the tokens). Notched like a panel. */
.tbanner {
  --panel-fill: rgba(255, 200, 97, 0.07);
  --panel-edge: rgba(255, 200, 97, 0.5);
  --cut: 9px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 14px;
  margin: 0 0 20px;
  padding: 11px 16px;
  background-color: var(--panel-fill);
  background-image: var(--notch-lines);
  background-repeat: no-repeat, no-repeat;
  background-position: top left, bottom right;
  background-size: var(--cut) var(--cut), var(--cut) var(--cut);
  background-origin: border-box;
  border: 1px solid var(--panel-edge);
  clip-path: var(--notch);
  color: var(--fg);
  font-size: 14.5px;
  line-height: 1.4;
  transition: background-color var(--fast), border-color var(--fast);
}

.tbanner:hover {
  --panel-fill: rgba(255, 200, 97, 0.13);
  --panel-edge: var(--gold);
  color: var(--fg);
}

.tbanner:focus-visible {
  outline: none;
  --panel-edge: var(--gold);
  box-shadow: inset 0 0 0 1px var(--gold);
}

.tbanner-tag {
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gold);
}

.tbanner-when { font-weight: 700; }
.tbanner-text { flex: 1 1 260px; min-width: 0; color: var(--fg-2); }
.tbanner-text b { color: var(--gold); font-family: var(--mono); }

.tbanner-go {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-weight: 600;
  color: var(--accent);
  white-space: nowrap;
}

.tbanner:hover .tbanner-go { color: var(--accent-hi); }

.tbanner-go svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ------------------------------------------------------------------ toast */

.toast {
  --panel-fill: var(--surface-3);
  --panel-edge: var(--line-hard);
  --cut: 9px;
  position: fixed;
  z-index: 60;
  left: 50%;
  bottom: max(22px, var(--gap-b));
  transform: translateX(-50%);
  margin: 0;
  max-width: min(92vw, 440px);
  padding: 11px 17px;
  font-size: 14px;
  animation: toast-in 180ms var(--ease);
}

.toast.err { --panel-edge: var(--bad); color: #ffdfe2; }

@keyframes toast-in {
  from { opacity: 0; transform: translate(-50%, 8px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}

/* ---------------------------------------------------------------- overlay */

.overlay {
  position: fixed;
  inset: 0;
  z-index: 30;
  display: grid;
  place-items: center;
  padding: max(14px, var(--gap-t)) 14px max(14px, var(--gap-b));
  overflow-y: auto;
  background: rgba(4, 6, 9, 0.9);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

/* ------------------------------------------------------------- responsive */

/* Hidden by default and revealed by the media query below. This declaration
   has to come FIRST: it and the one in the media query have the same
   specificity, so whichever is last in the file wins, and with them the other
   way round the Menu button was never drawn on a phone. */
.nav-toggle { display: none; }

/* Below this the site links no longer fit next to a name, so they fold into a
   pop under the Menu button. nav.js owns the `hidden` attribute on `.nav` and
   only ever sets it below this breakpoint, so the links are never left hidden
   from assistive tech on a wide screen. */
@media (max-width: 620px) {
  .nav-toggle { display: inline-flex; }

  .appbar .nav {
    --panel-fill: var(--surface-2);
    --panel-edge: var(--line-hard);
    position: absolute;
    top: calc(100% + 6px);
    left: max(12px, var(--gap-l));
    z-index: 50;
    flex-direction: column;
    align-items: stretch;
    gap: 1px;
    min-width: 210px;
    padding: 7px;
    background-color: var(--panel-fill);
    background-image: var(--notch-lines);
    background-repeat: no-repeat, no-repeat;
    background-position: top left, bottom right;
    background-size: var(--cut) var(--cut), var(--cut) var(--cut);
    background-origin: border-box;
    border: 1px solid var(--panel-edge);
    clip-path: var(--notch);
  }

  .appbar .nav .nav-link { padding: 11px; }

  /* The game bar's controls are already icons and stay in the row. */
  .appbar--game .nav { position: static; flex-direction: row; padding: 0;
                       border: 0; background: none; clip-path: none;
                       min-width: 0; gap: 6px; }

  /* Button labels go, icons carry the meaning. Without this the account chip
     ran off the right edge of a 375px phone. */
  .lbl-hide { display: none; }
}

/* The wordmark is the last thing to go. Below this the brand is the mark
   alone, which is still a home link and still recognisable. */
@media (max-width: 420px) {
  .appbar .brand-name { display: none; }
}

@media (max-width: 560px) {
  :root { --bar-h: 50px; --cut: 9px; }
  h1 { font-size: 23px; }
  .page { padding-top: 22px; }
  .appbar-in { gap: 8px; }
  .who-name { max-width: 8ch; }

  /* A phone in portrait cannot carry the whole game bar; the labels went at
     the breakpoint above. Tighten what is left. */
  .appbar--game .bar-read { gap: 9px; padding: 7px 10px; }
  .appbar--game .bar-read .stats { gap: 9px; }

  /* The connection light keeps its dot and loses its words. The text is still
     in the DOM, so a screen reader still hears "2 players" - it is the pixels
     that are the luxury here, not the information. */
  .appbar--game .status { font-size: 0; gap: 0; }
}

/* ------------------------------------------------------------- battle log */

/* THE BATTLE LOG ON EVERY PAGE (2026-09-13). Home, Controls, admin, and the
   garage on /game/ all carry the same panel - kills, flags, stations and the
   live scoreline - and every page is laid out AROUND it rather than under it:
   the panel is never covered, and nothing on the page is ever hidden behind
   it. See battlelog.js for what goes in it.

   Where it sits, as John chose:
     wide   (900px and up)  a full-height column on the right, under the bar
     narrow                 a band across the bottom, about a third of the
                            screen high

   Pages do not position themselves against the panel by hand. body.has-battle
   sets two tokens, and everything that has to clear the panel reads them:
     --bl-right   how much of the right edge it takes (0 on a narrow screen)
     --bl-bottom  how much of the bottom edge it takes (0 on a wide screen)
   `.page`, the toast, the Controls page's sticky Save bar and the garage
   overlay all use these, so the panel can change size in one place. */
:root {
  --bl-w: clamp(290px, 27vw, 380px);
  --bl-h: clamp(170px, 34vh, 300px);
  --bl-top: var(--bar-h);
  --bl-right: 0px;
  --bl-bottom: 0px;
}

body.has-battle { --bl-right: var(--bl-w); }

@media (max-width: 899px) {
  body.has-battle { --bl-right: 0px; --bl-bottom: var(--bl-h); }
}

.battle {
  position: fixed;
  top: var(--bl-top);
  right: 0;
  bottom: 0;
  width: var(--bl-w);
  /* Over page content and the site overlay (30), UNDER the app bar (40).
     It has to be under the bar itself, not just under the menus' 50: the bar
     is sticky/fixed with a z-index, so it is its own stacking context and
     every menu inside it paints at the bar's 40 no matter what the menu says.
     At 45 this panel covered the account menu that drops down over it. */
  z-index: 35;
  display: flex;
  flex-direction: column;
  background: rgba(8, 11, 17, 0.95);
  border-left: 1px solid var(--line);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

@media (max-width: 899px) {
  .battle {
    top: auto;
    left: 0;
    width: auto;
    height: var(--bl-h);
    padding-bottom: env(safe-area-inset-bottom);
    border-left: 0;
    border-top: 1px solid var(--line-hard);
  }
}

.bl-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 14px 7px;
}

.bl-head .eyebrow { color: var(--fg-2); }

/* Live / Connecting / Reconnecting / Paused. The dot carries the meaning. */
.bl-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-3);
}

.bl-status::before {
  content: "";
  width: 7px;
  height: 7px;
  background: var(--fg-3);
}

.bl-status[data-kind="live"]::before    { background: var(--ok); }
.bl-status[data-kind="waiting"]::before { background: var(--warn); }
.bl-status[data-kind="down"]::before    { background: var(--bad); }

/* The scoreline: two by two in the column, four across in the band. */
.bl-scores {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 4px;
  padding: 0 12px 9px;
}

@media (max-width: 899px) {
  .bl-scores { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

.bl-sc {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  padding: 5px 8px;
  background: var(--surface-3);
  border-left: 2px solid var(--line-hard);
  font-size: 12px;
  line-height: 1;
  white-space: nowrap;
}

.bl-sc.is-me { border-left-color: var(--accent); background: rgba(10, 26, 33, 0.9); }
.bl-dot { width: 9px; height: 9px; flex: none; }

.bl-n {
  overflow: hidden;
  text-overflow: ellipsis;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--fg-2);
}

.bl-v {
  margin-left: auto;
  font-family: var(--mono);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--fg);
}

.bl-f {
  flex: none;
  padding: 2px 4px;
  font-family: var(--mono);
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.bl-f.is-carried { background: var(--bad); color: #140407; }
.bl-f.is-dropped { background: var(--warn); color: #1a1204; }

/* Four chips across a phone is four very short chips: the team is the dot. */
@media (max-width: 520px) {
  .bl-n { display: none; }
  .bl-sc { padding: 5px 6px; gap: 5px; }
}

/* The lines. Newest at the bottom, its own scroll, and a thumb scrolling it
   does not scroll the page behind it. */
.bl-lines {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 6px 4px 10px 0;
  border-top: 1px solid var(--line-soft);
  font-size: 13px;
  line-height: 1.4;
  color: #dfe7ef;
  scrollbar-width: thin;
  scrollbar-color: rgba(150, 200, 235, 0.35) transparent;
}

.bl-lines::-webkit-scrollbar { width: 6px; }
.bl-lines::-webkit-scrollbar-thumb { background: rgba(150, 200, 235, 0.35); }

.bl-lines:empty::before {
  content: "Waiting for the first shot.";
  display: block;
  padding: 6px 12px;
  color: var(--fg-3);
}

/* "Waiting for the first shot" is only true of a CONNECTED log that has
   nothing in it yet. While the socket is still coming up the .bl-wait line
   above says so instead, and an empty log under it says nothing. */
.battle[data-conn]:not([data-conn="live"]) .bl-lines:empty::before { content: none; }

/* Connecting / Reconnecting / Paused, said in words inside the panel rather
   than only by the small chip in its corner. battlelog.js adds it. */
.bl-wait {
  margin: 0;
  padding: 8px 14px;
  border-top: 1px solid var(--line-soft);
  background: rgba(255, 171, 46, 0.06);
  font-size: 12.5px;
}
.bl-wait[data-kind="down"] { background: rgba(255, 77, 94, 0.07); }

/* One line, in the log here and in the match. The left edge is the tell for
   what kind it is - a bar rather than a wash, so a run of them still reads as
   a list. */
.fe {
  padding: 3px 10px 3px 9px;
  border-left: 2px solid transparent;
  color: #dfe7ef;
  font-family: inherit;
}
.fe + .fe { margin-top: 1px; }
.fe-n { font-weight: 600; color: #f2f6fa; }
.fe-n[data-team="red"]   { color: #ff8a73; }
.fe-n[data-team="green"] { color: #8fdc84; }
.fe-n[data-team="blue"]  { color: #8fb6ff; }
.fe-n[data-team="gray"]  { color: #d3d7dd; }
/* The weapon, in amber, so the "how" stands out from the "who". */
.fe-w { color: #ffc46b; font-weight: 600; }
.fe-who { color: var(--accent-hi); font-weight: 600; }

.fe.is-me    { border-left-color: var(--accent); background: rgba(53, 214, 240, 0.08); }
.fe.is-bad   { border-left-color: var(--bad); }
.fe.is-flag  { border-left-color: var(--accent); }
.fe.is-big   { border-left-color: var(--ok); background: rgba(79, 212, 138, 0.1); }
.fe.is-admin { border-left-color: var(--accent-hi); color: #eaf9ff; }
.fe.is-note  { color: #b9c4cf; }

/* THE PAGE, around the panel. The space is reserved on BODY, not on `.page`:
   the pages each cap their own width differently (1160px here, 940px on
   Controls, 980px on admin), and a padded body lets every one of them centre
   in what is left without knowing the panel exists. The bar is pulled back out
   to the full width with a negative margin, so it still spans the window and
   the panel hangs under it. On a narrow screen the bottom padding lets the
   last lines of a page scroll clear of the band. */
body.has-battle {
  padding-right: var(--bl-right);
  padding-bottom: var(--bl-bottom);
}

body.has-battle .appbar { margin-right: calc(-1 * var(--bl-right)); }

/* Not the game's bar: it is position:fixed with left:0/right:0, so it already
   spans the window, and the same negative margin pushed its right end - the
   account button - off the edge of the screen in the garage. */
body.has-battle .appbar--game { margin-right: 0; }

body.has-battle .toast {
  left: calc((100% - var(--bl-right)) / 2);
  bottom: calc(max(22px, var(--gap-b)) + var(--bl-bottom));
}

/* ------------------------------------------------------ waiting messages */

/* THE WAITING MESSAGE, one look everywhere something is still on its way: the
   battle log while its socket connects, the home page while the sign in is
   checked, the garage while it loads, and Deploy while the battle answers.
   Asked for 2026-09-13: "put a waiting message up while the socket is
   connecting and show the rest of the page". A pulsing amber square, in the
   same shape as the status dots, and a plain sentence. Red when it has gone
   wrong rather than slow. */
.wait {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--fg-2);
}
.wait::before {
  content: "";
  flex: none;
  width: 8px;
  height: 8px;
  background: var(--warn);
  animation: wait-pulse 1.1s ease-in-out infinite;
}
.wait[data-kind="down"]::before { background: var(--bad); animation: none; }

@keyframes wait-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.25; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
  /* An infinite animation squeezed to 0.01ms strobes. Hold the square still. */
  .wait::before { animation: none !important; }
}
