/* ══ Base — neumorphic console housing with CRT phosphor screens ══ */
* { margin: 0; padding: 0; box-sizing: border-box; }

:root {
  /* gunmetal console housing — the bezel now belongs to the same rack as the CRTs */
  --bg:      #1e242c;
  --shade:   #12161c;   /* neumorphic shadow */
  --light:   #2b333d;   /* neumorphic highlight */
  --ink:     #c3d5cb;
  --ink-dim: #8a9d92;
  --edge:    #2b323b;   /* hairline rules on dark */

  /* Screen colours, as raw r,g,b triplets. Everything on a CRT is drawn with
     a transparency, and js/index.js reads four of them — phos, amber, alert
     and flash — out of this block on load, so each colour is edited here and
     nowhere else and the housing and the screens cannot drift apart. Three of
     them are also wanted as a plain colour by a rule or two; those derive
     from the triplet rather than repeating it. */
  --accent-rgb: 85, 227, 154;   /* the hint pulse */
  --phos-rgb:   60, 255, 136;   /* green phosphor: grid, sweep, own hulls */
  --amber-rgb:  255, 180, 90;   /* hits and inbound fire */
  --alert-rgb:  255, 120, 90;   /* sunk hulls, illegal placement, defeat */
  --flash-rgb:  234, 255, 255;  /* lit lamp, victory */
  --accent: rgb(var(--accent-rgb));
  --amber:  rgb(var(--amber-rgb));
  --alert:  rgb(var(--alert-rgb));
  --flash:  rgb(var(--flash-rgb));

  --mono: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--mono);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  min-height: 100vh; gap: 16px; padding: 26px 16px;
  -webkit-font-smoothing: antialiased;
}

h1 {
  font-size: 12px; letter-spacing: 5px; text-transform: uppercase;
  color: var(--ink-dim); font-weight: 400; text-align: center;
}
h1 b { color: var(--accent); font-weight: 400; }

/* ── Module housing ─────────────────────────────────────── */
.module {
  background: var(--bg); border-radius: 22px; padding: 18px 22px 20px;
  box-shadow: 9px 9px 20px var(--shade), -9px -9px 20px var(--light);
  display: flex; flex-direction: column; gap: 14px;
  max-width: 1180px; width: 100%;
}
.module-label {
  font-size: 9px; letter-spacing: 4px; color: var(--ink-dim); text-transform: uppercase;
  padding-bottom: 10px; border-bottom: 1px solid var(--edge);
  /* tag and switch sit together on the left, the phase tag takes the rest
     and stays hard right — the 1fr is on the last track, not the outer two */
  display: grid; grid-template-columns: auto auto auto 1fr; align-items: center; gap: 9px;
}
.module-label .tag { display: flex; align-items: center; gap: 8px; justify-self: start; }
.module-label #phaseTag { justify-self: end; }

/* ── Segmented switch ───────────────────────────────────── */
.seg { display: inline-flex; align-items: center; gap: 6px; }
.seg-label { letter-spacing: 3px; }
/* carries the header's own separator across to the switch */
.seg-sep { letter-spacing: 4px; }
.seg-btn {
  font-family: var(--mono); border: none; cursor: pointer;
  background: var(--bg); color: var(--ink-dim); border-radius: 3px;
  font-size: 9px; letter-spacing: 2px; text-transform: uppercase; padding: 6px 12px;
  box-shadow: 2px 2px 4px var(--shade), -2px -2px 4px var(--light);
  transition: color .12s, box-shadow .12s;
}
/* hover only means anything on the one you could switch to */
.seg-btn:hover:not(.on) { color: var(--ink); }
/* selected reads as held down — the same language the Audio button uses */
/* pressed reads from the shadow and a brighter label — the accent is
   reserved for the hint pulse, which was getting lost among green toggles */
.seg-btn.on {
  color: var(--ink); cursor: default;
  box-shadow: inset 2px 2px 5px var(--shade), inset -2px -2px 5px var(--light);
}

