html, body {
  margin: 0;
  height: 100%;
  /* --font-ui already falls back through -apple-system/Segoe UI, so the local
     stack that used to be spelled out here is inside the token. */
  font-family: var(--font-ui);
  background: var(--bg);
  color: var(--ink);
}

#map {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 425px;
  /* Overrides leaflet.css's `.leaflet-container { background: #ddd }` — the
     colour behind the tiles, seen on first paint, during every pan into
     un-cached area, and past the edges of the world. #ddd is a mid grey chosen
     for neither theme; on Dark Matter it is a bright flash on every load.
     `#map` is an id (0,1,0 by id) so it outranks the class rule.

     --bg rather than a basemap-matched literal, and it happens to be the same
     thing: measured off the real tiles on 2026-08-07, Positron's ground is
     L 250 against --bg light #FCFBFA (252), and Dark Matter's is L 9 against
     --bg dark #0C0F11 (12). Within dL 3 in both themes, so the seam between
     "page" and "map not loaded yet" is invisible without spending a token on
     it. If the basemap pair ever changes, re-measure before assuming that
     still holds. */
  background: var(--bg);
}

/* R5.4 — the map's control stack. Leaflet's zoom control is moved to
   bottomright in map.js so it stays out of a screenshot's way; it is re-skinned
   rather than reimplemented (see the comment at L.control.zoom in map.js). */
.leaflet-control-zoom.leaflet-bar {
  border: 1px solid var(--line-strong);
  border-radius: var(--r-ctl);
  box-shadow: var(--shadow-panel);
  overflow: hidden;
}

.leaflet-control-zoom a.leaflet-control-zoom-in,
.leaflet-control-zoom a.leaflet-control-zoom-out {
  background: var(--surface);
  color: var(--ink);
  border-bottom-color: var(--line);
  transition: background var(--dur-ui) var(--ease);
}

.leaflet-control-zoom a.leaflet-control-zoom-in:hover,
.leaflet-control-zoom a.leaflet-control-zoom-out:hover {
  background: var(--surface-2);
  color: var(--ink);
}

/* Leaflet greys the label to #bbb at min/max zoom, which is neither theme's
   idea of "unavailable" — --ink-faint is. */
.leaflet-control-zoom a.leaflet-disabled {
  background: var(--surface);
  color: var(--ink-faint);
}

#panel {
  position: absolute;
  top: 0;
  right: 0;
  width: 425px;
  bottom: 0;
  overflow-y: auto;
  box-sizing: border-box;
  padding: var(--sp-4) 18px;
  background: var(--surface);
  color: var(--ink);
  border-left: 1px solid var(--line-strong);
}

/* ===========================================================================
   THE DOCKED HUB SUPPLY & DEMAND SHEET (#sd-dock, see index.html).
   ---------------------------------------------------------------------------
   ONE NUMBER SETS THE WHOLE LAYOUT. The dock's width and the map's new right
   edge are the same measurement, written once. As two literals they drift, and
   a 1% drift here is either a black stripe down the middle or a map clipped
   under the dock — neither of which reads as a bug until it is screenshotted.

   66% is a decision, not a default. Tom, 2026-08-07: the sheet "can take up
   more than half of the page". It has to be more than half: the sheet is two
   stacked charts BESIDE a grid whose own stylesheet reserves a 560px minimum
   track for the grid alone, so under roughly 900px of dock the grid starts
   eating the charts. At 1440 this gives the sheet 950px and leaves the map a
   490px rail — still wider than the 425px panel it replaces. The map gets
   smaller; it does not get unusable. */
:root {
  --sd-dock-width: 66%;
}

#sd-dock {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: calc(100% - var(--sd-dock-width));
  /* display/flex-direction, the background and the left border all come from
     .drawer in app.css. What is left here is only what is true of THIS dock:
     where it sits and what it must paint over.

     The background used to be a literal copy of hub_sd.css's --bg so the
     iframe's page and its host would not show a seam. That coupling is now
     through --bg itself: both files resolve the same token, in whichever theme
     is live, so the seam closes in BOTH themes instead of only in the dark one
     the literal was picked for. */
  /* Above Leaflet's panes (z 400) and its controls (z 800-1000). Those are
     positioned inside #map, and would otherwise paint over a sibling that
     merely comes later in the document. */
  z-index: 1200;
}

/* `hidden` on its own loses to `display: flex`: the attribute's rule comes from
   the USER-AGENT origin, and an author rule beats it at any specificity.
   Without this line the dock was on screen at every page load, over the map.

   KEPT, THOUGH app.css NOW CARRIES THE GENERAL FORM ([hidden][hidden], added
   WP4.4 after the same bug put t1_explorer's source drawer over its own page).
   This one is a local statement about a local element and costs nothing beside
   it; what would cost something is deleting it on the strength of a rule that
   lives in a different file and is only in force while index.html happens to
   link that file. */
#sd-dock[hidden] {
  display: none;
}

body.sd-open #map {
  right: var(--sd-dock-width);
}

/* The panel and the dock answer the same question at two different lengths,
   so only one is ever up. Hiding it is also what buys the sheet its width —
   425px of panel plus a 66% dock would leave the map 4% of the window. */
body.sd-open #panel {
  display: none;
}

/* Both sides of the resize, so the map and the dock arrive together instead of
   one snapping and the other sliding. Leaflet has to be told after this runs;
   see openHubSd()'s invalidateSize call. */
#map,
#sd-dock {
  transition: right 180ms ease, left 180ms ease;
}

@media (prefers-reduced-motion: reduce) {
  #map,
  #sd-dock {
    transition: none;
  }
}

/* The dock's own bar. Deliberately NOT a second copy of the sheet's top bar:
   the sheet, inside the frame, owns the hub and window selects. This owns only
   the two things the sheet has no way to know — which node the map has
   selected, and how that node's own figures are sourced.

   ITS ENTIRE APPEARANCE NOW COMES FROM app.css. The bar is .drawer-bar, the
   title is .drawer-title, the badge is .badge.badge-button, the prose strip is
   .drawer-note, the Full page link is .link and Close is .btn. Eleven rules and
   sixteen literals went; nothing about the bar's LAYOUT was specific to this
   dock, which is why none of it had to stay.

   The one thing that could not move is below: hiding an empty badge and an
   empty vintage is a fact about how THIS bar is filled, not about drawers. */

/* An empty badge still draws its 1px border and its padding, which reads as a
   deliberate blank swatch rather than as "this node has no provenance row".
   The badge is driven by [hidden] rather than :empty now that it is a button,
   because a button with no text is still focusable and would put a silent stop
   in the tab order. */
#sd-dock-kind[hidden],
#sd-dock-vintage:empty {
  display: none;
}

#sd-dock-basis[hidden] {
  display: none;
}

#sd-frame {
  flex: 1;
  min-height: 0;
  width: 100%;
  border: 0;
  display: block;
}

/* R2.4 puts the panel title on --font-display. Whether the serif survives on
   the DARK ground is the one open question WP2 stops on; it is set here rather
   than deferred so the gate has something real to look at. */
#panel h2 {
  font-family: var(--font-display);
  font-size: var(--t-display);
  font-weight: 500;
  margin: 0 0 10px 0;
  color: var(--ink);
}

#panel-body {
  font-size: var(--t-body);
  line-height: 1.5;
  color: var(--ink-body);
}

/* Key facts (renderPanel() and loadLive() both emit this same dl/dt/dd) as
   a two-column grid rather than the label-stacked-above-value list this was
   until 2026-07-31 (Tom: "when you click on a pipeline/refinery I think it
   can look better when listing the key facts, and take up less space").
   Stacked, every fact cost two lines plus a 10px gap — a 4-fact pipeline
   panel ran ~180px and pushed the chart below the fold. Side by side it is
   one line per fact, and the labels share a left edge and a right edge, so
   the values line up into a scannable column instead of a ragged stack.

   The label column is fixed, not `auto`: with `auto` the widest label in
   whichever panel you happened to open would set the column, so the values
   would jump horizontally every time you clicked a different feature. 128px
   is the widest label the pipeline/refinery panels use ("Nameplate
   capacity", measured at 126px) — check any new label against it, since a
   longer one wraps to two lines and costs back the space this layout
   saves. */
