/*
 * Console hardware.
 *
 * Everything in this file exists to make the page feel like a manufactured
 * object rather than a web page: painted steel with a grain to it, a screwed
 * bezel around the tube, bat-handle toggle switches, domed indicator lamps,
 * rotary selectors with real detents, and engraved legend plates carrying
 * Trans-Mordovian nomenclature over an English gloss.
 *
 * No images are used. The grain is an inline SVG turbulence, the metal is
 * layered gradients, and the screws are radial gradients — so the whole console
 * still loads instantly and stays a static site.
 *
 * Not one colour is written here. The painted-steel palette and the fixed
 * parts — lever, launch cap, lamp filament — are declared in theme.css with
 * everything else, so a theme is one block of one file.
 */

/* Paint grain. One inline turbulence, reused by everything metal. */
.metal,
.topbar,
.panel,
.ticker,
.bezel {
  background-color: var(--metal);
  background-image:
    linear-gradient(180deg, rgba(255, 255, 255, .10) 0%, rgba(255, 255, 255, .02) 14%,
      rgba(0, 0, 0, .06) 70%, rgba(0, 0, 0, .16) 100%),
    url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
  background-blend-mode: normal, overlay;
}

/* ------------------------------------------------------------- engraving */

/*
 * A control legend is one English line, cut deep enough to read at arm's
 * length. It used to be two — the Cyrillic stencil over its gloss — which on a
 * cap 92 px wide meant two 9px lines saying one thing, and inside a switch
 * meant four. The Cyrillic moved to the plates, where a plate can be read at
 * leisure; what is left here got the space back, as size.
 *
 * `i` is the quiet second line, used where a cap carries its state under its
 * verb: the launch cap says what it is waiting for, and the nomenclature
 * plates put their gloss under the stencil.
 */
.lg { display: inline-flex; flex-direction: column; line-height: 1.15; }
.lg b {
  font-weight: 700; letter-spacing: .08em; font-size: 11px;
  color: var(--engrave);
  text-shadow: 0 1px 0 rgba(0, 0, 0, .65), 0 -1px 0 rgba(255, 255, 255, .07);
}
.lg i {
  font-style: normal; font-size: 9px; letter-spacing: .1em;
  color: var(--engrave-dim);
}
.lg-stack { align-items: center; text-align: center; }

.data-plate {
  font-size: 7.5px; letter-spacing: .08em; line-height: 1.5; white-space: nowrap;
  color: var(--engrave-dim);
  border: 1px solid var(--metal-edge);
  background: linear-gradient(180deg, var(--metal-hi), var(--metal-lo));
  padding: 3px 6px; border-radius: 1px;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04);
}
.data-plate b { color: var(--engrave); font-weight: 700; }

.placard {
  border: 2px solid var(--hostile);
  color: var(--hostile);
  font-size: 7.5px; letter-spacing: .12em; text-align: center;
  padding: 2px 5px; border-radius: 1px; line-height: 1.35;
  background: rgba(0, 0, 0, .3);
}

/* Screw heads, dropped into the corners of a panel or bezel. */
.screw {
  position: absolute; width: 10px; height: 10px; border-radius: 50%; z-index: var(--z-screw);
  background:
    linear-gradient(180deg, rgba(255,255,255,.35), rgba(0,0,0,.45)),
    radial-gradient(circle at 35% 30%, var(--screw), var(--metal-lo) 70%);
  box-shadow: inset 0 0 2px rgba(0, 0, 0, .8), 0 1px 1px rgba(0, 0, 0, .5);
}
/* The slot. Every screw is driven to a slightly different angle, as they are. */
.screw::after {
  content: ''; position: absolute; inset: 0; margin: auto;
  width: 6px; height: 1.4px; background: var(--metal-edge); border-radius: 1px;
  box-shadow: 0 1px 0 rgba(255, 255, 255, .12);
}
.screw.a::after { transform: rotate(22deg); }
.screw.b::after { transform: rotate(-58deg); }
.screw.c::after { transform: rotate(78deg); }
.screw.d::after { transform: rotate(-14deg); }

/* ---------------------------------------------------------------- bezel */

/* The tube sits in a frame, recessed behind glass. */
.bezel {
  position: relative;
  padding: 12px;
  border: 1px solid var(--metal-edge);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .08), inset 0 -2px 6px rgba(0, 0, 0, .5);
  min-height: 0;
  display: flex;
}
.bezel > .screw:nth-of-type(1) { top: 5px; left: 5px; }
.bezel > .screw:nth-of-type(2) { top: 5px; right: 5px; }
.bezel > .screw:nth-of-type(3) { bottom: 5px; left: 5px; }
.bezel > .screw:nth-of-type(4) { bottom: 5px; right: 5px; }