/* ── Layout ─────────────────────────────────────────────── */
.scope-row {
  display: flex; align-items: flex-start; justify-content: center;
  gap: 22px; flex-wrap: wrap; width: 100%;
}
.scope-side {
  display: flex; flex-direction: column; gap: 14px;
  /* fixed, not min-width: sized to content the two columns came out 168.9 vs
     168.0 px, so the fleet panels never quite lined up */
  flex: 0 0 172px; min-width: 0;
  /* Engage swaps panels for roster rows and the columns would resize
     (346->365 left, 392->385 right). Since the body centres everything
     vertically, that nudged the boards and the log too. Holding the tallest
     state keeps the whole module still; panels just rearrange inside it. */
  min-height: 392px;
}
.boards { display: flex; gap: 20px; flex-wrap: wrap; justify-content: center; }
.board-wrap { display: flex; flex-direction: column; gap: 9px; align-items: center; }
.board-cap {
  font-size: 9px; letter-spacing: 3px; text-transform: uppercase; color: var(--ink-dim);
  display: flex; align-items: center; gap: 7px;
}
.board-cap.hot { color: var(--accent); }
/* only surfaced where the roster panels are hidden — see the phone rules */
.cap-count { display: none; margin-left: auto; font-weight: 400; color: var(--ink); }

/* ── CRT screen (scope-screen) ──────────────────────────── */
.scope-screen {
  position: relative;
  width: min(364px, 80vw);
  border: 6px solid var(--bg); border-radius: 18px;
  background: radial-gradient(ellipse at 50% 40%, #08251a 0%, #041710 60%, #020c08 100%);
  box-shadow:
    inset 3px 3px 6px rgba(0,0,0,0.75), inset -2px -2px 4px rgba(255,255,255,0.06),
    inset 0 0 38px rgba(0,0,0,0.85), inset 0 0 4px rgba(var(--phos-rgb),0.15),
    6px 6px 14px var(--shade), -6px -6px 14px var(--light);
  overflow: hidden; flex-shrink: 0;
}
/* The scope is square, and the canvas is what decides that — the bezel simply
   wraps it. Hanging the ratio on the bezel instead meant resolving it against a
   border-box with a 6px border, which browsers do not agree on: a fraction of a
   pixel of disagreement is a whole row at 3x, and that row is what showed up
   along the bottom edge on iOS. */
.scope-screen canvas {
  display: block; width: 100%; height: auto; aspect-ratio: 1 / 1;
  border-radius: 12px;
}
.scope-screen.armed { cursor: crosshair; }
/* after .armed on purpose — hovering a set hull wins over the crosshair */
.scope-screen.grab { cursor: grab; }
/* scanlines + vignette */
.scope-screen::after {
  content: ''; position: absolute; inset: 0; pointer-events: none; border-radius: 12px; z-index: 3;
  background:
    repeating-linear-gradient(to bottom, rgba(0,0,0,0) 0px, rgba(0,0,0,0) 2px, rgba(0,0,0,0.16) 3px, rgba(0,0,0,0.16) 4px),
    radial-gradient(ellipse at 50% 50%, rgba(0,0,0,0) 55%, rgba(0,0,0,0.45) 100%);
  animation: flick 5.5s steps(2, end) infinite;
}
/* glass glare */
.scope-screen::before {
  content: ''; position: absolute; top: 6px; left: 14px; right: 40%; height: 22px;
  border-radius: 50%; pointer-events: none; z-index: 4;
  background: linear-gradient(to bottom, rgba(120,255,180,0.10), rgba(120,255,180,0));
}
@keyframes flick { 0%,97% { opacity: 1; } 98% { opacity: 0.88; } 100% { opacity: 1; } }

/* ── LED ────────────────────────────────────────────────── */
.scope-led {
  width: 10px; height: 10px; border-radius: 50%; box-sizing: border-box; flex-shrink: 0;
  border: 1px solid rgba(0,0,0,0.4); background: #c2c9d6;
  transition: background .15s, box-shadow .15s, border-color .15s;
}
.scope-led.on { background: var(--flash); border-color: rgba(255,255,255,0.6); box-shadow: 0 0 7px 1px rgba(var(--flash-rgb),0.85); }
.scope-led.hot { background: var(--amber); border-color: rgba(var(--amber-rgb),0.6); box-shadow: 0 0 7px 1px rgba(var(--amber-rgb),0.8); }

/* ── Buttons ────────────────────────────────────────────── */
.seq-btn {
  font-family: var(--mono); border: none; cursor: pointer;
  background: var(--bg); color: var(--ink-dim); border-radius: 3px;
  text-transform: uppercase; transition: all .12s;
  box-shadow: 3px 3px 6px var(--shade), -3px -3px 6px var(--light);
}
.seq-btn { padding: 8px 18px; font-size: 11px; letter-spacing: 2px; }
.seq-btn:hover { color: var(--ink); }
.seq-btn.playing {
  color: var(--ink); box-shadow: inset 2px 2px 5px var(--shade), inset -2px -2px 5px var(--light);
}
.seq-btn:disabled { opacity: .4; cursor: default; pointer-events: none; }
/* fleet complete — the button breathes to say the game can start */
@keyframes ready {
  0%, 100% { color: var(--ink-dim);
             box-shadow: 3px 3px 6px var(--shade), -3px -3px 6px var(--light),
                         0 0 0 1px rgba(var(--accent-rgb),0.10); }
  50%      { color: var(--accent);
             box-shadow: 3px 3px 6px var(--shade), -3px -3px 6px var(--light),
                         0 0 0 1px rgba(var(--accent-rgb),0.55), 0 0 12px rgba(var(--accent-rgb),0.28); }
}
.seq-btn.ready { animation: ready 1.9s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
  .seq-btn.ready { animation: none; color: var(--accent);
    box-shadow: 3px 3px 6px var(--shade), -3px -3px 6px var(--light), 0 0 0 1px rgba(var(--accent-rgb),0.45); }
  .scope-screen::after { animation: none; }
}
.btn-row { display: flex; gap: 8px; flex-wrap: wrap; }
/* 1 1 0 — not plain `flex: 1`: equal halves regardless of word length,
   so ROTATE/AUTO and CLEAR/ENGAGE line up as one 2x2 keypad */
.btn-row .seq-btn { flex: 1 1 0; min-width: 0; text-align: center; padding: 8px 10px; }

/* ── Knobs / sliders ────────────────────────────────────── */
.knob-group { display: flex; flex-direction: column; gap: 7px; align-items: stretch; }
.knob-group label {
  font-size: 9px; letter-spacing: 2px; text-transform: uppercase; color: var(--ink-dim);
  display: flex; justify-content: space-between;
}
.knob-group label span { color: var(--ink-dim); }
input[type=range] {
  -webkit-appearance: none; appearance: none; width: 100%; height: 8px;
  background: transparent; border-radius: 4px; outline: none; cursor: pointer;
  box-shadow: inset 2px 2px 4px var(--shade), inset -2px -2px 4px var(--light);
}
input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none; width: 15px; height: 15px; border-radius: 50%;
  background: var(--bg); cursor: pointer;
  box-shadow: 2px 2px 4px var(--shade), -2px -2px 4px var(--light);
}
input[type=range]::-moz-range-thumb {
  width: 15px; height: 15px; border: none; border-radius: 50%;
  background: var(--bg); box-shadow: 2px 2px 4px var(--shade), -2px -2px 4px var(--light);
}