#panel dl {
  margin: 0;
  display: grid;
  grid-template-columns: 128px 1fr;
  column-gap: 12px;
  align-items: baseline;
}

#panel dt,
#panel dd {
  padding: 4px 0;
  border-top: 1px solid var(--line);
}

/* No rule above the first row — the panel title already separates it. */
#panel dl > dt:first-child,
#panel dl > dt:first-child + dd {
  border-top: none;
  padding-top: 0;
}

#panel dt {
  font-size: var(--t-label);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-body);
  margin: 0;
}

#panel dd {
  margin: 0;
  font-size: var(--t-body);
  color: var(--ink);
  /* `break-word`, not `anywhere`: values include long unbroken tokens (node
     ids, "EIA Form EIA-820, data as of 2026-01-01") that must be allowed to
     split rather than widen the panel, but `anywhere` splits them even when
     moving the whole word to the next line would have fit — it was cutting
     that date into "2026-01-" / "01". `min-width: 0` is what actually stops
     a long token from stretching the grid column, since a grid item's
     default `min-width: auto` is its min-content width. */
  min-width: 0;
  overflow-wrap: break-word;
}

#panel .placeholder {
  margin-top: 16px;
  padding: 10px 12px;
  background: var(--surface-2);
  border-left: 3px solid var(--ink-faint);
  font-size: var(--t-data);
  color: var(--ink-body);
  border-radius: 2px;
}

/* ---------------------------------------------------------------------
   Panel load state (loadOverview() in map.js) — the latest US/Canada
   headline balance, shown before any click so the page arrives with real
   numbers on it instead of an empty black column.
   --------------------------------------------------------------------- */
.overview-cards {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 14px;
}

.overview-card {
  display: block;
  width: 100%;
  box-sizing: border-box;
  text-align: left;
  background: var(--surface-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-ctl);
  padding: 12px 14px 10px 14px;
  color: inherit;
  font-family: var(--font-ui);
  cursor: pointer;
}

/* UI-01 WP4.4: the card is a <div>; its button stretches over it with
   ::after, so the whole card opens the country while the "?" link inside
   the stats stays a link of its own, raised above the stretch. */
.overview-card {
  position: relative;
}

.overview-card-open {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.overview-card-open::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--r-ctl);
}

.overview-card-open:focus-visible {
  outline: none;
}

.overview-card-open:focus-visible::after {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

.overview-help {
  position: relative;
  z-index: 1;
  display: inline-block;
  min-width: 16px;
  text-align: center;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  color: var(--ink-body);
  text-decoration: none;
  line-height: 14px;
}

.overview-help:hover,
.overview-help:focus-visible {
  border-color: var(--ink);
  color: var(--ink);
}

.overview-card:hover {
  border-color: var(--ink);
}

.overview-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.overview-card-name {
  font-size: var(--t-label);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--label-tracking);
  color: var(--ink-body);
}

.overview-card-arrow {
  font-size: var(--t-body);
  color: var(--ink-faint);
}

.overview-card:hover .overview-card-arrow,
.overview-card:hover .overview-card-name {
  color: var(--ink);
}

/* baseline, not center: the unit should sit on the figure's baseline
   rather than float halfway up its 30px cap height. */
.overview-hero {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin-top: 6px;
}

.overview-figure {
  font-size: 30px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.01em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.overview-unit {
  font-size: var(--t-data);
  color: var(--ink-body);
}

/* The overview's one month line, above both cards (UI-01 WP1.1). */
.overview-month {
  margin: 0 0 8px;
  font-size: var(--t-label);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--ink-faint);
}

.overview-caption {
  margin-top: 5px;
  font-size: var(--t-label);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--ink-faint);
}

/* Two label/value pairs per row: label above value, so long labels
   ("Refinery Inputs") never squeeze the number onto a second line the way
   a side-by-side label/value row would at this panel width. */
.overview-stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px 12px;
  margin: 12px 0 0 0;
  padding-top: 10px;
  border-top: 1px solid var(--line);
}

.overview-stats dt {
  font-size: var(--t-label);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--ink-faint);
  margin: 0;
}

.overview-stats dd {
  margin: 1px 0 0 0;
  font-size: 15px;
  line-height: 1.1;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.overview-hint {
  margin: 0;
  font-size: var(--t-data);
  line-height: 1.5;
  color: var(--ink-body);
}

#panel .live-section {
  margin-top: var(--sp-4);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--line);
}

/* The heading takes R2.3's treatment through .eyebrow (see renderLiveSection()
   in map.js) and only its LAYOUT is set here. It was teal at 12px/0.05em —
   a fourth uppercase-label style, and a teal that meant nothing: no data on
   this page renders in it, so the colour was pure decoration on the one
   element R2.3 says must not be restyled locally. --ink-body via
   .eyebrow-strong makes it findable without claiming anything. */
#panel .live-section h3 {
  margin: 0 0 var(--sp-1) 0;
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
}

/* "History →", pushed to the right of its own heading. Sentence case against
   the uppercase heading so it reads as an action, not as a second section
   whose table failed to render. */
#panel .live-section h3 a.hub-more {
  margin-left: auto;
  text-transform: none;
  letter-spacing: 0;
}

/* Weekly/Monthly toggle for the CER refining-region history panel
   (loadCerRefineryHistory in map.js). Shares the heading row so it doesn't
   cost extra vertical space. */
#panel .history-heading-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

#panel .history-heading-row h3 {
  margin: 0;
}

/* The Weekly/Monthly control is .seg / .seg-btn from app.css. Its "on" state
   moved from a class named `active` to `is-on` with it — `active` collides with
   the :active pseudo-class in every reader's head, and the pair was already
   confusing enough that the hover rule had to spell out `:not(.active)`. */

/* Sub-heading inside loadHubDetail's supply/demand sections (e.g.
   "Production by region", "Incoming pipelines"). Takes .eyebrow like every
   other label; only its margins are local. */
#panel .hub-subhead {
  margin: var(--sp-2) 0 var(--sp-1) 0;
}

/* loadHubDetail's supply/demand tables — labels combine a pipeline name +
   "(from/to Region)" and can run long, so (unlike every other
   .history-table, all of which have short date/name labels that are fine
   with nowrap) this variant wraps the label column instead of letting it
   push the value column off the edge of the fixed-width panel. */
#panel table.history-table.hub-table {
  table-layout: fixed;
  width: 100%;
}
#panel table.history-table.hub-table td:first-child {
  white-space: normal;
  width: 70%;
}
#panel table.history-table.hub-table td:last-child {
  width: 30%;
}

/* overflow-x is explicit, not left to the spec's "a non-visible overflow-y
   forces the other axis to auto" rule, because that rule is what made the
   Basis column silently disappear: the table overflowed, the axis quietly
   became scrollable, and nothing on screen said so. The single-edge table
   now fits outright (see the padding budget below); this is here for the
   multi-edge group tables (EPIC Crude's two legs), which add a Flow column
   per leg and cannot be made to fit at any padding. */
#panel .history-table-wrap {
  max-height: 220px;
  overflow-x: auto;
  overflow-y: auto;
  border: 1px solid var(--line);
  border-radius: var(--r-ctl);
}

/* NOTE: no border-collapse here, and that is load-bearing rather than an
   omission. .data-table sets `separate` + `border-spacing: 0` because a
   COLLAPSED table drops the borders of a position:sticky cell — the border
   belongs to the collapsed grid, not the cell, so it scrolls away and the
   pinned header row loses the rule under it. This rule used to say
   `collapse`, and being both later in the cascade and more specific it would
   have won and quietly broken the sticky header the moment the class was
   added. The font-size override went the same way, and that one WAS re-
   measured rather than assumed — see the padding note below. */