.tube {
  position: relative; flex: 1; min-width: 0; min-height: 0;
  /* The instrument, then its caption strip: see .scope-legend. */
  display: flex; flex-direction: column;
  border-radius: 10px;
  overflow: hidden;
  background: var(--tube-face);
  box-shadow:
    inset 0 0 0 2px var(--metal-edge),
    inset 0 0 24px rgba(0, 0, 0, .9),
    0 0 0 1px rgba(255, 255, 255, .05);
}

/* -------------------------------------------------------------- switches */

/*
 * A bat-handle toggle. Up is on, and it stays up — the position of the lever is
 * the state, readable across the room, which is the entire reason real panels
 * use them instead of buttons.
 *
 * Both positions are engraved beside the lever, permanently: the legend column
 * is its own column, so the switch body can never be drawn across its own
 * caption (ИЗЛУ●ЕНЬ), and neither label moves when the switch is thrown.
 */
.sw {
  position: relative;
  display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center;
  gap: 7px; text-align: left;
  background: linear-gradient(180deg, rgba(255, 255, 255, .05), rgba(0, 0, 0, .14));
  border: 1px solid var(--metal-edge); border-radius: 3px;
  padding: 4px 16px 4px 5px; cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .06);
}
/* The legends are lozenges cut to their own words, not a column of full-width
   bars: an engraved position marking is the size of the marking. */
.sw-legends { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; min-width: 0; max-width: 100%; }
/*
 * One line per position, 11px on 15px of leading. It was four lines of 8px
 * type on 8.4px leading — the Cyrillic and its gloss, twice — inside a 34px
 * switch, and at 100% the two pairs printed into each other. This is the same
 * switch with half the words and twice the size.
 */
.sw-pos {
  display: block; font-weight: 700; font-size: 11px; line-height: 15px;
  letter-spacing: .07em; color: var(--engrave-dim);
  padding: 0 6px; border-radius: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  border: 1px solid transparent;
}
/*
 * The position the lever is standing in is a LIT WINDOW, not a colour.
 *
 * Colour alone did not survive its own background: the same aria-pressed
 * RADIATE read lit on the surveillance card and inert on the battery card
 * below it, because the two cards' fills are different and the green was the
 * only difference between on and off. A dark well with the ink glowing in it
 * reads the same on any panel this console has.
 */
.sw-pos.is-on {
  color: var(--good);
  background: rgba(0, 0, 0, .55);
  border-color: rgba(0, 0, 0, .5);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, .75), 0 0 0 1px rgba(255, 255, 255, .05);
  text-shadow: 0 0 7px color-mix(in srgb, var(--good) 55%, transparent);
}
.sw-body {
  position: relative; width: 17px; height: 26px; border-radius: 3px;
  background: linear-gradient(180deg, var(--metal-lo), var(--metal-edge));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .08), 0 1px 2px rgba(0, 0, 0, .6);
  border: 1px solid var(--metal-edge);
}
.sw-lever {
  position: absolute; left: 50%; width: 7px; height: 11px;
  margin-left: -3.5px; border-radius: 4px 4px 2px 2px;
  background: linear-gradient(180deg, var(--lever-hi), var(--lever-mid) 60%, var(--lever-lo));
  box-shadow: 0 1px 2px rgba(0, 0, 0, .7);
  transition: top .09s ease-out, transform .09s ease-out;
  top: 11px; transform-origin: 50% 100%;
}
.sw[aria-pressed="true"] .sw-lever { top: 3px; }
.sw[aria-pressed="false"] .sw-lever { top: 12px; transform: rotate(180deg); }
/* Under the pointer the body lifts a little, the way a switch you are about
   to throw catches the light. */
.sw:hover:not(:disabled) .sw-body { border-color: var(--screw); box-shadow: inset 0 1px 0 rgba(255,255,255,.18), 0 0 5px rgba(0, 0, 0, .5); }
.sw:hover:not(:disabled) .sw-lever { filter: brightness(1.12); }
/*
 * A switch with nothing behind it is dead metal, not a faint switch: the
 * plating goes grey, the lever loses its highlight, and the body sits flush
 * in its shroud. Opacity alone read as "the panel is dim", which on a console
 * that genuinely dims in a blackout is exactly the wrong reading.
 */
