:root {
  color-scheme: dark;
  --ink: #e8e4d6;
  --dim: #8b8f80;
  --edge: #2a2f24;
  --panel: #12150f;
  --good: #4f9d5d;
  --warn: #e8c86a;
  /* The resource colours, taken from V5 unchanged along with the sprites, so
     the food mark is the same green here as it was there. A resource is
     identified by its colour as much as by its shape — that is half of why an
     icon is faster than a word — so these two values are not decoration and
     should not drift toward the panel palette. */
  --food: #82c45c;
  --materials: #d7b049;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  height: 100vh;
  overflow: hidden;
  background: #0d0f0b;
  color: var(--ink);
  font: 14px/1.5 ui-sans-serif, system-ui, -apple-system, sans-serif;
}

#map { position: absolute; inset: 0; }
#map canvas { cursor: crosshair; }

/* The selection box. Off-white, matching the hover outline and the candidate
   rings it puts on the tokens inside it — all three are the cursor describing
   itself, not a state anything is in.

   `pointer-events: none` because it is drawn directly under the cursor that is
   dragging it, and the mousemove that resizes it is read off the map. */
/* No z-index, deliberately. Positioned siblings paint in DOM order, and the box
   sits after #map and before the panels — so it is over the board and under the
   HUD, which is where a rectangle you are dragging across the map belongs. A
   z-index above 0 would lift it over the settlement panel. */
#marquee {
  position: absolute;
  pointer-events: none;
  border: 1px solid var(--ink);
  background: color-mix(in srgb, var(--ink) 8%, transparent);
  border-radius: 2px;
}
#marquee[hidden] { display: none; }

.panel {
  position: absolute;
  background: color-mix(in srgb, var(--panel) 92%, transparent);
  border: 1px solid var(--edge);
  border-radius: 10px;
  backdrop-filter: blur(6px);
  transition: opacity 480ms ease;
}

/* The interface arrives after the board does.
   ---------------------------------------------------------------------------
   `opening` is on <body> in the markup, not added by script, so the panels are
   absent from the *first* paint — boot is async (a 4.4 MB fixture, then the
   style load) and a class applied afterwards would show them for a third of a
   second and then take them away, which is worse than never hiding them. It
   comes off when the opening shot lands; see boot() in main.mjs.

   Opacity, not `display: none`. The panels have to lay out during the shot or
   the tunable list's scroll position and the log's overflow are measured
   against a collapsed box and settle wrong the moment they appear.
   `pointer-events: none` is what actually makes them inert — without it there
   is an invisible, clickable "Move into…" button over the map for four seconds.

   Staggered, barely: under a tenth of a second apart, which is not long enough
   to read as three separate events but is enough that the corner of the screen
   assembles rather than switching on. */
body.opening .panel { opacity: 0; pointer-events: none; }
#hud { transition-delay: 0ms; }
#colony { transition-delay: 90ms; }
#log { transition-delay: 180ms; }
/* None of these three is part of the arrival. The first two answer the cursor,
   and by the time either can be on screen the rest is long since up — the
   delays above would make the hover card lag a third of a second behind the
   pointer. The dev panel is not on screen at all until you ask for it, and it
   should appear the instant you do rather than fading in a beat later like
   something the game was getting round to. */
#hint, #tip, #dev { transition-delay: 0ms; }

@media (prefers-reduced-motion: reduce) {
  .panel { transition: none; }
}

/* --- Icon + value grammar --------------------------------------------------
   A quantity is a mark next to a figure, identically at every surface: the
   HUD totals, the ledger, the muster price, a zone, the hover card. Ported
   from V5's block of the same name; see view/icons.mjs for why. */
.icon {
  display: inline-block;
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  flex-shrink: 0;
}

.icon-food { color: var(--food); }
.icon-materials { color: var(--materials); }
.icon-people { color: var(--ink); }
.icon-farm { color: var(--good); }
.icon-standing, .icon-scout { color: var(--dim); }

.stat {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-variant-numeric: tabular-nums;
}
.stat .stat-num { color: var(--ink); font-weight: 600; }
/* Growing, holding, shrinking. `flat` is spelled out rather than left to the
   default because "this rounds to zero" is a verdict the interface is making,
   and a verdict should not be the absence of a rule. */
.stat.up .stat-num { color: var(--good); }
.stat.flat .stat-num { color: var(--ink); }
.stat.down .stat-num { color: #d08a6a; }
/* Marks are a touch larger than the text they sit in. At 1em a 24-viewBox
   glyph reads as a smudge next to a 12px figure — the shape has to survive
   being the thing you recognise instead of read. */
.stat .icon { width: 1.15em; height: 1.15em; }

/* Top left: the turn and the budget. The budget bar is the whole slice — it
   should be the first thing your eye lands on. */
#hud { top: 14px; left: 14px; width: 280px; padding: 14px 16px; }

/* The AD button. V5's gold square, kept recognisable and sized down to sit on
   the clock's line rather than on a bar of its own — V6 has no top bar, and the
   top-left corner of the HUD *is* the corner the old versions put it in.
   Slightly smaller than the dial beside it, so the brand mark does not
   out-shout the one thing on screen that says time is passing. */
#hud .ad {
  width: 30px; height: 26px; flex: 0 0 auto; padding: 0;
  display: grid; place-items: center;
  border: 1px solid #5a4620; border-radius: 4px;
  background: var(--warn); color: #171207;
  font: inherit; font-size: 13px; font-weight: 900; line-height: 1;
  letter-spacing: 0.02em; cursor: pointer;
}
#hud .ad:hover { background: #f2d886; }
#hud .ad[aria-expanded="true"] { background: #f2d886; box-shadow: 0 0 0 2px rgba(232, 200, 106, 0.25); }

/* Hangs under the button rather than pushing the panel around, so opening it
   does not move the day number you were reading. */