/* 4px sides, which is --sp-1, and it is the tightest step the scale has.
   Every column here is nowrap, so horizontal padding is the only slack in a
   386px-wide wrap, and 5 columns x 2 sides means each px costs 10px of table.

   RE-MEASURED 2026-08-07, because the type changed underneath the old
   numbers. This rule used to set 11.5px in the UI face; the table is now
   .data-table, so it is IBM Plex Mono at --t-data (12.8px) — bigger AND a
   mono face, which is the direction that loses columns. The old note recorded
   a min-content of 381px against a 386px wrap, i.e. 5px of headroom, so the
   change could not be waved through.

   Measured in the live panel against the widest row the model can produce —
   4-digit flow AND capacity (3,336 / 3,220), 3-digit utilisation, and the
   longest basis still in the vocabulary, FERC_ANNUAL_SPREAD at 18 characters:
   columns come out 66 / 50 / 84 / 51 / 135 = 386px, no cell clipped and
   scrollWidth == clientWidth. It fits, with the slack now living in the
   Nameplate and Utilisation columns rather than in the padding. Anything that
   makes a cell wider — a sixth column, a longer basis code — has to be
   re-measured the same way and NOT resolved by truncating the basis (R7.1). */
#panel table.history-table th,
#panel table.history-table td {
  padding: 3px var(--sp-1);
  text-align: right;
  white-space: nowrap;
}

#panel table.history-table th:first-child,
#panel table.history-table td:first-child {
  text-align: left;
}

/* .data-table in app.css supplies the sticky positioning, the surface, the
   R2.3 label treatment and the rule under it. This variant only re-centres. */
#panel table.history-table thead th {
  text-align: center;
}

/* Headers are centered even though the Month/Edge data columns beneath
   them are left-aligned (dates/names read better left-aligned; the
   header label itself reads better centered over its column) — needs its
   own rule since it's more specific than the general th:first-child ->
   left-align rule above. */
#panel table.history-table thead th:first-child {
  text-align: center;
}

/* ---------------------------------------------------------------------------
   HEADER COLOURS. Each column head takes the colour of its own series' line in
   the chart below, so the table and the graph read as one connected picture.

   THE OLD VERSION MADE THAT CLAIM AND DID NOT KEEP IT. The comment said "same
   hex values as the Chart.js dataset colors in map.js" — and it was true only
   for as long as someone kept two lists of literals in two files in step by
   hand. There is no mechanism there, just a promise. These now name --series-*,
   and WP3 points the Chart.js datasets at the SAME tokens, at which point the
   two cannot disagree because there is one value.

   SLOT ASSIGNMENT IS PER TABLE, NOT GLOBAL, because R3.7 gates pairs that
   CO-OCCUR IN ONE VIEW and these three genres never appear together — a panel
   shows a pipeline OR a PADD OR a basin. So each genre gets slots 1..n from the
   top of the ramp rather than sharing one global allocation, which would run
   out at six across thirteen columns.

   TWO COLUMNS DELIBERATELY GET NO SERIES SLOT. Nameplate and Gap are not
   categories of measurement, they are the frame the measurements are read
   against: a nameplate is a published reference level, and a gap is an ABSENCE.
   Giving either a series colour would put it in the same visual class as the
   flows and invite a reader to compare them as peers. --ink-faint says
   "context", which is what they are.

   NO EVIDENCE TOKEN APPEARS IN A HEADER, for the same reason none appears on
   the map: a column's evidence grade varies by ROW (one month is T1_MEASURED,
   the next is allocated), so a single header colour cannot carry it and would
   assert one grade for every month in the column. Evidence lives in the Basis
   cell and the flag cells below, which vary per row.

   THE SLOT IS A SWATCH, NOT A TEXT COLOUR (R3.4, ratified 2026-08-07). Every
   rule below is the same three declarations — the series slot, the toggle that
   makes app.css draw the block, and --ink-body for the letters. The trio is
   verbose on purpose: it is an assignment TABLE, and a table you can read down
   is worth more here than a shorthand that hides which columns have slots.

   WHY THE LETTERS GO UP TO --ink-body RATHER THAN STAYING --ink-faint. A
   swatched header just gave up its colour; --ink-faint is the weakest text on
   the page (4.57:1) and is what R2.3 gives to a label that is pure frame. The
   frame columns below — Nameplate and Gap — keep it, so swatch+body versus
   flat+faint separates "category" from "context" on two channels at once, and
   neither of them is hue.
   ------------------------------------------------------------------------ */
#panel table.history-table thead th.th-flow { --swatch: var(--series-1); --swatch-on: inline-block; color: var(--ink-body); }
#panel table.history-table thead th.th-nameplate { color: var(--ink-faint); }
#panel table.history-table thead th.th-effective { --swatch: var(--series-3); --swatch-on: inline-block; color: var(--ink-body); }
#panel table.history-table thead th.th-util { --swatch: var(--series-5); --swatch-on: inline-block; color: var(--ink-body); }

/* Multi-edge group tables (e.g. EPIC Crude's Permian/Delaware legs, pivoted
   to one Flow column per edge, see loadPipelineHistory()'s multiEdge
   branch): Total sits right after Month (leftmost data column, ahead of
   the per-leg breakdown), bolded and with a subtle separating border, so
   it reads as "the headline number" with the per-leg columns after it as
   supporting detail rather than another leg to add up.

   The bold and the --ink are app.css's .cell-total; only the separating
   border is specific to this table, because it depends on Total sitting in a
   fixed position that no other .data-table shares.

   .th-total IS ALSO WHAT AN AGGREGATE WEARS INSTEAD OF A SWATCH (WP3.4). The
   PADD5 and CER-basin tables both put a total immediately before the columns
   that decompose it, and both drop their category swatch when the breakdown is
   present — a swatch beside three others would read as a fourth peer rather
   than their sum. So this selector now marks two things that turn out to be one
   thing: "the headline number, and the columns after it are its parts". No
   colour is declared here on purpose; an un-swatched header is exactly what
   `<th>Month</th>` already is, and the aggregate should look like structure,
   not like a series that lost its colour. */
#panel table.history-table thead th.th-total,
#panel table.history-table td.cell-total {
  border-right: 1px solid var(--line-strong);
}

/* PADD genre — FIVE balance columns, which is one fewer than R3.4's six slots,
   and the spare slot is what let the PADD5 state split be solved above.

   These were PADD_COLORS, the map's own five PADD fills. That tie is gone
   because the fills are gone: R5.2 turned the five PADDs into one dashed
   boundary family, so "matching the map" now has nothing to match. The columns
   are just categories, and categories take the series ramp.

   PRODUCTION AND EXPORTS ARE THE SAME BLUE AT TWO LIGHTNESSES after WP5's
   re-register — --series-1 at H 249.5 and --series-4 at H 251.0, passing R3.7
   only on ΔL 0.168. It shows up harder in the chart than here: map.js plots
   Production / Refinery inputs / Exports / Residual as four lines on these same
   four slots, and two of them are solid blues. Recorded rather than fixed
   because there is nothing to fix it WITH — tokens.css proves at the ramp that
   five hue-separable series is the arithmetic ceiling once --accent-measured's
   green is reserved, so a sixth slot is a lightness step wherever it is put,
   and moving Exports to --series-6 only re-creates the Alaska collision WP3.4
   deliberately resolved twelve lines above. The remaining lever is a SECOND
   CHANNEL on the chart — a dash or a point style for Exports, the way
   t1_atlas.js separates filers — and that is a judgement about how the lines
   read at size, so it belongs to the rendered R3.8 pass, not to a swatch rule
   in a stylesheet. */
#panel table.history-table.genre-padd thead th.th-production { --swatch: var(--series-1); --swatch-on: inline-block; color: var(--ink-body); }
#panel table.history-table.genre-padd thead th.th-imports { --swatch: var(--series-2); --swatch-on: inline-block; color: var(--ink-body); }
#panel table.history-table.genre-padd thead th.th-refinput { --swatch: var(--series-3); --swatch-on: inline-block; color: var(--ink-body); }
#panel table.history-table.genre-padd thead th.th-exports { --swatch: var(--series-4); --swatch-on: inline-block; color: var(--ink-body); }
#panel table.history-table.genre-padd thead th.th-residual { --swatch: var(--series-5); --swatch-on: inline-block; color: var(--ink-body); }

/* API GRAVITY AND SULFUR HAVE NO SWATCH, AND DID NOT DESERVE ONE (WP3.4). They
   held --series-6 — both of them, one slot for two columns — and that was two
   problems wearing one colour. It became visible when Alaska took --series-6 in
   the state-split fix above and these appeared in the same table as an exact
   match; but the collision only exposed the older mistake, which is that a
   swatch says "I am one of the categories that stack in the chart below" and
   these are not. They are degrees and percent, not Mbbl/d; they are not
   summable, they are not plotted, and no legend entry anywhere is their peer.
   A pair of columns measuring the crude's PROPERTIES rather than its VOLUME is
   a different axis, and the italic marks it as one without claiming membership
   of a family it is not in. */