.sw:disabled { cursor: not-allowed; opacity: .6; }
.sw:disabled .sw-body { background: var(--metal-lo); box-shadow: inset 0 1px 3px rgba(0, 0, 0, .6); }
.sw:disabled .sw-lever { filter: grayscale(1) brightness(.6); box-shadow: none; }
.sw:disabled .sw-pos { color: var(--engrave-dim); text-shadow: none; }
.sw:disabled .sw-pos.is-on { color: var(--engrave-dim); background: rgba(0, 0, 0, .35); }
/* One ring, one shape, around the whole control — see the ring at the end of
   this file. The switch used to ring only its toggle graphic, in a different
   green, cutting across the caption inside the same control. */
/* The set that is cold with something in the sky: this switch IS the watch. */
.sw.is-urgent { border-color: var(--warn); animation: fire-ready 1.05s infinite; }
/* The I / O marks inside the bezel are gone with the state-labelled caption:
   both positions are now spelled out beside the lever, and the lever covered
   whichever mark it was standing on. */

/* ----------------------------------------------------------------- lamps */

/* Domed indicator, dark until it has something to say. Its caption is one
   English line — it used to be the Cyrillic over the gloss, two lines of 8px
   type to say READY. */
.lamp {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 9.5px; letter-spacing: .08em; color: var(--engrave-dim);
}
.lamp-cap b { font-weight: 700; }
.lamp-dome {
  width: 11px; height: 11px; border-radius: 50%; flex: 0 0 auto;
  background: radial-gradient(circle at 34% 30%, rgba(255,255,255,.28), var(--lamp-off) 62%);
  box-shadow: inset 0 0 3px rgba(0, 0, 0, .8), 0 1px 1px rgba(0, 0, 0, .4);
  border: 1px solid var(--metal-edge);
}
.lamp.is-lit .lamp-dome {
  background: radial-gradient(circle at 34% 30%, var(--lamp-filament), var(--lamp-colour, var(--accent)) 55%);
  box-shadow: 0 0 9px var(--lamp-colour, var(--accent)), inset 0 0 3px rgba(255, 255, 255, .6);
}
.lamp.is-lit { color: var(--engrave); }
.lamp.is-red { --lamp-colour: var(--hostile); }
.lamp.is-amber { --lamp-colour: var(--warn); }
.lamp.is-green { --lamp-colour: var(--good); }
.lamp.blinking.is-lit .lamp-dome { animation: lamp-blink .55s steps(1) infinite; }
@keyframes lamp-blink { 50% { box-shadow: none; background: radial-gradient(circle at 34% 30%, rgba(255,255,255,.2), var(--lamp-off) 62%); } }

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

/*
 * The key stencilled in the corner of the cap that answers to it.
 *
 * A fixed-width chip in the shroud's own corner rather than words appended to
 * the legend, so the key costs the caption no width, one control carries one
 * key, and every key on the console is printed in the same place in the same
 * type. Never wider than its own corner: it is furniture for the finger, not
 * a second caption.
 */
/*
 * One size of chip, in one corner, on every control that has a key.
 *
 * It was 7.5px type in an 11.8×12.5 box — below any floor a person can read —
 * and it grew with its legend, so the ⇧E chip on the FREE cap was wider and
 * darker than the Q and W beside it in the same rack. The box is fixed at the
 * widest single-key legend the console uses; the two- and three-glyph keys
 * (ESC, ALT1) get the wide box, and only cards with room for one carry them.
 */
.kc {
  position: absolute; top: 4px; right: 4px;
  display: grid; place-items: center;
  font-style: normal; font-size: 9px; line-height: 1; letter-spacing: .04em;
  font-weight: 700;
  color: var(--engrave);
  border: 1px solid var(--metal-edge); border-radius: 2px;
  width: 15px; height: 14px;
  background: rgba(0, 0, 0, .3);
  pointer-events: none;
}
.kc-wide { width: 30px; }
.pb:disabled .kc, .sw:disabled .kc { opacity: .5; }
/* A cap carrying a chip reserves the corner the chip sits in. (The rack does
   this for every cap whether it is carrying one or not, so that the chips can
   follow the selected battery without the rack moving.) */
.pb:has(.kc) { padding-right: 22px; }
.pb:has(.kc-wide) { padding-right: 38px; }