/* ── Grouped housing ────────────────────────────────────── */
.panel {
  border-radius: 12px; padding: 12px 13px 13px;
  background: var(--bg);
  box-shadow: inset 3px 3px 7px var(--shade), inset -3px -3px 7px var(--light);
  display: flex; flex-direction: column; gap: 9px;
}
.panel-title {
  font-size: 9px; letter-spacing: 3px; text-transform: uppercase; color: var(--ink-dim);
  display: flex; justify-content: space-between; align-items: center; gap: 6px;
}

/* The two columns are mirrored slot for slot. Left to themselves the pairs
   come out uneven — Deploy 111 vs Scope 137, Telemetry 72 vs the buttons 92 —
   so each slot is held to the taller of its pair and the panels line up
   across the boards. Together they fill the column's reserved 392px exactly. */
.slot-mid  { min-height: 137px; }   /* Deploy    / Scope  */
.slot-foot { min-height:  92px; }   /* Telemetry / Audio  */
/* Holding those heights leaves slack in whichever panel of the pair is the
   shorter one — Deploy against Scope, Telemetry against the buttons. A
   hairline takes it: margin-top:auto drops it to the bottom edge, and since
   the panel is already held at the pair's height, nothing else shifts.
   Only the short panels carry it — adding it to all four would push every
   panel 10px taller and undo the alignment. */
.slot-fill::after {
  content: '';
  flex: 1;                       /* takes exactly the leftover, whatever it is */
  min-height: 1px;
  /* a set count anchored to the bottom edge, not a pattern that fills: the
     ruling keeps the same look however much slack the pair happens to leave */
  background-repeat: no-repeat;
  background-image: linear-gradient(var(--edge), var(--edge));
  background-size: 100% 1px;
  background-position: 0 100%;
}

/* Engage costs the column its middle panel and grows the roster from 4 rows
   to 10 — but not by the same amount: 135+14+137 = 286 of deployment panels
   against 279 of hulls. That 7px was the jump under Telemetry. Letting the
   roster take up whatever the column has left absorbs it, so the panel below
   never moves and the bottom edges stay put. */