#panel table.history-table.genre-padd thead th.th-quality {
  color: var(--ink-faint);
  font-style: italic;
}

/* AN EXPORT FIGURE EIA DID NOT PUBLISH, marked as such. EIA leaves the
   PADD-level crude export series blank in a large minority of months; the
   balance derives what the national total determines and assumes only what it
   has separately checked, and either way the cell has to say so. Deliberately
   NOT a colour: this table already spends all five --series-* slots on its
   columns and a sixth hue would read as a category rather than a caveat. The
   dagger is the caveat, the dimmed figure is the confidence, and the title
   attribute carries the sentence. The mark takes --accent-modelled, which is
   the token that already means derived/allocated/carried-forward — this is
   precisely that, so it gets the existing word rather than a new one. */
#panel table.history-table.genre-padd td.td-inferred {
  color: var(--accent-modelled);
  cursor: help;
}
#panel table.history-table.genre-padd td.td-inferred .inferred-mark {
  margin-left: 0.25em;
  font-size: 0.85em;
  vertical-align: super;
}
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}

/* The three PADD5 state-split columns and the one production column on an
   EIA producing-area panel, which map.js used to colour with an inline
   `style="color: var(--series-N)"`. They are the same kind of column as the
   six above and now carry the same swatch; the classes exist so that the
   assignment lives here with its peers rather than inside a template
   literal, where the next person to edit the string would not see the rule
   it is participating in.

   CLOSED WP3.4, 2026-08-08. The finding was that the PADD5 panel renders TEN
   columns over a chart plotting THREE (California/Alaska/Other, stacked, under
   a dashed visible-total line), so two swatches repeated:

     Total production (--series-1)  ==  California (--series-1)
     Exports          (--series-4)  ==  Alaska     (--series-4)

   The first was always defensible — California is a PART of that total, and a
   shared swatch says so. The second was not: Exports and Alaska are unrelated,
   and a swatch ASSERTS identity in a way a tinted label never quite did.

   THE FIX WAS AN ALLOCATION, AND THE THING THAT GAVE WAY WAS THE ONE THAT WAS
   NEVER A PEER. Eight co-occurring categories do not fit six slots plus Other,
   so Total production surrendered its swatch: it is the SUM of Production +
   Imports - Exports' siblings, not a seventh category, and .cell-total already
   gives up its colour on the same grounds. That freed --series-1 for California
   outright, and Alaska moved to --series-6 — the last free slot, and free
   precisely because the balance genre only ever uses four. Seven categories,
   seven distinct swatches, nothing shared and nothing greyed. The chart in
   map.js draws the visible total as a dashed --ink reference line, which is
   what this header now matches. */
#panel table.history-table.genre-padd thead th.th-state-ca { --swatch: var(--series-1); --swatch-on: inline-block; color: var(--ink-body); }
#panel table.history-table.genre-padd thead th.th-state-ak { --swatch: var(--series-6); --swatch-on: inline-block; color: var(--ink-body); }
#panel table.history-table.genre-padd thead th.th-state-other { --swatch: var(--series-other); --swatch-on: inline-block; color: var(--ink-body); }
#panel table.history-table thead th.th-area-production { --swatch: var(--prod-5); --swatch-on: inline-block; color: var(--ink-body); }

/* Basin genre. */
#panel table.history-table.genre-basin thead th.th-basin-production { --swatch: var(--series-1); --swatch-on: inline-block; color: var(--ink-body); }
/* Per-crude-type breakdown columns (CER Canada nodes only) — the swatch is set
   inline per-column as `--swatch: var(--series-N)` (MODE_SLOT in map.js) to
   match that type's chart colour, because which types exist is a property of
   the data, not of this stylesheet. It is a var() reference and not a resolved
   literal so the header repaints with the theme like everything else.
   font-weight here just keeps them visually secondary to the total-production
   header, which is .th-total when these columns are present. */
#panel table.history-table.genre-basin thead th.th-basin-mode { font-weight: 400; --swatch-on: inline-block; color: var(--ink-body); }
#panel table.history-table.genre-basin thead th.th-outflow { --swatch: var(--series-2); --swatch-on: inline-block; color: var(--ink-body); }
#panel table.history-table.genre-basin thead th.th-gap { color: var(--ink-faint); }

/* ---------------------------------------------------------------------------
   TWO-LEVEL ROW STRIPING, and the one substantive change in this table.

   WHAT IT KEEPS. The 2x2 structure is intact and so is every distinction it
   carried: alternating base shade per year (.year-0/.year-1) plus a lighter/
   darker variant per month inside that year (.month-0/.month-1), so a
   multi-decade history stays scannable both across a row and down a year. Four
   levels in, four levels out. Nothing about which rows group together changed.

   WHAT IT DROPS, ON PURPOSE. The tint was a HUE, and it varied by genre:
   PADD-blue and pipeline-gold on the general table, blue and green on a PADD,
   red and gold on a basin — twelve rgba() washes over six chart colours.
   Under R3 that is a colour making a claim. `rgba(242,201,76,.12)` is the amber
   band, which R3.6 reserves absolutely for --warn; every other row of every
   pipeline history was therefore washed in the product's own "unexplained or
   stale" colour, on rows where nothing was wrong. The basin table did the same
   thing in the negative band, which is --data-neg. The hue was decoration and
   bands are not available for decoration.

   The genre-specific variants go with it, and that loses nothing a reader can
   use: only one table is ever on screen, so a stripe colour that identifies the
   genre identifies it against nothing. The headers already say which table
   this is, and they still do it in colour.

   WHY color-mix AND NOT A NEW TOKEN. Four steps of one neutral is a RAMP, not
   four meanings, and R1.2 wants tokens to be semantic — --stripe-3 would be a
   token named after its appearance. Mixing --ink into transparent derives all
   four from a token that both themes already define, so the ramp inverts with
   the theme for free (dark ink on light paper, light ink on dark) with no
   second block to keep in step. The steps are 0/4/8/12% — even, so no two of
   the four bands are closer to each other than any other pair, and low enough
   that each reads as a band rather than as a colour, which is the whole job.
   ------------------------------------------------------------------------ */
#panel table.history-table tbody tr.year-0.month-0 { background: transparent; }
#panel table.history-table tbody tr.year-0.month-1 { background: color-mix(in oklab, var(--ink) 4%, transparent); }
#panel table.history-table tbody tr.year-1.month-0 { background: color-mix(in oklab, var(--ink) 8%, transparent); }
#panel table.history-table tbody tr.year-1.month-1 { background: color-mix(in oklab, var(--ink) 12%, transparent); }

/* R3.1, and these two ARE evidence claims rather than categories: HIGH means
   near capacity and OVER_NAMEPLATE means past it. Amber then red is exactly
   what those tokens are for, and it is the same pair the util chart's threshold
   lines use. Unlike the header colours above, these vary per row — which is why
   they may carry a grade at all. */
#panel table.history-table td.flag-high { color: var(--warn); }
#panel table.history-table td.flag-over { color: var(--data-neg); }
/* .cell-basis in app.css: --t-label, which matches the header row's size and is
   the last of the width budget above (381px min-content vs the 386px wrap, on
   the widest edge). This column is the widest non-numeric one —
   "FERC_ANNUAL_SPREAD" sets it now that FERC_CARRIED_FWD is gone, and it is 2
   characters wider — so a half-point here buys more than it would anywhere
   else. R7.1: the code is never truncated to make it fit. */

/* SOURCE CARD under every pipeline history table (renderEdgeSourceCard(),
   added 2026-08-07). Names the publisher behind the flow and behind the
   nameplate, how far the data reaches, and the NATIVE cadence of each basis
   on screen — so a table that repeats one figure across a quarter says why
   rather than looking smoothed. Deliberately quiet: this is provenance a
   reader should be able to find, not a callout competing with the data. */