#hud { position: absolute; }
.menu {
  position: absolute; z-index: 6; top: 46px; left: 16px; min-width: 132px;
  padding: 4px; border: 1px solid var(--edge); border-radius: 8px;
  background: #161a11; box-shadow: 0 10px 26px rgba(0, 0, 0, 0.5);
}
.menu button {
  display: block; width: 100%; padding: 7px 10px; text-align: left;
  border: 0; border-radius: 5px; background: none; color: var(--ink);
  font: inherit; font-size: 12.5px; cursor: pointer;
}
.menu button:hover { background: #202719; }

/* The board chooser.
   ---------------------------------------------------------------------------
   Not a .panel: the panels are held at opacity 0 through the opening shot and
   are pointer-events: none while they are, and a dialog the player just asked
   for must never be subject to that. It carries its own backdrop instead. */
.sheet {
  position: absolute; inset: 0; z-index: 20;
  display: grid; place-items: center;
  background: rgba(6, 8, 5, 0.62); backdrop-filter: blur(3px);
}
.sheet[hidden] { display: none; }
.sheet-card {
  width: 380px; max-width: calc(100vw - 32px); padding: 16px 18px;
  border: 1px solid var(--edge); border-radius: 12px;
  background: color-mix(in srgb, var(--panel) 96%, transparent);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.6);
}
.sheet-card h2 { margin: 0; font-size: 15px; letter-spacing: 0.01em; }
.sheet-note { margin: 3px 0 12px; color: var(--dim); font-size: 12px; }

.board-choice {
  display: block; width: 100%; margin-bottom: 7px; padding: 9px 11px;
  text-align: left; border: 1px solid var(--edge); border-radius: 8px;
  background: #171c11; color: var(--ink); font: inherit; cursor: pointer;
}
.board-choice:hover { border-color: var(--good); }
.board-choice .name { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; font-size: 13px; font-weight: 600; }
.board-choice .here { color: var(--warn); font-size: 10.5px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; }
.board-choice .blurb { margin-top: 2px; color: var(--dim); font-size: 11.5px; line-height: 1.4; }

/* A neighbour, talking (§5).
   ---------------------------------------------------------------------------
   `.clear` is the whole difference from the chooser above, and it is a design
   rule rather than a style preference. §9.13 rejected a full-screen settlement
   overlay because "a screen over the map costs the map, and the map is the
   game", and §3.5's call-in stops the clock precisely so the board stays there
   to point at. So no dim and no blur: the border they are telling you about has
   to still be on screen behind them.

   The backdrop is still there and still catches clicks, which is the other half
   of §3.5 — everything holds while you answer.

   Horizontally centred puts it in the clear strip between the left panel column
   (280 px) and the dev panel (260 px), so it covers map and not interface.
   **Vertically it sits at the bottom**, and that is not a preference: a centred
   card lands exactly where the camera puts its subject, so the first playtest
   had the greeting sitting on top of the party that had just walked up to
   them. The whole argument for not dimming the map is that you can see the
   border being talked about; covering the middle of it gives that back.
   Clear of the hint strip, which lives at bottom 18. */
.sheet.clear {
  background: transparent; backdrop-filter: none;
  place-items: end center; padding-bottom: 62px;
}

.parley-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.parley-sub { color: var(--dim); font-size: 11.5px; }
/* Them speaking, so it is set apart from the readout under it: warmer than the
   dim grey the panels use for labels, and indented behind a rule the way a
   quotation is. This is the only prose in the game that is somebody's voice
   rather than the game's narration. */
.parley-said {
  margin: 10px 0 12px; padding-left: 10px;
  border-left: 2px solid var(--edge);
  color: var(--ink); font-size: 12.5px; line-height: 1.5;
}
.parley-rows { display: flex; flex-direction: column; gap: 3px; margin-bottom: 10px; }
.parley-note { margin: 0 0 12px; color: var(--dim); font-size: 11.5px; }
.parley-note:empty { display: none; }

/* A call-in (§3.5).
   ---------------------------------------------------------------------------
   Almost entirely the greeting's card plus the board chooser's choice list, on
   purpose — two existing shapes rather than a third. What is new here is small
   and all of it is about the answers.

   `.ambush-cause` is §3.6's "it must be legible *why* it fired", so it is set
   in the warn colour rather than the dim grey a caption would take: it is the
   reason the card exists, not a footnote to it. */
.ambush-cause {
  margin: 0 0 12px; color: var(--warn);
  font-size: 11.5px; line-height: 1.4;
}
.ambush-cause:empty { display: none; }

/* The price, on the thing you press (§3.6, §10.1 — a mark and a figure). Pushed
   to the right of the choice's name so the column of prices can be read down
   without reading the labels, which is the comparison being invited. */
.board-choice .price { display: inline-flex; align-items: center; gap: 6px; color: var(--ink); font-size: 11.5px; }

/* A choice you cannot take stays on the list and says what is missing (§7.5's
   greyed-not-hidden rule). Muted rather than removed, because "you could pay
   them in salvage if you had 22 more" is the sentence that tells you what to go
   and do — and a button that vanished would take the question with it. */
.board-choice:disabled { cursor: default; opacity: 0.55; }
.board-choice:disabled:hover { border-color: var(--edge); }
/* The refusal takes the warn colour where the consequence takes dim, so the two
   are distinguishable at a glance down the list: one is what would happen, the
   other is why it will not. */
.board-choice .missing { margin-top: 2px; color: var(--warn); font-size: 11.5px; line-height: 1.4; }

/* Walking away is not a transaction and must not look like one. No price, and
   the consequence set in the same muted red the hover card uses for a bad row
   (#d08a6a, and it is a literal there too) — this is the one button on the card
   that spends a person rather than a number. */