/* Legend-cap pushbutton: a moulded cap on a metal shroud. */
.pb {
  position: relative; cursor: pointer;
  padding: 5px 7px 4px; min-width: 48px;
  border: 1px solid var(--metal-edge); border-radius: 3px;
  background: linear-gradient(180deg, var(--metal-hi), var(--metal-lo));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .14), 0 2px 0 var(--metal-edge), 0 3px 4px rgba(0, 0, 0, .45);
  transition: transform .05s, box-shadow .05s;
}
.pb:hover:not(:disabled) { filter: brightness(1.1); }
.pb:active, .pb.is-down {
  transform: translateY(2px);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, .55), 0 0 0 var(--metal-edge);
}
/*
 * A cap with nothing wired to it: grey, sunk into its shroud, and legended in
 * the dim engraving rather than the bright one. A cap that was merely faint
 * was indistinguishable from the console browning out.
 */
.pb:disabled {
  cursor: not-allowed; transform: none; opacity: .72;
  filter: grayscale(.85) brightness(.72);
  background: var(--metal-lo);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, .5);
}
.pb:disabled .lg b, .pb:disabled .lg i { color: var(--engrave-dim); text-shadow: none; }

/*
 * The guarded cap: the one control on the console that cannot be undone.
 *
 * LEAVE POST has been both wrong things. It was the widest, brightest, most
 * raised cap in the topbar — the console's whole visual grammar pointed at its
 * one destructive verb — and the correction demoted it to bare dim text with
 * no housing, no border and no ground, so on a strip of machined caps it read
 * as a caption and the player could not see it was a button at all.
 *
 * A guarded switch on real hardware is neither. It is the same cap as its
 * neighbours, in the same rail, at the same height — and it is SUNK into its
 * housing rather than standing out of it, ringed in the alarm colour, with the
 * hazard hatching painted on the housing beside it. You can see it is a
 * control, you can see it is not one of the ordinary ones, and you have to
 * mean it. It still asks before it acts.
 */
.pb-danger {
  background:
    repeating-linear-gradient(135deg,
      color-mix(in srgb, var(--hostile) 32%, transparent) 0 4px,
      transparent 4px 9px) left / 9px 100% no-repeat,
    linear-gradient(180deg, var(--metal-lo), var(--metal-edge));
  border-color: color-mix(in srgb, var(--hostile) 70%, var(--metal-edge));
  box-shadow:
    inset 0 2px 4px rgba(0, 0, 0, .6),
    inset 0 0 0 1px color-mix(in srgb, var(--hostile) 22%, transparent),
    0 1px 0 rgba(255, 255, 255, .06);
  padding-left: 15px;
}
/* Engraved, with a warning in the ink — not alarm text. The cap must read as
   the dangerous one without being the brightest thing on the bar. */
.pb-danger .lg b { color: color-mix(in srgb, var(--hostile) 32%, var(--engrave-dim)); }
.pb-danger:hover:not(:disabled) {
  filter: none;
  border-color: var(--hostile);
  box-shadow:
    inset 0 2px 4px rgba(0, 0, 0, .6),
    inset 0 0 0 1px color-mix(in srgb, var(--hostile) 45%, transparent),
    0 0 10px color-mix(in srgb, var(--hostile) 40%, transparent);
}
.pb-danger:hover:not(:disabled) .lg b { color: var(--hostile); }
/* Already sunk: pressing it presses it further in, it does not fall. */
.pb-danger:active, .pb-danger.is-pressed {
  transform: none;
  box-shadow: inset 0 3px 6px rgba(0, 0, 0, .8), inset 0 0 0 1px var(--hostile);
}

/*
 * The launch cap.
 *
 * It is the largest, reddest, most raised object on the panel in every state,
 * and it says which state it is in with paint rather than with movement. It
 * used to say ARMED with an animated halo and nothing else — so a player with
 * prefers-reduced-motion set (where the global rule collapses every animation
 * to a single microsecond cycle) was shown a live cap and a cold one as
 * literally the same pixels, on the one control the game exists to press. The
 * border, the ink and the inner ring carry it now; the halo is the flourish
 * on top.
 */