#panel .edge-source-card {
  margin: var(--sp-2) 0 2px;
  padding: var(--sp-2);
  border-left: 2px solid var(--line-strong);
  background: var(--surface-2);
  border-radius: var(--r-ctl);
  font-family: var(--font-data);
  font-size: var(--t-label);
  line-height: 1.5;
  color: var(--ink-body);
}
#panel .edge-source-card > div { margin-bottom: 2px; }
#panel .edge-source-card > div:last-child { margin-bottom: 0; }

/* .src-key carries NO type or colour of its own — the label treatment is the
   .eyebrow component (R2.3: "that treatment IS the eyebrow/label component;
   never restyle it locally"), and this class now supplies only the two things
   that are about POSITION rather than appearance: it has to sit on the same
   line as the value it labels, with a gap. It used to be its own fourth
   uppercase-label treatment at 9px/0.04em, which is exactly the drift app.css
   exists to end. */
#panel .edge-source-card .src-key {
  display: inline;
  margin-right: var(--sp-1);
}

/* A citation the reader can pull the rest of out of, via title=. The dotted
   underline plus cursor:help is the whole affordance — there is no other
   signal that a hover is available, so it may not be dropped for tidiness. */
#panel .edge-source-card .src-cite {
  color: var(--ink);
  border-bottom: 1px dotted var(--line-strong);
  cursor: help;
}
#panel .edge-source-card .src-note {
  color: var(--ink-faint);
  font-style: italic;
  margin-top: var(--sp-1);
}
#panel .edge-source-card .src-note-inline {
  color: var(--ink-faint);
  font-style: italic;
}

/* The control that opens the card above is app.css's .disclosure — the same
   component the T-1 pages use — so its eleven declarations are gone from here
   rather than tokenised in place. See renderSourcesDisclosure() in map.js,
   which now emits `class="disclosure src-toggle"`; src-toggle survives only as
   a hook for anything that needs to find this particular one. */

/* Comment marker: a row whose month carries a structural-change note — a
   nameplate step, an in-service date, anything in config/data_notes.yaml.
   CLICK opens the note underneath (see applyNoteToRow() in map.js); it used
   to be a `title` tooltip on `cursor: help` and could not be read.
   Neutral ink (not red/gold) so it reads as "info", not as a
   flag-high/flag-over style warning. */
#panel table.history-table tbody tr.has-note td:first-child {
  position: relative;
  cursor: pointer;
}
#panel table.history-table tbody tr.has-note td:first-child::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  border-width: 0 7px 7px 0;
  border-style: solid;
  border-color: transparent var(--ink-body) transparent transparent;
}
/* The open note. Full row width, wrapping prose — these run to several
   hundred characters of verbatim filing quote, which is exactly what a
   one-line native tooltip could not show. `white-space: normal` because the
   history table sets nowrap on its cells, and `user-select: text` because a
   citation is something a reader copies. */
#panel table.history-table tbody tr.note-row td {
  white-space: normal;
  user-select: text;
  padding: var(--sp-2) var(--sp-2);
  background: var(--surface-2);
  color: var(--ink-body);
  font-family: var(--font-ui);
  /* --t-body, not the --t-label the surrounding cells use. Those are figures
     in a dense grid and read at a glance; this is a paragraph of filing
     citation, read a line at a time. */
  font-size: var(--t-body);
  line-height: 1.45;
  border-left: 2px solid var(--ink-faint);
}
#panel table.history-table tbody tr.has-note.note-open td:first-child::after {
  border-color: transparent var(--ink) transparent transparent;
}

#panel .history-chart-wrap {
  margin-top: var(--sp-3);
  height: 230px;
  position: relative;
}

/* ---------------------------------------------------------------------
   Map key (keyPanel() in js/shell.js; UI-04, 2026-10-28). The card's look,
   its sections, eyes and chips are css/shell.css, shared with the Texas map;
   what is left here is how THIS map sizes it.

   Thomas, 2026-10-28: "Include the permanent side par like theirs with the
   key on the side." Grid Status's layer panel is about 300px; 280px holds
   the longest legend line ("T-1 estimate (Texas RRC filings)") on one line
   beside its chip and leaves the map most of a 1280px window with the
   425px side panel open. It was 252px when it was a closed corner box.
   --------------------------------------------------------------------- */
.map-key {
  width: 280px;
}

/* ---------------------------------------------------------------------
   R5.4 — the hover card (bindHoverCard() in map.js).

   Leaflet's stock tooltip is a white box with a black border and a white
   arrow, all three hardcoded in leaflet.css. Every one of them is overridden
   here rather than replaced with a hand-rolled div, because Leaflet is
   already doing the pointer tracking and the viewport-edge flipping.

   THE ARROW IS THE FIDDLY PART. leaflet.css draws it as a CSS triangle out of
   BORDER colours on ::before, one rule per direction, and it does not inherit
   the box's background — so a themed card with an unthemed arrow is a white
   spike hanging off a dark card. All four directions are re-pointed at
   --surface below; `direction: "top"` is what we ask for, but Leaflet flips to
   bottom/left/right on its own near an edge, and the flipped arrow is exactly
   the one nobody tests.

   --font-data, not --font-ui: these cards state identities that get compared
   across hovers (operator names, node pairs), and the monospace keeps them on
   a common rhythm. --------------------------------------------------------- */
.leaflet-tooltip.hover-card {
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-ctl);
  box-shadow: var(--shadow-panel);
  color: var(--ink-body);
  font-family: var(--font-data);
  font-size: var(--t-label);
  line-height: 1.45;
  padding: 6px var(--sp-2);
  white-space: nowrap;
}

.leaflet-tooltip.hover-card .hover-card-name {
  color: var(--ink);
  font-weight: 500;
}

/* A Texas T-1 line's estimate line (t1HoverLine in map.js, 2026-10-21): the
   one hover line long enough to need wrapping, so it wraps at a fixed width
   instead of stretching the card off a phone screen. A dotted rule in the
   pipeline colour ties it to the dotted line under the cursor. */
.leaflet-tooltip.hover-card .hover-card-t1 {
  white-space: normal;
  max-width: 260px;
  margin-top: 2px;
  padding-top: 2px;
  border-top: 2px dotted var(--map-pipe);
}

.leaflet-tooltip-top.hover-card::before { border-top-color: var(--surface); }
.leaflet-tooltip-bottom.hover-card::before { border-bottom-color: var(--surface); }
.leaflet-tooltip-left.hover-card::before { border-left-color: var(--surface); }
.leaflet-tooltip-right.hover-card::before { border-right-color: var(--surface); }

/* The Key is the left edge's ONE control (UI-01 WP4.3 folded the Supply &
   Demand nav into it; UI-04 made it the open side panel), so its body may
   run to the edge's foot: 100px is Leaflet's 10px top margin, the 2px
   border, the 30px header and 26px off the map's foot, plus slack. It
   scrolls only on a short window. border-box (shell.css), or max-height
   would size the CONTENT. */
.map-key-body {
  max-height: calc(100vh - 100px);
}

/* THE PUBLISHED MAP IS 81px SHORTER THAN THE VIEWPORT (2026-10-24; 45px
   until UI-04 put the crude sub-bar under the shared one, 2026-10-28).
   site_build._flows_us() pins #map at `top: 81px` under the two bars, but
   the cap above is written in vh, so on /flows/us/ the Key would be
   budgeted 81px of map that is not there. Keyed on the sub-bar, which only
   the published copy has, so /web/index.html keeps the cap above.
   min-width: under 820px the Key is off the map in #map-tools and uncapped,
   and this selector would outrank that. The 81 is the two bars' desktop
   height, the same number site_build writes; change one, change both. */
@media (min-width: 821px) {
  body:has(.flows-tabs) .map-key-body {
    max-height: calc(100vh - 81px - 100px);
  }
}

/* ---------------------------------------------------------------------
   THE MAP'S OWN LOADING NOTE (UI-05, 2026-10-28). map.js puts it on the map
   while the lead layers are on their way and takes it off once they are
   drawn, so a slow connection shows a map that says it is coming rather
   than an empty one. A small pill on the credits pill's surface, at the top
   of the part of the map the Key leaves open; it takes no pointer.
   --------------------------------------------------------------------- */