.board-choice[data-choice="walk"] .blurb { color: #d08a6a; }
#hud h1 { margin: 0; font-size: 15px; letter-spacing: 0.01em; }
#hud .sub { color: var(--dim); font-size: 12px; margin: 2px 0 12px; }

#hud h1 { font-variant-numeric: tabular-nums; }

/* The day on the left, the valley-wide totals hard right. `baseline` and not
   `center` so the figures sit on the same line as the day rather than
   floating beside it — they are part of the same readout. */
/* Centred rather than baseline-aligned, because the row leads with a circle
   now and an SVG has no baseline to align anything to. */
.clock-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.clock-row .totals { display: flex; align-items: center; gap: 11px; font-size: 12.5px; flex: none; }
.clock-row .day { display: flex; align-items: center; gap: 9px; }

/* The clock. A dial with one hand, one turn to the day.

   The digital readout it replaces went through two rounds of being made less
   distracting — live minutes, then hours only — before the answer turned out to
   be that a number was the wrong instrument. Changing *digits* are a shape
   mutation, which is precisely what peripheral vision is built to catch; a hand
   turning at six degrees a second is smooth, slow and predictable, and the eye
   lets it alone. Same information, and it is now readable without being read. */
.dial { width: 32px; height: 32px; flex: none; }
.dial-face { fill: #10130c; stroke: var(--edge); stroke-width: 1.5; }
.dial-ticks line { stroke: #4a5040; stroke-width: 1.1; stroke-linecap: round; }
.dial-ticks .quarter { stroke: var(--dim); stroke-width: 1.4; }
/* The long hand, parked at twelve. Thin, because it must not compete with the
   one that is actually telling you something — see the markup for why it is
   here at all. */
.dial-minute { stroke: var(--dim); stroke-width: 1.3; stroke-linecap: round; }
.dial-hand {
  stroke: var(--ink); stroke-width: 2.2; stroke-linecap: round;
  /* Rotated by attribute from render(), not by CSS transform: an SVG transform
     attribute takes its own centre of rotation, so there is no transform-box /
     transform-origin dance and nothing to get wrong per browser. */
}
.dial-pin { fill: var(--ink); }
/* Paused says so on the face as well as on the button. A frozen hand with no
   explanation is indistinguishable from a stopped clock. */
.dial.paused .dial-hand { stroke: var(--dim); }
.dial.paused .dial-pin { fill: var(--dim); }
.dial.paused .dial-face { stroke: #23281c; }

/* The clock controls. Pause is deliberately not "0×" — it is a different kind
   of thing from a speed, and Paradox games all separate them. */
.speeds { display: flex; gap: 6px; margin-top: 14px; }
button.speed {
  flex: 1; padding: 7px 0; border-radius: 6px;
  border: 1px solid var(--edge); background: #1a1f14; color: var(--dim);
  font: inherit; font-size: 12px; font-weight: 600; cursor: pointer;
}
button.speed:hover { border-color: var(--good); color: var(--ink); }
button.speed.active { background: #23301c; border-color: var(--good); color: #c9e8a8; }
/* The glyph is two solid bars with no gap of its own — without letter-spacing
   they read as one block, indistinguishable from a stop icon. */
#pause { letter-spacing: 3px; text-indent: 3px; }
button:focus-visible { outline: 2px solid var(--good); outline-offset: 2px; }

/* Bottom left: the log. Short — this is a movement slice, not a game. */
#log { bottom: 14px; left: 14px; width: 280px; max-height: 128px; overflow-y: auto; padding: 10px 14px; }
#log p { margin: 0 0 6px; font-size: 12px; color: var(--dim); }
#log p:last-child { margin-bottom: 0; }
#log p.good { color: #a9cf9a; }
#log p.warn { color: var(--warn); }
#log p.day { color: var(--ink); font-weight: 600; margin-top: 8px; }

/* Bottom centre: the route readout, only while hovering. */
#hint {
  bottom: 18px; left: 50%; transform: translateX(-50%);
  padding: 8px 14px; font-size: 12.5px; white-space: nowrap;
  opacity: 0; transition: opacity 90ms ease; pointer-events: none;
}
#hint.on { opacity: 1; }
#hint .m { font-variant-numeric: tabular-nums; color: var(--warn); font-weight: 600; }
#hint .short { color: #d08a6a; }

/* The hover card, anchored over whatever is under the cursor.

   `pointer-events: none` is not a detail: the card sits directly above the
   thing it describes, so anything else would put a dead patch over the exact
   pixels you are aiming at and the click would land on the tooltip instead of
   the building. V5 learned this the hard way with an alert banner that ate
   clicks. Nothing in here is interactive, so nothing needs the exception. */
#tip {
  position: absolute;
  z-index: 6;
  min-width: 168px;
  /* Wide enough for a real OSM name. "Hillside Church of Marin - Lucas Valley"
     is not an outlier here — the fixture is full of them. */
  max-width: 300px;
  padding: 8px 10px;
  transform: translate(-50%, -100%);
  /* The card must not eat the board. It sits directly over the footprint it is
     describing, so a card that swallowed clicks would make the building under
     it unselectable and the ground beside it unorderable. */
  pointer-events: none;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.5);
}
/* ...except when it is carrying a verb, which has to be clickable. Set from
   renderTip rather than by a :has() on the button, because the button is only
   `hidden` and :has() support is not something to bet an action on. */
#tip.actionable { pointer-events: auto; }

/* The bridge. positionTip leaves a 14 px gap between the cursor and the bottom
   of the card so the card does not sit on top of what it describes — and that
   gap is board, so moving the mouse up toward the button crosses it, hovers
   whatever is underneath, and rebuilds the card out from under you. This is an
   invisible extension of the card's own hit area across the gap. Only present
   while the card is actionable, so it never blocks the board otherwise. */
#tip.actionable::after {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 100%;
  height: 18px;
}
#tip[hidden] { display: none; }

/* `button.wide` carries the shape; this is the gold, because the button and the
   footprint it is over are the same statement — the roof under the cursor is
   drawn in exactly this colour when you may move into it. */
#tip-action {
  margin-top: 8px; padding: 6px 10px; font-size: 12px;
  border-color: #6d5a2b; color: var(--warn);
}
#tip-action:hover:not(:disabled) { border-color: var(--warn); background: #241f11; }
#tip-action[hidden] { display: none; }

#tip-title {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  font-size: 12.5px; font-weight: 600;
}

/* The name takes the row and the chip takes what it needs, rather than both
   shrinking in proportion to their content — which is the default, and which
   is why a long chip beside a long name gave the *name* a four-character
   column and wrapped it down five lines. `min-width: 0` because a flex item
   will not shrink below its longest word without it. */
