:root {
  --bg: #0f141b; --panel: #171e28; --panel2: #1d2632; --line: #2a3646;
  --ink: #dbe4ef; --muted: #7d8ea3; --blue: #4b9fff; --orange: #f0964b;
  --green: #4bd08b; --red: #e35d6a; --gray: #8b97a8;
}
* { box-sizing: border-box; margin: 0; }
body {
  background: var(--bg); color: var(--ink);
  font: 14px/1.45 "Segoe UI", system-ui, sans-serif; min-height: 100vh;
}
a { color: var(--blue); text-decoration: none; }
header {
  display: flex; align-items: center; gap: 18px; padding: 10px 18px;
  background: var(--panel); border-bottom: 1px solid var(--line);
}
header .brand { font-weight: 600; letter-spacing: .4px; }
header .brand span { color: var(--blue); }
header nav { display: flex; gap: 14px; }
header nav a { color: var(--muted); padding: 4px 2px; }
header nav a.active, header nav a:hover { color: var(--ink); }
header .spacer { flex: 1; }
header .who { color: var(--muted); font-size: 12.5px; }
main { padding: 16px 18px; max-width: 1400px; margin: 0 auto; }
h2 { font-size: 15px; margin: 0 0 10px; color: var(--ink); font-weight: 600; }
.panel {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 8px; padding: 14px;
}
.row { display: flex; gap: 16px; align-items: flex-start; }
.grow { flex: 1; min-width: 0; }
button {
  background: var(--blue); color: #08111d; border: 0; border-radius: 6px;
  padding: 7px 14px; font-weight: 600; cursor: pointer; font-size: 13px;
}
button.ghost { background: var(--panel2); color: var(--ink); border: 1px solid var(--line); }
button.danger { background: var(--red); color: #fff; }
button:disabled { opacity: .45; cursor: default; }
input, select, textarea {
  background: var(--panel2); color: var(--ink); border: 1px solid var(--line);
  border-radius: 6px; padding: 7px 9px; font-size: 13px;
}
textarea { width: 100%; font: 12.5px/1.5 Consolas, monospace; }
table { border-collapse: collapse; width: 100%; }
th, td { text-align: left; padding: 7px 10px; border-bottom: 1px solid var(--line); }
th { color: var(--muted); font-weight: 500; font-size: 12px; text-transform: uppercase; }
.badge {
  display: inline-block; padding: 1px 8px; border-radius: 10px;
  font-size: 11px; font-weight: 600; letter-spacing: .3px;
}
.badge.sim { background: #3a2f16; color: var(--orange); }
.badge.real { background: #16324a; color: var(--blue); }
.badge.role { background: var(--panel2); color: var(--muted); }
.dot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; margin-right: 6px; }
.dot.online { background: var(--green); }
.dot.stale { background: var(--orange); }
.dot.offline { background: var(--red); }
.dot.idle { background: var(--gray); }
.dot.standby { background: #4a6285; }
.dot.starting { background: var(--blue); }
.muted { color: var(--muted); }
.kv { display: grid; grid-template-columns: auto 1fr; gap: 3px 14px; font-size: 13px; }
.kv .k { color: var(--muted); }
/* Group heading inside a device panel. Twenty fields in one flat list is not
   more informative than six; these name the question each group answers. */
.devsec { margin: 10px 0 4px; font-size: 10.5px; letter-spacing: 0.09em;
  text-transform: uppercase; color: var(--muted); border-bottom: 1px solid var(--line);
  padding-bottom: 3px; }
canvas { background: #0a0f15; border: 1px solid var(--line); border-radius: 8px; width: 100%; }
.msg { margin-top: 8px; font-size: 12.5px; min-height: 18px; }
.msg.ok { color: var(--green); } .msg.err { color: var(--red); white-space: pre-wrap; }
.login-wrap { display: grid; place-items: center; min-height: 100vh; }
.login-box { width: 340px; }
.login-box .panel { display: flex; flex-direction: column; gap: 10px; }
.status-line { font-size: 13px; color: var(--muted); margin-top: 6px; }
.mono { font-family: Consolas, monospace; font-size: 12.5px; }

/* connection badge (header) */
.conn { font-size: 11px; font-weight: 700; letter-spacing: .6px; padding: 2px 9px;
  border-radius: 10px; }
.conn.live { background: #143526; color: var(--green); }
.conn.reconnecting { background: #3a2020; color: var(--red);
  animation: connpulse 1.2s ease-in-out infinite; }
@keyframes connpulse { 50% { opacity: .45; } }

/* event feed */
.events { max-height: 180px; overflow-y: auto; font-size: 12px; }
.evt { display: flex; gap: 8px; padding: 2px 0; border-bottom: 1px solid #131b26;
  align-items: baseline; }
.evt-t { color: var(--muted); font-family: Consolas, monospace; font-size: 11px;
  flex: none; }
.evt-src { color: var(--blue); flex: none; min-width: 76px; }
.evt.warning .evt-src { color: var(--orange); }
.evt.error .evt-src { color: var(--red); }
.evt-x { color: var(--ink); }
.evt.warning .evt-x { color: var(--orange); }
.evt.error .evt-x { color: var(--red); }

/* help page */
.help h3 { font-size: 14px; margin: 18px 0 6px; color: var(--blue); }
.help p, .help li { font-size: 13px; color: var(--ink); }
.help ul { margin: 4px 0 8px 20px; padding: 0; }
.help dt { color: var(--blue); font-weight: 600; font-size: 13px; margin-top: 8px; }
.help dd { margin: 1px 0 4px 0; font-size: 13px; color: var(--ink); }
.help kbd { background: var(--panel2); border: 1px solid var(--line);
  border-radius: 4px; padding: 0 6px; font: 12px Consolas, monospace; }
.help .swatch { display: inline-block; width: 10px; height: 10px;
  border-radius: 50%; margin-right: 5px; }

/* config schema reference */
.schemaref details { margin-bottom: 6px; }
.schemaref summary { cursor: pointer; color: var(--blue); font-size: 13px;
  padding: 3px 0; }
.schemaref .sdesc { color: var(--muted); font-size: 12px; margin: 3px 0 6px; }
.schemaref table { font-size: 12px; margin-bottom: 6px; }
.schemaref td, .schemaref th { padding: 3px 8px; }

.maphint { font-size: 11.5px; color: var(--muted); }

/* header ops clock: local + UTC offset + Zulu */
.clock { font-family: Consolas, monospace; font-size: 12.5px; color: var(--ink); }
.clock .muted { font-size: 10.5px; }
.clock .zulu { color: var(--blue); font-size: 11.5px; margin-left: 4px; }

/* operating-mode banner — unmistakable at a glance */
.modebanner { margin-bottom: 12px; padding: 7px 14px; border-radius: 8px;
  font-weight: 700; letter-spacing: .8px; font-size: 13px; border: 1px solid;
  display: flex; align-items: center; gap: 10px; }

/* conditions chip (in the banner): how optimal for passive optical */
.cond { font-size: 11.5px; font-weight: 700; letter-spacing: .4px;
  padding: 1px 9px; border-radius: 10px; cursor: help; }
.cond.good { background: #143526; color: var(--green); }
.cond.fair { background: #3a2f14; color: var(--orange); }
.cond.poor { background: #3a1418; color: #ff8d96; }
.cond.unknown { background: var(--panel2); color: var(--muted); }
.modebanner.mode-live { background: #3a1418; color: #ff8d96; border-color: #7c2a32;
  animation: livepulse 2.4s ease-in-out infinite; }
@keyframes livepulse { 50% { background: #47181d; } }
.modebanner.mode-sim { background: #3a2f14; color: var(--orange); border-color: #6d5623; }
.modebanner.mode-replay { background: #232a33; color: var(--gray); border-color: var(--line); }
.modebanner.mode-idle { background: var(--panel); color: var(--muted); border-color: var(--line); }

/* 2D/3D view toggle */
.viewbar { display: flex; gap: 6px; margin-bottom: 8px; align-items: center; }
.viewbtn { background: var(--panel2); color: var(--muted); border: 1px solid var(--line);
  padding: 5px 14px; font-size: 12.5px; }
.viewbtn.active { background: #16324a; color: var(--blue); border-color: #2b5a83; }

/* run-control tabs */
.tabbar { display: flex; gap: 6px; margin-bottom: 10px; }
.tab { background: transparent; color: var(--muted); border: 0;
  border-bottom: 2px solid transparent; border-radius: 0; padding: 4px 10px;
  font-size: 13px; }
.tab.active { color: var(--ink); border-bottom-color: var(--blue); }
#tablive.active { color: #ff8d96; border-bottom-color: var(--red); }
.live-btn { background: var(--red); color: #fff; }

/* map layers popover */
.layerspanel { position: absolute; right: 0; top: 34px; z-index: 20;
  background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
  padding: 10px 14px; display: flex; flex-direction: column; gap: 5px;
  box-shadow: 0 6px 18px rgba(0,0,0,.45); min-width: 170px; }
.layerspanel label { display: flex; align-items: center; gap: 8px;
  font-size: 12.5px; cursor: pointer; color: var(--ink); }
.layerspanel input { accent-color: var(--blue); }

/* data-provenance watermark over the 3D view (2D draws its own on canvas):
   a corner tag, not a splash — the mode banner carries the detail. */
.watermark { position: absolute; top: 10px; left: 14px; pointer-events: none;
  font-size: 15px; font-weight: 800; letter-spacing: 2px;
  color: rgba(240, 150, 75, 0.55); user-select: none; }

/* M5 track table */
#tracktable { overflow-x: auto; }  /* wide rows scroll inside the panel */
table.tracks { width: 100%; border-collapse: collapse; font-size: 12px; }
table.tracks th { text-align: left; color: var(--muted); font-weight: 500;
  padding: 2px 4px; border-bottom: 1px solid var(--line); }
table.tracks td { padding: 3px 4px; border-bottom: 1px solid #131b26; }
table.tracks tr[data-id] { cursor: pointer; }
table.tracks tr.selrow td { background: #16202e; }
table.tracks select.identity { background: var(--panel2); color: inherit;
  border: 1px solid var(--line); border-radius: 4px; font-size: 11px; padding: 1px 2px;
  max-width: 88px; }

/* alert strip (tasks/gcs-alerting-evidence.md) */
#alertstrip { font-size: 13px; }
#alertstrip button.ackbtn { background: #2a1215; color: #e35d6a; border: 1px solid #e35d6a;
  border-radius: 3px; padding: 1px 8px; cursor: pointer; font-weight: 600; }
#alertstrip button.ackbtn:hover { background: #3a181d; }
@keyframes alertpulse { 50% { opacity: .55; } }

/* -- the resilient console (OPS A1/A2/A5) --------------------------------- */
/* Status chip beside the mode text: LIVE / RECONNECTING / STALE <age>. */
.storechip { font-size: 11px; font-weight: 700; letter-spacing: .6px;
  padding: 2px 9px; border-radius: 10px; margin-left: 10px; }
.storechip.live { background: #143526; color: var(--green); }
.storechip.reconnecting { background: #3a2020; color: var(--red); }
.storechip.stale { background: #3a2f16; color: var(--orange); }

/* A panel whose data cannot update (link down, past its threshold): dimmed
   and hatched, never hidden — an old picture beats a blank one. */
.staledata { opacity: .55;
  background-image: repeating-linear-gradient(45deg, transparent,
    transparent 14px, rgba(240, 150, 75, .05) 14px, rgba(240, 150, 75, .05) 16px); }
.stalenote { font-size: 11px; color: var(--orange); padding: 2px 0; }
/* A stale alert strip must not pulse: the CRITICAL animation is a liveness
   claim, and pre-disconnect data pulsing reads as a live alarm. !important
   beats the strip's inline animation style. */
#alertstrip.staledata { animation: none !important; }

/* Pending / failed action states on POST buttons (state lives in the
   pending-action store; the class is re-applied on every rebuild). */
button.actpending { opacity: .6; cursor: progress; }
button.actfailed { border-color: var(--red) !important; color: var(--red) !important; }

/* Storm-mode banner over the capped track table. */
.stormbanner { font-size: 12px; font-weight: 700; color: var(--orange);
  border: 1px solid var(--orange); border-radius: 4px; padding: 3px 8px;
  margin-bottom: 6px; background: rgba(240, 150, 75, .08); }

/* -- what a scenario demonstrates (the scene's `claim:`) ------------------- */
/* Under the picker's summary: what this run proves, which panel shows it, and
   what is NOT visible on the console yet. The gap line is deliberately as
   prominent as the claim — four shipped scenes produce a CORRECT negative
   result that looks like a broken run, and a reviewer who is not told reads a
   working refusal as a fault. */
.claim { margin-top: 6px; font-size: 12.5px; line-height: 1.45; }
.claim b { color: var(--ink); }
.claim-gap { border-left: 2px solid var(--orange); padding-left: 8px;
  color: var(--orange); }
.claim-src { color: var(--muted); font-size: 11.5px; }
.claim-none { color: var(--orange); }

/* -- duty-officer surface (OPS A9) ---------------------------------------- */
/* Always present under the banner strips: compact and dim when quiet,
   severity-tinted (inline, following the alert engine) when a threat is
   promoted. The geometry lives here; the colours arrive inline because they
   follow the data. */
.dutystrip { display: flex; gap: 12px; align-items: center; flex-wrap: wrap;
  margin-bottom: 12px; padding: 6px 12px; border: 1px solid var(--line);
  border-radius: 8px; font-size: 13px; min-height: 34px; }
.dutystrip.quiet { color: var(--muted); }
.dutylabel { font-size: 10.5px; font-weight: 700; letter-spacing: .12em;
  color: var(--muted); border: 1px solid var(--line); border-radius: 4px;
  padding: 1px 6px; cursor: help; }
.dutystrip .dutykv { white-space: nowrap; }
.dutystrip .dutykv .k { color: var(--muted); font-size: 11px; }
.dutystrip button.ackbtn { background: #2a1215; color: #e35d6a;
  border: 1px solid #e35d6a; border-radius: 3px; padding: 1px 8px;
  cursor: pointer; font-weight: 600; }
.dutystrip button.ackbtn:hover { background: #3a181d; }
.dutystrip button.spotbtn, .dutystrip button.dutybtn { padding: 1px 8px;
  font-size: 12px; }

/* W2 the CUED banner. Cyan, like the cue wedge on the map, and deliberately
   NOT the alert palette: a cue is not a threshold crossing and colouring it
   like one would put an external sensor's routine report at alarm weight.
   The no-track case is brighter than the tracking case, because that is the
   state worth acting on — once we hold our own track the cue is history. */
.cuebanner { margin-bottom: 12px; padding: 6px 12px; border-radius: 8px;
  font-size: 13px; border: 1px solid #60d0e0; color: #a8e6ef;
  background: rgba(96, 208, 224, 0.12); }
.cuebanner.tracking { border-color: rgba(96, 208, 224, 0.45);
  background: rgba(96, 208, 224, 0.06); color: var(--muted); }

/* A5 the recorded-flyout provenance stamp. The HANDED case is amber-warned and
   the EARNED case is quiet, which is the whole point of the treatment: the
   claim that needs qualifying is the one this repository has had to withdraw
   four times, and the one that does not need qualifying should not shout. The
   two classes are set from `track_provenance`, which is DERIVED from the
   recording's own stamped/gap lists — so the day the stages start being
   stamped, the banner cools down because the fact changed. */
.interceptorstamp { margin-bottom: 12px; padding: 6px 12px; border-radius: 8px;
  font-size: 13px; border: 1px solid #ffb048; color: #ffd6a0;
  background: rgba(255, 176, 72, 0.12); }
.interceptorstamp.earned { border-color: rgba(120, 220, 160, 0.55);
  color: #bfe8cf; background: rgba(120, 220, 160, 0.08); }
.interceptorstamp .interceptorline { margin-top: 4px; display: flex;
  flex-wrap: wrap; gap: 8px; align-items: center; font-size: 12px; }
.interceptorstamp .slowmo { color: #ffd6a0; letter-spacing: 0.04em; }

/* -- the AI operator in the console (K1/K2/K4) ----------------------------- */
/* The posture banner sits in the chrome beside the P8 operating-mode banner
   and follows its geometry deliberately: an operator already reads that line
   for "what am I looking at", and "is a mind also acting" belongs in the same
   glance. Four postures, four treatments — and UNKNOWN is NOT styled like
   MANUAL, because "the agents may only read" and "we could not ask" are
   different facts and only one of them is reassuring. */
.agentbanner { margin-bottom: 12px; padding: 6px 12px; border-radius: 8px;
  border: 1px solid var(--line); display: flex; align-items: center;
  gap: 10px; flex-wrap: wrap; font-size: 12.5px; min-height: 32px; }
.agentbanner b { letter-spacing: .08em; }
.agentbanner.posture-manual { background: var(--panel); color: var(--muted);
  border-color: var(--line); }
.agentbanner.posture-assist { background: #232a33; color: var(--blue);
  border-color: #2f4a6b; }
.agentbanner.posture-auto { background: #143526; color: var(--green);
  border-color: #2a5c41; }
.agentbanner.posture-seized { background: #3a2f14; color: var(--orange);
  border-color: #6d5623; }
.agentbanner.posture-unknown { background: var(--panel2); color: var(--muted);
  border-color: var(--orange); }
/* A live agent session brightens the line — the banner is quiet furniture on
   a station nothing is connected to, and must not be on one where a mind is
   acting. */
.agentbanner.agents-live { font-weight: 600; }
.agentbanner button.modebtn { background: var(--panel2); color: var(--muted);
  border: 1px solid var(--line); border-radius: 4px; padding: 1px 9px;
  font-size: 11.5px; font-weight: 700; letter-spacing: .06em; cursor: pointer; }
.agentbanner button.modebtn.on { background: var(--panel); color: var(--ink);
  border-color: currentColor; }
.agentbanner button.danger { padding: 2px 10px; font-size: 12px;
  font-weight: 700; letter-spacing: .04em; }
.warnchip { font-size: 10.5px; font-weight: 700; letter-spacing: .08em;
  border: 1px solid var(--orange); color: var(--orange); border-radius: 4px;
  padding: 0 5px; margin-left: 4px; cursor: help; }

/* The session panel. One turn per row: WHEN, WHO, WHAT, on WHICH ENTITY, and
   the rationale UNEXPANDED underneath — K1's bar is that a human reading it
   can say "no, not that" and act, which a rationale behind a disclosure
   triangle fails. */
#agentturns { max-height: 320px; overflow-y: auto; }
.agentrow { display: flex; justify-content: space-between; gap: 8px;
  padding: 2px 0; font-size: 12px; }
.agentrow.stopped { opacity: .65; }
.turn { border-left: 3px solid var(--line); padding: 3px 0 4px 7px;
  margin-bottom: 4px; font-size: 12px; }
.turn.selectable { cursor: pointer; }
.turn.selectable:hover { background: rgba(75, 159, 255, .06); }
.turn-head { display: flex; gap: 7px; align-items: baseline; flex-wrap: wrap; }
.turn-when { color: var(--muted); font-family: Consolas, monospace;
  font-size: 11px; }
.turn-who { color: #d9a441; font-size: 11px; cursor: help; }
.turn-act { font-weight: 700; }
.turn-subj { color: var(--blue); }
.turn-out { color: var(--muted); font-size: 11px; }
.turn-why { color: var(--ink); font-style: italic; padding: 1px 0 0 2px; }
.turn-reason { color: var(--red); font-size: 11.5px; padding: 1px 0 0 2px; }
.turn-args { color: var(--muted); font-size: 11px;
  font-family: Consolas, monospace; }
.turn-done { border-left-color: var(--green); }
.turn-refused { border-left-color: var(--red); }
.turn-read { border-left-color: var(--line); opacity: .8; }
.turn-replay { border-left-color: var(--blue); }
.turn-more { border-left-color: var(--orange); color: var(--orange);
  font-size: 11.5px; }
.turn-gap { border-left-color: var(--orange); color: var(--orange);
  font-size: 11.5px; }
.turn-explain { border: 1px dashed var(--line); border-radius: 4px;
  margin-top: 4px; padding: 4px 6px; font-size: 11.5px; }
.turn-gate.ok { color: var(--green); }
.turn-gate.no { color: var(--red); }
button.revertai { padding: 0 6px; font-size: 10.5px; }

/* K3 — the nudges are for the human too. Group J's recommendations,
   investigation prompts and root-cause trees, rendered where an operator is
   already looking: the track panel and the unit panel. The provenance chip is
   the load-bearing piece — a 0.5 whose basis says "the expected reduction is
   assumed" and a 0.5 resting on a measurement are the same digit and different
   claims, and the console must not be the layer that loses the difference. */
.nudgeblock { grid-column: 1 / -1; }
.nudge { border-left: 3px solid var(--line); border-radius: 3px;
  padding: 4px 0 5px 8px; margin: 5px 0; font-size: 12px; }
.nudge-head { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; }
.nudge-state { font-size: 10.5px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; cursor: help; }
.nudge-act { font-weight: 700; }
.nudge-say { padding: 2px 0 0 1px; line-height: 1.4; }
.nudge-num { display: flex; gap: 12px; flex-wrap: wrap; color: var(--muted);
  font-size: 11.5px; padding: 3px 0 0 1px; }
.nudge-default { color: var(--muted); font-size: 11px; padding: 3px 0 0 1px;
  cursor: help; }
.nudge-stale { color: var(--orange); font-size: 11.5px; cursor: help;
  padding: 2px 0; }
/* DO / cannot / do-not, and they are three colours because they are three
   different next moves. `blocked` is kept rather than dropped (J4). */
.nudge-do { border-left-color: var(--green); }
.nudge-do .nudge-state { color: var(--green); }
.nudge-blocked { border-left-color: var(--orange); }
.nudge-blocked .nudge-state { color: var(--orange); }
.nudge-no { border-left-color: var(--red); }
.nudge-no .nudge-state { color: var(--red); }
/* "Nothing available resolves this" is a MEASURED answer (P20), so it gets a
   row of its own rather than an empty panel an operator would read as "the
   system has no opinion". */
.nudge-wall { border-left-color: #8b6bd9; }
.nudge-wall .nudge-state { color: #8b6bd9; }
/* A refused measurement, with its reason — the J1 gate from the consumer side.
   Never a probability, and never a reason to act. */
.nudge-refusedmeas { border-left-color: var(--muted); }
.nudge-refusedmeas .nudge-state { color: var(--muted); }
.nudge-suspect { border-left-color: #d9a441; }
.nudge-suspect .nudge-state { color: #d9a441; }
/* The four provenance words. Deliberately NOT four shades of one hue: the one
   a reader would otherwise supply as "measured" is the one that has to look
   different from it. */
.prov { font-size: 9.5px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; border-radius: 3px; padding: 0 4px;
  border: 1px solid currentColor; cursor: help; white-space: nowrap; }
.prov-measured { color: var(--green); }
.prov-derived { color: var(--blue); }
.prov-assumed { color: var(--orange); }
.prov-unknown { color: var(--muted); }
/* UNKNOWN never renders as OK, and never as 0. */
.prov-unknown-text { color: var(--orange); font-style: italic; }

/* ==========================================================================
   Responsive: the console on a tablet and a phone
   ==========================================================================
   Every page already carried a `viewport` meta tag and this stylesheet had NO
   media query at all — so the tags promised a mobile layout that did not
   exist. The dashboard is the hard case: a flex `.row` that never wrapped, a
   fixed-width side column beside it, and wide tables, which together made the
   PAGE scroll sideways on a phone rather than the content inside it.

   Three rules shape what follows:

   - The page must never scroll horizontally. Where content is genuinely wide
     (tables, the map) the CONTAINER scrolls, not the document.
   - Nothing is hidden on a small screen. An operator on a phone is usually
     there because something is happening, and a layout that drops the panel
     they need is worse than one they have to scroll.
   - Touch targets and input font sizes change, because both have thresholds
     that are about fingers and browsers rather than about taste (below).
   ========================================================================== */

/* The dashboard's side column and 3D view were inline widths/heights, which a
   media query cannot override without `!important`. As classes they are just
   CSS, and the desktop values are unchanged. */
/* THE SIDE COLUMN MUST NOT BE WIDENED BY ITS OWN CONTENT (2026-08-21).
   `width` on a flex item is only a basis hint: `min-width` defaults to `auto`,
   so the item cannot shrink below its CONTENT's min-content width. The
   scenario `<select>` takes its intrinsic width from its longest option and is
   populated by a fetch about a second after load — so the page laid out
   correctly, then the picker arrived, pushed the column past 330 px, and
   `.grow` (which correctly carries `min-width: 0`) collapsed, taking the map
   with it. Reported as "placed correctly initially on refresh, then after ~1
   second it widens to fill entire width", which is exactly a content-driven
   reflow rather than the CSS breakpoint it looked like.
   `flex: 0 0 330px` plus `min-width: 0` makes the column's width a property of
   the LAYOUT rather than of whatever text a scene happens to be called. */
.sidecol { flex: 0 0 330px; min-width: 0; display: flex;
           flex-direction: column; gap: 14px; }
/* And the select itself: without this it still reports a wide min-content size
   to its own column, which reintroduces a horizontal scrollbar inside the
   panel even once the column is pinned. */
.sidecol select, .sidecol input { width: 100%; min-width: 0; max-width: 100%; }
.scene3d { height: 640px; }

@media (max-width: 1000px) {
  /* Stack the map and the side column. `.row` is used across every page for
     side-by-side panels, so wrapping it here fixes all of them at once. */
  .row { flex-wrap: wrap; }
  .sidecol { flex: 1 1 100%; width: 100%; }
  main { padding: 14px 12px; }

  /* A wide table is the commonest cause of a sideways-scrolling page. Let the
     panel holding it scroll instead, so the rest of the layout stays put. */
  .panel { overflow-x: auto; }

  /* Panels given a fixed flex basis INLINE — the designs page pairs a
     `flex: 0 0 330px` with a `flex: 0 0 430px` — must take the full width once
     the row has wrapped, or a 430 px panel overflows a 390 px phone and the
     wrap has bought nothing. `!important` is the only thing that beats an
     inline `flex` shorthand, and the rule is scoped to a breakpoint where the
     row is already wrapping so it cannot affect the desktop layout. */
  .row > .panel { flex-basis: 100% !important; }

  /* The map canvas keeps its aspect ratio from its width/height attributes;
     the 3D view has no intrinsic size, so it needs an explicit one. */
  .scene3d { height: 60vh; min-height: 320px; }
}

@media (max-width: 720px) {
  header { flex-wrap: wrap; gap: 8px 14px; padding: 8px 12px; }
  /* The nav takes its own line and scrolls sideways if it must, rather than
     pushing the identity and connection badges off the screen. */
  header nav {
    order: 3; width: 100%; overflow-x: auto; gap: 16px;
    padding-bottom: 2px; scrollbar-width: none;
  }
  header nav::-webkit-scrollbar { display: none; }
  header nav a { white-space: nowrap; }
  header .who { font-size: 12px; }

  main { padding: 12px 10px; }
  .panel { padding: 12px; }

  /* Touch targets. A 30 px control is comfortably clickable with a mouse and
     not with a thumb; ~40 px is the low end of every published guideline.
     Scoped to this breakpoint so desktop density is untouched. */
  button { min-height: 40px; padding: 9px 16px; }
  input, select, textarea {
    min-height: 40px;
    /* 16 px is not a style choice: iOS Safari zooms the page when a focused
       input is smaller, which leaves the layout scrolled sideways with no
       obvious way back. */
    font-size: 16px;
  }
  /* Cells stay dense — they are read, not tapped. */
  th, td { padding: 6px 8px; }

  .events { max-height: 220px; }
}

@media (max-width: 460px) {
  header .brand { font-size: 13px; }
  main { padding: 10px 8px; }
  .panel { padding: 10px; border-radius: 6px; }
  .scene3d { height: 52vh; min-height: 260px; }
  /* `width: 340px` alone overflows a 360 px screen once the wrapper has
     padding, which is the whole login page scrolling sideways. */
  .login-box { width: 100%; max-width: 340px; }
  .login-wrap { padding: 16px; }
}

/* The browser must not claim map gestures for its own scroll and zoom. The
   canvas sets `touch-action: none` from script when it installs its touch
   handlers; this is the declarative backstop for the same thing. */
@media (hover: none) and (pointer: coarse) {
  #map { touch-action: none; }
}
=======
/* The run's track churn (created vs durable vs held). Muted on purpose: it is
   context for what the table shows, not a warning — several shipped scenes
   fragment BY DESIGN and colouring this as a fault would be wrong for them. */
.churn { font-size: 11.5px; color: var(--muted); margin-bottom: 6px;
  cursor: help; }

/* ===================================================================
   (end of stylesheet)
   =================================================================== */