.map-loading {
  position: absolute;
  top: var(--sp-3);
  left: 50%;
  transform: translateX(-50%);
  z-index: 900;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 4px var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  box-shadow: var(--shadow-panel);
  color: var(--ink-body);
  font-family: var(--font-ui);
  font-size: var(--t-label);
  white-space: nowrap;
  pointer-events: none;
}

/* Centred on what the open Key leaves: its 280px and Leaflet's 10px margin,
   halved. Folded, or off the map on a phone, it is the plain centre.
   Position and surface restated, not inherited (test_web_tokens' "no class
   means two things"). */
.leaflet-container:has(.leaflet-left .map-key:not(.collapsed)) .map-loading {
  position: absolute;
  left: calc(50% + 145px);
  background: var(--surface);
}

/* The pipeline gold, the map's lead colour, breathing. */
.map-loading-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--map-pipe);
  animation: map-loading-pulse 1.2s ease-in-out infinite;
}

@keyframes map-loading-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: .3; }
}

@media (prefers-reduced-motion: reduce) {
  .map-loading-dot {
    animation: none;
  }
}

/* ---------------------------------------------------------------------
   The Supply & Demand entry points, the last group in the Key (UI-01 WP4.3;
   they were their own corner box, SdNavControl). One section per country:
   the national total as its head, that country's market hubs as chips.
   --------------------------------------------------------------------- */
.sd-nav-country + .sd-nav-country {
  margin-top: 8px;
  padding-top: 7px;
  border-top: 1px solid var(--line);
}

/* The country row: name + "National Total" on the left, arrow right. Full
   width and transparent so it reads as a section heading you can click,
   not as another chip in the list below it. */
.sd-nav-total {
  color: inherit;
  display: flex;
  align-items: baseline;
  gap: 7px;
  width: 100%;
  box-sizing: border-box;
  background: none;
  border: none;
  padding: 0 0 7px 0;
  font-family: var(--font-ui);
  text-align: left;
  cursor: pointer;
}

/* Same tokens as .map-key-title, which is the thing it sits at eye level with
   once both controls are open. Was 11.5px/0.09em — close enough to read as a
   near-miss rather than a deliberate difference. */
.sd-nav-name {
  font-size: var(--t-label);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--label-tracking);
  color: var(--ink);
}

/* .map-key-heading's tokens, minus the weight: this is the muted uppercase
   qualifier on the row above, not a heading in its own right. */
.sd-nav-sub {
  font-size: var(--t-label);
  text-transform: uppercase;
  letter-spacing: var(--label-tracking);
  color: var(--ink-faint);
}

/* Pushed right by auto margin rather than justify-content: space-between,
   which would also spread the name and its sublabel apart. */
.sd-nav-arrow {
  margin-left: auto;
  font-size: var(--t-data);
  /* .map-key-chevron's grey. Was #4a525c, dark enough that the arrow read as
     a rendering artefact rather than an affordance next to the key's chevron. */
  color: var(--ink-body);
}

/* The ARROW carries the hover, not the name. The name is a section heading and
   already rests at --ink (it is .map-key-title's peer), so with interaction no
   longer holding a hue of its own there is nothing above it to promote to —
   keeping .sd-nav-name in this selector would be a rule that computes to no
   visible change, which is worse than no rule because it reads as working.
   The arrow rests at --ink-body and has the room. */
.sd-nav-total:hover .sd-nav-arrow {
  color: var(--ink);
}

/* Names the genre of the chips below it, which are market hubs and nothing
   else. Same tokens as .map-key-heading — deliberately, since these are the
   two controls sharing the left edge and a screenshot should read as one
   designed surface. Only the bottom margin differs: .sd-nav-total above
   already pads 7px below itself. */
.sd-nav-heading {
  font-size: var(--t-label);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--label-tracking);
  color: var(--ink-faint);
  margin: 0 0 5px 0;
}

/* Two per row. Kept 2-up even though the list is now 5 US + 3 CA rather than
   seventeen mixed basins/provinces/hubs: one-per-row would make this control
   tall enough to collide with .map-key in the opposite corner at a typical
   1440x810 screenshot size, and the chips ellipsis-clip at this width (hence
   the `title` on every SD_NAV_REGIONS entry). */
.sd-nav-regions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 3px;
}

.sd-nav-region {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 2px 7px;
  font-family: var(--font-ui);
  /* .map-key-label's tokens. These chips and the key's layer names are the
     same thing — the clickable leaf of each control — so they get the same
     size and the same white; at 10.5px/#b6bec7 the nav read as the key's
     footnote instead of its peer. Checked at 12.5px: the longest label
     ("Corpus Christi") still clears the 2-up chip width without ellipsis. */
  font-size: var(--t-data);
  line-height: 1.25;
  color: var(--ink-body);
  text-align: left;
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: color var(--dur-ui) var(--ease),
              border-color var(--dur-ui) var(--ease);
}

.sd-nav-region:hover {
  border-color: var(--ink);
  color: var(--ink);
}

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

/* "These filings on the Texas map" (loadTexasFilers() in map.js) — the same
   muted navigation link, under the section it belongs to. */
.texas-filers-link {
  display: block;
  margin-top: 10px;
  font-size: var(--t-data);
  color: var(--ink-body);
}
.texas-filers-link:hover {
  color: var(--ink);
}

/* The T-1 estimate note above a Texas line's flow history (t1EstimateNote in
   map.js, 2026-10-21): the placeholder's quiet block, with a dotted edge in
   the pipeline colour so it ties to the dotted line drawn on the map. */
#panel .t1-estimate-note {
  margin: 8px 0 10px;
  padding: 8px 10px;
  background: var(--surface-2);
  border-left: 3px dotted var(--map-pipe);
  border-radius: 2px;
  font-size: var(--t-data);
  color: var(--ink-body);
}
#panel .t1-estimate-head {
  color: var(--ink);
  font-weight: 600;
  margin-bottom: 2px;
}
#panel .t1-estimate-list {
  margin: 6px 0 0;
  padding-left: 16px;
}
#panel .t1-estimate-list li + li {
  margin-top: 4px;
}
.t1-estimate-link {
  display: inline-block;
  margin-top: 6px;
  color: var(--ink-body);
}
.t1-estimate-link:hover {
  color: var(--ink);
}
/* A month a T-1 filing has no figure for: a row, so the gap is visible in
   the table, and muted, so it is not read as a reading. */
#panel .history-table tr.t1-gap-row td {
  color: var(--ink-faint);
  font-style: italic;
}

/* The currently-selected pipeline / refinery / market hub — see
   selectFeature() in map.js. Slightly bigger, and recoloured to --map-selected.

   This replaced a teal glow on 2026-07-31 (Tom: "make the light up just be an
   enlarged colour of red, less bright and kiddish"). Two things made the glow
   look toy-like and are deliberately NOT here: the halo itself — a saturated
   drop-shadow bleeding several px past the line — and the brightness of that
   accent against a muted basemap. Size plus a sober colour carries the same
   "this is selected" signal without either. Don't reintroduce a glow.

   The red itself is gone as of 2026-08-07 and --map-selected is --ink. The
   substance of that request survives — flat, muted, no glow, restrained scale
   — but a red stroke is one hue-step from --data-neg, which everywhere else on
   this page means "a negative number", and a selection makes no claim about
   the data at all. --ink is also the one value that is maximally contrasty
   against BOTH basemaps, so the selection needs no per-theme special case.

   Selected PIPELINES are styled entirely from JS (setStyle in selectFeature),
   so .map-selected itself needs no rule — the class stays on paths purely as a
   marker for what's currently selected. Only markers need CSS, below.

   Every marker rule below styles the INNER div, never .map-selected itself:
   that outer element is the .leaflet-marker-icon, whose transform Leaflet owns
   for positioning (it writes translate3d there), so scaling it would move the
   marker off its coordinates.

   Each marker SHAPE gets its own selector rather than sharing one generic
   `.map-selected > div`. The triangles and the diamonds want different
   properties for the same effect — a triangle is a border trick recoloured
   through border-bottom-color and grows from its base, a diamond is a
   background-filled box already carrying a rotation and grows from its centre
   — so a shared rule would have to be overridden by whichever shape lost, and
   the transform is the dangerous one: a rule that scaled without repeating
   rotate(45deg) would silently un-rotate every selected diamond into a square.
   Scoping by class makes that structurally impossible. */

