/* ============================================================================
   AGENT EVE — the spectator console.

   The palette and the geometry below are MEASURED, not chosen. Every colour in
   `:root` was sampled out of the chosen mocks rather than picked by eye:

     docs/design/graphics-2026-07-30/samples/profile-e6-broadcast.png
     docs/design/graphics-2026-07-30/samples/screen-grants.png
     docs/design/graphics-2026-07-30/samples/profile-e-evenative.png

   That matters because the generated art in client/assets/ was prompted with
   the same six numbers. An interface that is 80% hand-written CSS and 20%
   generated PNG reads as two products unless the CSS is DERIVED FROM the art
   rather than merely adjacent to it.

     accent cyan  #19d7f2  — the single largest cyan block in profile-e6
     failure red  #ca010f  — the single largest red block in profile-e6
     panel fill   #000d12  — the modal pixel of screen-grants
     ground       #00060a  — the modal pixel of the e-evenative chrome
     dim text     #6f8288  — the modal low-saturation text pixel
     hairline     #123038  — the panel border value

   Geometry measured off screen-overview.png at 1280 wide: table row pitch
   22.2px, hairlines at 1px, panel padding 8px, corner radius ZERO. The radius
   is load-bearing — the terminal variant reads as credible partly because
   nothing on it is rounded.

   ONE ALARM COLOUR, USED SPARINGLY. Red means a promise was broken, and it
   means nothing else. If red appears on a screen for any other reason that is
   a bug, not a style choice.
   ========================================================================= */

:root {
  /* ── the measured palette ─────────────────────────────────────────────── */
  --void: #00060a;
  --panel: #000d12;
  --panel-2: #00090e;
  --panel-3: #011016;
  --rule: #123038;
  --rule-dim: #0a1c22;
  --rule-bright: #1d4a56;

  --text: #cfdadd;
  --text-2: #9aa9ad;
  --dim: #6f8288;
  --dimmer: #3f5158;

  --cyan: #19d7f2;
  --cyan-mid: #4fb9cc;
  --cyan-deep: #0a6a7d;
  --cyan-wash: rgba(25, 215, 242, 0.09);
  --cyan-wash-2: rgba(25, 215, 242, 0.16);

  --red: #ca010f;
  --red-text: #e34a3f;
  --red-wash: rgba(202, 1, 15, 0.15);
  --red-wash-2: rgba(202, 1, 15, 0.3);

  --amber: #d89c42;
  --amber-wash: rgba(216, 156, 66, 0.13);

  /* ── the measured geometry ────────────────────────────────────────────── */
  --row-h: 24px;   /* the mock is 22px at 1280 = 24.8px at 1440 */
  --hair: 1px;
  --pad: 8px;
  --pad-tight: 5px;
  --gap: 6px;

  /* FIVE STEPS, and the ladder is the whole ladder. The first build declared
     nine and rendered six on one screen; the mocks resolve to four visible
     sizes — micro label / body / section head / hero number. `--fs-micro` was
     9px and carried six different roles at the smallest size in the system,
     which is how the tile notes, the badges and the legend all ended up
     borderline at 1440. Measured against the mocks at the same effective
     width, body was ~11% small. */
  --fs-micro: 10px;
  --fs-base: 12px;
  --fs-lg: 15px;
  --fs-xl: 30px;
  --fs-2xl: 46px;
  /* aliases kept so a caller reads as its role, not as a number */
  --fs-sm: var(--fs-micro);
  --fs-md: var(--fs-base);

  --track: 0.09em;
  --track-wide: 0.16em;

  /* System stacks only. No CDN, no webfont, no build step — the deploy is an
     rsync of static files, and one missing font request is one more way for
     the Reckoning to look broken to an audience. */
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  --cond: "Roboto Condensed", "Helvetica Neue", "Arial Narrow", Inter, ui-sans-serif, system-ui, sans-serif;
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; }

body {
  background: var(--void);
  color: var(--text-2);
  font: var(--fs-base)/1.35 var(--mono);
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}

/* The backdrop plate + the procedural starfield both live here, behind
   everything, and neither ever carries data. */
#backdrop {
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(ellipse 120% 90% at 50% 42%, #01161e 0%, #000a10 46%, #00060a 100%);
}
#backdrop img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; opacity: 0.5;
}
#stars { position: absolute; inset: 0; width: 100%; height: 100%; }

#shell {
  position: relative; z-index: 1;
  display: flex; flex-direction: column; height: 100%;
}
/* A 1px dot grid at 2% over everything. It is the single cheapest thing that
   moves the modal-colour share off 79% and it is in every one of the mocks. */
#stage::before {
  content: ""; position: absolute; inset: 0; z-index: 4; pointer-events: none;
  background-image: radial-gradient(circle at 1px 1px, rgba(79, 185, 204, 0.05) 1px, transparent 0);
  background-size: 5px 5px;
}
/* ⚑ **AND IT COMES OFF THE MAP.** The dot grid is a 5 px lattice putting ~16%
   of every pixel at luminance 12–13. On a table screen that is texture; over a
   star field it is the direct competitor, and in raw count the REGULAR lattice
   outnumbered the stars ~800:1 — so the sky read as graph paper. The two map
   screens are the only ones with a sky, and they are the only ones that lose
   it. */
#stage:has(#mapfull)::before { opacity: 0; }

/* ══════════════════════════════════════════════════ chrome: the tab bar ══ */

#chrome {
  display: flex; align-items: stretch; flex: 0 0 auto;
  height: 28px; background: var(--panel-2);
  border-bottom: var(--hair) solid var(--rule);
  font-family: var(--cond); font-size: var(--fs-sm);
  letter-spacing: var(--track); text-transform: uppercase;
  user-select: none;
}
#brand {
  display: flex; align-items: center; gap: 7px; white-space: nowrap;
  padding: 0 14px 0 10px; color: var(--text);
  letter-spacing: var(--track-wide);
  border-right: var(--hair) solid var(--rule);
}
#brand .mk { width: 11px; height: 11px; opacity: 0.85; }
nav#tabs { display: flex; }
/* A STRIP OF BORDERED CELLS, not bare text. The mock boxes every tab and
   terminates the strip in an empty cell; that cell structure is most of why
   its chrome reads as an application rather than as a web page. */
nav#tabs button {
  appearance: none; background: var(--panel); border: 0; white-space: nowrap;
  border-right: var(--hair) solid var(--rule);
  color: var(--dim); padding: 0 13px; cursor: pointer;
  font: inherit; letter-spacing: var(--track);
  position: relative;
}
nav#tabs button:hover { color: var(--text-2); background: var(--panel-3); }
nav#tabs button[aria-current="true"] {
  color: var(--void); background: var(--cyan);
  font-weight: 600;
}
nav#tabs button .fk {
  font-size: 8px; color: var(--dimmer); margin-right: 5px; letter-spacing: 0;
}
nav#tabs button[aria-current="true"] .fk { color: rgba(0, 6, 10, 0.55); }

#clock {
  margin-left: auto; display: flex; align-items: center; gap: 0;
  border-left: var(--hair) solid var(--rule);
}
#clock .seg {
  padding: 0 11px; display: flex; align-items: center; gap: 6px; height: 100%;
  border-left: var(--hair) solid var(--rule-dim); color: var(--dim);
  font-family: var(--mono); letter-spacing: 0.04em; white-space: nowrap;
}
#clock .seg:first-child { border-left: 0; }
#clock .seg b { color: var(--cyan); font-weight: 500; }
#clock .seg.warn b { color: var(--amber); }
#clock .seg.stale { color: var(--red-text); }
#clock .seg em {
  font-style: normal; font-size: var(--fs-micro); color: var(--dimmer);
  font-family: var(--cond); letter-spacing: var(--track);
}

/* the freeze pip — the one thing on the chrome that moves every tick */
.pip {
  width: 6px; height: 6px; background: var(--cyan); display: inline-block;
  animation: beat 2s steps(1, end) infinite;
}
@keyframes beat { 0%, 60% { opacity: 1; } 61%, 100% { opacity: 0.15; } }

/* ═══════════════════════════════════════════════════════════ the stage ══ */

/* one gutter, as the mock has. Panels docked hard to the window edge with
   no margin read as a web page that ran out of room, not as an application. */
#stage { flex: 1 1 auto; overflow: hidden; position: relative; padding: var(--gap); }
.screen { position: absolute; inset: 0; overflow: auto; display: none; }
.screen[data-on="1"] { display: block; }
.screen.nogrow { display: none; }

/* the layout primitives — everything is a grid of hairline panels */
.grid { display: grid; gap: var(--gap); }
.g-2 { grid-template-columns: 1fr 1fr; }
.g-3 { grid-template-columns: 1fr 1fr 1fr; }
.g-4 { grid-template-columns: repeat(4, 1fr); }
.rows { display: flex; flex-direction: column; gap: var(--gap); }
.fill { height: 100%; }

/* ══════════════════════════════════════════════════════════════ panels ══ */