.pb-fire {
  width: 100%; padding: 11px 8px 10px;
  background: linear-gradient(180deg, var(--fire-cap), var(--fire-cap-lo));
  border-color: var(--fire-cap-edge);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .18), 0 3px 0 var(--fire-cap-edge), 0 4px 6px rgba(0, 0, 0, .5);
}
.pb-fire .lg b { color: var(--fire-ink); font-size: 15px; letter-spacing: .24em; }
.pb-fire .lg i { color: var(--fire-ink-dim); font-size: 8.5px; letter-spacing: .08em; }
/* ARMED: a white-hot rim, brighter ink, and the halo on top of both. */
.pb-fire.is-armed {
  border-color: var(--fire-ink);
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--fire-cap) 82%, #ff6a58), var(--fire-cap-lo));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .3),
    inset 0 0 0 2px rgba(var(--fire-halo-rgb), .55),
    0 3px 0 var(--fire-cap-edge), 0 0 14px rgba(var(--fire-halo-rgb), .45);
  animation: fire-ready 1.05s infinite;
}
.pb-fire.is-armed .lg b { color: #fff; text-shadow: 0 0 8px rgba(var(--fire-halo-rgb), .8); }
/* Armed, but the firing tables do not think much of the shot: the cap is live
   and says so in the ink rather than pretending the odds are good. */
.pb-fire.is-marginal .lg i { color: var(--warn); }
/* A dead launch cap is still the launch cap — a red cap safed under its
   shroud, not a piece of grey panel. It keeps its shape, its rim and its
   lettering; what it loses is the fire in the ink. */
.pb-fire:disabled {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--fire-cap) 58%, var(--metal-edge)),
    color-mix(in srgb, var(--fire-cap-lo) 58%, var(--metal-edge)));
  border-color: color-mix(in srgb, var(--fire-ink-dim) 34%, var(--fire-cap-edge));
  filter: none; opacity: 1;
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, .6), inset 0 0 0 1px rgba(0, 0, 0, .35);
}
.pb-fire:disabled .lg b { color: color-mix(in srgb, var(--fire-ink) 52%, var(--metal-edge)); text-shadow: none; }
.pb-fire:disabled .lg i { color: color-mix(in srgb, var(--fire-ink-dim) 60%, var(--metal-edge)); }
@keyframes fire-ready {
  0%, 100% { box-shadow: inset 0 1px 0 rgba(255,255,255,.3), inset 0 0 0 2px rgba(var(--fire-halo-rgb), .55), 0 3px 0 var(--fire-cap-edge), 0 0 10px rgba(var(--fire-halo-rgb), .3); }
  50% { box-shadow: inset 0 1px 0 rgba(255,255,255,.3), inset 0 0 0 2px rgba(var(--fire-halo-rgb), .8), 0 3px 0 var(--fire-cap-edge), 0 0 24px rgba(var(--fire-halo-rgb), .8); }
}

/*
 * BREAK OFF is the same cap as LOCK with the opposite meaning, so it wears the
 * warning ink: a press that throws away a firing solution must not look like a
 * press that takes one.
 */
.pb-breakoff { border-color: color-mix(in srgb, var(--warn) 60%, var(--metal-edge)); }
.pb-breakoff .lg b { color: var(--warn); }

.pb-accept { background: linear-gradient(180deg, var(--accept-cap), var(--accept-cap-lo)); border-color: var(--accept-cap-edge); }
.pb-refuse { background: linear-gradient(180deg, var(--refuse-cap), var(--refuse-cap-lo)); border-color: var(--refuse-cap-edge); }

/* ------------------------------------------------------------------ knob */

/* Rotary selector with a machined pointer and a detent ring. */
.knob-group { display: inline-flex; align-items: center; gap: 7px; }
.knob {
  position: relative; width: 40px; height: 40px; border-radius: 50%;
  cursor: pointer; border: 1px solid var(--metal-edge); padding: 0;
  background:
    radial-gradient(circle at 38% 30%, var(--metal-hi), var(--metal-lo) 68%, var(--metal-edge));
  box-shadow: 0 2px 4px rgba(0, 0, 0, .55), inset 0 1px 0 rgba(255, 255, 255, .12);
}
.knob::before {
  /* Knurling. */
  content: ''; position: absolute; inset: 3px; border-radius: 50%;
  background: repeating-conic-gradient(rgba(255,255,255,.09) 0 3deg, rgba(0,0,0,.16) 3deg 6deg);
  mask: radial-gradient(circle, transparent 55%, var(--tube-face) 57%);
  -webkit-mask: radial-gradient(circle, transparent 55%, var(--tube-face) 57%);
}
.knob-pointer {
  position: absolute; left: 50%; top: 5px; width: 2.5px; height: 13px;
  margin-left: -1.25px; border-radius: 2px;
  background: var(--engrave); box-shadow: 0 0 3px rgba(0, 0, 0, .6);
  transform-origin: 50% 15px;
  transition: transform .12s ease-out;
}
.knob:hover { filter: brightness(1.12); }
.knob:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.knob-readout { font-size: 9px; letter-spacing: .1em; color: var(--engrave); min-width: 4.4em; }

/* ------------------------------------------------------------ unit strip */