/* Refinery PLANTS (refineryIcon()): a dot, since UI-02 (2026-10-28). They
   were triangles in --map-no-data, off by default; then, for one commit,
   13-36px triangles in --ink, which turned the Gulf Coast into a black mass
   (HQ's ruling: small and coloured, the Gulf read as many small dots).

   THE ICON BUILDER EMITS GEOMETRY ONLY: the dot's width and height inline,
   because they are per-feature (sqrt-scaled by capacity, 5-15px), and
   nothing else. The fill and the halo are here, so a theme swap repaints
   every plant on the map without a single marker being rebuilt or a single
   themechange handler being written. The outer box is the hit area (at least
   12px, see refineryIcon) and centres the dot inside it. */
.refinery-icon {
  display: flex;
  align-items: center;
  justify-content: center;
}
.refinery-icon > div {
  flex: none;
  border-radius: 50%;
  background: var(--map-refinery);
  /* A thin halo in the opposite direction to the ground, so a dot stays
     legible on a pipeline, a basin fill, or a neighbouring plant. */
  box-shadow: 0 0 0 1px var(--map-halo);
}
/* Selected: the selection ink and a modest grow, from the centre, which is
   also the dot's anchor, so the plant does not drift off its site. */
.refinery-icon.map-selected > div {
  background: var(--map-selected);
  transform: scale(1.4);
}

/* CER-region triangles (cerRegionIcon(), className "refinery-triangle-icon
   cer-region-icon"). The plants used this same triangle until UI-02.

   THE ICON BUILDER EMITS GEOMETRY ONLY. It writes the three border WIDTHS
   inline and nothing else. The border STYLE, the transparent sides and the
   fill are here, so a theme swap repaints every marker without a rebuild.
   That is also why the !important that used to sit on the selected colour is
   gone: there is no longer an inline colour for it to beat. */
.refinery-triangle-icon > div {
  width: 0;
  height: 0;
  border-style: solid;
  border-top-width: 0;
  border-left-color: transparent;
  border-right-color: transparent;
  border-bottom-color: var(--map-no-data);
  /* A 1px halo in the opposite direction to the ground, so a triangle stays
     legible where it overlaps a dark basin fill or a pale one. */
  filter: drop-shadow(0 0 1px var(--map-halo));
}

/* The 3 CER regional rollups: their own fill, because they are aggregates of
   the plants rather than plants. */
.cer-region-icon > div {
  border-bottom-color: var(--map-refinery-region);
}

/* transform-origin sits at the bottom, matching iconAnchor's own y
   (size * 0.65, near the triangle's base), so growing it doesn't drift the
   marker away from its site. */
.refinery-triangle-icon.map-selected > div {
  border-bottom-color: var(--map-selected);
  /* Restrained on purpose (Tom: "make it not as big when clicked" — this was
     1.45 for part of 2026-07-31). The colour is doing most of the work; the
     scale is only here so a selected triangle still stands out. */
  transform: scale(1.18);
  transform-origin: 50% 80%;
}

/* Market-hub diamonds (marketHubMarker()). Size, fill, hairline and halo all
   live here for the reason above; the rotation lives here for the older reason
   in that function's own comment — so the selected rule below can restate it. */
.market-hub-icon > div {
  width: 12px;
  height: 12px;
  background: var(--map-hub);
  border: 1px solid var(--map-halo);
  filter: drop-shadow(0 0 1px var(--map-halo));
  transform: rotate(45deg);
}
/* Added 2026-07-31: clicking a hub used to change nothing on the map at all
   (neither hub layer called selectFeature), so the corner nav's chips opened
   a panel with no visible link back to the diamond they came from.

   The transform MUST repeat rotate(45deg): transform is a single property, so
   listing only the scale would silently un-rotate every selected diamond back
   into an upright square.

   transform-origin is left at the 50%/50% default: iconAnchor is the
   diamond's centre, so it grows in place. Scaled harder than the triangle's
   1.18 because the marker is only 12px and usually sits in a cluster of
   refineries. */
.market-hub-icon.map-selected > div {
  background: var(--map-selected);
  transform: rotate(45deg) scale(1.35);
}

/* Pipeline route card (routeGraphic() in map.js) — origin -> destination as a
   small diagram above the key facts rather than two more label/value rows in
   them.

   Tom asked for it to look like the map key ("I like the UI of the key,
   replicate that style"), and it used to get there by copying the key's four
   literals into four more rules. Both are now .panel from app.css, so they are
   the same object rather than two objects kept in step by hand — which is the
   only version of "replicate that style" that survives a palette change. The
   card's own chrome, its header treatment and its padding are all gone from
   this file; what is left below is the DIAGRAM, which is genuinely local. */
.route-card {
  margin-bottom: var(--sp-3);
}

.route-node {
  display: grid;
  /* Fixed dot column so both nodes' text starts on one vertical line, and the
     connector below can be centred on that column with a matching margin. */
  grid-template-columns: 9px 1fr;
  column-gap: 9px;
  align-items: center;
}

/* --map-pipe: the same stroke the map draws a pipeline we hold flows for in,
   and the map key's solid Pipelines swatch. The card only ever renders for
   matched pipelines — see routeGraphic() — so there is no muted variant of
   these two rules.

   The dot's FILL is --surface and not the card's background, because .panel is
   --surface: the dot is a ring, and the hole in it has to be the thing behind
   it or the ring reads as a filled disc with a dark centre. */
.route-dot {
  width: 9px;
  height: 9px;
  border-radius: var(--r-pill);
  background: var(--surface);
  border: 2px solid var(--map-pipe);
  box-sizing: border-box;
}

.route-connector {
  /* The 2px border IS the line, so width stays 0 — giving it a width too would
     make the box 4px and push the line 1px right of the dots' centre. 3.5px =
     half the 9px dot column minus half the 2px border, so the line sits on the
     dots' centre. */
  margin: 3px 0 3px 3.5px;
  width: 0;
  height: 16px;
  border-left: 2px solid var(--map-pipe);
  opacity: 0.9;
}

.route-name {
  font-size: var(--t-body);
  line-height: 1.25;
  color: var(--ink);
  /* Grid children default to min-width:auto (= min-content), which would let a
     long node name widen the card past the panel instead of wrapping. */
  min-width: 0;
  overflow-wrap: break-word;
  text-transform: capitalize;
}

/* Node provenance card (sourceCard() in map.js) — who published this node's
   number, what period it really reaches, and whether it is a measurement or a
   modelling assumption. Shares .panel and .route-card with the route diagram,
   so all that is left here is its label-over-value row rhythm. The badge is
   .badge from app.css and its variant is chosen by badgeClass() in map.js, in
   ONE place, because the dock's strip renders the same fact beside it. */
.source-card-row + .source-card-row {
  margin-top: var(--sp-2);
}

.source-card-value {
  font-size: var(--t-body);
  line-height: 1.35;
  color: var(--ink);
  /* `basis` is a full paragraph of prose, unlike every other value in this
     panel — it must wrap rather than widen the card past the panel. */
  min-width: 0;
  overflow-wrap: break-word;
}

/* Drill-down links in loadCountryHistory()'s panel (jump straight to a
   PADD/province's own panel without hunting for it on the map — see
   jumpToComponent() in map.js). Styled as small pill chips, not underlined
   text links, so they read as clickable "next level down" buttons. */
.drill-down-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
/* .chip in app.css does all of it. The class is kept as a hook because
   jumpToComponent() finds these by it. */
.drill-down-link {
  padding: var(--sp-1) var(--sp-2);
}

/* R5.4 bans the default Leaflet popup chrome outright, and the map now uses a
   custom hover card instead — but leaflet.css still styles these two classes,
   so if anything ever calls bindPopup() they must not arrive white. */
.leaflet-popup-content-wrapper {
  background: var(--surface);
  color: var(--ink);
}

.leaflet-popup-tip {
  background: var(--surface);
}