.panel {
  background: linear-gradient(180deg, #01131a 0%, var(--panel) 70px);
  border: var(--hair) solid var(--rule);
  display: flex; flex-direction: column; min-width: 0; min-height: 0;
}
.panel > h2 {
  flex: 0 0 auto; margin: 0; padding: var(--pad-tight) var(--pad);
  font: 600 var(--fs-sm)/1 var(--cond);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  color: var(--cyan-mid); background: var(--panel-2);
  border-bottom: var(--hair) solid var(--rule);
  display: flex; align-items: center; gap: var(--pad); white-space: nowrap;
  /* the header itself must clip, or nothing inside it is constrained and the
     subtitle's `text-overflow: ellipsis` never fires — the whole row just
     overflows the panel and the right slot gets cut instead. */
  overflow: hidden;
}
.panel > h2 .sub {
  color: var(--dim); font-weight: 400; letter-spacing: var(--track);
  text-transform: none; font-size: var(--fs-micro);
  /* GROW as well as shrink: with `flex: 0 1 auto` the subtitle never took
     the slack, so `.right`'s `margin-left: auto` had no free space to push
     into and the right slot was what got clipped instead. */
  flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
}
/* the title and the right slot never shrink; only the subtitle does, and it
   ellipsizes rather than hard-cutting a number mid-digit */
.panel > h2 > :first-child, .panel > h2 .right { flex: 0 0 auto; }
.panel > h2 .right { margin-left: auto; color: var(--dim); font-weight: 400; }
.panel > h2.alarm { color: var(--red-text); }
.panel .body { flex: 1 1 auto; min-height: 0; overflow: auto; }
.panel .body.pad { padding: var(--pad); }
.panel .foot {
  flex: 0 0 auto; padding: 4px var(--pad); border-top: var(--hair) solid var(--rule-dim);
  color: var(--dimmer); font-size: var(--fs-micro); font-family: var(--cond);
  letter-spacing: var(--track); text-transform: uppercase;
}

/* ═══════════════════════════════════════════════════════════════ tables ══ */

table.t { width: 100%; border-collapse: collapse; font-size: var(--fs-base); }
table.t thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--panel-2); color: var(--dim);
  font: 400 var(--fs-micro)/1 var(--cond);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  text-align: left; padding: 6px var(--pad); white-space: nowrap;
  border-bottom: var(--hair) solid var(--rule);
  cursor: pointer; user-select: none;
}
table.t thead th:hover { color: var(--cyan-mid); }
table.t thead th.num, table.t td.num { text-align: right; font-variant-numeric: tabular-nums; }
table.t thead th .ar { color: var(--dimmer); margin-left: 3px; }
table.t thead th[data-sorted] { color: var(--cyan); }
table.t tbody tr { height: var(--row-h); border-bottom: var(--hair) solid var(--rule-dim); }
table.t tbody tr:hover { background: var(--cyan-wash); }
table.t tbody tr.sel { background: var(--cyan-wash-2); }
/* RED = a promise broke. AMBER = value at risk or taken. Nothing else tints. */
table.t tbody tr.bad { background: var(--red-wash); }
table.t tbody tr.bad:hover { background: var(--red-wash-2); }
table.t tbody tr.warn { background: rgba(216, 156, 66, 0.055); }
table.t tbody tr.warn:hover { background: var(--amber-wash); }
table.t td {
  padding: 0 var(--pad); white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; color: var(--text-2);
}
table.t td.k { color: var(--text); }
table.t td.dim { color: var(--dim); }
table.t.click tbody tr { cursor: pointer; }