/* One battery: its own sub-panel, screwed to the rack. */
.unit {
  position: relative;
  border: 1px solid var(--metal-edge);
  border-radius: 3px;
  margin: 6px 5px;
  padding: 6px 8px 8px;
  background: linear-gradient(180deg, rgba(255,255,255,.045), rgba(0,0,0,.16));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .07), 0 1px 3px rgba(0, 0, 0, .4);
  /* The rack is a fixed width; nothing in a unit strip may push past it. */
  overflow: hidden;
}
.unit > .screw { width: 7px; height: 7px; top: 4px; right: 4px; }
.unit > .screw::after { width: 4.5px; }
/*
 * Selection and ownership are drawn OUTSIDE the card's content box.
 *
 * `border-left: 3px` on the home battery took two pixels out of the grid the
 * rack is laid on, so the same three caps measured x=1297 w=92 on that card
 * and x=1295 w=93 on the ones above it — the whole rack stepped sideways as
 * the selection moved. Both marks are shadows now: one inset rail for the
 * battery that is yours, one ring around the card that is selected, neither
 * of which the layout can see.
 */
.unit.is-selected {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 55%, transparent),
    inset 0 1px 0 rgba(255, 255, 255, .07);
}
.unit.is-mine { box-shadow: inset 3px 0 0 var(--friendly), inset 0 1px 0 rgba(255,255,255,.07), 0 1px 3px rgba(0,0,0,.4); }
.unit.is-mine.is-selected {
  box-shadow: inset 3px 0 0 var(--friendly),
    0 0 0 2px color-mix(in srgb, var(--accent) 55%, transparent);
}
.unit.is-dead { filter: grayscale(.7); opacity: .55; }
/* A battery card is a control: clicking it selects the battery the keys and
   the shootlist then act on, so it answers the pointer like one. */
.unit[data-site] { cursor: pointer; }
.unit[data-site]:hover { background: linear-gradient(180deg, rgba(255,255,255,.085), rgba(0,0,0,.12)); }

/*
 * The card's head is a name and a type plate. At district and national
 * command the names run to eighteen characters in a two-hundred-pixel rack,
 * and the type plate was taking fifty-six per cent of it by inline style —
 * so LANCE CAPITAL, THISTLE BRASOV and nine others were ellipsised down to
 * "12. THISTLE BR…". The head wraps instead: the name keeps its own width
 * and the plate drops to the line below when they will not both fit.
 */
/* The corner screw is a physical object on the card and the nomenclature may
   not run under it: the head reserves its width. */