/* ===========================================================================
   THE PHONE AND SMALL-TABLET LAYOUT (820px and under, 2026-10-24).
   ---------------------------------------------------------------------------
   Everything above is the desktop page, and it is one absolute layout: the map
   is `right: 425px` and the panel is a fixed 425px. At 375px that left the map
   -50px wide, so Leaflet drew nothing and the panel was cut off at its left
   edge. K found it on the live site on 2026-10-21.

   UNDER THE BREAKPOINT THE PAGE STACKS, and it is the only change. Nothing
   outside this block moved, so a desktop window renders exactly as it did; the
   desktop screenshot and a computed-style dump of every element were compared
   before and after. 820px is the Texas map's own breakpoint (t1_map.css), so
   the two maps fold at the same width, and it keeps a 768px tablet on this
   side: there the old layout would leave a 343px map under two 254px boxes.

     map         full width, the viewport less a strip, so the panel's title
                 is still on screen under it and a tap visibly does something
     #map-tools  the key and the S&D nav, moved out of the map by
                 placeCornerControls() in map.js and folded at load
     #panel      in the page's flow, scrolling with it, 16px side gutter

   The page scrolls instead of the panel, so html/body give up their 100%
   heights. The map keeps its own height, which Leaflet needs to have.

   The breakpoint is written twice, here and as PHONE_LAYOUT in map.js;
   tests/test_us_map_phone.py fails if they differ. */

/* On a desktop the strip is empty and takes no box at all; the block below is
   the only place it is shown. */
.map-tools {
  display: none;
}

@media (max-width: 820px) {
  html,
  body {
    height: auto;
  }

  /* position: relative and not static: Leaflet's panes are absolute and need a
     positioned container, and Leaflet only sets one itself when it finds the
     box static at load. `right` is reset in both states because a relative
     box still honours it as an offset, and body.sd-open's `right: 66%` would
     slide the map two thirds off screen. The 168px strip under the map is the
     two folded control headers (two rows at phone width) plus the panel's
     title, measured at 375 x 812. svh after vh, so a phone whose toolbar
     shows and hides does not resize the map on every scroll.

     `body #map`, not `#map`: the published copy of this page (/flows/us/,
     model/site_build.py's _flows_us) appends an inline
     `#map, #panel, #sd-dock { top: 45px }` AFTER this sheet to clear the
     shared top bar. At equal specificity that later rule wins, and on a
     relative box `top: 45px` is an offset, so the map slid 45px down over
     the controls under it. One element selector more is enough to outrank
     it without touching the desktop rule it is right about. */
  body #map,
  body.sd-open #map {
    position: relative;
    top: auto;
    right: auto;
    bottom: auto;
    left: auto;
    height: calc(100vh - 168px);
    height: calc(100svh - 168px);
    min-height: 300px;
  }

  /* THE PUBLISHED PAGE HAS TWO MORE ROWS ABOVE THE MAP: the shared top bar
     (42px at 375) and the crude sub-bar (36px, css/shell.css). Both are in
     the page's flow on every width since UI-04 (the sub-bar was a pill
     pinned over the map before, centred, which on a phone sat on Alberta);
     restated here so the phone block says what it relies on. The map gives
     up those two rows (42 + 36, with 2px to spare in the 248) so the
     panel's title is still on screen under it. Keyed on the sub-bar itself,
     so /web/index.html, which has neither, keeps the taller map. */
  body .flows-tabs {
    display: flex;
    position: static;
  }

  body:has(.flows-tabs) #map {
    height: calc(100vh - 248px);
    height: calc(100svh - 248px);
  }

  /* One --surface sheet from the map's bottom edge down, so the controls and
     the panel read as the panel, and the map keeps the full-bleed ground. Two
     columns where 240px each fit (a 768px tablet), one on a phone. */
  .map-tools {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: var(--sp-2);
    align-items: start;
    padding: var(--sp-3) var(--sp-4) 0;
    background: var(--surface);
    border-top: 1px solid var(--line-strong);
  }

  /* Out of Leaflet's corners they lose the corner's margins but keep
     .leaflet-control's float and clear, which would shrink-wrap them in a
     grid cell. The fixed 252px and the viewport-height budgets in the desktop
     rules are both about sharing the map's left edge, which they no longer
     do: the boxes take their cell's width and their full height. */
  .map-tools .leaflet-control {
    float: none;
    clear: none;
    margin: 0;
  }

  /* Background restated, not inherited: it is the same --surface the bare
     rules give them, said here so the boxes visibly keep their own surface
     on the strip's (test_web_tokens' "no class means two things"). */
  .map-tools .map-key {
    width: auto;
    background: var(--surface);
    box-shadow: none;
  }

  .map-tools .map-key-body {
    max-height: none;
    overflow-y: visible;
  }

  #panel {
    position: static;
    width: auto;
    overflow-y: visible;
    padding: var(--sp-4);
    border-left: 0;
  }

  /* The hub sheet becomes a full-screen sheet. There is no rail to leave the
     map in: two thirds of 375px is a 248px sheet beside a 127px map, and the
     sheet's own grid wants 560px. Close (and Esc) bring the map back where it
     was. The page under it stops scrolling while it is up. `top: 0` against
     the published page's 45px for the same reason as `body #map`: fixed to
     the viewport, a 45px gap would show whatever the reader had scrolled
     to, not the top bar. */
  body #sd-dock {
    position: fixed;
    top: 0;
    left: 0;
    border-left: 0;
  }

  body.sd-open {
    overflow: hidden;
  }

  /* The bar is one nowrap row on a desktop and clips its overflow, which at
     375px clipped Close: the one control on it a reader must be able to
     reach. Here it wraps instead. The id outranks app.css's .drawer-bar, so
     the shared class is left alone for the pages that use it. */
  #sd-dock-bar {
    flex-wrap: wrap;
    height: auto;
    min-height: 30px;
    row-gap: var(--sp-1);
    padding: var(--sp-1) var(--sp-2);
    white-space: normal;
  }

  /* Hover cards only exist where there is a mouse (bindHoverCard skips them
     on a touch screen), which here means a narrow desktop window. The card is
     nowrap so its lines compare on one rhythm; on a narrow map a long
     operator name would run it off the edge, so it may wrap at the map's
     width less a gutter each side. `width: max-content` is what makes that
     a cap and not a squeeze: Leaflet's tooltip pane is a zero-width box, so
     a wrapping card left to shrink-to-fit collapses to its longest WORD
     (measured: a 76px card, one word per line). max-content keeps each line
     whole until the cap, and only then wraps. */
  .leaflet-tooltip.hover-card {
    white-space: normal;
    width: max-content;
    max-width: calc(100vw - 2 * var(--sp-4));
  }
}

/* ---------------------------------------------------------------------
   PADD net-flow labels (UI-01 WP4.1; map.js paddLabels). A divIcon in a
   pane with no pointer events, so the label never takes the PADD's click;
   the inner box centres itself on the label point because Leaflet owns the
   icon's own transform. The halo is the hub symbol's, so the type holds on
   either basemap.

   SMALL AND QUIET since 2026-10-28 (Thomas: "the padd regions take the main
   stage. That data is easy and not important"). The label was a name over a
   --t-title net-receipts figure in --ink, the biggest type on the map, over a
   "kb/d net in" line; HQ's UI-02 ruling took it to the NAME ONLY, in
   --t-label --ink-body. The figures stay in the PADD's click panel.

   THE TAG IS BACK (UI-04; Thomas, 2026-10-28: "Those images are missing the
   padd IV or whatever tags"): main's label shape, the name over a second
   line, but the second line is the region's short name ("Rockies"), not
   the figure. Both at --t-label, so the tag stays the quiet type it became.
   --------------------------------------------------------------------- */
.padd-label {
  background: none;
  border: 0;
}

.padd-label-in {
  position: absolute;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  white-space: nowrap;
  font-family: var(--font-ui);
  line-height: 1.2;
  color: var(--ink-body);
  text-shadow: 0 0 3px var(--map-halo), 0 0 3px var(--map-halo);
  pointer-events: none;
}

.padd-label-name {
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: var(--label-tracking);
  text-transform: uppercase;
  color: var(--ink-body);
}

.padd-label-sub {
  font-size: var(--t-label);
  color: var(--ink-body);
}

/* The detail panel's title row (index.html .panel-head, UI-04): the title,
   and the × that goes back one view (js/shell.js goBack). */
.panel-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-2);
}

/* The first country under the key's Supply & Demand title. */
.sd-nav-country:first-of-type {
  margin-top: var(--sp-2);
}