#tip-name { flex: 1 1 auto; min-width: 0; }
#tip-kind {
  flex: 0 0 auto;
  color: var(--dim); font-weight: 400; font-size: 9.5px;
  text-transform: uppercase; letter-spacing: 0.05em; white-space: nowrap;
}
#tip-kind:empty { display: none; }
#tip-note {
  margin-top: 4px; color: var(--dim); font-size: 11px; line-height: 1.4;
}
#tip-note:empty { display: none; }
/* The note is the one line on the card that is an *answer*, so the verdict is
   carried in colour as well as in words — the same two tones the ledger's net
   line uses. */
#tip-note.bad { color: #d08a6a; }
#tip-note.good { color: var(--good); }
#tip-note .stat { display: inline-flex; gap: 3px; vertical-align: baseline; opacity: 0.85; }
#tip-rows { margin-top: 5px; font-size: 11.5px; }
#tip-rows:empty { display: none; }
#tip-rows .row { display: flex; justify-content: space-between; gap: 10px; padding: 1px 0; }
#tip-rows .row .k { color: var(--dim); }
#tip-rows .row .v { color: var(--ink); font-variant-numeric: tabular-nums; }
#tip-rows .row.bad .k { color: #d08a6a; }

/* An unsearched building gets the same card with the numbers withheld — §9.2's
   fog is about state, not about the shape — so it is dimmed rather than absent
   to make the withholding legible. */
#tip.unknown #tip-name { color: var(--dim); }
#tip.civic #tip-name { color: var(--warn); }
/* Held ground (§4.1) is named after the settlement holding it, in the gold the
   board already uses for home and for the claim itself — so the card and the
   street under it are the same colour, and that colour has meant "yours" since
   the first settlement marker. */
#tip.territory #tip-name { color: var(--materials); }

/* Top right: the dev panel. Present from day one on purpose — the numbers in
   here are guesses and the only way to settle them is to drag them. */
/* A column, so the heading and the tab strip stay put, #tunables takes the
   slack and scrolls, and #stats stays pinned to the bottom of the panel. The
   `min-height: 0` on the scroller is load-bearing: a flex child will not shrink
   below its content without it, so the panel would grow past the viewport
   instead of the list scrolling. */
#dev {
  top: 14px; right: 14px; width: 260px; padding: 12px 14px;
  display: flex; flex-direction: column;
  max-height: calc(100vh - 28px);
  /* Above every ordinary panel and below the menu that opens it (6) and the
     board chooser (20). This is what makes it a pop-up rather than a column:
     it reserves no space, so anything that lands on this side of the screen
     later keeps its position and is simply covered while the tools are open.
     Panels otherwise paint in DOM order with no z-index at all. */
  z-index: 5;
  /* An opaque backdrop, unlike the other panels' 92%. A translucent sheet of
     sliders over a board you can still half-read is the worst of both. */
  background: var(--panel);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.55);
}
/* `display: flex` above beats the UA's `[hidden] { display: none }`, so the
   hidden state has to be spelled out or the panel never closes. */
#dev[hidden] { display: none; }
/* Marked as not-of-the-game, the way V5's modal wore a red bar. Muted, because
   V6 does not shout anywhere else — but it is the one heading on screen that
   should never be mistaken for a game surface. */
#dev h2 {
  margin: 0 0 2px; font-size: 12px; text-transform: uppercase;
  letter-spacing: 0.07em; color: #b4553f; font-weight: 700;
}

/* The tab strip. Same grammar as the speed buttons in the HUD — this is the
   same kind of control, a small exclusive row, and it should not invent a
   second look for it. */
#tuning-tabs { display: flex; gap: 5px; margin-top: 8px; }
#tuning-tabs .tab {
  flex: 1; padding: 5px 0; border-radius: 6px;
  border: 1px solid var(--edge); background: #1a1f14; color: var(--dim);
  font: inherit; font-size: 11px; font-weight: 600; cursor: pointer;
}
#tuning-tabs .tab:hover { border-color: var(--good); color: var(--ink); }
#tuning-tabs .tab.active { background: #23301c; border-color: var(--good); color: #c9e8a8; }

/* Scroll padding on the right so a slider's thumb is not flush against the
   scrollbar when one appears. */
#tunables { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding-right: 4px; margin-right: -4px; }
#dev .tunable { margin-top: 12px; }
#dev .tunable .head { display: flex; justify-content: space-between; align-items: baseline; }
#dev .tunable .name { font-size: 12.5px; }
#dev .tunable .num { font-size: 12.5px; font-variant-numeric: tabular-nums; color: var(--warn); }
#dev .tunable .why { color: var(--dim); font-size: 11px; margin-top: 3px; line-height: 1.4; }
#dev input[type="range"] { width: 100%; margin: 6px 0 0; accent-color: var(--good); }
/* Mode dials (the fog switches). Sized like the sliders they sit between so a
   panel of both does not look like two panels. */
#dev select {
  width: 100%;
  margin: 6px 0 0;
  padding: 3px 4px;
  font: inherit;
  font-size: 12px;
  color: var(--ink);
  background: #171b13;
  border: 1px solid var(--edge);
  border-radius: 3px;
}

#stats { flex: 0 0 auto; margin-top: 14px; padding-top: 10px; border-top: 1px solid var(--edge); font-size: 11.5px; color: var(--dim); }
#stats div { display: flex; justify-content: space-between; align-items: center; }
#stats .n { font-variant-numeric: tabular-nums; color: var(--ink); }
/* The board picker. It is a select, so `#dev select` above already claims it and
   would give it the full-width shape the mode dials have — which is right in the
   tunables list and wrong here, where every other row is label-left value-right. */
#stats select { width: auto; margin: 0; padding: 1px 3px; font-size: 11.5px; }

/* --- The settlement view (§7.1, §7.5) -------------------------------------- */

