/* thotsplat.com — landing page
   Mono instrument styling borrowed from the WX Engine; palette taken from the
   background art rather than the mockup: lime is the scenery and the UI, and
   magenta belongs to the brain and everything it does on impact. */

:root {
  --ink:        #00101a;   /* the sky colour, sampled out of bg.jpg */
  --lime:       #8bff4a;
  /* THE PLAYER. Magenta in the WORLD and on the one control that maps to a
     world action. Lime stays the site's brand accent and means nothing about
     allegiance -- see README, "the palette". */
  --player:     #ff3ea5;
  --lime-dim:   #57a832;
  --magenta:    #ff2bd1;
  --text:       #e6f2ec;
  --muted:      #6f8d86;
  --rule:       rgba(139, 255, 74, 0.28);
  /* THE BAR'S OWN CHASSIS, a step up from --rule so the perimeter reads over
     the mountain, and a step down again inside it so the box is the first
     thing the eye gets and the divisions are the second. */
  --chassis:    rgba(139, 255, 74, 0.42);
  --chassis-in: rgba(139, 255, 74, 0.24);
  /* DARKER THAN IT WAS, AND THE NUMBER IS MEASURED.

     At 0.72 the bar's own ground over the valley glow -- the brightest thing
     on the map, and directly behind the bar -- sits at luma 0.084, and the
     chassis stroke on top of it manages 2.52:1 against a 3:1 bar. Raising
     the STROKE to clear it needs about 0.65 alpha, which is a loud line on a
     control bar that is supposed to be defined and quiet. Darkening the
     ground costs one step of translucency and fixes the labels, the values
     and the perimeter at once. Same lever the hint's chip pulled, for the
     same reason, measured the same way. */
  --bar-bg:     rgba(0, 14, 22, 0.88);

  --mono: "JetBrains Mono", ui-monospace, "SF Mono", "Cascadia Mono", "Roboto Mono", Menlo,
          Consolas, "DejaVu Sans Mono", monospace;

  /* safe-area aware gutters */
  --gx: max(18px, env(safe-area-inset-left));
  --gb: max(14px, env(safe-area-inset-bottom));
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow: hidden;
  background: var(--ink);
  color: var(--text);
  font-family: var(--mono);
  -webkit-text-size-adjust: 100%;
  overscroll-behavior: none;
}

body {
  height: 100dvh;
  position: relative;
  /* the canvas owns every gesture inside the scene */
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
}

#scene {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* ---------------------------------------------------------------- header */

.top {
  position: fixed;
  top: max(16px, env(safe-area-inset-top));
  left: var(--gx);
  right: var(--gx);
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  pointer-events: none;   /* the scene beneath stays draggable */
  z-index: 3;
}

.brand h1 {
  margin: 0;
  font-size: clamp(19px, 3.6vw, 34px);
  font-weight: 600;
  letter-spacing: 0.20em;
  line-height: 1.05;
  color: #fff;
  text-shadow: 0 0 22px rgba(0, 16, 26, 0.95), 0 2px 6px rgba(0, 0, 0, 0.7);
}
.brand .tld { color: var(--lime); }

/* THE DATELINE, under the masthead and above the feed.

   It replaces the tagline, and it is a different KIND of line: the tagline
   was the site talking about itself, this is the war saying which battle you
   are looking at. So it takes the muted colour the tagline had -- it is not
   an army and not an instrument -- with the letter spacing opened up a step,
   which is what makes a line read as a dateline rather than as a sentence. */
.dateline {
  margin: 8px 0 0;
  font-size: clamp(9.5px, 1.25vw, 12px);
  letter-spacing: 0.16em;
  color: var(--muted);
  text-shadow: 0 0 14px rgba(0, 16, 26, 0.95);
  font-variant-numeric: tabular-nums;
}

/* AND A RULE UNDER IT, WHICH IS WHERE THE VOICES CHANGE.

   The site's voice and the war's voice ran straight together: masthead,
   tagline, then three lime dispatches, all on one left rail with nothing
   between them. A masthead block and a running report are two speakers.

   A hairline, at the width the dateline occupies rather than across the
   whole rail -- a rule that runs past the text it divides reads as a border
   round the page. It is drawn in the HUD's own lime at whisper opacity, the
   same family as the bar's chassis, so the two rules on the page are
   obviously the same rule. */
.dateline::after {
  content: '';
  display: block;
  height: 1px;
  margin-top: 9px;
  background: linear-gradient(to right,
              rgba(139, 255, 74, 0.34), rgba(139, 255, 74, 0.05) 78%,
              rgba(139, 255, 74, 0));
}