.unit-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 6px; flex-wrap: wrap; padding-right: 14px;
}
.unit-name {
  font-size: 11px; letter-spacing: .1em; color: var(--engrave); font-weight: 700;
  flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/*
 * The nomenclature plate. `overflow: hidden` is here to put an ellipsis on a
 * name too long for the rack, and it was clipping the LINE BOX as well as the
 * column: at the default leading the breve on Й was cut clean off, so the two
 * surviving Russian-looking legends on the console read «ШИРОКИИ ГЛАЗ» and
 * «НИЗКИИ ВЗГЛЯД» — the one place the game speaks Cyrillic, misspelled, at
 * every device scale. The line box is taller than the tallest glyph now.
 */
.unit-type {
  font-size: 9px; letter-spacing: .08em; color: var(--engrave-dim);
  line-height: 1.65;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 52%;
}
/* A figure that may not be broken: an envelope, a range, a bearing. */
.nb { white-space: nowrap; }
/* Bilingual lines are twice as long as monolingual ones; where the gloss
   matters more than the single-line layout, let it wrap instead of truncate. */
.unit-type.wrap {
  white-space: normal; overflow: visible; max-width: none; line-height: 1.35;
}
/* The type plate on a card's head: right-hand column, one line, and truncated
   with an ellipsis rather than cut through a glyph by the card's own edge. */
.unit-type.is-plate {
  flex: 0 1 auto; text-align: right; max-width: 100%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.unit-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 5px; }
/*
 * The lamp row is three cells on a fixed grid, not a flex row of lamps as
 * wide as their words.
 *
 * As a flex row, any change to one caption moved the lamps beside it: the
 * emissions lamp swapping RADIATING for WARMING when the switch was thrown
 * slid INBOUND ARM thirteen pixels along the row, and the ARM countdown
 * appended to its caption did the same to the reach figure. On a grid each
 * lamp owns a third of the row whatever it says, its caption is cut with an
 * ellipsis rather than pushing anything, and its figure — the ARM seconds —
 * has a column of its own.
 */
.unit-row.unit-lamps {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 4px; align-items: center;
}
.unit-lamps .lamp {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center; gap: 0 4px; min-width: 0;
}
.unit-lamps .lamp-cap { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.unit-lamps .lamp-fig, .lamps .lamp-fig {
  font-variant-numeric: tabular-nums; font-weight: 700; color: inherit;
}
.unit-lamps .unit-type.reach { justify-self: end; margin-left: 0; }
/* A line that is present as context rather than as news. */
.unit-row.is-quiet .unit-type, .unit-unfit.is-quiet span { color: var(--engrave-dim); }
.unit-row.is-spaced { margin-top: 9px; }
/*
 * The rack, on a track.
 *
 * Six columns, three caps to a row: every cap on a card is the same width as
 * every other, every row ends on the same right edge, and the card's pitch does
 * not move when a watch fits one more cap. It was a wrapping flex row, which
 * measured 77+93 / 142+76 / 82+95 in a 258 px column — three different right
 * edges on one card, and a lone RELOAD cap on a row of its own at the bottom.
 * A last row that is short spans the width instead of trailing a hole.
 */
.unit-controls {
  display: grid; grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 6px; margin-top: 7px;
  padding-top: 6px; border-top: 1px solid rgba(0, 0, 0, .3);
}
.unit-controls > * { grid-column: span 2; min-width: 0; }
.unit-controls > :nth-child(3n+1):last-child { grid-column: span 6; }
.unit-controls > :nth-child(3n+1):nth-last-child(2),
.unit-controls > :nth-child(3n+2):last-child { grid-column: span 3; }
/* The switches sit above the caps, on their own track: two side by side where
   the rack is wide enough for both sets of legends, one across where it is
   not. Neither can ever be drawn over the other's caption. */
.unit-switches {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(104px, 1fr));
  gap: 5px; margin-top: 7px;
}
.unit-controls .pb { align-self: stretch; }
.unit-controls .pb .lg { width: 100%; }
.unit-controls .pb .lg b, .unit-controls .pb .lg i {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}
/* Rack legends: one line, 10.5px, and every cap on the rack reserves the same
   corner for its key chip whether it is carrying one or not — so the chips can
   follow the selected battery without a single cap changing width. */
.unit-controls .pb { padding-right: 22px; }
.unit-controls .pb .lg b { font-size: 10.5px; letter-spacing: .05em; }
/* The weapons state: three latching positions, the live one pressed in. */
.pb-weapons.is-live .lg b { color: var(--good); }

/* Ammunition, shown as rounds on a rail rather than as a number. */
/* The launcher: one round lamp per rail, lit green with a round on it and red
   when that tube is empty. Domed like the console's other indicators, because
   that is what they are. */
.tubes { display: flex; gap: 3px; align-items: center; flex-wrap: wrap; }
.tubes i {
  display: block; width: 9px; height: 9px; border-radius: 50%;
  border: 1px solid var(--metal-edge);
  background: radial-gradient(circle at 34% 30%, rgba(255,255,255,.25), var(--lamp-off) 68%);
}
.tubes i.is-loaded {
  background: radial-gradient(circle at 34% 30%, rgba(255,255,255,.55), var(--good) 62%);
  box-shadow: 0 0 5px color-mix(in srgb, var(--good) 65%, transparent);
}
.tubes i.is-spent {
  background: radial-gradient(circle at 34% 30%, rgba(255,255,255,.18), var(--hostile) 70%);
  box-shadow: inset 0 0 3px rgba(0,0,0,.7);
  opacity: .75;
}
/* The rail the loaders are on. Amber, and it pulses, because a rail that is
   about to be a round is neither loaded nor spent — and watching one lamp
   come up is how the reload reads now that the rack fills a round at a time. */
.tubes i.is-loading {
  background: radial-gradient(circle at 34% 30%, rgba(255,255,255,.4), var(--warn) 66%);
  box-shadow: 0 0 5px color-mix(in srgb, var(--warn) 60%, transparent);
  animation: tube-hoist 1.6s ease-in-out infinite;
}
@keyframes tube-hoist { 0%, 100% { opacity: .45; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .tubes i.is-loading { animation: none; } }

/* Reloading or displacing, on the rack's cards: the bar empties and the
   seconds count down beside it, so "out of the fight" has a number on it. The
   cabin states the same thing in its own ammunition strip, which is in the
   command pad rather than in the scrolling readouts — see .cabin-ammo. */
.reload-row { align-items: center; gap: 6px; }
.reload-left { font-variant-numeric: tabular-nums; color: var(--warn); min-width: 3.2em; text-align: right; }

/* A meter with a needle-ish fill, for exposure and timers. */
.gauge {
  height: 7px; border-radius: 1px; overflow: hidden; flex: 1; min-width: 60px;
  background: linear-gradient(180deg, var(--tube-face), var(--well));
  border: 1px solid var(--metal-edge);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, .8);
}
.gauge i { display: block; height: 100%; background: var(--good); transition: width .25s; }
.gauge.is-warn i { background: var(--warn); }
.gauge.is-hot i { background: var(--hostile); }

/* Punched paper strip for the log — the console prints, it does not scroll. */
.strip {
  background: repeating-linear-gradient(90deg, rgba(255,255,255,.02) 0 1px, transparent 1px 14px), var(--paper);
  border-top: 1px solid var(--metal-edge);
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, .7);
}