/* `top` is a hard-coded contract with the HUD above it, and the two numbers on
   this line are the whole of it: the HUD is absolutely positioned and does not
   push anything, so this offset has to clear the HUD at its **tallest**, not at
   the height it usually is.

   The HUD is 170 px now, in every state it has: clock, speeds, idle pager. So
   this is 184 — the tall case as measured, plus the same 14 px gutter
   everything else on this side of the screen uses. Measured and not derived,
   because the pager wraps to two lines at some labels and arithmetic off its
   line-height would be a number that is right until the copy changes.

   It has been 236, then 292, and both were the same mistake in slow motion: 236
   fitted one come-and-go button with 15 px to spare and gave no sign a budget
   was being spent at all, so adding a second silently slid it under this panel
   — clickable to a driver reading the DOM and unclickable to a person — and 292
   bought that back by leaving 122 px of empty screen between two panels.
   Neither button lives here any more (every unit verb is over its unit; see
   renderUnitActions), which is what let this come back down. The smoke measures
   the overlap directly, and now also asserts no unit verb has crept back into
   the HUD, so the next thing to land here fails loudly instead of quietly.

   `max-height` is the same contract at the other end: 184 top + 142 for the log
   below (128 tall, 14 clear) = 326. */
#colony { top: 184px; left: 14px; width: 280px; padding: 12px 14px; max-height: calc(100vh - 326px); overflow-y: auto; }

.stat-row { display: flex; justify-content: space-between; align-items: baseline; font-size: 12.5px; color: var(--dim); }
.stat-row .n { color: var(--ink); font-variant-numeric: tabular-nums; font-weight: 600; }

/* Section headings inside the panel. Small and quiet: they group, they do not
   announce. §10's "nothing that can be a row is a sentence" applies here too —
   a heading earns its line only because the block under it is a different kind
   of thing, not because it has something to say. */
#colony h3.section {
  margin: 14px 0 7px; padding-top: 10px; border-top: 1px solid var(--edge);
  font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--dim); font-weight: 600;
}

/* The pager. Stellaris' ◀ 2/2 ▶, and hidden rather than disabled at one
   settlement — see page() in main.mjs. */
.pager { display: flex; align-items: flex-start; gap: 6px; }
.pager .pager-name { flex: 1; min-width: 0; }
button.page {
  width: 22px; height: 22px; padding: 0; line-height: 1; border-radius: 4px; flex: none;
  border: 1px solid var(--edge); background: #1a1f14; color: var(--dim);
  font: inherit; font-size: 11px; cursor: pointer;
}
button.page:hover { border-color: var(--good); color: var(--ink); }
.pager.single button.page { visibility: hidden; }

.designation { margin: -6px 0 10px; font-size: 11.5px; color: var(--good); letter-spacing: 0.02em; }

/* The tab strip. Drawn as tabs and not as a row of buttons, which is what these
   were and what they read as: three separated pills say "three things you can
   press" where a tab has to say "three faces of one thing, and you are looking
   at this one".

   Three cues do that, and it needs all three — any one alone still reads as a
   pressed button:
     1. **A seam.** The strip carries a rule along its bottom, and the live tab
        *breaks* it (`border-bottom-color: transparent` over `margin-bottom:
        -1px`). That gap is the whole illusion: the tab and the pane below it
        are one shape with the join opened.
     2. **Square shoulders.** Rounded at the top, square at the bottom, so the
        tab grows out of the pane instead of floating above it.
     3. **Joined, not spaced.** A 2 px gutter rather than 6 px: sheets in a
        folder, not a toolbar.

   Everything is scoped under #colony on purpose. The dev panel has its own
   `#tuning-tabs` pills, and this file previously styled a bare `button.tab`
   that reached across and leaked `display: inline-flex` and a gap into them. */
#colony .tabs {
  display: flex; gap: 2px; margin: 0 0 12px;
  border-bottom: 1px solid var(--edge);
}
#colony .tabs .tab {
  flex: 1; margin-bottom: -1px; padding: 7px 2px 6px;
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  border: 1px solid transparent; border-bottom: 1px solid var(--edge);
  border-radius: 7px 7px 0 0;
  background: transparent; color: var(--dim);
  font: inherit; font-size: 11.5px; font-weight: 600; cursor: pointer;
}
/* The dormant tabs lift slightly under the cursor without pretending to be
   selected — hover is "you could go here", not "you are here". */
#colony .tabs .tab:hover { background: #171c11; color: var(--ink); }
#colony .tabs .tab[aria-selected="true"] {
  border-color: var(--edge);
  border-bottom-color: transparent;
  background: color-mix(in srgb, var(--good) 13%, transparent);
  color: #c9e8a8;
  /* The green rail along the top edge. The seam says *which* tab is open; this
     says it at a glance, from the colour alone. */
  box-shadow: inset 0 2px 0 var(--good);
}

/* How many timers are running on a tab you cannot see. Gold rather than green:
   it is the queue's colour everywhere else on the panel, and this is the same
   kind of clock reported from outside the pane it runs in. */
#colony .tab-count {
  min-width: 14px; padding: 0 4px; border-radius: 999px;
  background: color-mix(in srgb, var(--warn) 22%, transparent);
  color: var(--warn); font-size: 9.5px; font-variant-numeric: tabular-nums;
}
#colony .tab-count[hidden] { display: none; }

#colony .pane[hidden] { display: none; }
/* A pane's first heading has nothing above it to be divided from, so it drops
   the rule and the space the divider needs. */
#colony .pane > h3.section:first-child { margin-top: 0; padding-top: 0; border-top: none; }

/* Production / Upkeep had a grid here, two columns and a net line on the Status
   tab. Both the tab and this block are gone (§10.2) — the working is the food
   chip's `title` now, which is plain text and needs no rules. */

/* The build queue — §8.1's pulse. The head item is being worked and the rest
   wait, which is why only the first one gets a live bar. */