.slot-top { flex-grow: 1; }
.off { display: none; }
/* the desk has room for the read-out the whole time, and its column height
   is reserved anyway, so it ignores the deploy-phase hiding */
@media (min-width: 1212px) { #telemetryPanel.off { display: flex; } }

/* ── Fleet roster ───────────────────────────────────────── */
.roster { display: flex; flex-direction: column; gap: 5px; }
.fleet-row {
  display: flex; align-items: center; gap: 8px; padding: 4px 6px; border-radius: 4px;
  font-size: 10px; letter-spacing: 1px; color: var(--ink-dim);
  background: none; border: none; font-family: var(--mono); text-align: left;
}
button.fleet-row { cursor: pointer; transition: all .12s; }
button.fleet-row:hover { color: var(--ink); }
button.fleet-row.sel { color: var(--ink); box-shadow: inset 2px 2px 4px var(--shade), inset -2px -2px 4px var(--light); }
button.fleet-row:disabled { opacity: .4; cursor: default; }
.fleet-row .hull { display: flex; gap: 2px; }
.hull i {
  width: 9px; height: 9px; border-radius: 2px; background: #333c46;
  box-shadow: inset 1px 1px 2px var(--shade);
}
.fleet-row.live .hull i { background: var(--accent); box-shadow: 0 0 4px rgba(109,143,201,.5); }
.fleet-row.dead { opacity: .45; }
.fleet-row.dead .hull i { background: var(--alert); box-shadow: none; }
.fleet-row .n { margin-left: auto; color: var(--ink-dim); }

/* ── Stats ──────────────────────────────────────────────── */
.stats { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 10px; font-size: 10px; letter-spacing: 1px; }
.stats span { color: var(--ink-dim); }
.stats b { color: var(--ink); font-weight: 400; float: right; }

/* ── Log screen (small CRT readout) ─────────────────────── */
.log-screen {
  position: relative; border-radius: 12px; overflow: hidden; border: 5px solid var(--bg);
  background: radial-gradient(ellipse at 50% 30%, #08251a 0%, #041710 65%, #020c08 100%);
  box-shadow:
    inset 3px 3px 6px rgba(0,0,0,0.7), inset 0 0 22px rgba(0,0,0,0.7),
    5px 5px 12px var(--shade), -5px -5px 12px var(--light);
  height: 92px;
}
.log-screen::after {
  content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 2;
  background: repeating-linear-gradient(to bottom, rgba(0,0,0,0) 0px, rgba(0,0,0,0) 2px, rgba(0,0,0,0.18) 3px, rgba(0,0,0,0.18) 4px);
}
#log {
  height: 100%; overflow-y: auto; padding: 8px 12px;
  font-size: 10.5px; line-height: 1.65; letter-spacing: 1px;
  color: rgba(var(--phos-rgb),0.72); text-shadow: 0 0 6px rgba(var(--phos-rgb),0.45);
  scrollbar-width: thin; scrollbar-color: rgba(var(--phos-rgb),.3) transparent;
}
#log::-webkit-scrollbar { width: 5px; }
#log::-webkit-scrollbar-thumb { background: rgba(var(--phos-rgb),.25); border-radius: 3px; }
#log div { white-space: pre-wrap; }
/* Text on a phosphor screen needs to sit above its own glow, so these two are
   deliberately lighter relatives of --amber and --alert rather than the colours
   themselves. Restyle them alongside the palette. */
#log .hit  { color: #ffcf8f; text-shadow: 0 0 7px rgba(var(--amber-rgb),.6); }
#log .kill { color: #ff9a7a; text-shadow: 0 0 8px rgba(var(--alert-rgb),.6); }
#log .sys  { color: rgba(var(--phos-rgb),0.42); }
#log .win  { color: var(--flash); text-shadow: 0 0 9px rgba(var(--flash-rgb),.7); }

.hint { font-size: 9px; letter-spacing: 2px; color: var(--ink-dim); text-transform: uppercase; text-align: center; }
.touch-only { display: none; }
/* device, not width: a narrow desktop window still has a mouse */
@media (hover: none) and (pointer: coarse) {
  .pointer-only { display: none; }
  .touch-only { display: inline; }
}
.hint kbd {
  font-family: var(--mono); font-size: 9px; padding: 1px 5px; border-radius: 3px;
  background: var(--bg); box-shadow: 1px 1px 3px var(--shade), -1px -1px 3px var(--light); color: var(--ink-dim);
}

/* ── Adaptive ───────────────────────────────────────────
   Off the 3-column desk the row unstacks: boards on top, then each
   side's panels reflowing into however many columns fit. Every track
   is auto-fit, so there is no per-device breakpoint to maintain. */
/* ── Boot gate ──────────────────────────────────────────
   Browsers only let audio start from a real gesture, so the game waits
   behind one button instead of failing silently on load. */
#boot {
  position: fixed; inset: 0; z-index: 50;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 26px; padding: 24px;
  background: radial-gradient(ellipse at 50% 45%, #1b232c 0%, #12161c 70%, #0c0f13 100%);
  transition: opacity .5s ease;
}
#boot.gone { opacity: 0; pointer-events: none; }
#boot h2 {
  font-size: 12px; letter-spacing: 6px; text-transform: uppercase; text-align: center;
  color: var(--ink-dim); font-weight: 400; line-height: 2;
}
#boot h2 b { color: var(--accent); font-weight: 400; }
.play-btn {
  font-family: var(--mono); border: none; cursor: pointer; background: var(--bg);
  color: var(--accent); border-radius: 6px; text-transform: uppercase;
  padding: 17px 52px; font-size: 13px; letter-spacing: 7px;
  animation: bootPulse 2.2s ease-in-out infinite;
  transition: color .5s ease;      /* without this the hover snaps in one frame */
}
/* a lift within the phosphor family, not a jump to white */
.play-btn:hover { color: #a6f0cb; }
@keyframes bootPulse {
  0%, 100% { box-shadow: 5px 5px 13px #0c0f13, -5px -5px 13px var(--light),
                         0 0 0 1px rgba(var(--accent-rgb),0.14); }
  50%      { box-shadow: 5px 5px 13px #0c0f13, -5px -5px 13px var(--light),
                         0 0 0 1px rgba(var(--accent-rgb),0.6), 0 0 20px rgba(var(--accent-rgb),0.3); }
}
.boot-note {
  font-size: 9px; letter-spacing: 3px; text-transform: uppercase; color: var(--ink-dim);
  text-align: center; line-height: 2;
}
@media (prefers-reduced-motion: reduce) { .play-btn { animation: none; } }

/* The white ring is the UA focus indicator — autofocus on Play makes it show
   from the start. Keep the indicator (Enter must still work), lose the
   white: outline, not box-shadow, so the pulse animation stays untouched. */
.play-btn:focus-visible, .seq-btn:focus-visible, .seg-btn:focus-visible, button.fleet-row:focus-visible {
  outline: 1px solid rgba(var(--accent-rgb),0.65); outline-offset: 3px;
}
.play-btn:focus:not(:focus-visible) { outline: none; }

/* 1211px, not a round 1000: the three-column desk needs 1136px of row and
   only clears it from 1212 up. Below that the side columns tore off under
   the boards — which is exactly what an iPad Pro shows in portrait (1024). */
@media (max-width: 1211px) {
  /* Anchor the stack to the top. Centred vertically, the page re-centres
     itself whenever the panels below the boards grow or vanish, and the
     boards slide by half the difference — 53px at Engage. The desktop keeps
     its centring because there the column height is reserved instead. */
  body { justify-content: flex-start; }

  /* nowrap matters: wrapping in a column direction makes the container
     resolve a definite height, which then stretches the grid rows inside
     each side and leaves dead space between the panels */
  .scope-row { flex-direction: column; flex-wrap: nowrap; align-items: stretch; gap: 14px; }
  .scope-side {
    /* the desktop basis is a width; in this column flow it would become a
       height, so hand sizing back to the content — and the reserved height
       would only be dead space once the panels reflow into a grid */
    flex: 0 0 auto;
    min-width: 0; width: auto; min-height: 0;
    display: grid; gap: 12px; align-items: start;
    grid-template-columns: repeat(auto-fit, minmax(158px, 1fr));
  }
  /* the slots sit side by side here, not across the boards — a held height
     would only be dead space inside each card */
  .slot-mid, .slot-foot { min-height: 0; }
  /* and with no slack left to fill, the rule would just be a stray hairline
     plus the panel's row gap — 10px of height for nothing */
  .slot-fill::after { display: none; }
  .boards {
    display: grid; gap: 14px; width: 100%;
    /* min() keeps the track from overflowing a phone narrower than 288px */
    grid-template-columns: repeat(auto-fit, minmax(min(288px, 100%), 1fr));
    /* boards first in the stack: the panels below may grow or vanish at
       Engage, and nothing that happens to them should move the grids */
    order: -1;
  }
  .board-wrap { width: 100%; min-width: 0; }
  .scope-screen { width: 100%; max-width: 460px; margin: 0 auto; min-width: 0; }

  /* The scope knobs are a desk instrument — off the desktop they are gone
     for good, in both phases, so they can never appear or vanish here. */
  #scopePanel, #scopePanel.off { display: none; }

  /* Everything here is touched, not clicked: 29px tall was well under the
     ~44px a fingertip needs. */
  .btn-row .seq-btn { padding: 15px 10px; }
  /* the boot button too — at its desk size it was a small target adrift in
     the middle of a tablet screen */
  .play-btn { width: 100%; max-width: 420px; padding: 21px 52px; font-size: 14px; }
}

/* Tablet width: the two boards already sit side by side, so the panels below
   should belong to the board above them — own fleet under own waters, enemy
   fleet under enemy waters. Below this the boards stack and a split would put
   the panels under the wrong grid, so the single column stays. */
@media (min-width: 680px) and (max-width: 1211px) {
  .scope-row { display: grid; grid-template-columns: 1fr 1fr; align-items: start; }
  .boards { grid-column: 1 / -1; }
  /* one panel per row, each as wide as the board above it. Two tracks left a
     roster sitting in one of them and a hole in the other. */
  .scope-side { grid-template-columns: 1fr; }
  /* Everything sitting under a fleet roster shares one height: the two that
     trade places at Engage (Deploy, Telemetry) and the controls opposite
     them. So the swap changes what the slot holds and not how tall it is,
     and both columns finish on the same line. */
  #deployPanel, #telemetryPanel, #ctrlPanel { min-height: 139px; }

  /* At this width the old two-column read-out stranded each value far from
     its label and left the lower half of the panel empty. Four gauges in a
     row instead, label over value, centred in the space they have. */
  #telemetryPanel .stats {
    flex: 1; grid-template-columns: repeat(4, 1fr);
    align-content: center; gap: 8px;
  }
  #telemetryPanel .stats span {
    display: flex; flex-direction: column; align-items: center; gap: 7px;
    font-size: 9px; letter-spacing: 2px;
  }
  #telemetryPanel .stats b {
    float: none; font-size: 21px; letter-spacing: 1px; color: var(--ink);
  }
}