/* the handle chip — a principal is always drawn the same way, everywhere */
.h {
  color: var(--cyan); text-decoration: none; cursor: pointer;
  border-bottom: var(--hair) dotted var(--cyan-deep);
}
.h:hover { color: #fff; border-bottom-color: var(--cyan); }
.h.bad { color: var(--red-text); border-bottom-color: rgba(202, 1, 15, 0.5); }
.sysl { color: var(--cyan-mid); cursor: pointer; text-decoration: none; }
.sysl:hover { color: var(--cyan); }

/* small square status swatch — the mocks lean on these hard */
.sw { display: inline-block; width: 8px; height: 8px; margin-right: 6px; vertical-align: -1px; }
/* the one place a viewer decodes the 3-step tier ladder. At 8×8 with a 1 px
   inline border the FILL was 27 of 105 px — 74% frame — so all three swatches
   read as the same outlined box and the key could not key anything. */
.sw.tier { width: 18px; height: 11px; vertical-align: -2px; }
.sw.cy { background: var(--cyan); }
.sw.cyd { background: var(--cyan-deep); }
.sw.rd { background: var(--red); }
.sw.am { background: var(--amber); }
.sw.gy { background: #35464b; }
.sw.hollow { background: none; border: var(--hair) solid var(--dim); }

/* state tags */
.tag {
  display: inline-block; padding: 1px 5px; font-size: var(--fs-micro);
  font-family: var(--cond); letter-spacing: var(--track); text-transform: uppercase;
  border: var(--hair) solid var(--rule-bright); color: var(--dim);
}
.tag.cy { color: var(--cyan); border-color: var(--cyan-deep); }
.tag.rd { color: #fff; background: var(--red); border-color: var(--red); }
.tag.am { color: var(--amber); border-color: rgba(216, 156, 66, 0.45); }
.tag.solid { color: var(--void); background: var(--cyan); border-color: var(--cyan); }

/* ══════════════════════════════════════════════════════════ stat tiles ══ */

/* `flex: 0 0 auto` is load-bearing. Round 3's `.rows > * { min-height: 0 }`
   removed the floor under this strip, and on the one screen that
   over-constrains — OVERVIEW in a mature world — the tile box collapsed
   from 76px to 36px and cut every headline number in half. */
.tiles { display: flex; gap: var(--gap); flex: 0 0 auto; }
.tile {
  flex: 1 1 0; min-width: 0; background: var(--panel);
  border: var(--hair) solid var(--rule); padding: 7px var(--pad) 8px;
}
.tile .lab {
  font: 400 var(--fs-micro)/1 var(--cond); letter-spacing: var(--track-wide);
  text-transform: uppercase; color: var(--dim); margin-bottom: 5px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tile .val {
  font: 400 var(--fs-xl)/1 var(--mono); color: var(--cyan);
  font-variant-numeric: tabular-nums; letter-spacing: -0.02em;
}
.tile .val.sm { font-size: var(--fs-lg); }
.tile .val.neutral { color: var(--text); }
.tile .val.dimval { color: var(--dim); }
.tile.bad { border-color: rgba(202, 1, 15, 0.55); background: var(--red-wash); }
.tile.bad .val { color: var(--red-text); }
.tile.bad .lab { color: var(--red-text); }
.tile.warn { border-color: rgba(216, 156, 66, 0.45); }
.tile.warn .val { color: var(--amber); }
.tile.warn .lab { color: var(--amber); }
.tile .note {
  font-size: var(--fs-micro); color: var(--dimmer); margin-top: 4px;
  line-height: 1.45; overflow: hidden; display: -webkit-box;
  -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}

/* ═════════════════════════════════════════════════════════ empty state ══ */
/*
   An empty panel must read as "nothing yet", never as a broken layout. This
   project's oldest defect is a capability nobody can tell from a missing one;
   reproducing it in the renderer would be a poor joke. So an empty panel says
   WHICH key is empty and WHAT would fill it.
*/
.empty {
  display: flex; align-items: center; gap: 8px;
  padding: 6px var(--pad); cursor: help;
  border-bottom: var(--hair) solid var(--rule-dim);
}
.empty .mark {
  width: 8px; height: 8px; flex: 0 0 8px;
  border: var(--hair) solid var(--rule-bright);
}
.empty .say {
  font: 400 var(--fs-micro)/1.5 var(--cond); letter-spacing: var(--track);
  text-transform: uppercase; color: var(--dimmer); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}

/* the sparse skeleton — structure without content */
.skel { padding: 3px 0; }
.skel-row {
  display: flex; gap: var(--pad); height: var(--row-h); align-items: center;
  padding: 0 var(--pad); border-bottom: var(--hair) solid var(--rule-dim);
}
.skel-row i { display: block; height: 5px; background: rgba(18, 48, 56, 0.5); }
.skel-row:nth-child(2n) i { opacity: 0.55; }
.skel-row:nth-child(3n) i { opacity: 0.35; }
.nul { color: var(--dimmer); }

/* ═══════════════════════════════════════════════════════════ the ticker ══ */

#ticker {
  flex: 0 0 auto; height: 24px; display: flex; align-items: center;
  background: var(--panel-2); border-top: var(--hair) solid var(--rule);
  font-size: var(--fs-micro); color: var(--dim); white-space: nowrap;
}
/* The marquee gets its OWN clipped track. It used to run underneath the lead
   chip — the strip named after A5, visibly broken on every frame. */
#ticker .track { flex: 1 1 auto; overflow: hidden; min-width: 0; }
#ticker .lead {
  flex: 0 0 auto; z-index: 1; padding: 0 10px; height: 100%; display: flex; align-items: center;
  background: var(--cyan); color: var(--void);
  font: 600 var(--fs-micro)/1 var(--cond); letter-spacing: var(--track-wide);
}
/* The marquee starts at the left edge, not off-screen: a viewer who lands on
   the page during the 90 s of empty lead-in concludes the ticker is broken. */
#ticker .run { display: inline-block; white-space: nowrap; animation: run 70s linear infinite; }
#ticker .run i { font-style: normal; padding: 0 24px; }
#ticker .run i::before { content: "◇"; color: var(--cyan-deep); padding-right: 10px; }
#ticker:hover .run { animation-play-state: paused; }
@keyframes run { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* ══════════════════════════════════════════════════════════════ the map ══ */

/* The map's own ground is --void, DARKER than the outermost band, so the
   FRONTIER boundary reads as an edge instead of dissolving into the panel. */
#mapwrap { position: relative; width: 100%; height: 100%; overflow: hidden; background: var(--void); }
#mapsvg { display: block; width: 100%; height: 100%; cursor: grab; }
#mapsvg.drag { cursor: grabbing; }

/* ★ THREE BANDS OF DIFFERENT GROUND, not three node colours. Every generated
   map in the sweep failed this. The three fills are three real steps of value
   with a lit boundary between each, and they run LIGHTEST AT THE CORE: the
   Commons is settled and safe, the Frontier is raw. A stranger has to be able
   to put a finger on the line between two bands without reading a word. */
/* Monotonic toward the core, and the panel behind them (#000d12) is darker
   than all three — so the outermost boundary reads as an edge rather than as
   nothing. The FRONTIER band was originally #010c12, one value off the panel
   fill, which meant the outer band was invisible and the map looked like two
   tiers and a blank. */
/* The fills are written inline from `MapView.BAND_FILL`. There is no CSS class
   for them ON PURPOSE: a second set of literals here is exactly the bug the JS
   comment says it fixed, and it survived one file over for a whole round. */
.band-edge { fill: none; stroke-width: 3; }
/* THE COMMONS is drawn CATEGORICALLY differently, not merely lighter: a
   neutral dotted edge, no bloc colour, and no fence ever crosses it. A8 makes
   hostile action there INVALID rather than punished, and the drawing says so. */
/* THE COMMONS keeps a DIFFERENT LINE STYLE — A8 makes hostile action there
   invalid rather than punished, and the drawing has to say that categorically
   rather than by being a bit lighter. But `4 5` at 1.3 px read as a fence with
   holes in a file whose own rule is that a hole is a false claim. A tight
   2 / 2.5 at 2.4 px is unmistakably a different KIND of line and unmistakably
   continuous. */
.band-edge.commons { stroke: #cfe3e7; stroke-dasharray: 2 2.5; stroke-width: 2.4; }
.band-label {
  fill: #3a5c66; font: 600 12px var(--cond); letter-spacing: 0.34em;
  text-transform: uppercase; pointer-events: none;
  paint-order: stroke; stroke: #00060a; stroke-width: 3px; stroke-linejoin: round;
}
.band-label.commons { fill: #93a6aa; font-size: 9px; letter-spacing: 0.2em; }
/* ★ BRIGHTER, BECAUSE IT IS NOW AN AFFORDANCE AND NOT A WATERMARK.
   At #1c3b44 the constellation arcs were 1.15:1 against the band they ride
   and read as texture. They are now the click target for the drill-down, and
   an invisible affordance is this project's own named defect: a capability
   nobody exercises is indistinguishable from one that is missing. */
.con-label {
  fill: #2e5c69; font: 600 15px var(--cond); letter-spacing: 0.34em;
  pointer-events: none; text-anchor: middle;
}

.lane { stroke: #17414c; stroke-width: 1; fill: none; }
.lane.strait { stroke: #2a5f6b; }
.lane.severed { stroke: #4a6068; stroke-dasharray: 6 4; }
.waist { stroke: var(--cyan-deep); stroke-width: 1.4; fill: none; }
.notch { fill: #06171d; stroke: var(--cyan-deep); stroke-width: 1; }
.notch-t { fill: var(--cyan); font: 9px var(--mono); text-anchor: middle; pointer-events: none; }
/* a severing strait is GEOGRAPHY. It was the single largest red region on
   the map, and nobody lied. Solid, bright, unmistakable — and not red. */
.door { fill: #cfdadd; stroke: #00060a; stroke-width: 1; }
.door-t {
  fill: #cfdadd; font: 9px var(--mono); text-anchor: middle; pointer-events: none;
  paint-order: stroke; stroke: #00060a; stroke-width: 3px; stroke-linejoin: round;
}

/* ⚑ **THE STROKES CAME UP, AND THAT IS THE OTHER HALF OF THE STAR RULE.**
   A node's visible PEAK is its 1 px stroke, and at #2c6b78 that peaked at
   L≈95 — under the brightest background star (L≈98). Nine of thirty charted
   systems were dimmer than a dim unnamed decoration, which is the hard rule
   failing on 30% of the map. Capping the stars alone left it marginal; the
   fills are now dark enough that the strokes can afford this, and it buys
   node-against-ground contrast at the same time. */
.node { stroke-width: 1.2; cursor: pointer; }
.node-COMMONS { fill: #0a2c34; stroke: #a8bcc0; }
.node-MARCHES { fill: #08313b; stroke: #4592a3; }
.node-FRONTIER { fill: #0a3540; stroke: #52a8b8; }
.node:hover { stroke: var(--cyan); stroke-width: 1.8; }
.node.sel { stroke: #fff; stroke-width: 2; }
/* Halos, because 30 labels on a 30-node graph WILL collide somewhere and an
   unreadable name is worse than a name overlapping a lane. */
/* 4.4 px of halo, not 3.2. The tier ladder now lives on three 3 px BRIGHT
   strokes rather than on three flat fills, and a bright hairline crossing a
   9 px label is a strikethrough where a dark fill was merely a background:
   `sys-08`, `sys-07`, `sys-19` and `sys-20` were each cut by a band edge. The
   halo has to be wider than the thing it is defending against. */
.node-lab {
  fill: #b7c5c9; font: 9px var(--mono); pointer-events: none;
  paint-order: stroke; stroke: #00060a; stroke-width: 4.4px; stroke-linejoin: round;
}
.node-id {
  fill: #7d979e; font: 9px var(--mono); pointer-events: none;
  paint-order: stroke; stroke: #00060a; stroke-width: 4.4px; stroke-linejoin: round;
}
/* ⚑ **FUEL IS AN ATTRIBUTE, NOT AN ALARM.** Amber was carrying four meanings
   at once — a raid, a claim in arrears, a WORKS spinning up, and "this ground
   yields fuel". On the FRONTIER every single system yields fuel, so on the
   `con-4` drill-down all eight discs wore an amber ring and amber became
   AMBIENT there: present everywhere, therefore warning of nothing. The numeral
   (`· 10f`) already carries the fact; the ring just needed to stop shouting. */
.fuel-ring { fill: none; stroke: #4a7d86; stroke-width: 1; stroke-dasharray: 2 3; opacity: 0.9; }

/* THE VERGE — one continuous solid outline. Never dashed: a dash is a hole,
   and a hole in a fence reads as a claim that is not there. */
/* painted last so no label halo can punch a hole in it, and therefore
   deliberately quiet: at full weight on top of everything the fences read
   as bright bubbles competing with the nodes they enclose. */
.verge {
  fill-opacity: 0.085; stroke-width: 1.5; stroke-opacity: 0.8;
  stroke-linejoin: round; fill-rule: nonzero;
}
.verge-lab {
  font: 600 10px var(--cond); letter-spacing: 0.18em; text-transform: lowercase;
  pointer-events: none;
}

.claim-tint { stroke-width: 1.2; fill-opacity: 0.22; }
.works-mk { fill: none; stroke: var(--cyan-mid); stroke-width: 1; opacity: 0.9; }
.ruin-mk { fill: none; stroke: #8a969a; stroke-width: 2; }
.ruin-ring { fill: #05090c; stroke: #6f8288; stroke-width: 1.4; stroke-dasharray: 3 3; }

/* a raid is a LOSS, not a betrayal. Amber. */
.raid-arc { fill: none; stroke: var(--amber); stroke-width: 1.6; }
.raid-halo { fill: none; stroke: var(--amber); stroke-width: 1; opacity: 0.5; animation: pulse 1.6s ease-out infinite; }
@keyframes pulse { 0% { r: 12px; opacity: 0.55; } 100% { r: 30px; opacity: 0; } }

.compact-arc { fill: none; stroke: var(--cyan); stroke-width: 1; opacity: 0.55; }
.compact-arc.snapped { stroke: var(--red); stroke-dasharray: 5 4; opacity: 0.85; }
.convoy-path { fill: none; stroke: var(--cyan); stroke-width: 1; opacity: 0.4; stroke-dasharray: 4 4; }
.convoy-dot { fill: var(--cyan); }
/* ★ THE SEAT — where the Levy's tribute converges. `tributeLines[]` has
   carried 13 rows all along and the map drew none of them. */
/* SIX SPOKES POINTING IN — the convergence the contract names, and the only
   mark on either screen drawn as spokes. It replaced a dotted white ring that
   was the FOURTH dotted ring in the vocabulary and the same hue and pitch as
   the COMMONS boundary. */
.seat-spoke { stroke: #cfe3e7; stroke-width: 1.4; opacity: 0.75; }
.seat-spoke.hot { stroke: var(--amber); opacity: 0.95; }
.seat-spoke.red { stroke: var(--red-text); opacity: 1; }
.seat-hit { fill: none; stroke: none; pointer-events: all; }
.seat-t {
  fill: #cfe3e7; font: 9px var(--mono); text-anchor: middle; pointer-events: none;
  paint-order: stroke; stroke: #00060a; stroke-width: 4.4px; stroke-linejoin: round;
}
.seat-t.hot { fill: var(--amber); }
/* RED is unpaid at the freeze, which is a word broken. The round-2 build
   mapped `e.red` to the AMBER class — an invariant whose subject cannot occur,
   written in CSS: invisible only because no frame carries RED yet. */
.seat-t.red { fill: var(--red-text); }

/* the FRONT was the biggest object on the map and it was in no legend line */
.front-cone { fill: var(--amber); fill-opacity: 0.06; stroke: var(--amber); stroke-opacity: 0.32; stroke-width: 1; stroke-dasharray: 3 4; }

/* ════════════════════════════════════════════ ★ THE STAR FIELD (tactical) ══
 *
 * DECORATION, AND IT HAS TO BE UNMISTAKABLE ABOUT IT. Four buckets, top out at
 * 0.30 — a charted node is a 6–12 px stroked disc at full opacity carrying a
 * NAME and an ID, so the two channels are ~4 opacity steps and ~50× the area
 * apart. `pointer-events: none` on the group, so a star can never eat a click
 * meant for the graph underneath it, and no star carries a <title>.
 *
 * The deep-cyan bucket is 4% of the field and exists only so the sky is not one
 * flat grey; it is still dimmer than the dimmest lane (#17414c). */
/* the field is a CANVAS sibling under the SVG. It rides the same pan/zoom as a
   CSS transform, so a drag composites instead of repainting 28,000 rects. */
.starcv {
  position: absolute; left: 0; top: 0; pointer-events: none;
  transform-origin: 0 0; will-change: transform;
}

/* ═══════════════════════════════════ ★ THE RETICLE AND THE CALLOUT ══ */
.selg { pointer-events: none; }
.ret-arc { fill: none; stroke: var(--cyan); stroke-width: 1.4; opacity: 0.95; }
.ret-tick { stroke: var(--cyan); stroke-width: 1.4; opacity: 0.95; }
.ret-lead { fill: none; stroke: var(--cyan); stroke-width: 1.3; opacity: 0.9; }
/* ⚑ **OPAQUE. A CALLOUT IS A MASK, NOT A TINT.**
   At 0.88 a white `STRANDS 4` chip and an exit stub underneath still came
   through at 12% — enough to garble 10 px type, and what it garbled was
   `▸ DRILL INTO CON-4 · 8 SYSTEMS`, rendering it as
   `▸ DRILL INTO CON-#ANDS-8-SYSTEMS`. The only new interaction affordance on
   the screen, made illegible by the one panel that is supposed to answer
   things. Nothing is worth seeing through a callout. */
.co-box { fill: #00080d; stroke: var(--cyan-deep); stroke-width: 1; }
.co-t { font: 10px var(--mono); pointer-events: none; }
.co-h {
  font: 600 10px var(--cond); letter-spacing: 0.16em; fill: var(--cyan);
}
.co-b { fill: var(--text-2); }
.co-c { fill: var(--cyan-mid); }
.co-a { fill: var(--amber); }
.co-r { fill: var(--red-text); }
.co-d { fill: var(--dim); }
/* peak L 75.5 against a 152–176 body — the only CLICKABLE element in the
   concept's signature component, and the dimmest thing in it. The callout was
   made opaque specifically so this line would read; that fixed the
   bleed-through and left the contrast. */
.co-go { fill: var(--cyan-mid); font-size: 10px; letter-spacing: 0.08em; }
.co-rule { stroke: var(--rule); stroke-width: 1; }
.co-go-hit { fill: transparent; pointer-events: all; cursor: pointer; }
.co-go-hit:hover { fill: var(--cyan-wash); }
.co-go-hit:hover + .co-rule { stroke: var(--cyan-deep); }
.con-label.go { pointer-events: all; cursor: pointer; }
.con-label.go:hover { fill: var(--cyan-deep); }

/* ══════════════════════════════════════ ★ TACTICAL: THE FLOATING PANELS ══
 *
 * The concept's value is a LAYOUT change, not a rendering one: the galaxy goes
 * full-bleed and the panels stop taking grid space. The docked build spent
 * 322 px of a 1788 px stage on a rail, which is 18% of the map's width, and
 * then squeezed the galaxy into an ellipse inside what was left.
 *
 * ⚑ **THE THING THAT MAKES FLOATING SAFE IS `MapView` s `inset`, NOT THE
 * ALPHA.** The first build of the docked map overlaid its legend on a live
 * FRONTIER system and that is why everything got docked in the first place.
 * Going back to floats is only legitimate because the LAYOUT now reserves the
 * covered edges — the galaxy is centred in the free area and no node is ever
 * laid out under a panel. Translucency is then a bonus (you can see the star
 * field and the odd lane continue behind a rail), never the mitigation.
 *
 * 0.72, not 0.70: measured against the concept the panel interiors read at
 * roughly 28% of the ground behind them, and at 0.70 flat the 10 px table text
 * lost enough contrast against a passing lane to fail a squint test. */
/* ⚑ **OPAQUE GROUND, AND THAT IS A DETERMINISM FIX, NOT A STYLE CALL.**
   `#backdrop` runs an ANIMATED canvas star field behind the whole app — it
   drifts and twinkles by design, which is right for a page and wrong for a
   map. Left transparent, the map screens showed TWO skies at once and the
   brighter of the two was the one that moves, which is exactly what hard rule
   #1 forbids: 400 background stars that reshuffle break §6.2's pinned layout
   with more pixels than the layout it protects. */
#mapfull { position: absolute; inset: 0; overflow: hidden; background: #00060a; }
#mapfull #mapwrap { position: absolute; inset: 0; background: transparent; }
/* NO `position` HERE ON PURPOSE — `.mfloat` is the translucent treatment and
   nothing else. Two of the three floats live inside an absolutely-positioned
   rail and would collapse if the class positioned them itself. */
.mfloat {
  background: rgba(0, 9, 14, 0.72);
  border: var(--hair) solid var(--rule);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
  display: flex; flex-direction: column; min-height: 0;
  box-shadow: 0 0 0 1px rgba(0, 6, 10, 0.55);
}
.mfloat.at { position: absolute; z-index: 3; }
.mfloat > h2 { background: rgba(0, 6, 10, 0.62); }
.mfloat .body { overflow: auto; }
/* the header strip — the concept's one-line world summary, top-left */
.mstrip {
  position: absolute; z-index: 3; left: 0; top: 0;
  display: flex; align-items: center; height: 25px;
  background: rgba(0, 9, 14, 0.72); border: var(--hair) solid var(--rule);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
  font: var(--fs-micro)/1 var(--cond); letter-spacing: var(--track-wide);
  text-transform: uppercase; color: var(--dim); white-space: nowrap;
}
.mstrip .seg { padding: 0 11px; border-right: var(--hair) solid var(--rule-dim); }
.mstrip .seg:last-child { border-right: 0; }
.mstrip b { color: var(--text-2); font-weight: 400; }
.mstrip .hot { color: var(--amber); }
.mstrip .hint { color: var(--dimmer); }
/* the FRONTIER is not empty, it is UNTAKEN — and that is a story rather than
   an outage. Cyan, because nothing here is wrong. */
.mstrip .untaken { color: var(--cyan-mid); }
.mstrip .untaken b { color: var(--cyan); }
.mstrip button {
  appearance: none; background: none; border: 0; border-left: var(--hair) solid var(--rule-dim);
  color: var(--dim); font: inherit; letter-spacing: var(--track-wide);
  text-transform: uppercase; padding: 0 10px; height: 100%; cursor: pointer;
}
.mstrip button:hover { color: var(--cyan); background: var(--cyan-wash); }
.mstrip button[aria-pressed="true"] { color: var(--void); background: var(--cyan-deep); }

/* the layer chips, top-right of the free area — one row, not a 2-col grid of
   checkbox labels, because at 7 rows that widget was 100 px of chrome */
.mchips {
  position: absolute; z-index: 3; display: flex; height: 21px;
  background: rgba(0, 9, 14, 0.72); border: var(--hair) solid var(--rule);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
}
.mchips button {
  appearance: none; background: none; border: 0;
  border-right: var(--hair) solid var(--rule-dim);
  font: 9px var(--cond); letter-spacing: var(--track); text-transform: uppercase;
  color: var(--dimmer); padding: 0 8px; cursor: pointer;
}
.mchips button:last-child { border-right: 0; }
.mchips button[aria-pressed="true"] { color: var(--cyan-mid); }
.mchips button:hover { background: var(--cyan-wash); }

/* ★ THE PROMISES RAIL — stolen from mapconcept-terrace.
 *
 * The first time the game's own SCOREBOARD has appeared on the map at all. The
 * map drew territory, ground and motion and never once drew a promise, which
 * is the thing the product is named after. It is a table and it costs 4 rows
 * of the rail. */
.prom { font-size: var(--fs-micro); }
.prom .r {
  display: flex; align-items: center; gap: 7px; height: 21px; padding: 0 8px;
  border-bottom: var(--hair) solid var(--rule-dim); white-space: nowrap; cursor: pointer;
}
.prom .r:hover { background: var(--cyan-wash); }
.prom .r.sel { background: var(--cyan-wash-2); }
.prom .kd {
  flex: 0 0 52px; color: var(--dimmer); font-family: var(--cond);
  letter-spacing: var(--track); text-transform: uppercase;
}
.prom .fr, .prom .to { flex: 1 1 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.prom .ar { flex: 0 0 auto; color: var(--dimmer); }
.prom .st {
  flex: 0 0 68px; text-align: right; color: var(--cyan-mid);
  font-family: var(--cond); letter-spacing: var(--track); text-transform: uppercase;
}
.prom .r.bad .st { color: var(--red-text); }
.prom .r.bad .fr, .prom .r.bad .to { color: var(--red-text); }
.prom .r.warn .st { color: var(--amber); }
.prom .hd {
  display: flex; gap: 7px; padding: 3px 8px; color: var(--dimmer);
  font: 9px var(--cond); letter-spacing: var(--track); text-transform: uppercase;
  border-bottom: var(--hair) solid var(--rule);
}
.prom .hd .kd { flex: 0 0 52px; }
.prom .hd .fr, .prom .hd .to { flex: 1 1 0; }
.prom .hd .st { flex: 0 0 68px; text-align: right; }

/* ══════════════════════════════════ ★ THE CONSTELLATION (the drill-down) ══
 *
 * One constellation at ~5× the map's node size, so that every field the frame
 * carries per system has somewhere to be drawn. The map's own channels are
 * kept IDENTICAL — THE LODE still sizes, the tier still fills, the claim ramp
 * is still bloc → amber → red, THE VERGE is still one continuous outline — so
 * this is the same map further in, not a second visual language. */
#zoomwrap { position: absolute; inset: 0; overflow: hidden; }
#zoomsvg { display: block; width: 100%; height: 100%; }

.zdisc { stroke-width: 1.4; }
.zdisc.t-COMMONS { fill: #0a2c34; stroke: #7f9296; }
.zdisc.t-MARCHES { fill: #08313b; stroke: #2c6b78; }
.zdisc.t-FRONTIER { fill: #0a3540; stroke: #35808f; }
/* the claim ring, over the tier fill — the SAME three-step ramp as the map's
   claim tint, because a viewer who learned it there must not relearn it. */
.zclaim { fill-opacity: 0.14; stroke-width: 2.2; }
.zclaim.hot { stroke-width: 3; fill-opacity: 0.2; }
.zfuel { fill: none; stroke: #4a7d86; stroke-width: 1.2; stroke-dasharray: 2 4; opacity: 0.9; }
.zhit { fill: transparent; cursor: pointer; }
.zsys .zhit:hover ~ * { pointer-events: none; }
.zsys:hover .zdisc { stroke: var(--cyan); }

/* ★ ONE SQUARE PER WORKS. Filled = taking a share; hollow = SPINNING UP and
   taking nothing yet. A viewer counting productive ground can count. */
.zworks { fill: #12525f; stroke: #4fb9cc; stroke-width: 1; }
.zworks.up { fill: none; stroke: var(--amber); stroke-dasharray: 3 2; }
.zruin { fill: #06111a; stroke: #6f8288; stroke-width: 1; }
.zruin-x { stroke: #8a969a; stroke-width: 1.4; fill: none; }
.zmore { fill: var(--dim); font: 9px var(--mono); }
/* unworked ground is labelled with THE LODE against its tier — the field that
   differs between two bare systems — not with the word BARE eight times. */
.zbare { fill: var(--dim); font: 400 13px var(--mono); }
.zbare.up { fill: var(--cyan-mid); }
.zbare.dn { fill: #6d7f84; }
/* 1.40:1 measured. This caption is the ONLY thing that says `+333` is a rate
   of richness against its tier and not an ore count or a balance — the unit on
   the mark that replaced `BARE`, rendered as decoration. 5.7:1 now. */
.zbare-u {
  fill: var(--text-2); font: 8px var(--cond); letter-spacing: 0.14em; text-anchor: middle;
  paint-order: stroke; stroke: #00060a; stroke-width: 2.6px; stroke-linejoin: round;
}

/* THE ANCHOR. `anchorHot` has been on every claim line since the field was
   written and no pixel has ever depended on it: a cold claim collects nothing
   and, on the map, is tinted exactly like one collecting from seven tenants. */
.zanchor { fill: none; stroke-width: 1.8; stroke-linecap: round; }
.zanchor.hot { stroke: var(--cyan); }
.zanchor.cold { stroke: #4a5c62; stroke-dasharray: 3 2.5; }
.zanchor-h.hot { fill: none; stroke: var(--cyan); stroke-width: 1.8; }
.zanchor-h.cold { fill: none; stroke: #4a5c62; stroke-width: 1.5; }

.zlane { stroke: #1d5462; stroke-width: 1.4; }
.zlane.strait { stroke: #2f7686; }
.zlane.severed { stroke: #4a6068; stroke-dasharray: 7 5; }
.zlane.exit { stroke: #17414c; stroke-dasharray: 4 4; }
.zwaist { stroke: var(--cyan-deep); stroke-width: 1.6; fill: none; }
.zdoor { fill: #cfdadd; stroke: #00060a; stroke-width: 1; }
.zpinch-t {
  fill: var(--cyan); font: 10px var(--mono); text-anchor: middle; pointer-events: none;
  paint-order: stroke; stroke: #00060a; stroke-width: 3px; stroke-linejoin: round;
}
/* ⚑ **NOT RED, AND THIS FILE ALREADY SAID SO 260 LINES UP.** `.door-t`'s
   comment: "a severing strait is GEOGRAPHY. It was the single largest red
   region on the map, and nobody lied. Solid, bright, unmistakable — and not
   red." The drill-down reintroduced the exact defect the map documents fixing,
   and on `con-4` — where nothing is held, claimed or worked — `STRANDED 4` was
   the ONLY red mark on the screen: the alarm colour spent, alone, on a fact
   about the road. */
.zpinch-t.rd { fill: #cfdadd; font-size: 9px; }
.zexit { cursor: pointer; }
.zexit-t, .zexit-n {
  paint-order: stroke; stroke: #00060a; stroke-width: 3.4px; stroke-linejoin: round;
}
.zexit-t { fill: var(--cyan-mid); font: 10px var(--mono); }
.zexit-n { fill: var(--dimmer); font: 9px var(--mono); }
.zexit:hover .zexit-t { fill: var(--cyan); }
.zexit:hover .zexit-n { fill: var(--dim); }

/* the COLLAR — a per-system tag in the holder's colour, not a fence. See the
   argument in zoomview.js: at this zoom a bloc's ground is genuinely a set of
   separate places, and a fence made of gaps is a false claim. */
.zcollar { fill: none; stroke-width: 3.2; stroke-linecap: round; stroke-opacity: 0.9; }
.zverge-lab {
  font: 600 11px var(--cond); letter-spacing: 0.2em; text-transform: lowercase;
  pointer-events: none;
  paint-order: stroke; stroke: #00060a; stroke-width: 3px; stroke-linejoin: round;
}

/* the label stack. Halos on every one of them — at this size the discs are
   large enough that a name WILL cross a neighbour's verge or lane. */
.zid, .zname, .zyield, .zstate, .zhold {
  pointer-events: none;
  paint-order: stroke; stroke: #00060a; stroke-width: 3.4px; stroke-linejoin: round;
}
.zid { fill: var(--dim); font: 9px var(--mono); }
.zname { fill: var(--text); font: 600 14px var(--cond); letter-spacing: 0.14em; }
.zyield { fill: var(--cyan); font: 400 15px var(--mono); }
.zstate { font: 9px var(--cond); letter-spacing: 0.13em; }
.zstate.cy { fill: var(--cyan-mid); }
.zstate.am { fill: var(--amber); }
.zstate.rd { fill: var(--red-text); }
.zstate.ce { fill: #8496c4; }
/* 2.06–2.12:1 measured, on the line that carries SWAY and REACHERS — §9's
   force projection, the most strategically loaded fact on this screen, and the
   least readable text on it. 4.9:1 now. */
.zstate.dm { fill: var(--dim); }
.zhold { fill: var(--dim); font: 9px var(--mono); }
.zhold.tr { fill: var(--cyan-deep); }
/* a QUANTITY beside a handle, when the STATE beside it would have been the
   same word ten times. See zoomview.js — the ring becomes a leaderboard. */
.zhold.q { fill: var(--text-2); }
.zhold.more { fill: var(--dimmer); }

/* what is HAPPENING here, over what is standing here. Same marks as the map:
   the venture ring is A13's glyph, and a raid is amber because it is a LOSS
   and not a lie. */
.zglyph { overflow: visible; }
.zraid { fill: none; stroke: var(--amber); stroke-width: 1.6; opacity: 0.55; }
.zraid.live { opacity: 1; stroke-dasharray: 7 4; }
.zfront {
  fill: var(--amber); fill-opacity: 0.05; stroke: var(--amber);
  stroke-opacity: 0.3; stroke-width: 1; stroke-dasharray: 3 4;
}

/* the locator inset */
.locsvg { background: rgba(0, 6, 10, 0.5); }
.loc-lane { stroke: #10333c; stroke-width: 0.8; }
.loc-n { fill: #2b555f; }
.loc-n.on { fill: var(--cyan); }
.loc-box { fill: none; stroke: var(--cyan); stroke-width: 1; stroke-opacity: 0.8; }
.loc-t { fill: var(--cyan); font: 9px var(--cond); letter-spacing: 0.2em; }

/* the selected system's name, big, at the top of the rail */
.zhead {
  display: flex; align-items: baseline; gap: 9px; padding: 7px 9px 6px;
  border-bottom: var(--hair) solid var(--rule);
}
.zhead .nm { font: 400 22px/1 var(--mono); color: var(--cyan); letter-spacing: -0.01em; }
.zhead .sid { font-size: var(--fs-micro); color: var(--dim); }

.ruinstrip { border-top: var(--hair) solid var(--rule); }
.ruinstrip .r {
  display: flex; gap: 8px; align-items: center; padding: 4px 8px;
  font-size: var(--fs-micro); color: var(--dim);
}
.ruinstrip .x { color: #8a969a; }
.ruinstrip .e { margin-left: auto; color: var(--dimmer); }

/* the record strip along the bottom, as the concept has it */
.zlog {
  position: absolute; z-index: 3; bottom: 0; height: 62px; overflow: hidden;
  background: rgba(0, 9, 14, 0.72); border: var(--hair) solid var(--rule);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
  display: grid; grid-template-columns: 1fr 1fr; grid-auto-rows: 19px;
  padding: 2px 0;
}
.zlog .ln {
  display: flex; gap: 8px; padding: 0 10px; align-items: center;
  font-size: var(--fs-micro); color: var(--dim); white-space: nowrap; overflow: hidden;
}
.zlog .ln .tk { color: var(--dimmer); flex: 0 0 auto; }
.zlog .ln .de { overflow: hidden; text-overflow: ellipsis; }
.zlog .ln.bad .de { color: var(--red-text); }
.zlog .ln.warn .de { color: var(--amber); }

/* the collapsed key — the two COLOUR ramps, always on screen. The shape marks
   fold away because a shape has a <title> on the canvas and a colour does not. */
.keybar { padding: 5px 8px 6px; }
.keybar .kb {
  display: flex; flex-wrap: wrap; gap: 3px 11px; align-items: center;
  font: 9px var(--cond); letter-spacing: var(--track); text-transform: uppercase;
  color: var(--dim); line-height: 1.5;
}
.keybar .kb + .kb { margin-top: 4px; padding-top: 4px; border-top: var(--hair) solid var(--rule-dim); }
.keybar .kb span { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
.keybar .kb.dim { color: var(--dimmer); }

/* All three map controls are DOCKED in the side column, never floating over
   the canvas. An overlay legend covered a live FRONTIER system in the first
   build; the mock docks hard to the edge for exactly this reason. */
.legend { padding: 7px 9px; font-size: var(--fs-micro); line-height: 1.8; }
.legend .row { color: var(--dim); white-space: nowrap; }
.legend hr { border: 0; border-top: var(--hair) solid var(--rule-dim); margin: 6px 0; }

label.lyr {
  display: flex; align-items: center; gap: 6px; padding: 4px 9px;
  font: var(--fs-micro)/1.4 var(--cond); letter-spacing: var(--track);
  text-transform: uppercase; color: var(--dim); cursor: pointer; white-space: nowrap;
  border-bottom: var(--hair) solid var(--rule-dim);
}
label.lyr:hover { color: var(--text-2); background: var(--panel-3); }
label.lyr input { accent-color: var(--cyan); margin: 0; width: 10px; height: 10px; }

.inspect { font-size: var(--fs-micro); }
.inspect .kv { display: flex; padding: 2.5px var(--pad); border-bottom: var(--hair) solid var(--rule-dim); }
.inspect .kv span:first-child {
  width: 94px; flex: 0 0 94px; color: var(--dimmer);
  font-family: var(--cond); letter-spacing: var(--track); text-transform: uppercase;
}
.inspect .kv span:last-child { color: var(--text-2); }

/* ══════════════════════════════════════════════════ the venture glyph ══ */
/*
   §A13: a venture is a ring whose hollow arc is the part riding on someone's
   word. An unfilled role is an empty socket that pulses.
*/
.glyph { display: block; }
/* the escrowed half is a REAL colour, not a shade of the ground: at
   --cyan-deep on --panel the 65% arc was invisible next to the 35% one. */
.glyph .g-escrow { fill: none; stroke: #17798d; }
.glyph .g-elective { fill: none; stroke: var(--cyan); stroke-linecap: butt; }
.glyph .g-elective.snap { stroke: var(--red); }
.glyph .g-socket { fill: none; stroke: var(--dim); stroke-dasharray: 2 3; animation: socket 1.7s ease-in-out infinite; }
@keyframes socket { 0%, 100% { opacity: 0.28; } 50% { opacity: 0.95; } }
.glyph .g-break { stroke: var(--red); stroke-width: 1.6; }

/* ═══════════════════════════════════════════════ the E6 broadcast scale ══ */

.hero {
  border: var(--hair) solid var(--rule); background: var(--panel);
  padding: 14px var(--pad) 16px; display: flex; align-items: baseline; gap: 18px;
}
.hero .big {
  font: 400 var(--fs-2xl)/0.95 var(--mono); color: var(--cyan);
  font-variant-numeric: tabular-nums; letter-spacing: -0.03em;
}
.hero .big.bad { color: var(--red-text); }
.hero .lab {
  font: 600 var(--fs-sm)/1 var(--cond); letter-spacing: var(--track-wide);
  text-transform: uppercase; color: var(--dim);
}

/* 48px and filling the bar. At 35px in a 1220px strip with a 240px label the
   banner read as an alert rather than as a headline; the mock fills 49% of its
   bar and this is the poster half of an E1/E6 pair. */
.banner {
  padding: 11px 18px 13px; display: flex; align-items: baseline; gap: 20px;
  font: 700 48px/1 var(--cond); letter-spacing: 0.01em;
  text-transform: uppercase;
}
.banner.good { background: var(--cyan); color: var(--void); }
.banner.bad { background: var(--red); color: #fff; }
.banner .clause {
  font: 400 var(--fs-md)/1.3 var(--mono); letter-spacing: 0;
  text-transform: none; opacity: 0.82; margin-left: auto; text-align: right;
}

/* ═══════════════════════════════════════════════════════ the event log ══ */

.log { font-size: var(--fs-base); }
.log .ln {
  display: flex; gap: var(--pad); padding: 2px var(--pad);
  border-bottom: var(--hair) solid var(--rule-dim); white-space: nowrap;
}
.log .ln:hover { background: var(--cyan-wash); }
.log .tk { color: var(--dimmer); width: 46px; flex: 0 0 46px; font-variant-numeric: tabular-nums; }
.log .ty {
  color: var(--dim); width: 118px; flex: 0 0 118px; font-family: var(--cond);
  letter-spacing: var(--track); text-transform: uppercase; font-size: var(--fs-sm);
}
.log .de { color: var(--text-2); overflow: hidden; text-overflow: ellipsis; }
.log .ln.bad .ty, .log .ln.bad .de { color: var(--red-text); }

/* ══════════════════════════════════════════════════════ misc utilities ══ */

.bar { height: 7px; background: var(--panel-2); border: var(--hair) solid var(--rule-dim); position: relative; }
.bar i { display: block; height: 100%; background: var(--cyan-deep); }
.bar i.hot { background: var(--red); }
.bar i.warm { background: var(--amber); }

.meter { display: flex; align-items: center; gap: var(--pad); padding: 3px var(--pad); }
.meter .nm {
  width: 62px; flex: 0 0 62px; color: var(--dim); font-family: var(--cond);
  letter-spacing: var(--track); text-transform: uppercase; font-size: var(--fs-sm);
}
.meter .bar { flex: 1 1 auto; }
.meter .qt { width: 62px; flex: 0 0 62px; text-align: right; font-variant-numeric: tabular-nums; color: var(--text-2); }

.kv2 { display: flex; padding: 2.5px var(--pad); border-bottom: var(--hair) solid var(--rule-dim); }
.kv2 > span:first-child {
  flex: 0 0 152px; color: var(--dim); font-family: var(--cond);
  letter-spacing: var(--track); text-transform: uppercase; font-size: var(--fs-sm);
}
.kv2 > span:last-child { color: var(--text); text-align: right; margin-left: auto; font-variant-numeric: tabular-nums; }
.kv2 > span:last-child.bad { color: var(--red-text); }

.pips { display: inline-flex; gap: 3px; vertical-align: -1px; }
.pips i { width: 8px; height: 8px; border: var(--hair) solid var(--cyan-deep); display: block; }
.pips i.on { background: var(--cyan); border-color: var(--cyan); }

.note-line {
  padding: 5px var(--pad); font-size: var(--fs-micro); color: var(--dimmer);
  border-top: var(--hair) solid var(--rule-dim); line-height: 1.6;
}
.note-line code { color: var(--cyan-deep); }

.scroller { display: flex; gap: 4px; align-items: center; padding: 4px var(--pad); flex-wrap: wrap; }
.scroller button {
  appearance: none; background: var(--panel-2); border: var(--hair) solid var(--rule);
  color: var(--dim); font: var(--fs-micro) var(--mono); padding: 2px 7px; cursor: pointer;
}
.scroller button:hover { color: var(--cyan); border-color: var(--cyan-deep); }
.scroller button[aria-current="true"] { background: var(--cyan); color: var(--void); border-color: var(--cyan); }

.plate { display: block; max-width: 100%; }
/* inline-block, never block: a block crest wraps the handle onto its own line
   and turns a 22px row into a 50px one. */
.crest { display: inline-block; vertical-align: -3px; background-repeat: no-repeat; }

.good-ico { width: 15px; height: 15px; vertical-align: -3px; margin-right: 5px; }

.mark-ico { width: 15px; height: 15px; vertical-align: -3px; }
.mark-ico.lg { width: 26px; height: 26px; vertical-align: -7px; }

/* the reel */
.reel { display: flex; flex-direction: column; }
.reel .band {
  display: flex; border-bottom: var(--hair) solid var(--rule);
  min-height: 76px;
}
.reel .band:last-child { border-bottom: 0; }
.reel .no {
  flex: 0 0 44px; display: flex; align-items: center; justify-content: center;
  font: 400 var(--fs-xl)/1 var(--mono); color: var(--rule-bright);
  border-right: var(--hair) solid var(--rule);
}
.reel .bd { flex: 1 1 auto; padding: 9px 14px; min-width: 0; }
.reel .bt {
  font: 600 var(--fs-sm)/1 var(--cond); letter-spacing: var(--track-wide);
  text-transform: uppercase; color: var(--cyan-mid); margin-bottom: 8px;
}
.reel .bt .q { color: var(--dimmer); font-weight: 400; letter-spacing: var(--track); }
.reel .said {
  color: var(--text); font-size: var(--fs-md); line-height: 1.55; margin: 3px 0;
  display: flex; gap: 12px;
}
.reel .said .who { color: var(--dim); flex: 0 0 118px; }
/* The big treatment is reserved for CONTRADICTED. An HONOURED seal is the
   normal outcome and shouting it spends the emphasis that the one real
   betrayal on the night needs. */
.reel .verdict {
  display: inline-block; padding: 6px 16px; border: 1.4px solid var(--red);
  color: var(--red-text); font: 600 var(--fs-xl)/1 var(--cond);
  letter-spacing: var(--track); text-transform: uppercase;
}
.reel .verdict.ok {
  border: var(--hair) solid var(--cyan-deep); color: var(--cyan);
  font-size: var(--fs-md); padding: 3px 11px; letter-spacing: var(--track-wide);
}

hr.rule { border: 0; border-top: var(--hair) solid var(--rule-dim); margin: var(--gap) 0; }

.pill {
  font: var(--fs-micro) var(--cond); letter-spacing: var(--track);
  text-transform: uppercase; color: var(--dim);
  border: var(--hair) solid var(--rule); padding: 1px 6px;
}

::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-track { background: var(--panel-2); }
::-webkit-scrollbar-thumb { background: #17414c; }
::-webkit-scrollbar-thumb:hover { background: var(--cyan-deep); }

#boot {
  position: fixed; inset: 0; z-index: 99; display: flex;
  align-items: center; justify-content: center; background: var(--void);
  color: var(--dim); font-family: var(--cond); letter-spacing: var(--track-wide);
  text-transform: uppercase; font-size: var(--fs-sm);
}
#boot.gone { display: none; }

/* ══════════════════════════════════════════ additions after critic round 1 ══ */

/* the three-step alarm ramp. RED is a broken word; the two steps below it are
   value at risk, and they exist so red does not have to carry everything. */
.hero .big.warn { color: var(--amber); }
.tag.rd2 { color: #fff; background: #8f2118; border-color: #8f2118; }
.log .ln.warn .de { color: var(--amber); }
.log .ln.warn .ty { color: var(--amber); }

/* THE STATE STATUS strip — the mock keeps a permanent key of every state a
   badge can read. Without it a stranger meets LIVE / PAID / UNUSED / DRAWN /
   RED with nothing on screen defining any of them. It costs 26px. */
.states {
  display: flex; flex-wrap: wrap; gap: 0 18px; padding: 5px var(--pad);
  font: var(--fs-micro)/1.6 var(--cond); letter-spacing: var(--track);
  text-transform: uppercase; color: var(--dim);
}
.states span { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.states i { width: 7px; height: 7px; display: block; }

/* second-level slicing, as the mock has it. Sorting is not slicing. */
.slices {
  display: flex; gap: 0; border-bottom: var(--hair) solid var(--rule);
  background: var(--panel-2);
}
.slices button {
  appearance: none; background: none; border: 0;
  border-right: var(--hair) solid var(--rule-dim);
  padding: 4px 12px; cursor: pointer; color: var(--dim);
  font: var(--fs-micro)/1.5 var(--cond); letter-spacing: var(--track-wide);
  text-transform: uppercase;
}
.slices button:hover { color: var(--text-2); background: var(--panel-3); }
.slices button[aria-current="true"] { color: var(--void); background: var(--cyan); font-weight: 600; }
.slices .count { color: var(--dimmer); margin-left: 6px; }
.slices button[aria-current="true"] .count { color: rgba(0, 6, 10, 0.6); }

/* the standings callout — the three-second answer, written in words. The mock
   puts it directly under the table and it is the thing a stranger reads. */
.callout { padding: 7px var(--pad); border-top: var(--hair) solid var(--rule); }
.callout div { display: flex; gap: 14px; align-items: baseline; padding: 2px 0; font-size: var(--fs-base); }
.callout .who { flex: 0 0 96px; font-size: var(--fs-lg); }
.callout .good .who { color: var(--cyan); }
.callout .bad .who { color: var(--red-text); }
.callout .good { color: var(--cyan-mid); }
.callout .bad { color: var(--red-text); }

/* a window-chrome cell at the end of the tab strip, as the mock has */
nav#tabs { flex: 1 1 auto; }
#chrome .fillcell { flex: 1 1 auto; border-right: var(--hair) solid var(--rule); background: var(--panel); }
#chrome .wc {
  display: flex; align-items: center; gap: 13px; padding: 0 12px;
  color: var(--dimmer); letter-spacing: 0.2em; border-left: var(--hair) solid var(--rule);
}

/* THE HALL OF FAME entry: an icon cell divided by a vertical hairline, as the
   mock draws it, rather than a bare horizontal rule. */
.fame { display: flex; border-bottom: var(--hair) solid var(--rule-dim); }
.fame .ic {
  flex: 0 0 52px; display: flex; align-items: center; justify-content: center;
  border-right: var(--hair) solid var(--rule-dim); background: var(--panel-2);
}
.fame .tx { padding: 7px 10px; min-width: 0; }
.fame .ti { font: 600 var(--fs-micro)/1 var(--cond); letter-spacing: var(--track-wide); }
.fame .cl { font-size: var(--fs-micro); color: var(--dim); margin-top: 4px; line-height: 1.55; }
.title-ico { width: 30px; height: 30px; display: block; background-repeat: no-repeat; }

/* ★ THE PROVABLE WARNING. The mock's full-width A6 callout: the worst case, the
   two LIMITS, and the one sentence that makes it provable rather than asserted. */
.warncall {
  flex: 0 0 auto; border: var(--hair) solid var(--rule-bright);
  background: linear-gradient(90deg, rgba(25, 215, 242, 0.07), transparent 62%);
  padding: 8px var(--pad) 9px;
}
.warncall .l1 {
  font-size: var(--fs-base); color: var(--text-2);
  display: flex; align-items: baseline; gap: 4px; flex-wrap: wrap;
}
.warncall .l1 b { color: var(--cyan); font-weight: 500; font-variant-numeric: tabular-nums; }
.warncall .gid { color: var(--dim); }
.warncall .arrow { color: var(--cyan-deep); }
.warncall .st {
  font: var(--fs-micro) var(--cond); letter-spacing: var(--track);
  border: var(--hair) solid var(--cyan-deep); color: var(--cyan); padding: 1px 6px;
}
.warncall .tail { margin-left: auto; color: var(--dim); font-size: var(--fs-micro); }
.warncall .l2 { margin-top: 5px; font-size: var(--fs-micro); color: var(--dimmer); }

/* The reel fills its panel: four bands sharing the height rather than four
   fixed bands and 230px of nothing underneath them. */
/* Only THE DEED grows. THE GRANT, THE WORDS and THE SEAL are each one fact,
   and giving them equal height to the thing that actually happened is how the
   ceremony ended up 78% one colour. */
.reel { height: 100%; }
.reel .band { flex: 0 0 auto; min-height: 0; }
.reel .band:last-child { flex: 1 1 auto; }

/* A null act of the reel keeps its number and its title and gives its height
   back. Half a ceremony at equal billing with the other half is not honesty,
   it is padding that happens to be true. */
.reel .band.nul { flex: 0 0 auto; min-height: 0; }
.reel .band.nul .bd { padding: 6px 14px 7px; }
.reel .band.nul .bt { margin-bottom: 4px; }

label.lyr.off { opacity: 0.4; cursor: default; }
label.lyr.off:hover { color: var(--dim); background: none; }

/* ★ `min-height: 0` ON EVERY FLEX CHILD, AND IT IS NOT COSMETIC.
   A flex item defaults to `min-height: auto`, which means its CONTENT can push
   it past its own `flex-basis`. So `flex: 0 0 208px` on a row holding an
   eight-row table silently became 745px and ate the panel above it — the
   STANDINGS table vanished entirely and the page looked like a render failure.
   Every fixed-height row in this client is a flex child, so this rule is the
   difference between the declared layout and the one that renders. */
.rows > *, .grid > * { min-height: 0; }

/* The Hall of Fame's fourth clause and THE KEY's paragraph were being sliced
   through the x-height by a fixed panel height. Both panels size to content
   and the rail scrolls if the three together do not fit. */
.fame .cl, .legend .row { overflow-wrap: anywhere; }

/* ═══════════════════════════════════════════════════════════════════════════
   THE LANDING and THE DOSSIER — the front door (2026-08-02).

   Mobile-FIRST, unlike everything above it, because these two screens are
   where a Reddit ad lands and Reddit traffic is phones. The console keeps its
   operator density; the door is built at 390 px and widens, not the reverse.
   Palette and type are the same tokens — one product, two zoom levels, which
   is the same argument the chrome makes about e1/e6.
   ═══════════════════════════════════════════════════════════════════════════ */

body.landing-mode #chrome, body.landing-mode #ticker { display: none; }
body.landing-mode #stage { padding: 0; }

.landing {
  min-height: 100vh; min-height: 100dvh;
  display: flex; flex-direction: column; align-items: center;
  gap: 22px; padding: clamp(20px, 6vh, 64px) 18px 40px;
  max-width: 720px; margin: 0 auto;
}
.l-hero { text-align: center; }
.l-mark svg { width: clamp(44px, 10vw, 64px); height: auto; animation: l-spin 60s linear infinite; }
@keyframes l-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .l-mark svg { animation: none; } }
.l-hero h1 {
  margin: 14px 0 10px; font-size: clamp(28px, 8vw, 52px);
  letter-spacing: 0.14em; font-weight: 500; color: var(--text);
}
.l-tag {
  margin: 0 auto; max-width: 46ch; color: var(--text-2);
  font-size: clamp(14px, 3.6vw, 16px); line-height: 1.55;
}

.l-strip {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 8px 18px;
  padding: 10px 16px; border: 1px solid var(--rule); background: var(--panel);
}
.l-seg { display: inline-flex; gap: 7px; align-items: baseline; white-space: nowrap; }
.l-seg em { font-style: normal; color: var(--dim); font-size: 10px; letter-spacing: 0.12em; }
.l-seg b { color: var(--cyan); font-weight: 500; font-size: 14px; }
.l-seg.l-broken b { color: var(--red-text); }

.l-pane { width: 100%; border: 1px solid var(--rule); background: var(--panel); }
.l-tabs { display: flex; border-bottom: 1px solid var(--rule); }
.l-tab {
  flex: 1; padding: 14px 8px; min-height: 48px;  /* a thumb, not a cursor */
  background: var(--panel-2); color: var(--dim); border: 0;
  border-right: 1px solid var(--rule); font: inherit; font-size: 12px;
  letter-spacing: 0.14em; cursor: pointer;
}
.l-tab:last-child { border-right: 0; }
.l-tab[aria-selected="true"] { background: var(--panel); color: var(--cyan); box-shadow: inset 0 2px 0 var(--cyan); }
.l-body { padding: 18px 16px 20px; }

.l-steps { margin: 0 0 16px; padding-left: 22px; display: grid; gap: 12px; }
.l-steps li { color: var(--text-2); font-size: 14px; line-height: 1.5; }
.l-steps b { color: var(--text); font-weight: 500; }

.l-paste {
  margin: 0 0 14px; padding: 14px 16px;
  background: var(--void); border: 1px solid var(--rule-dim);
  color: var(--text-2); font-size: 12px; line-height: 1.55;
  /* pre-WRAP. The v1 block held 80-column lines in white-space:pre, which
     h-scrolled inside the door panel and grew scrollbar chrome on two edges —
     the owner's "doesn't fit right", verbatim. The copied text keeps its
     newlines; the display bends them. */
  white-space: pre-wrap; word-break: break-word;
  max-height: 46vh; overflow-y: auto;
}
.l-agent-note { margin: 0 0 12px; color: var(--dim); font-size: 12.5px; line-height: 1.5; }
.l-alt { margin: 12px 0 0; font-size: 12.5px; color: var(--dim); }
.l-alt a { color: var(--cyan-mid); }

.l-copy {
  display: block; width: 100%; min-height: 48px; padding: 13px;
  background: var(--cyan); color: var(--void); border: 0; cursor: pointer;
  font: inherit; font-size: 13px; letter-spacing: 0.16em; font-weight: 600;
}
.l-copy:active { transform: translateY(1px); }
.l-copy.did { background: var(--text); }

.l-watch { display: flex; width: 100%; gap: 0; }
.l-input {
  flex: 1; min-height: 48px; padding: 0 14px;
  background: var(--panel); border: 1px solid var(--rule); border-right: 0;
  color: var(--text); font: inherit; font-size: 15px;
}
.l-input::placeholder { color: var(--dimmer); }
.l-input:focus { outline: none; border-color: var(--cyan-deep); }
.l-go {
  min-height: 48px; padding: 0 18px; border: 1px solid var(--rule);
  background: var(--panel-2); color: var(--cyan); font: inherit;
  font-size: 12px; letter-spacing: 0.12em; cursor: pointer;
}
.l-names { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.l-names a {
  padding: 7px 12px; border: 1px solid var(--rule-dim); color: var(--text-2);
  text-decoration: none; font-size: 12px;
}
.l-names a:hover { color: var(--cyan); border-color: var(--cyan-deep); }
.l-console { color: var(--dim); font-size: 12px; letter-spacing: 0.14em; text-decoration: none; padding: 10px; }
.l-console:hover { color: var(--cyan); }

/* ── the dossier ── */
.dossier { max-width: 860px; margin: 0 auto; padding: clamp(14px, 3vw, 28px); display: grid; gap: 16px; }
.d-head { display: flex; gap: 14px; align-items: center; }
.d-head h2 { margin: 0; font-size: clamp(22px, 6vw, 34px); letter-spacing: 0.06em; font-weight: 500; }
.d-addr { color: var(--dim); font-size: 12.5px; }
.d-standing { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; }
.d-tile {
  border: 1px solid var(--rule); background: var(--panel);
  padding: 12px 10px; display: grid; gap: 6px; text-align: center;
}
.d-tile b { font-size: clamp(18px, 5vw, 26px); font-weight: 500; color: var(--text); }
.d-tile em { font-style: normal; font-size: 9.5px; letter-spacing: 0.12em; color: var(--dim); }
.d-tile.d-red b { color: var(--red-text); }
.d-tile.d-amber b { color: var(--amber); }
.d-line { color: var(--dim); font-size: 12px; }
.d-sec { border: 1px solid var(--rule); background: var(--panel); padding: 12px 14px; }
.d-sec h3 { margin: 0 0 8px; font-size: 11px; letter-spacing: 0.14em; color: var(--cyan-mid); font-weight: 500; }
.d-row, .d-beat { padding: 7px 0; border-top: 1px solid var(--rule-dim); font-size: 13px; color: var(--text-2); line-height: 1.45; }
.d-row:first-of-type, .d-beat:first-of-type { border-top: 0; }
.d-beat b { color: var(--text); font-weight: 500; }
.d-beat.d-red { color: var(--red-text); }
.d-links { display: flex; gap: 18px; flex-wrap: wrap; }
.d-links a { color: var(--cyan-mid); font-size: 12.5px; text-decoration: none; }

/* The two door screens SCROLL. The console's #stage is overflow:hidden on
   purpose — its panels manage their own scroll — but the landing and the
   dossier are documents, and a document inside an overflow:hidden stage is
   simply unreadable below the fold (measured: the WATCH input was unreachable
   on the very first desktop screenshot). */
body.landing-mode #stage, body.dossier-mode #stage { overflow-y: auto; }

/* ── the console on a phone: survivable, pending the full ladder ── */
@media (max-width: 720px) {
  /* #chrome is height:28px by design on an operator screen. Wrapped onto a
     phone, the fixed height made the extra rows OVERLAP the page under them —
     the dossier's own name was under the tab strip. Auto height, and every
     row pushes content down like it is 2026. */
  #chrome { flex-wrap: wrap; height: auto; min-height: 28px; row-gap: 0; }
  #brand { padding: 8px 10px; }
  #tabs { order: 3; width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
  #tabs::-webkit-scrollbar { display: none; }
  #tabs button { min-height: 42px; }
  /* The clock keeps TICK and the countdown; the decorative window furniture
     (─ ▢ ✕) is a desktop joke and a phone's precious pixels. */
  #clock { margin-left: auto; flex-wrap: wrap; }
  #clock .seg { padding: 0 8px; }
  #clock .wc { display: none; }
  #stage { padding: 8px; overflow-y: auto; }
  #ticker .lead { display: none; }
}

/* ── map v2: ink follows stakes ── */
.node-quiet { opacity: 0.55; }
.node-stk1 { stroke: var(--amber); stroke-width: 1.6; }
.node-stk2 { stroke: var(--red-text); stroke-width: 1.8; }
.plate-box { fill: rgba(0, 13, 18, 0.88); stroke: var(--rule-bright); }
.plate-box.plate-red { stroke: var(--red-text); }
.plate-box.plate-amber { stroke: var(--amber); }
.plate-lead { stroke: var(--rule-bright); stroke-width: 1; }
.plate-lead.plate-red { stroke: var(--red-text); }
.plate-lead.plate-amber { stroke: var(--amber); }
.plate-t1 { fill: var(--text); font: 600 10px "Roboto Condensed", sans-serif; letter-spacing: 0.08em; }
.plate-t2 { fill: var(--text-2); font: 10px ui-monospace, monospace; }
.plate-t2.plate-red { fill: var(--red-text); }
.plate-t2.plate-amber { fill: var(--amber); }

/* ── the map ladder (phone) ── */
.ladder { display: grid; gap: 10px; padding: 4px 2px 24px; }
.lad-seat {
  display: flex; justify-content: space-between; align-items: center; gap: 10px;
  border: 1px solid var(--amber); background: var(--amber-wash); padding: 12px 14px;
}
.lad-seat b { color: var(--amber); font-size: 13px; letter-spacing: 0.1em; }
.lad-seat span { color: var(--amber); font-size: 12px; }
.lad-card { border: 1px solid var(--rule); background: var(--panel); }
.lad-head {
  display: flex; justify-content: space-between; padding: 10px 12px;
  border-bottom: 1px solid var(--rule);
}
.lad-head b { color: var(--cyan-mid); font-size: 11px; letter-spacing: 0.14em; }
.lad-head span { color: var(--dim); font-size: 10.5px; }
.lad-row {
  display: flex; justify-content: space-between; align-items: center; gap: 10px;
  width: 100%; min-height: 48px; padding: 8px 12px;
  background: none; border: 0; border-top: 1px solid var(--rule-dim);
  color: var(--text-2); font: inherit; text-align: left; cursor: pointer;
}
.lad-row:first-of-type { border-top: 0; }
.lad-name b { color: var(--text); font-size: 14px; font-weight: 500; }
.lad-name i { font-style: normal; color: var(--dimmer); font-size: 10.5px; }
.lad-note { font-size: 11px; color: var(--dim); text-align: right; }
.lad-row.lad-amber .lad-note { color: var(--amber); }
.lad-row.lad-amber .lad-name b { font-size: 16px; }
.lad-row.lad-red .lad-note { color: var(--red-text); }
.lad-row.lad-red .lad-name b { font-size: 16px; }
.lad-row.is-open { background: var(--panel-2); }
.lad-detail { padding: 4px 14px 12px; border-top: 1px dashed var(--rule-dim); }
.lad-detail div { color: var(--text-2); font-size: 12px; padding: 3px 0; }
.lad-legend { color: var(--dimmer); font-size: 9.5px; letter-spacing: 0.08em; text-align: center; padding: 4px; }

/* ── THE DOOR: the landing as an overlay over the live world ── */
#door {
  position: fixed; inset: 0; z-index: 50;
  display: flex; align-items: flex-start; justify-content: center;
  padding: clamp(10px, 4vh, 48px) 14px;
  /* A dim, NOT a blur (owner direction): the running world behind the panel
     is the pitch, and it should read crisply — plates, motion, ticker — not
     as frosted glass. The dim is just enough for the panel's own contrast. */
  background: rgba(0, 4, 7, 0.55);
  overflow-y: auto;
}
.door-panel {
  position: relative; width: 100%; max-width: 560px;
  background: var(--panel); border: 1px solid var(--rule-bright);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.6), 0 24px 80px rgba(0, 0, 0, 0.7);
  padding: 22px 20px 20px; display: grid; gap: 14px;
  margin: auto 0;
}
.door-x {
  position: absolute; top: 8px; right: 8px; width: 34px; height: 34px;
  background: none; border: 0; color: var(--dim); font: 14px/1 inherit; cursor: pointer;
}
.door-x:hover { color: var(--text); }
.door-hero h1 { margin: 2px 0 8px; font-size: clamp(22px, 5vw, 30px); letter-spacing: 0.14em; font-weight: 500; }
.door-hero p { margin: 0; color: var(--text-2); font-size: 13px; line-height: 1.55; max-width: 52ch; }
.door-enter {
  min-height: 44px; background: none; border: 1px solid var(--rule);
  color: var(--dim); font: inherit; font-size: 11px; letter-spacing: 0.14em; cursor: pointer;
}
.door-enter:hover { color: var(--cyan); border-color: var(--cyan-deep); }
#door .l-tabs { border: 1px solid var(--rule); }
#door .l-body { padding: 14px 2px 0; }

/* the chrome chips */
#doorchip {
  background: var(--panel); border: 0; border-right: var(--hair) solid var(--rule);
  color: var(--amber); font: inherit; font-size: var(--fs-sm);
  letter-spacing: var(--track); text-transform: uppercase;
  padding: 0 12px; cursor: pointer; white-space: nowrap;
}
#doorchip:hover { background: var(--amber-wash); }
#mychip {
  display: flex; align-items: center; padding: 0 12px; white-space: nowrap;
  border-right: var(--hair) solid var(--rule); color: var(--cyan);
  font-size: var(--fs-sm); letter-spacing: var(--track); text-decoration: none;
}
#mychip:hover { background: var(--cyan-wash); }

/* ── the map band on OVERVIEW ── */
/* `flex: 0 0 auto` — the SAME load-bearing declaration app.css already
   documents at the tile strip: `.rows > * { min-height: 0 }` plus the default
   `flex: 0 1 auto` shrank this band to a 39px sliver on its first render,
   with the plates squashed inside it. Measured, then fixed the way the file's
   own comment says to. */
.ovmap { border: 1px solid var(--rule); background: var(--panel); display: flex; flex-direction: column; flex: 0 0 auto; }
.ovmap-head {
  display: flex; gap: 14px; align-items: baseline; padding: 8px 12px;
  border-bottom: 1px solid var(--rule);
}
.ovmap-head b { color: var(--cyan-mid); font-size: 11px; letter-spacing: 0.14em; font-weight: 500; }
.ovmap-head span { color: var(--dim); font-size: 10.5px; }
.ovmap-head a { margin-left: auto; color: var(--cyan-mid); font-size: 10.5px; letter-spacing: 0.1em; text-decoration: none; }
.ovmap-head a:hover { color: var(--cyan); }
.ovmap-host { height: min(44vh, 430px); position: relative; overflow: hidden; }
.ovmap-host svg { width: 100%; height: 100%; display: block; }
@media (max-width: 720px) {
  .ovmap-host { height: 300px; }
  #doorchip { font-size: 10px; padding: 0 8px; }
}