.build { border: 1px solid var(--edge); border-radius: 7px; padding: 8px 10px; margin-bottom: 7px; background: #15190f; }
.build.working { border-color: var(--warn); }
.build.waiting { opacity: 0.55; }
.build .head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.build .name { font-size: 12.5px; font-weight: 600; }
.build .n { font-size: 11.5px; color: var(--warn); font-variant-numeric: tabular-nums; }
.build .track { height: 5px; margin-top: 6px; border-radius: 999px; background: #1e2218; overflow: hidden; }
.build .track > div { height: 100%; background: var(--warn); transition: width 200ms linear; }
.build .sub { margin-top: 4px; font-size: 11px; color: var(--dim); }
#queue .empty { margin: 0 0 8px; font-size: 11.5px; color: var(--dim); }

/* Materials live in the HUD beside the day, not here — they are the one
   stockpile that is not per-settlement (§7.5), and a valley-wide number inside
   a panel you page between settlements with would read as belonging to
   whichever one you happened to be looking at. */

/* Heads and the store used to be two mark-only rows in the Status pane. They
   are chips above the tab strip now — see .standing below — because both are
   true of the settlement rather than of the tab, and Status was the one tab that
   needed them least. */

/* A labelled count and its figure. Started life under the growth bar on Status,
   holding Garden plots; that row went with the tab (§10.2 — the Work tab's
   "4 / 4" was already saying it), and the territory block on Building is what
   uses the shape now.

   Deliberately never a bar. A bar is for a quantity filling toward an event,
   and it reads as a thing to fill — the game is not asking you to fill these,
   it is asking whether to go out and act on them. A count says that and a
   meter does not. */
.capacity { margin: 8px 0 2px; }

/* The standing figures, between the pager and the tab strip.

   This was a bare flex row of three chips with `space-between`, and it read
   badly for three separate reasons worth naming, because each has a different
   fix: the figures landed at whatever x their own content produced so nothing
   lined up; there was no box, so they crowded the designation above and sat 2px
   off the tab strip below and looked like they belonged to it; and a chip with
   no label is only legible to somebody who already knows what it is.

   So: a real bordered block that owns its own space, equal columns with a rule
   between them so the two figures are centred on fixed axes rather than wherever
   they happen to fall, and a micro-label under each. The label is a word, which
   §10.1's mark-and-figure rule would normally forbid — but that rule is about a
   dense scanned *list*, where the words are the thing slowing you down. Two
   chips read once and then recognised by position are the opposite case, and the
   cost of being unreadable the first time is higher than the cost of a word. */
.standing {
  display: grid;
  grid-template-columns: 1fr 1px 1fr;
  margin: 12px 0 14px;
  border: 1px solid var(--edge);
  border-radius: 8px;
  /* Lifted off the panel rather than outlined alone: the border says where it
     ends, the fill says it is one object. */
  background: #171b12;
  overflow: hidden;
  font-variant-numeric: tabular-nums;
}
.standing .rule { background: var(--edge); }
.standing .fig {
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  padding: 7px 8px 6px;
}
.standing .line { display: flex; align-items: center; gap: 4px; font-size: 14px; }
.standing .line .n { font-weight: 600; color: var(--ink); }
.standing .k { display: inline-flex; }
.standing .tag {
  font-size: 9px; font-weight: 600; letter-spacing: 0.07em;
  text-transform: uppercase; color: var(--dim);
}

/* The capacity half of "10 / 12". Dimmed and lighter than the figure before it,
   because it is the *ceiling* and not a second quantity — same treatment the
   store's rate gets, for the same reason. */
.standing .of { color: var(--edge); font-weight: 400; }
.standing .cap { color: var(--dim); font-weight: 600; }
/* Full up, or over. The pair goes amber together — colouring only the 12 would
   say the ceiling is the problem, when what is true is that the two figures have
   met. Over capacity it is the down-colour, because then people are leaving. */
#people-fig.full .n, #people-fig.full .cap { color: var(--warn); }
#people-fig.over .n, #people-fig.over .cap { color: #d08a6a; }

#food-fig .n { color: var(--food); }
/* The rate, smaller than the figure it modifies — an annotation on the store
   and not a third number. */
.standing .delta { font-size: 11px; font-weight: 600; }
.standing .delta.up { color: var(--good); }
.standing .delta.down { color: #d08a6a; }
.standing .delta.flat { color: var(--dim); }

/* The pool the three job rows are cut from. It sits above them rather than
   below as a total, because it is the thing they are shares *of* — a sum
   underneath would read as a fourth row.

   There was a `.pool-head` here printing "Everyone here 10" over the bar. The
   people chip above the tab strip already prints that figure, so it went — the
   bar alone carries the partition and the chip carries the total. */
.pool { margin-bottom: 7px; }
/* One track, three segments, no gaps between them: the cut has to look like one
   bar divided and not like three bars parked end to end, so the segments share
   an edge and the seam is a 1 px rule in the panel's own background. */
.split {
  display: flex; height: 7px; margin-top: 5px; border-radius: 999px;
  background: #1e2218; overflow: hidden;
}
.split .seg { height: 100%; transition: width 200ms linear; }
.split .seg + .seg { box-shadow: inset 1px 0 0 #10130c; }
/* The segment colours are the mark colours, and that is the whole mechanism by
   which the bar and the rows are legible as the same three things. Changing one
   without the other breaks the block. */
.split .seg.gardens { background: var(--good); }
.split .seg.idle { background: #4a4f42; }
.split .seg.away { background: var(--warn); }

/* Six columns — mark, word, minus, count, ceiling, plus — so the three counts
   share one right edge whatever sits next to them. The ceiling is a column of
   its own rather than part of the figure for exactly that reason: with "4 / 4"
   as one centred cell, the 4 landed three pixels right of the 6 below it, and
   this block only works if the numbers can be read down a line. The two rows
   without buttons place their count in column 4 by hand rather than padding the
   row with empty spans, which drift out of sync the moment a column moves. */
.jobs { display: grid; gap: 5px; }
/* The count and ceiling columns are **fixed widths, not `auto`**. Each .job is
   its own grid, so `auto` columns are sized per row and nothing lines up
   between them: the gardens row sized its count column to "4" and the rows
   below sized theirs to "6", which put the two figures ten pixels apart. A
   fixed width is the cheap fix that does not need subgrid or `display:
   contents`, and two digits is all these ever hold — the population ceiling is
   nowhere near three. */
.job {
  display: grid; grid-template-columns: auto 1fr 20px 1.8em 2.4em 20px;
  align-items: center; gap: 8px; font-size: 12.5px; color: var(--dim);
}
.job .label { display: inline-flex; align-items: center; }
.job .word { justify-self: start; }
.job[data-job="idle"] .n, .job[data-job="away"] .n { grid-column: 4; }
/* The job marks are the size of the row, not the size of the text: the shape
   still leads the row now that the word is beside it. */
.job .label .icon { width: 15px; height: 15px; }
.job .n { color: var(--ink); font-variant-numeric: tabular-nums; text-align: right; font-weight: 600; }
/* The ceiling is quieter than the count and does not push it around: the figure
   that moves when you press a button is the one being read, the denominator is
   context beside it. Its own gap is inside the span, so the grid's 8 px does
   not open a hole between the number and its slash. */
.job .of { color: var(--dim); font-weight: 500; margin-left: -4px; justify-self: start; }
/* Each row's word takes its mark's colour, which is what ties the row to its
   segment in the bar above. Idle and away were both `--dim` — two of the three
   marks were the same colour, which is a fair part of why the block did not
   read. */
.job[data-job="gardens"] .word { color: var(--good); }
.job[data-job="away"] .word,
.job[data-job="away"] .icon-scout { color: var(--warn); }
/* The ceiling in "4 / 6" is quieter than the count: the figure that moves when
   you press a button is the one being read, and the denominator is context. */
.job .n .of { color: var(--dim); font-weight: 500; }

/* A reason under a block, in the same slot every time. No refusal colour: this
   is a readout of a settlement working normally, not an answer to something you
   pressed — the muster note one section down is the one that goes red.

   Was `.jobs-note`, for the block it was written against. That note said which
   of the two gardening ceilings was binding and has been deleted: it was built
   out of the gardens row's `4 / 4` and the Idle count, both of them a few
   pixels above it, so it never carried anything the rows did not. Only the
   territory note uses this now, and that one does say something no row on the
   panel has — see renderTerritory. */
.panel-note { margin: 6px 0 12px; font-size: 11px; line-height: 1.4; color: var(--dim); }
.panel-note:empty { margin: 0 0 12px; }
button.tick {
  width: 20px; height: 20px; padding: 0; line-height: 1; border-radius: 4px;
  border: 1px solid var(--edge); background: #1a1f14; color: var(--ink);
  font: inherit; font-size: 13px; cursor: pointer;
}
button.tick:hover:not(:disabled) { border-color: var(--good); }
button.tick:disabled { opacity: 0.3; cursor: default; }

.upgrade {
  border: 1px solid var(--edge); border-radius: 7px; padding: 9px 10px; margin-bottom: 8px;
  background: #15190f;
}
.upgrade.affordable { border-color: var(--good); }
.upgrade.built { opacity: 0.45; }
/* Forbidden here means "this building is not that kind of place" (§7.5), and
   the row stays on the list saying so. Dimmer than merely unaffordable,
   because unaffordable is a matter of time and this is a matter of where you
   settled — a different answer that deserves to look different. */
.upgrade.forbidden { opacity: 0.4; border-style: dashed; }
.upgrade .head { display: flex; justify-content: space-between; align-items: baseline; }
.upgrade .name { font-size: 12.5px; font-weight: 600; }
.upgrade .cost { font-size: 12px; font-variant-numeric: tabular-nums; color: var(--warn); }
.upgrade .blurb { font-size: 11px; color: var(--dim); margin-top: 3px; line-height: 1.4; }
.upgrade button { margin-top: 7px; padding: 5px 0; font-size: 12px; }

button.wide {
  width: 100%; padding: 8px 12px; border-radius: 7px; margin-top: 4px;
  border: 1px solid var(--edge); background: #1a1f14; color: var(--ink);
  font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer;
}
button.wide:hover:not(:disabled) { border-color: var(--good); }
button.wide:disabled { opacity: 0.4; cursor: default; }
button.wide.arming { border-color: var(--warn); color: var(--warn); }

.zone { border-top: 1px solid var(--edge); margin-top: 10px; padding-top: 9px; }
.zone .head { display: flex; align-items: center; gap: 8px; font-size: 12.5px; }
.zone .head .label { flex: 1; color: var(--dim); }
.zone .track { height: 5px; margin: 6px 0 4px; border-radius: 999px; background: #1e2218; overflow: hidden; }
.zone .track > div { height: 100%; background: #c9a05a; }
.zone .sub { font-size: 11px; color: var(--dim); }
.zone.exhausted .sub { color: #d08a6a; }

.unsettled { margin: 0; font-size: 12px; line-height: 1.5; color: var(--warn); }
.unsettled em { color: var(--ink); font-style: normal; font-weight: 600; }
#unsettled.hidden, #settled-body.hidden { display: none; }
.zone .sub .eff { color: var(--warn); }
.zone .sub .eff.poor { color: #d08a6a; }

/* Home's house glyph. Lifted from V5 (which lifted it from V4) so the same
   place looks the same across versions. The dark stroke under the fill is what
   keeps it legible against both the gold home fill and the dark ground. */
/* The house arrives rather than appearing. Half a second, overshooting a little
   before it settles — the only marker in the game that is placed by a decision
   rather than by walking somewhere. See view/founding.mjs for the rest of it.

   On the **svg**, never on `.base-marker`. MapLibre positions a Marker by
   writing `transform: translate(...)` onto the element you hand it, so a
   keyframe animating transform on that same element fights the map for it and
   the house flies to the top-left corner for half a second. */
.base-marker svg { animation: base-marker-in 520ms cubic-bezier(0.2, 1.5, 0.4, 1) both; }
@keyframes base-marker-in {
  from { transform: scale(0.2); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .base-marker svg { animation: none; }
}

.base-marker svg {
  display: block;
  width: 24px;
  height: 24px;
  stroke: #10151a;
  stroke-width: 1.6;
  paint-order: stroke fill;
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.7));
}

.home-name { margin: 0 0 10px; font-size: 13px; font-weight: 600; color: var(--warn); line-height: 1.35; }

/* The name over a token. Small, and shadowed rather than boxed — a chip around
   every unit would compete with the buildings, which are the thing you are
   actually reading the board for. */
.party-label {
  font: 600 10.5px/1 ui-sans-serif, system-ui, -apple-system, sans-serif;
  letter-spacing: 0.04em;
  color: #c9e8a8;
  white-space: nowrap;
  text-shadow: 0 0 3px #0d0f0b, 0 1px 2px #0d0f0b, 0 0 6px #0d0f0b;
}
.party-label.caravan { color: var(--warn); font-size: 11.5px; }
.party-label.selected { color: var(--ink); }

/* A unit's verb, floated over that unit (board.mjs syncPartyActions): settle
   here, break ground, bring them in. Deliberately not the flat text the label
   above it is — this one is pressable, and on a board made of translucent
   washes the only thing that reliably says "control" is an opaque panel with a
   border on it. Gold because every one of these *spends* something, which is
   also why the idle pager below the clock stays green: it spends nothing, it
   only moves the camera. */
.unit-action {
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  padding: 5px 10px;
  border: 1px solid var(--warn);
  border-radius: 6px;
  /* Near-opaque, not translucent. The roofs under this are a busy field of
     outlines and a see-through button sitting on them is unreadable at exactly
     the zoom you use it at. */
  background: rgba(18, 21, 15, 0.94);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.55);
  color: var(--warn);
  font: 600 11px/1.25 ui-sans-serif, system-ui, -apple-system, sans-serif;
  white-space: nowrap;
  cursor: pointer;
}
.unit-action:hover:not(:disabled) { background: rgba(30, 35, 22, 0.96); border-color: #f2d886; }
/* Grey and inert, but still *there*: "Nothing to strip here" is the answer to
   why the verb is missing, and a button that vanished would leave the crew
   looking broken instead of badly parked. */
.unit-action:disabled {
  border-color: var(--edge); color: var(--dim); cursor: default; box-shadow: none;
}
.unit-action .verb { letter-spacing: 0.02em; }
/* The arithmetic, quieter than the verb — §3.6 wants the sums shown before you
   commit, and it does not want them shouting over the thing they are about. */
.unit-action .sums {
  display: flex; align-items: center; gap: 4px;
  color: var(--dim); font-weight: 500; font-size: 10px;
}
.unit-action:disabled .sums { color: var(--edge); }

/* One column, not a row. It used to be side by side on the reasoning that both
   buttons are the same kind of decision — spend people and rations on somebody
   outside the walls — and that is true, but "the same kind of decision" is also
   true of a third and fourth kind (§3.7's fighters, defenders, diplomats), and
   a row only fits two before it has to be rebuilt. A column takes a fifth kind
   for free. */
.musters { display: flex; flex-direction: column; gap: 6px; }
.musters button.wide { margin-top: 0; }

/* Packing crews reuse .build wholesale — same border, same track, same
   countdown — because a timer under way should look the same wherever it is
   running. The only difference is the mark in the sub line, which a build has
   no equivalent of: a build spends materials, a crew has already taken people
   off the rows above.

   Laid out as a flex row rather than left as inline text, and that is a fix,
   not a preference. `.stat` is `inline-flex`, and an inline-flex box takes its
   baseline from its first flex item — here the `<svg>`, which is a replaced
   element with no baseline of its own, so the box gets aligned by the icon's
   bottom edge instead of by the digit inside it. Sitting in a sentence that
   floated the figure visibly above the words around it. Making the line itself
   a flex row with a common centre takes the question away: nothing here is
   being aligned by inline baseline rules any more. */
#muster-list .muster-row .sub { display: flex; align-items: center; gap: 5px; }
#muster-list .muster-row .sub .stat { color: var(--warn); }
#muster-list .muster-row .sub .stat-num { font-weight: 600; }

/* Crew as a readout. It used to be a pair of +/- buttons over a pool at home;
   a zone's crew is now the work party that walked out and was spent on it, so
   there is nothing to increment. */
.zone .head .crew { font-size: 11.5px; color: var(--warn); font-variant-numeric: tabular-nums; }

/* Go to the next party standing about. The key rides on the right-hand end, so
   the label and the shortcut are one control rather than a button with a note
   beside it. */
#idle-next {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  margin-top: 8px; font-size: 12px;
}
#idle-next[hidden] { display: none; }
/* Live, it wears the scouts' green — it is about your people standing idle, and
   green is what they are drawn in on the board. The unit verb next to it is
   gold because it *spends* something; this only moves the camera. */
#idle-next:not(:disabled) { border-color: var(--good); color: #c9e8a8; }

/* The shortcut, as a key rather than as text. Quiet enough that the label wins
   at a glance and legible enough to be learned by somebody who never reads a
   manual, which is the entire reason it is on screen instead of in a title. */
#idle-next kbd {
  flex: 0 0 auto;
  min-width: 18px; padding: 1px 5px;
  border: 1px solid var(--edge); border-bottom-width: 2px; border-radius: 4px;
  background: #12160e; color: var(--dim);
  font: inherit; font-size: 11px; font-weight: 700; line-height: 1.35;
  text-align: center;
}
#idle-next:not(:disabled) kbd { border-color: #2f4a2c; color: #9fc78c; }
#idle-next:disabled kbd { opacity: 0.7; }

/* The price, beside the verb on the button itself — one line, verb left,
   cost right, so the button reads at a glance instead of in two passes. */
.musters button.wide {
  display: flex; align-items: center; justify-content: space-between; line-height: 1.25;
}
.musters button.wide small {
  display: flex; align-items: center; gap: 10px;
  font-size: 10.5px; font-weight: 400; color: var(--dim);
  font-variant-numeric: tabular-nums;
}
/* The price is two marks under a verb, so it must not outweigh the verb. */
.musters button.wide small .stat-num { font-weight: 500; color: var(--dim); }
.musters button.wide:disabled small { opacity: 0.7; }

/* What a build does, as marks and figures above the flavour line. Built from
   the upgrade's own fields each frame (see upgradeEffects in main.mjs) rather
   than typed into the blurb, so the garden-yield slider cannot make a card
   lie. */
.upgrade .effects { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 5px; font-size: 11.5px; }
.upgrade .effects .stat-num { font-weight: 600; }