.nav {
  display: flex;
  gap: clamp(12px, 2.4vw, 30px);
  pointer-events: auto;
  padding-top: 4px;
}

.navlink {
  appearance: none;
  background: none;
  border: 0;
  padding: 4px 2px;
  font: inherit;
  font-size: clamp(10px, 1.35vw, 13px);
  letter-spacing: 0.16em;
  color: var(--text);
  cursor: pointer;
  text-shadow: 0 0 14px rgba(0, 16, 26, 0.95);
  border-bottom: 1px solid transparent;
}
.navlink:hover,
.navlink:focus-visible { color: var(--lime); border-bottom-color: var(--rule); }

/* ------------------------------------------------------------ target pins */

.pins {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 2;
}

.pin {
  position: absolute;
  transform: translate(-50%, -100%);
  pointer-events: auto;
  white-space: nowrap;
  font-size: clamp(9.5px, 1.15vw, 12.5px);
  letter-spacing: 0.15em;
  text-decoration: none;
  color: var(--text);
  padding: 4px 7px;
  border: 1px solid transparent;
  /* A label can land over the bright white ridge, where a text-shadow alone
     loses the first letter or two. The pill keeps it readable over anything
     in the scene without reading as a button. */
  background: rgba(0, 16, 26, 0.55);
  text-shadow: 0 0 10px rgba(0, 16, 26, 0.98), 0 0 22px rgba(0, 16, 26, 0.8);
}
.pin:hover,
.pin:focus-visible {
  color: var(--lime);
  border-color: var(--rule);
  background: rgba(0, 16, 26, 0.85);
}
/* decoys read as inactive; hitting one is its own reward */
.pin.decoy { color: var(--muted); }
.pin.hit { animation: pinhit 0.5s ease-out; }
@keyframes pinhit {
  0%   { color: #fff; text-shadow: 0 0 18px var(--magenta); }
  100% { color: inherit; }
}

/* ------------------------------------------------------------ instruments */

.loaded {
  position: fixed;
  left: var(--gx);
  bottom: calc(var(--gb) + 74px);
  margin: 0;
  font-size: clamp(10px, 1.25vw, 12.5px);
  letter-spacing: 0.08em;
  color: var(--muted);
  text-shadow: 0 0 14px rgba(0, 16, 26, 0.95);
  pointer-events: none;
  z-index: 3;
}
.loaded span { color: var(--text); }

/* THE STAT BOX. Two columns, one army each; one figure a row.

   IT WAS A DOT-SEPARATED LINE and it had run out of room. Every concession
   that line made, it made for WIDTH -- and all of them are measured, in this
   file's own history: it reached across the lit valley floor until the
   contrast guard read GREEN at 1.15:1 against a 3:1 bar, so it took a chip;
   it compressed shots to "2.6k" because five digits cost too much; and it
   said "up" and "out" because "in position" and "sorties" did not fit.

   A COLUMN IS AS WIDE AS ITS WIDEST NUMBER, not as wide as the sum of all of
   them. So all three concessions come back: the words are the right words,
   the shots keep every digit, and the box is NARROWER than the line it
   replaced even carrying two more characters of label.

   IT KEEPS THE CHIP. Not for width any more -- it is over the dark left rail
   now -- but because the ground is what the contrast guard measures through,
   and a box of small figures over a scene that moves needs one more than a
   line of text did.

   NOT LIME. Lime is this page's HUD colour: the brand, the live target. These
   are the ARMIES, so the figures take the armies' own greens and blues, a
   step deeper than the interface. The labels are the page speaking, so they
   take the muted grey every other label on the page takes. */
.score {
  position: fixed;
  left: var(--gx);
  bottom: calc(var(--gb) + 96px);
  margin: 0;
  display: grid;
  /* label, then two number columns sized to their own content -- so the box
     grows with a five-digit shot count and not before */
  grid-template-columns: auto auto auto;
  column-gap: 13px;
  align-items: baseline;
  padding: 6px 10px 7px;
  background: rgba(0, 14, 22, 0.90);
  font-size: clamp(8.5px, 0.92vw, 10px);
  letter-spacing: 0.045em;
  line-height: 1.62;
  text-shadow: 0 0 3px rgba(0, 14, 22, 0.98), 0 0 6px rgba(0, 14, 22, 0.95),
               0 0 14px rgba(0, 16, 26, 0.95);
  pointer-events: none;
  z-index: 3;
}
/* the two army names, over their own columns */
.sc-h {
  letter-spacing: 0.15em;
  padding-bottom: 4px;
}
.sc-h.s-green, .sc-h.s-blue { text-align: right; }
.sc-lab {
  color: var(--muted);
  letter-spacing: 0.10em;
  white-space: nowrap;
  padding-right: 4px;
}
/* TABULAR FIGURES, RIGHT ALIGNED. Digits that do not line up in a column are
   the whole reason a column is worth having; JetBrains Mono is monospaced, so
   this is belt and braces for the fallback stack. */
.sc-n {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* A RULE UNDER THE TWO ROWS THAT COUNT MEN.

   Above it: how many were put in, and how many are standing. Below it: what
   they did. That is a real division and the reader needs it, because the two
   groups are different KINDS of number -- IN POSITION is this second, and
   every figure under the rule is a running total for the battle. A reader
   unable to tell those apart is the census finding that started all of this.

   ONE ELEMENT ACROSS THE GRID. A border-bottom on the three cells of the row
   was the obvious version and it was wrong on sight: the column gaps break it
   into three separate dashes, which reads as three underlines. */
.sc-rule {
  grid-column: 1 / -1;
  border-bottom: 1px solid rgba(139, 255, 74, 0.16);
  margin: 3px 0 4px;
}

/* The feed sits above the scorecard: the same left rail, the same plain text,
   no box. Lime rather than either army's colour, because it is the page
   reporting rather than a side claiming. */
/* IT LIVES IN THE SKY. It began under the scorecard on the bottom-left rail,
   where three long lines reach across the lit face of the green mountain: the
   guard measured 1.74:1 at its widest, against 3.08:1 for whatever it happened
   to be saying at the time. The sky band is the darkest thing on the page and
   the only place a lime line can be relied on to read -- and a running report
   belongs up there anyway, where a ticker goes. */
.feed {
  position: fixed;
  left: var(--gx);
  /* PAST THE DATELINE'S RULE. The rail used to start straight under the
     tagline; the dateline is the same height but now carries a hairline under
     it, and at 5.4em the first dispatch landed ON that rule. The gap is what
     makes the rule a divider rather than an underline. */
  top: calc(max(16px, env(safe-area-inset-top)) + 6.8em);
  margin: 0;
  font-size: clamp(9.5px, 1.15vw, 11.5px);
  letter-spacing: 0.10em;
  line-height: 1.55;
  color: var(--lime);
  text-shadow: 0 0 14px rgba(0, 16, 26, 0.95);
  /* THE LINES TAKE POINTER EVENTS, the rail does not. Hovering a line pulses
     a marker at the place it happened, so the spans have to be hittable --
     but the paragraph box around them stretches across the sky and must not
     eat clicks meant for the scene. */
  pointer-events: none;
  z-index: 3;
  max-width: min(46ch, 52vw);
}
/* IT WRAPS. IT DOES NOT TRUNCATE.

   This was `nowrap` with `text-overflow: ellipsis`, and the ellipsis ate the
   payoff word: "GREEN POSITION UNDER FIRE AT THE HAIRP..." defeats the whole
   named-terrain system, because the name is the only part of the line that
   says WHERE. Losing the last three characters of a place name is worse than
   losing a line.

   A wrapped continuation is indented past the first word, so a two-line entry
   reads as one entry rather than as two lines that happen to be adjacent. The
   suite asserts that no line the feed can generate is clipped -- worst prefix
   against the longest name in the vocabulary, measured in the real box. */
/* AND EACH LINE CARRIES A TICK. A dispatch is a separate utterance from the
   masthead above it and from the one before it, and on a left rail with no
   box the only thing saying so was the line break. A short lime tick at the
   left edge does it without adding chrome -- the same mark a wire service
   puts in front of a filed item, and the same colour family as the rule under
   the dateline and the bar's chassis. It sits in the padding the hanging
   indent already reserves, so a wrapped continuation has no tick and reads as
   part of the line above it, which is exactly the distinction wanted. */
.feed span { display: block; white-space: normal; overflow: visible;
             overflow-wrap: break-word; padding-left: 2em;
             text-indent: -1em; pointer-events: auto; cursor: default;
             position: relative; }
/* The tick sits in a gutter the hanging indent makes room for: the span is
   padded two ems and the first line pulled back one, so the first line starts
   at 1em, a wrapped continuation at 2em, and the tick has the leading half-em
   to itself. Putting it at the box edge with a 1.1em hanging indent -- which
   is where it went first -- laid it straight under the first character. */
.feed span::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 6px;
  height: 1px;
  background: currentColor;
  opacity: 0.6;
}
.feed span.old { opacity: 0.55; }
.feed span.lit { opacity: 1; color: #fff; }
.s-green { color: #74c23f; }
.s-blue  { color: #93c4dd; }

.call {
  appearance: none;
  border: 0;
  border-left: 1px solid var(--chassis-in);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: clamp(11px, 1.4vw, 13px);
  letter-spacing: 0.14em;
  padding: 0 clamp(10px, 1.6vw, 18px);
  cursor: pointer;
}
.call:hover, .call:focus-visible { background: rgba(255,255,255,0.06); }
.call[disabled] { opacity: 0.4; cursor: default; }
/* RELOADING is not the same as LOCKED. Locked means something is already in
   the air; reloading means this particular asset is still coming back, and it
   has to be visibly a state of THIS button rather than of the whole bar --
   there is no ammunition counter anywhere on this page, so the button is the
   only place the player can read scarcity. */
.call.reloading[disabled] { opacity: 0.62; }
.call.reloading[disabled]::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2px;
  background: var(--lime);
  opacity: 0.5;
  transform-origin: left center;
  transform: scaleX(var(--reload, 0));
}
.call { position: relative; overflow: hidden; }

/* ------------------------------------------------------- the split buttons

   ALLEGIANCE IS CHOSEN IN THE TAP. Left half green, right half blue, and
   which one you are pressing is the same information as who it is for -- so
   there is no mode to be wrong about.

   PALE AND TRANSLUCENT. Each half is a washed, see-through tint of its army's
   colour: the terrain breathes behind the bar rather than the bar being a
   panel laid over it. Saturated fills here would make the bar the loudest
   thing on a page whose whole subject is behind it.

   THE LABEL CARRIES THE CONTRAST, NOT THE FILL. A translucent wash over the
   white mountain is exactly the trap the muzzle flash fell into twice, so the
   text is near-white with a dark halo under it and the visibility guard
   measures it against the brightest thing the scene can put there. The fill
   is allowed to be quiet because it is not what has to be read. */
/* SIZED TO THE WORD, not to a fixed width. A split button carries the same
   label the plain one did, so it needs the same room -- and asking for a
   minimum in vw crowded the DEPTH and ETA cells until "CREST" and "8.0s" ran
   into each other at desktop width. It takes its share like everything else
   in the bar and its own label sets the floor. */
/* THE LABEL SIZES THE BUTTON; THE HALVES SIT BEHIND IT.

   The other way round -- halves in flow, label floated over them -- gave the
   button no intrinsic width, so it took whatever the flex row would give and
   crowded the DEPTH and ETA cells until "CREST" and "8.0s" ran into each
   other. The word is what a split button is as wide as, exactly as it was
   when it was a plain one, so the word is in flow and the two coloured halves
   are laid underneath it. */
.call.split {
  padding: 0 clamp(10px, 1.6vw, 18px);
  display: block;
  position: relative;
}
.call.split .lab2 {
  position: relative;
  display: block;
  line-height: 1;
  padding: 13px 0;
  text-align: center;
  white-space: nowrap;
  pointer-events: none;
  z-index: 2;
  color: #f2f7f4;
  /* the halo is what carries it over a lit slope; see the guard */
  text-shadow: 0 0 3px rgba(0,14,10,0.95), 0 0 8px rgba(0,14,10,0.85),
               0 1px 0 rgba(0,14,10,0.9);
}
.call.split .half {
  appearance: none;
  border: 0;
  position: absolute;
  top: 0; bottom: 0;
  width: 50%;
  background: transparent;
  cursor: pointer;
  font: inherit;
  padding: 0;
  z-index: 1;
}
.call.split .half.green { left: 0; }
.call.split .half.blue { right: 0; }
/* AND THE TWO HALVES ARE DIVIDED, which is the difference between a pair of
   buttons and a tint that happens to change colour half way across. Over the
   valley floor -- pale ground, and where the bar sits -- the green and blue
   washes are close enough in luminance that the boundary between them
   disappeared entirely and the whole control read as one soft smear. The
   divider is the chassis rule, so it belongs to the same box. */
.call.split .half.blue { border-left: 1px solid var(--chassis-in); }
.call.split .half.green { background: rgba(124, 220, 80, 0.14); }
.call.split .half.blue  { background: rgba(130, 190, 245, 0.14); }
.call.split .half.green:hover, .call.split .half.green:focus-visible {
  background: rgba(124, 220, 80, 0.30);
}
.call.split .half.blue:hover, .call.split .half.blue:focus-visible {
  background: rgba(130, 190, 245, 0.30);
}
/* ARMED: the side you are about to call for, held while the reticle is that
   colour, so the bar and the reticle say the same thing. */
.call.split .half.armed.green { background: rgba(124, 220, 80, 0.42); }
.call.split .half.armed.blue  { background: rgba(130, 190, 245, 0.42); }
/* ...AND THE STATES STILL READ THROUGH THE PALLOR. Inbound and cooling are
   properties of the whole asset, not of one half, so they dim both. */
.call.split.reloading .half { opacity: 0.5; }
.call.split.locked .half { opacity: 0.3; cursor: default; }
.call.split.locked .lab2, .call.split.reloading .lab2 { opacity: 0.62; }
/* the reload sweep belongs to the asset, so it runs under both halves */
.call.split.reloading::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2px;
  background: var(--lime);
  opacity: 0.5;
  transform-origin: left center;
  transform: scaleX(var(--reload, 0));
  z-index: 3;
}
/* BRAIN stays saturated: the one vivid thing on the bar, and the one thing
   that serves nobody. */
.call.fire { color: var(--player); }

.bar {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: var(--gb);
  display: flex;
  align-items: stretch;
  background: var(--bar-bg);
  backdrop-filter: blur(7px);
  -webkit-backdrop-filter: blur(7px);
  /* THE CHASSIS.

     The bar had a perimeter already and the cells were divided by a rule at
     0.16 alpha, which is below the threshold at which a thin line reads as a
     line over busy terrain -- and the SPLIT BUTTONS had no divider at all, so
     three of the six cells floated as unframed tints against the mountain.
     A command bar that a viewer has to infer the edges of is not a chassis.

     Defined, not loud: the perimeter is the same lime-family rule the
     dateline's hairline uses, one pixel, and the internal dividers are the
     same colour a step quieter so the outside of the box reads before the
     inside of it. Every one of them is measured -- see the chassis case in
     the visibility block -- against the brightest thing the scene can put
     behind the bar. */
  border: 1px solid var(--chassis);
  box-shadow: 0 0 0 1px rgba(0, 14, 22, 0.55);
  z-index: 4;
  /* NARROWER NOW THAT WIND IS GONE. The cells are flex:1 and the calls are
     their own width, so retiring a cell handed its whole width to DEPTH and
     ETA and squeezed the three split buttons until their labels touched the
     edges of their own tints. The freed width goes to the chassis work, which
     means giving it back to the box rather than to the two cells that
     happened to be elastic. */
  width: min(688px, calc(100vw - 2 * var(--gx)));
}

.cell {
  flex: 1 1 0;
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 9px;
  padding: 13px 8px;
  /* CAPPED, so the two instruments do not eat the three controls. With WIND
     retired the cells are the only elastic things in the bar and they took
     the whole freed width between them. */
  max-width: 172px;
  border-right: 1px solid var(--chassis-in);
  min-width: 0;
}

.lab {
  font-size: clamp(9px, 1.1vw, 11px);
  letter-spacing: 0.17em;
  color: var(--muted);
}
.val {
  font-size: clamp(14px, 1.9vw, 20px);
  color: #fff;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.03em;
}
.val .unit { font-size: 0.7em; color: var(--muted); }

.fire {
  appearance: none;
  flex: 0 0 auto;
  min-width: clamp(96px, 15vw, 150px);
  padding: 13px 20px;
  font: inherit;
  font-size: clamp(14px, 1.9vw, 20px);
  letter-spacing: 0.22em;
  color: var(--player);
  background: rgba(255, 62, 165, 0.10);
  border: 0;
  border-left: 1px solid var(--rule);
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.fire:hover:not(:disabled),
.fire:focus-visible:not(:disabled) { background: rgba(255, 62, 165, 0.24); color: #fff; }
.fire:active:not(:disabled) { background: rgba(255, 62, 165, 0.36); }
.fire:disabled { color: var(--muted); background: none; cursor: default; }

/* THE HINT GETS THE SAME DARK GROUND THE BAR HAS.

   It was the only text on the page sitting on the raw scene: no chip, no box,
   muted grey at 75% opacity, floating just above the bar. Over the valley
   floor -- which is the brightest ground on the map and is directly behind
   it -- it disappeared. Every other text element on this page is either on
   the bar's own dark ground or measured by the visibility guard; this one was
   neither, which is why it was the last thing anybody noticed.

   A chip of the bar's own background rather than a new colour, so it reads as
   part of the same instrument stack, and it is now IN the guard: see the hint
   case in the visibility block. */
.hint {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(var(--gb) + 56px);
  margin: 0;
  font-size: 10.5px;
  letter-spacing: 0.1em;
  color: var(--text);
  opacity: 0.92;
  pointer-events: none;
  white-space: nowrap;
  z-index: 3;
  transition: opacity 0.5s;
  padding: 5px 12px;
  /* A DENSER GROUND THAN THE BAR'S, and the difference is measured. The bar
     is a tall box of large text and 0.72 alpha is plenty behind it; the hint
     is one thin line of small text and it sits over the valley floor, the
     brightest ground on the map, where 0.72 leaves a ground at luma 0.30 and
     the line measures 2.57:1 against a 3:1 bar. At 0.90 the ground is 0.14
     and the line clears it with room. See the hint case in the visibility
     block, which composites this alpha over the worst scene pixel rather than
     taking anybody's word for it. */
  background: rgba(0, 14, 22, 0.90);
  backdrop-filter: blur(7px);
  -webkit-backdrop-filter: blur(7px);
  border: 1px solid var(--chassis-in);
}
.hint.gone { opacity: 0; }

.status {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(var(--gb) + 60px);
  margin: 0;
  font-size: clamp(10.5px, 1.3vw, 13px);
  letter-spacing: 0.13em;
  color: var(--magenta);
  text-shadow: 0 0 16px rgba(0, 16, 26, 0.95);
  pointer-events: none;
  white-space: nowrap;
  z-index: 3;
  opacity: 0;
  transition: opacity 0.2s;
}
.status.show { opacity: 1; }

/* ----------------------------------------------------------------- panel */

.panel {
  position: fixed;
  inset: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(0, 8, 14, 0.82);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}
.panel[hidden] { display: none; }

.panel-box {
  position: relative;
  width: min(520px, 100%);
  max-height: 80vh;
  overflow: auto;
  padding: 26px 26px 22px;
  background: rgba(0, 16, 26, 0.96);
  border: 1px solid var(--rule);
}
.panel-box h2 {
  margin: 0 0 18px;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.22em;
  color: var(--lime);
}
.panel-x {
  position: absolute;
  top: 10px;
  right: 12px;
  appearance: none;
  background: none;
  border: 0;
  font: inherit;
  font-size: 24px;
  line-height: 1;
  color: var(--muted);
  cursor: pointer;
  padding: 4px 8px;
}
.panel-x:hover, .panel-x:focus-visible { color: var(--lime); }

.plist { list-style: none; margin: 0; padding: 0; }
.plist li { padding: 12px 0; border-top: 1px solid rgba(139, 255, 74, 0.14); }
.plist li:first-child { border-top: 0; }
.plist a {
  color: #fff;
  letter-spacing: 0.15em;
  font-size: 13px;
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
}
.plist a:hover, .plist a:focus-visible { color: var(--lime); }
.plist .dead { color: var(--muted); letter-spacing: 0.15em; font-size: 13px; }
.plist span {
  display: block;
  margin-top: 6px;
  font-size: 11.5px;
  letter-spacing: 0.04em;
  color: var(--muted);
}
.pbody p {
  margin: 0 0 13px;
  font-size: 12.5px;
  line-height: 1.75;
  letter-spacing: 0.03em;
  color: var(--text);
}
.pbody p:last-child { margin-bottom: 0; }
.pbody a { color: var(--lime); }
.pnote {
  margin: 16px 0 0;
  font-size: 11px;
  color: var(--muted);
  letter-spacing: 0.05em;
}

/* --------------------------------------------------------- narrow screens */

@media (max-width: 620px) {
  .bar {
    flex-wrap: wrap;
    width: calc(100vw - 2 * var(--gx));
  }
  /* HALF EACH, BECAUSE THERE ARE TWO OF THEM. This said 33.333% from when
     the bar had three instruments; WIND was retired and nobody resized the
     survivors, so DEPTH and ETA took two thirds of the row and the TROOPS
     button climbed up to fill the gap. The bar came out as [DEPTH ETA TROOPS]
     / [PARA AIR] / [BRAIN], which is only visible on a phone and was hiding
     under the mountain until the readouts moved into a sheet. */
  .cell { padding: 10px 4px; gap: 6px; flex-basis: 50%; }
  .cell:nth-child(2) { border-right: 0; }
  /* THE THREE SPLIT BUTTONS GET THEIR OWN ROW.

     They are `display: block` so the label can size them, which means they no
     longer take a flex basis from `.call` -- and on a phone they collapsed to
     their padding and disappeared between the instrument row and BRAIN. The
     whole bar was DEPTH / ETA / WIND and a brain, with no way to call
     anything. Photographed at 390px, and it is why the bar is measured at
     both widths rather than at the one the desktop harness uses. */
  .call.split {
    flex: 1 0 33.333%;
    padding: 0;
    border-top: 1px solid var(--chassis-in);
  }
  .call.split:nth-of-type(1) { border-left: 0; }
  .call.split .lab2 { padding: 13px 0; }
  .fire {
    flex: 1 0 100%;
    min-width: 0;
    border-left: 0;
    border-top: 1px solid var(--chassis-in);
    padding: 15px 10px;
    font-size: 17px;
  }
  /* the bar is three rows on a phone now -- instruments, the three split
     buttons, and BRAIN -- so everything stacked above it moves up with it.
     At the old offsets the OBJECTIVE line sat behind the buttons. */
  .loaded { bottom: calc(var(--gb) + 162px); font-size: 10.5px; }
  .score { bottom: calc(var(--gb) + 184px); font-size: 9.5px; }
  /* THE HINT WRAPS ON A PHONE. It is nowrap by design at desktop, where the
     one line is shorter than the bar; with a chip round it, nowrap at 390px
     is a BOX running off both edges of the screen, which is worse than the
     text running off was. Photographed at 390 and fixed there. */
  .hint, .status { bottom: calc(var(--gb) + 138px); font-size: 10.5px; }
  .hint {
    /* ONE LINE, SMALLER, rather than a wrapped block. Wrapping it was tried
       first and the chip became three lines tall, which covered the
       scorecard and the OBJECTIVE line -- a hint that hides the state is a
       worse trade than a hint that is hard to read. At this size the line is
       about 340px against 362px of usable width, measured at 390. */
    font-size: 8.5px;
    letter-spacing: 0.04em;
    padding: 5px 9px;
  }
  .pin { font-size: 10px; letter-spacing: 0.1em; padding: 3px 5px; }
}

@media (max-height: 460px) {
  /* landscape phone: reclaim the vertical */
  .loaded { display: none; }
  .hint { display: none; }
  .cell { padding: 8px 6px; }
  .fire { padding: 8px 16px; }
}

@media (prefers-reduced-motion: reduce) {
  .pin.hit { animation: none; }
  .hint, .status { transition: none; }
}

/* THE BUTTONS ARE LABELS. Per-side numbers lived in the halves for one
   round and moved to the scorecard: a button is a control, and figures on a
   control are hard to read where figures on the card are not. */
.call.split .lab2 { padding: 13px 0; }

/* ============================================================ battle first

   ON A PHONE THE BATTLE GETS THE WHOLE SCREEN.

   Kyle's ruling, from watching the live site on a phone: everything that is
   not the war -- the stat box, the objective line, the feed, the command bar
   -- is off by default, and one button pulls the lot up as a sheet. A phone
   visitor is a WATCHER, not a commander, so the bar comes up with the rest of
   it rather than sitting across the mountain.

   ONE COPY OF EVERY READOUT, TWO LAYOUTS. #readouts wraps them all and is
   display:contents everywhere else, so on a desktop it is not a box at all
   and every child keeps the fixed position it always had. The desktop layout
   is not touched by anything in here -- which is the ruling, and is also why
   this is one media block and not a rewrite.

   THE SHEET IS NOT `hidden`. The attribute would hide the readouts at every
   width, including the desktop where they must always be up. It is a class on
   <body> that only this block reads, so a wide viewport cannot be affected by
   the state of a control it does not display. */
#readouts { display: contents; }
.readouts-toggle { display: none; }

@media (max-width: 620px) {
  /* the wrapper becomes a real box, and its children come out of fixed
     positioning and stack inside it in DOM order */
  #readouts {
    display: flex;
    flex-direction: column;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    max-height: 82vh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 14px var(--gx) calc(var(--gb) + 8px);
    background: rgba(0, 12, 19, 0.95);
    backdrop-filter: blur(9px);
    -webkit-backdrop-filter: blur(9px);
    border-top: 1px solid var(--chassis);
    z-index: 6;
    /* IT SLIDES, IT DOES NOT APPEAR. A sheet that pops is a thing that
       happened to you; a sheet that slides is a thing you opened. */
    transform: translateY(101%);
    transition: transform 0.22s ease-out;
  }
  body.readouts-open #readouts { transform: translateY(0); }

  /* Every child goes back into normal flow. The desktop rules pin these to
     fixed corners of the viewport; inside the sheet they are a stack. */
  #readouts > * {
    position: static;
    transform: none;
    left: auto;
    right: auto;
    top: auto;
    bottom: auto;
    margin: 0 0 12px;
    max-width: 100%;
    pointer-events: auto;
  }
  #readouts > *:last-child { margin-bottom: 0; }

  /* THE ORDER IN THE SHEET IS THE ORDER OF THE QUESTIONS. What is this
     battle (the box), what just happened (the feed), what am I aiming at
     (the objective), and only then the controls. The DOM order is the
     desktop's, so the sheet reorders rather than the markup moving. */
  #readouts > .score  { order: 1; }
  #readouts > .feed   { order: 2; }
  #readouts > .loaded { order: 3; }
  #readouts > .bar    { order: 4; }
  #readouts > .hint   { order: 5; }

  .score {
    /* full width in the sheet, and the two army columns pushed apart so the
       figures are not crowded against their labels on a narrow screen */
    display: grid;
    grid-template-columns: 1fr auto auto;
    column-gap: 26px;
    font-size: 11.5px;
    padding: 9px 11px 10px;
    background: rgba(0, 18, 28, 0.72);
  }
  .feed { font-size: 11px; max-height: 26vh; overflow: hidden; }
  .loaded { font-size: 11px; }
  .bar { width: 100%; }
  /* THE HINT WRAPS IN THE SHEET. It is nowrap by design -- one line under a
     bar that is wider than it -- and inside a 354px sheet that means the last
     third of the sentence is simply cut off. Photographed exactly like that.
     It has the sheet 's own dark ground under it, so it needs no chip. */
  .hint {
    font-size: 9.5px;
    opacity: 0.75;
    padding: 0;
    background: none;
    white-space: normal;
    line-height: 1.5;
  }

  /* THE ONE BUTTON.

     Bottom right, small, and out of the way of the mountain -- the corner the
     scene has least in it. It is the only interface on a phone until it is
     pressed, so it has to be findable and must not be a banner: a chip with
     the page's own chassis on it, in the page's own lime, at the size of a
     tap target and no bigger. */
  .readouts-toggle {
    display: block;
    position: fixed;
    right: var(--gx);
    bottom: var(--gb);
    z-index: 7;
    margin: 0;
    /* 45px tall, measured. It is the only control on the screen until it is
       pressed, so it has to clear the 44px tap target every other target on
       this page is held to -- the suite caught the first version at 36px
       tall, which is unobtrusive in the wrong way. At 93x45 it is still one
       per cent of the screen. */
    padding: 15px 16px;
    font: inherit;
    font-size: 10.5px;
    letter-spacing: 0.16em;
    color: var(--lime);
    background: var(--bar-bg);
    backdrop-filter: blur(7px);
    -webkit-backdrop-filter: blur(7px);
    border: 1px solid var(--chassis);
    border-radius: 3px;
    cursor: pointer;
    transition: transform 0.22s ease-out;
  }
  /* IT RIDES UP WITH THE SHEET rather than being covered by it, so the thing
     that opened the sheet is the thing that closes it and it never has to be
     hunted for. */
  body.readouts-open .readouts-toggle {
    transform: translateY(calc(-1 * var(--sheet-h, 40vh)));
  }

  /* A SCRIM, so a tap anywhere off the sheet closes it, and so the sheet has
     something to sit against rather than floating on the battle. */
  .readouts-scrim {
    position: fixed;
    inset: 0;
    z-index: 5;
    background: rgba(0, 8, 14, 0.45);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.22s ease-out;
  }
  body.readouts-open .readouts-scrim { opacity: 1; pointer-events: auto; }

  /* THE STATUS LINE STAYS OUT OF THE SHEET. It is a momentary confirmation of
     something the viewer just did, and it belongs over the battle they did it
     to -- but it must not land under the button. */
  .status { bottom: calc(var(--gb) + 58px); }
}

/* A LANDSCAPE PHONE IS NOT A PHONE WIDTH. 844x390 is wider than the
   breakpoint above, so it keeps the desktop layout and the existing
   short-viewport rules below. Noted rather than solved: the ruling says
   phone WIDTHS, and a landscape phone has a desktop's width and a
   letterbox's height, which is a different problem. */