/* The rails and, beside them, the store they are filled from: the lamps on
   the left, the count on the right, one row, never wrapping — the store is
   a figure the operator watches go down all night. */
.unit-row.unit-rails { flex-wrap: nowrap; justify-content: space-between; }
.unit-type.store {
  flex: 0 0 auto; margin-left: auto; white-space: nowrap;
  font-variant-numeric: tabular-nums; letter-spacing: .08em;
}
.unit-type.store b { color: var(--engrave); font-size: 10.5px; }
/* Empty is news: the count and the loaders' line go to the warning colour. */
.unit-type.store.is-empty, .unit-type.store.is-empty b { color: var(--hostile); }
/* Envelope constants on the battery card: true all night, so they defer to
   the numbers that change. */
.unit-row .lo { opacity: .62; }
/* A surveillance set's reach, at the right-hand end of its lamp row. */
.unit-type.reach { margin-left: auto; }

/* What the thing IS, on the card itself — one small line, not a hover. */
.unit-explain { font-size: 10px; line-height: 1.4; opacity: .7; margin: 2px 0 5px; }

/* The switch the whole game runs on, sized like it: the surveillance sets'
   emissions switch, and the one in the cabin, are the controls a watch cannot
   be played without. */
.sw-primary { padding: 6px 22px 6px 7px; }
.sw-primary .sw-body { height: 36px; width: 19px; }
.sw-primary .sw-pos { font-size: 12px; line-height: 17px; letter-spacing: .09em; }
.sw-primary .sw-lever { height: 13px; }
.sw-primary[aria-pressed="true"] .sw-lever { top: 5px; }
.sw-primary[aria-pressed="false"] .sw-lever { top: 18px; }

/*
 * A cap that is being held stays down.
 *
 * `:active` is the browser's version of this and it lets go the moment the
 * pointer slides off the cap, which is the wrong reading: the press is still
 * committed until the finger comes up. app.js sets this class from pointerdown
 * to pointerup, which is exactly as long as the panel underneath is frozen.
 */
.pb.is-pressed, .sw.is-pressed {
  transform: translateY(2px);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, .55), 0 0 0 var(--metal-edge);
}
/* The launch cap stands proudest of the pad, so it travels furthest. */
.pb-fire.is-pressed { transform: translateY(3px); }

/* --------------------------------------------------------------- the ring */

/*
 * The focus ring, drawn as a piece of this console rather than as a recoloured
 * browser default.
 *
 * A 2px solid outline hugging the control was what the browser would have done
 * on its own. This is three things a lit ring on a painted panel actually has:
 * a dark score line cut into the paint around the control, a bright filament
 * standing off it, and a soft bloom on the metal beyond. It is drawn on a
 * pseudo-element so it composes with the cap's own moulding instead of
 * replacing it, and `outline: none` is safe here only because the ring below
 * is unconditional and unmissable.
 */
.pb:focus-visible,
.sw:focus-visible,
.knob:focus-visible,
.unit[tabindex]:focus-visible {
  outline: none;
}
.pb:focus-visible::after,
.sw:focus-visible::after,
.knob:focus-visible::after {
  content: ''; position: absolute; inset: -5px; pointer-events: none;
  border: 1.5px solid var(--accent);
  border-radius: 6px;
  box-shadow:
    inset 0 0 0 1.5px rgba(0, 0, 0, .72),
    0 0 0 1px rgba(0, 0, 0, .5),
    0 0 12px 1px color-mix(in srgb, var(--accent) 60%, transparent);
}
.knob:focus-visible::after { border-radius: 50%; inset: -6px; }
@media (prefers-reduced-motion: no-preference) {
  .pb:focus-visible::after, .sw:focus-visible::after { animation: ring-settle .14s ease-out; }
}
@keyframes ring-settle { from { opacity: 0; transform: scale(1.06); } }