@media (max-width: 620px) {
  body { padding: 14px 10px; gap: 12px; }
  h1 { font-size: 10px; letter-spacing: 3px; }
  .module { padding: 14px 13px 15px; border-radius: 18px; }
  .module-label {
    font-size: 8px; letter-spacing: 2.5px; padding-bottom: 8px;
    grid-template-columns: 1fr auto; row-gap: 11px;
  }
  /* The phase belongs beside the name, on the first row. Left to auto placement
     it dropped below the switches and sat right-aligned to the first column —
     which reads as neither centred nor aligned to anything. */
  .module-label #phaseTag { grid-row: 1; grid-column: 2; }
  .module-label .seg { grid-column: 1 / -1; justify-self: start; gap: 8px; }
  .seg-label { min-width: 46px; }      /* holds both rows of segments to one left edge */
  /* One size for every segment. ON and OFF came out 39 and 46 wide against 61
     for SHIPS and BLIPS, so no two rows lined up; and 22px tall is not a thumb
     target when every other button on a phone is 43. */
  .seg-btn { min-width: 84px; padding: 14px 12px; }
  /* on its own row the separator would just dangle after nothing */
  .seg-sep { display: none; }
  .panel-title { letter-spacing: 2px; }
  .log-screen { height: 78px; }
  .hint { letter-spacing: 1px; line-height: 1.8; }

  /* A phone gets the game and nothing else: the telemetry read-out and the
     fleet rosters are for a desk, not for a thumb (the scope knobs are
     already gone for the whole stacked range). */
  #telemetryPanel, .slot-top { display: none; }
  /* the one thing the rosters carried that still matters moves into the
     caption of the grid it counts, where the eye already is */
  .cap-count { display: inline; }

  /* What is left stacks one panel per row, and the controls become
     full-width bars the way an app's actions usually are. */
  .scope-side { grid-template-columns: 1fr; }
  .btn-row { flex-direction: column; }
  /* height comes from the touch rule above — don't undo it here */
  .btn-row .seq-btn { flex: 0 0 auto; }
}
