/* The brand typeface (@font-face) and the colour / type tokens (:root) are in
   tokens.css, which every page links before this sheet. */

* { box-sizing: border-box; margin: 0; padding: 0; }

html { height: 100%; }
/* Column layout: #app (the map; until G7 the app bar over it) takes the whole
   height.
   #measurements (the dock) is fixed to the viewport's bottom and OVERLAYS the
   map (G1, Taylor 2026-10-20), as the phone sheet always has: dragging the
   dock changes nothing on the map. */
body {
  height: 100%; font-family: var(--font-ui); font-size: 14px; line-height: var(--lh-ui);
  display: flex; flex-direction: column;
  /* The dock body's height (S3-b): the old 180px dock less its ~32px header.
     Moved by the resize grip; the dock's own height is this plus its chrome. */
  --mp-body-h: 24px; /* one Summary row + its padding: the dock opens on its first line (Taylor, 2026-10-22); drag the tab for more */
}
/* #app stacks #panel (empty but for an init-error banner, since G7) over the
   map (applet-ui-cleanup frame slice: the left sidebar is gone). */
#app { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }

/* ---- #panel: the init-error banner's slot ----
   It was the app bar (desktop) and the gear menu until G7 (Taylor,
   2026-10-20/21), which folded the bar into the legend's head row
   (.page-head, below) and Advanced / Units into the legend's Display fold.
   It holds only hidden furniture now, so it is an empty block over the map
   until app.js prepends an init-error banner to it. */
#panel { flex: 0 0 auto; position: relative; z-index: 20; }
/* The banner itself (app.js _showInitErrorBanner): a strip across the top of
   the page, over the map's top edge (the map runs below it), in the warning
   tokens. First seen on screen 2026-10-21, forced with a 404 / 500 on a boot
   file: readable and clear of the legend on a desktop as it was; its colours
   moved here from inline literals. The phone places it above the sheet
   (mobile.css). */
.init-error {
  display: flex; align-items: center; gap: 10px; padding: 8px 12px;
  background: var(--warn-bg); border-bottom: 1px solid var(--warn-border);
  color: var(--warn-text); font-size: 13px; line-height: 1.35;
}
.init-error-msg { flex: 1 1 auto; min-width: 0; }
.init-error button {
  flex: none; padding: 3px 12px; font: inherit; font-weight: 600; cursor: pointer;
  color: var(--warn-text); background: var(--n-ff); border: 1px solid var(--warn-border); border-radius: 4px;
}

/* ---- The page head (G7; _page_head.html) ----
   One row: lockup (22px, links to ives.st) | rule | "Connectome" | the page
   menu (☰), at the head of the map page's legend card and of the
   Calibration page's head card. Sizes are D22 of shots-2026-10-20-g7 (Taylor,
   2026-10-21). The phone keeps the row and the sizes but for its 20px lockup
   (mobile.css). White-label: one ellipsized span replaces lockup, rule and
   title. */
.page-head {
  display: flex; align-items: center; gap: 7px; white-space: nowrap;
  margin: 0 0 7px; padding: 0 0 6px; border-bottom: 1px solid var(--n-dd);
}
.brand-lockup { display: flex; align-items: center; flex: 0 0 auto; }
.brand-lockup svg { display: block; height: 22px; width: auto; aspect-ratio: 1531 / 630; }
.brand-lockup:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: 2px; }
.brand-rule { flex: 0 0 1px; width: 1px; height: 16px; background: var(--brand-slate); opacity: .5; }
.page-head-title {
  font-size: 18px; font-weight: 700; color: var(--brand-ink); line-height: 1;
  overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
/* The wordmark (_connectome_wordmark.html) is sized from the font size so the
   phone's smaller size follows: height = 1460/2000 em is the viewBox's cap
   height + descent band, and the -30/2000 em drop puts its baseline on the
   text baseline. 18px desktop / 17px phone (Taylor, 2026-10-30, over 15px). */
.page-head-wordmark {
  display: inline-block; height: .73em; width: auto; aspect-ratio: 11746 / 1460;
  vertical-align: -.015em;
}
.page-head-title.white-label { font-size: 15px; line-height: 1.15; }
/* The page menu (page_menu.js): a 30x26 three-bar button (Taylor,
   2026-10-21), the same box on every page since every head card is the
   legend's 248px; the list is placed `position: fixed` under it by script,
   so the legend's own scroll cannot clip it. The 4px negative right margin
   lines the bars up with the card's padding edge rather than the button's. */
.page-menu { position: relative; margin-left: auto; flex: 0 0 auto; }
#page-menu-btn {
  display: flex; align-items: center; justify-content: center; position: relative;
  width: 30px; height: 26px; margin: 0 -4px 0 0; padding: 0;
  border: 0; border-radius: 5px; background: none; cursor: pointer;
  font-size: 13px; font-weight: 700; color: var(--brand-ink);
}
#page-menu-btn:hover, #page-menu-btn[aria-expanded="true"] { background: var(--sel-soft); }
#page-menu-btn:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
.page-menu-bars { display: block; width: 18px; height: 14px; fill: currentColor; }
#page-menu-list {
  display: flex; flex-direction: column; position: fixed; z-index: 20;
  min-width: 170px; padding: 4px 0;
  background: var(--ground-raised); border: 1px solid var(--n-cc); border-radius: 8px;
  box-shadow: 0 4px 14px var(--shade-15);
}
#page-menu-list[hidden], .page-menu-backdrop[hidden] { display: none; }
/* Catches the closing click so it does not reach the map (page_menu.js). */
.page-menu-backdrop { position: fixed; inset: 0; z-index: 19; }
#page-menu-list a {
  display: flex; align-items: center; justify-content: space-between;
  min-height: 32px; padding: 0 14px;
  font-size: 13px; color: var(--brand-ink); text-decoration: none;
}
#page-menu-list a:hover { background: var(--hover); }
#page-menu-list a.active { font-weight: 700; color: var(--brand-ink); box-shadow: inset 3px 0 0 var(--gold); }
#page-menu-list a.active::after { content: "\2713"; font-size: 12px; margin-left: 12px; color: var(--brand-ink); }

/* The Advanced switch, at the foot of the page menu on every page
   (geo-aggregation S1, sketch round 2): a label and a subline, a gold
   switch at the right. The input is the real checkbox (#chk-advanced,
   role=switch), drawn as the track; its ::before is the knob. */
#page-menu-list nav { display: flex; flex-direction: column; }
.page-menu-adv {
  display: flex; align-items: center; gap: 12px;
  margin-top: 4px; padding: 9px 14px; border-top: 1px solid var(--n-e8);
  font-size: 13px; color: var(--brand-ink); cursor: pointer;
}
.page-menu-adv-text { display: flex; flex-direction: column; flex: 1; line-height: 1.25; }
.page-menu-adv-sub { font-size: 11px; color: var(--text-muted); }
.page-menu-adv input {
  appearance: none; -webkit-appearance: none; position: relative; flex: none;
  width: 30px; height: 17px; margin: 0; border-radius: 9px; cursor: pointer;
  background: var(--n-cc); transition: background .15s;
}
.page-menu-adv input::before {
  content: ""; position: absolute; top: 2px; left: 2px; width: 13px; height: 13px;
  border-radius: 50%; background: var(--n-ff); box-shadow: 0 1px 2px var(--shade-16);
  transition: left .15s;
}
.page-menu-adv input:checked { background: var(--gold); }
.page-menu-adv input:checked::before { left: 15px; }
.page-menu-adv input:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
/* Advanced is a *user* gate (CSS class); the inline display:none data gates
   mean "this study cannot show this" and must still win when Advanced is on. */
body:not(.adv-on) .adv-only { display: none !important; }
/* Native controls (rung 4). A discrete on/off state is gold -- the browser
   paints a dark tick on it by itself, which is the brand's gold-shape-with-ink
   again -- while a continuous control's handle stays slate. Same reason the
   phone sheet's drag handle is slate on a parchment sheet: a grab affordance
   has to be legible, and gold on a light ground is 1.7:1. Left unstyled these
   render in the browser's own blue, which is the one blue R4 would otherwise
   miss: it is a default, not a declaration, so it survives a grep. */
/* Form controls do not inherit font-family — left alone they render in the UA's own
   form face, which is the same trap as rung 4's undeclared checkbox colour: a
   default carries no declaration, so no grep of this sheet finds it. Only the face
   is inherited; size and weight stay whatever each control's own rule set them to. */
button, input, select, textarea, optgroup { font-family: inherit; }

/* MapLibre's own sheet sets `font: 12px/20px Helvetica Neue, Arial, ...` on
   .maplibregl-map, and its attribution line, scale bar and popups inherit it —
   which is why those 16 elements survived every font change in this sheet. It is
   third-party CSS, but the attribution is chrome the reader sees inside our
   applet, so it takes the brand face. Only the family is overridden; MapLibre's
   own 12px/20px sizing is left alone. Delete this rule to revert. */
.maplibregl-map { font-family: var(--font-ui); }

/* Tabular figures wherever digits line up in a column or update in place (rung 3).
   Overpass's figures are PROPORTIONAL by default and its `1` is ~40% narrower than
   its other digits, so a column of numbers goes ragged and a value that re-renders
   on hover jitters sideways. Running text keeps proportional figures, where they
   read better. */
table,
#legend, .snap-tooltip, .tip,
.pn-val, .pn-dc-val, .dcp-band-val, .dcp-col-sum {
  font-variant-numeric: tabular-nums;
}

input[type="checkbox"], input[type="radio"] { accent-color: var(--sel); }
input[type="range"] { accent-color: var(--brand-slate); }

/* ---- Collapsible sidebar sections ---- */
.sidebar-section { border-bottom: 1px solid var(--n-dd); }
.section-header {
  display: flex; flex-direction: column;
  padding: 7px 12px; cursor: pointer;
  font-size: 12px; font-weight: 600; color: var(--brand-ink);
  user-select: none; background: var(--ground-band);
}
.section-header:hover { background: var(--ground-band-hover); }
.sec-label {
  padding: 7px 12px;
  font-size: 12px; font-weight: 600; color: var(--brand-ink);
  background: var(--ground-band);
  border-bottom: 1px solid var(--n-e0);
  display: flex; justify-content: space-between; align-items: center;
}
.sec-header-top {
  display: flex; justify-content: space-between; align-items: center;
}
.sec-arrow { font-size: 10px; color: var(--text-muted); transition: transform 0.15s; }
.sidebar-section.expanded .sec-arrow { transform: rotate(90deg); }
.section-body { display: none; padding: 8px 10px; }
.sidebar-section.expanded .section-body { display: block; }
.sec-summary { font-size: 12px; color: var(--brand-ink); font-weight: 500; margin-top: 3px; white-space: normal; line-height: 1.3; }
.sidebar-section.expanded .sec-summary { display: none; }

/* ---- Dropdowns ---- */
.ctrl-group { display: flex; flex-direction: column; gap: 3px; margin-bottom: 6px; }
.ctrl-group:last-child { margin-bottom: 0; }
.ctrl-group label { font-size: 11px; font-weight: 600; color: var(--text-muted); }
.ctrl-group select {
  width: 100%; padding: 4px 6px; border: 1px solid var(--n-cc);
  border-radius: 4px; background: var(--n-ff); font-size: 12px; cursor: pointer;
}
.ctrl-group select:focus { outline: 2px solid var(--focus); }
.ctrl-group select:disabled { background: var(--n-f0); color: var(--n-aa); cursor: not-allowed; }

/* ---- Tooltip-indicator icon ----
   Disabled <select>s and tightly-packed grid cells consume hover area and
   suppress the native title-attr tooltip from showing on their parents.
   `.tip` is a small `(?)` glyph placed next to a label that carries the
   title= itself: always discoverable, always hoverable, regardless of
   neighboring disabled controls. */
.tip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 13px; height: 13px;
  margin-left: 3px;
  border-radius: 50%;
  background: var(--nc-c2);
  color: var(--on-dark);
  font-size: 9px;
  font-weight: 700;
  cursor: help;
  vertical-align: middle;
  line-height: 1;
  user-select: none;
}
.tip:hover { background: var(--sel); color: var(--on-accent); }

/* ---- Traveler profile — dev-only since PLAN-dock-controls S4 (`?dev=1`
   sets html.dev, app.js): the dock's tree is the profile control and the
   dock's #param-profile chip its readout. The section survives, hidden, as
   the profile's second renderer for the sweeps. Deleted at S6. ---- */
#traveler-section {
  display: none;
  border-bottom: 1px solid var(--n-dd);
  padding: 7px 10px 8px;
  background: var(--ground-band);
}
html.dev #traveler-section { display: block; }
.traveler-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 6px;
}
.traveler-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.uc-toggle-strip {
  display: flex;
  border: 1px solid var(--n-cc);
  border-radius: 4px;
  overflow: hidden;
}
.uc-toggle-btn {
  padding: 3px 10px;
  border: none;
  background: var(--n-ff);
  font-size: 11px;
  cursor: pointer;
  color: var(--brand-ink);
  line-height: 1.4;
}
.uc-toggle-btn:hover { background: var(--hover); }
.uc-toggle-btn.active { background: var(--sel); color: var(--on-accent); font-weight: 600; }
/* PLAN-s37 S5 — the reset that replaced the All/Specific toggle. Shown only
   while the selection is narrowed (app.js `syncTravelerChrome`). */
.uc-reset-btn {
  padding: 3px 10px;
  border: 1px solid var(--n-cc);
  border-radius: 4px;
  background: var(--n-ff);
  font-size: 11px;
  cursor: pointer;
  color: var(--brand-ink);
  line-height: 1.4;
}
.uc-reset-btn:hover { background: var(--hover); border-color: var(--sel-border); color: var(--label-em); }
/* The subset's own status line: how many profiles it folds, and — while the
   slices are in flight — how many have landed and roughly what they cost. The
   honest alternative to a cap (PLAN-s37 S5, Taylor 2026-09-12). */
.uc-subset-note {
  font-size: 10.5px;
  line-height: 1.35;
  color: var(--text-muted);
  background: var(--ground-raised);
  border: 1px solid var(--n-dd);
  border-radius: 4px;
  padding: 4px 7px;
  margin-bottom: 6px;
}
.uc-subset-note.loading { color: var(--label-em); border-color: var(--gold-edge); background: var(--gold-wash); }
.uc-subset-note .uc-subset-bar {
  display: block;
  height: 3px;
  margin-top: 4px;
  border-radius: 2px;
  background: var(--hover-2);
  overflow: hidden;
}
.uc-subset-note .uc-subset-bar > i {
  display: block;
  height: 100%;
  background: var(--sel);
  transition: width 120ms linear;
}
.traveler-grid {
  display: grid;
  grid-template-columns: 1.5fr 1fr 1fr;
  gap: 8px;
}
.traveler-grid .ctrl-group { min-width: 0; }
.traveler-extras {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 6px;
}
.traveler-extras:empty,
.traveler-extras:not(:has(> *:not([style*="display:none"]))) {
  margin-top: 0;
}
#uc-specific-controls { display: none; }
#uc-specific-controls.visible { display: block; }

/* ---- Indicator list ----
   `.ind-group` was an accordion; SPEC-s22 S2 flattens the panel into one
   ordered column of buttons. The four `[data-group]` wrapper divs survive as
   plain grouping containers only — grpSel() and state.activeGroup resolution
   (app.js, compare.js, indicators.js) key off `.ind-group[data-group="X"]` —
   so they carry no border/background/radius and no open/closed state; every
   row in every group is always rendered. `.ind-group-body` also survives as a
   nesting level but is always visible now. */
.ind-group { margin-bottom: 10px; }
.ind-group-body { padding: 0; }
/* The Indicators section is dev-only since PLAN-dock-controls S3 (`?dev=1`
   sets html.dev, app.js): the dock's picker and parameter strip are the
   reader's controls, and the section survives, hidden, as the registry's
   second renderer for the sweeps. Deleted at S6. */
#sec-indicators { display: none; }
html.dev #sec-indicators { display: block; }

.ind-btn {
  display: block; width: 100%; text-align: left;
  padding: 4px 7px; margin-bottom: 2px;
  border: 1px solid var(--n-dd); border-radius: 3px;
  background: var(--n-ff); cursor: pointer; font-size: 11px; color: var(--brand-ink);
}
.ind-btn:last-child { margin-bottom: 0; }
.ind-btn:hover { background: var(--hover); }
.ind-btn.active {
  background: var(--sel); color: var(--on-accent); border-color: var(--sel-border); font-weight: 600;
}

/* ---- Normalization strips (per km² / total / per person / %) ---- */
/* Compact pill row. width:fit-content + margin-left:auto keeps it small
   without needing a float (the legend's units and colour-scale strips still
   right-align this way; in the dock's parameter strip the margin is zeroed).
   SPEC-s22 §5.3 moved these out of the indicator groups: they are not one
   fixed toggle per group but elements whose buttons renderNormStrip()
   rebuilds for whichever indicator is mapped — in #mp-params since S3. */
.norm-toggle {
  display: flex;
  border: 1px solid var(--n-cc);
  border-radius: 3px;
  overflow: hidden;
  margin: 0 0 3px auto;
  width: fit-content;
}
.norm-btn {
  padding: 0 8px;
  border: none;
  background: var(--n-ff);
  font-size: 10px;
  color: var(--text-muted);
  cursor: pointer;
  line-height: 1.6;
}
.norm-btn + .norm-btn { border-left: 1px solid var(--n-dd); }
.norm-btn:hover { background: var(--hover); }
.norm-btn.active { background: var(--sel); color: var(--on-accent); font-weight: 600; }
/* A strip that stands down (Show under the per-trip benefit, part B): greyed,
   the remembered pick still marked. */
.norm-toggle--idle .norm-btn:disabled { background: var(--n-f4); color: var(--n-bb); cursor: not-allowed; }
.norm-toggle--idle .norm-btn.active:disabled { background: var(--n-dd); color: var(--text-muted); }


/* ---- Outbound indicator buttons ---- */
.od-btn {
  display: block; width: 100%; text-align: center;
  padding: 3px 1px; border: 1px solid var(--n-dd); border-radius: 3px;
  background: var(--n-ff); cursor: pointer; font-size: 11px; color: var(--text-muted);
  transition: background 0.1s;
}
.od-btn:hover:not(:disabled) { background: var(--hover); color: var(--brand-ink); }
.od-btn.active { background: var(--sel); color: var(--on-accent); border-color: var(--sel-border); }
.od-btn:disabled { background: var(--n-f4); color: var(--n-cc); cursor: not-allowed; border-color: var(--n-ee); }

/* ---- Composite indicator chips (Mode choice / Destination choice) ----
   Indicators with no live per-mode column read as text buttons in the
   .ind-btn idiom. They keep .od-btn so every selection/disable path still
   finds them; these rules only undo .od-btn's centred glyph and muted
   colour. The .active and
   :disabled rules are the same in both classes, so they need no override. */
.ind-row { margin-bottom: 4px; }
.ind-row:last-child { margin-bottom: 0; }
.od-chip {
  text-align: left; color: var(--brand-ink); padding: 4px 7px; margin-bottom: 2px;
}
.od-chip:last-child { margin-bottom: 0; }

/* ---- Mode-family row labels ----
   `.imp-row-label` is the static row (`#row-msplit`, `#row-vm`) that stays in
   the flat list for a mode-chip family; the mode itself is a parameter in the
   dock (app.js buildImpedanceButtons / syncModeChip). The old `#row-gtm`
   (Generalized impedance by mode) and the `.imp-btns` / `.imp-btn` chips were
   cut in W2, 2026-10-22. */
.imp-row-label {
  font-size: 10px; color: var(--brand-ink); padding: 2px 3px 3px 0;
  display: flex; align-items: center; line-height: 1.2;
  cursor: pointer; border-radius: 3px;
}
/* Clickable (app.js wires a click handler): the row IS the family — mode is
   a parameter in the dock since PLAN-trips-by-mode S(a), so this label is the
   whole of the family's presence on the panel, and it is what carries
   `.active` when one of the family's indicators is mapped (R9). */
.imp-row-label:hover { background: var(--hover); }
/* No padding change with `.active`: the label's own padding is asymmetric
   (nothing on the left, so it lines up with the row text above it), and
   adding any here would shift the words sideways as the selection moves. */
.imp-row-label.active {
  background: var(--sel); color: var(--on-accent); font-weight: 600;
}

/* ---- Layer toggles ---- */
.layer-toggle-row {
  display: flex; align-items: center; gap: 6px;
  font-size: 11px; cursor: pointer; padding: 2px 0;
}
.layer-toggle-row input[type="checkbox"] { cursor: pointer; flex-shrink: 0; }
.layer-swatch {
  display: inline-block; width: 20px; height: 4px;
  border-radius: 2px; flex-shrink: 0;
}
.transit-freq-legend {
  margin: 2px 0 2px 22px; display: flex; flex-direction: column; gap: 2px;
}
.transit-freq-row {
  display: flex; align-items: center; gap: 5px; font-size: 10px; color: var(--text-muted);
}
.transit-freq-swatch {
  display: inline-block; width: 14px; height: 3px; border-radius: 1px; flex-shrink: 0;
}

/* ---- Map area ---- */
/* The map runs from the top of the window (the app bar's foot until G7) to the
   viewport's bottom on both layouts; the
   dock lies over its foot (G1, Taylor 2026-10-20). Before that #map shrank
   with the dock, and since MapLibre keeps the camera's centre at the canvas
   middle and its distance in proportion to the canvas height, every dock
   drag slid the map and steepened the 3D perspective. Now the canvas never
   resizes on a drag. What the dock covers is published as --sheet-h on
   #map-area (scenario_picker.js publishSheetH, a ResizeObserver on the dock,
   both layouts), and three things follow it: MapLibre's bottom corners (the
   attribution), the OD hint and the Flip frame. The camera takes the dock's
   height at boot as a transform padding instead (basemap.js openingCamera
   `dockPadding`), so fits and pitch changes centre on the uncovered map. */
#map-area { flex: 1; min-height: 0; display: flex; flex-direction: column; position: relative; }
#map { flex: 1; }
#map .maplibregl-ctrl-bottom-left, #map .maplibregl-ctrl-bottom-right { bottom: var(--sheet-h, 0px); }

/* Loading overlay. Two jobs in one element:
   BOOT -- it ships in the markup already visible (no `hidden`), fixed to the
   viewport, so it is on screen at first paint rather than at map `load`. The
   sidebar behind it is chrome with empty dropdowns and a "Loading..." snapshot
   at that point, so the boot variant is OPAQUE ground; app.js drops `.boot`
   once init settles and the element reverts to the veil.
   IN FLIGHT -- the same element over the loaded app while a scenario's
   lazily-fetched data is on the wire (showSpinner/hideSpinner, ref-counted).
   Fixed rather than absolute-inside-#map-area since session 51: at boot there
   is no populated map area to cover, and covering the half-built sidebar is
   the point. */
#loading-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  flex-direction: column;
  gap: 20px;
  align-items: center;
  justify-content: center;
  background: var(--veil-45);
}
#loading-overlay.boot { background: var(--ground); }
#loading-overlay[hidden] { display: none; }
/* The same mark and veil for a wait that leaves the old figures up (map.js
   markBusy). It takes no clicks: the controls stay live. Gives way to the
   overlay, which sits immediately before it in the markup. Centred by margin,
   not transform: the spin animation owns the mark's transform. */
#busy-mark {
  position: fixed;
  inset: 0;
  z-index: 950;
  background: var(--veil-45);
  pointer-events: none;
}
#busy-mark .spinner { position: absolute; margin: -36px 0 0 -36px; }
#busy-mark[hidden], #loading-overlay:not([hidden]) + #busy-mark { display: none; }
/* The Ives Street badge mark, spinning. Inlined as a data URI rather than
   fetched: an asset whose whole job is to be on screen before anything else
   must not itself be a request the reader waits on. 288px covers 4x DPI at the
   72px display size; the un-encoded source sits beside this sheet as
   ives-badge.png (source only -- the build does not copy it). Re-encode with
   `base64 -w0 ives-badge.png` if the mark ever changes.
   3.2s, four times the old ring's 0.8s (Taylor, 2026-09-14): a wordless ring
   reads as "busy" at any speed, but a logo has a right way up, and the slower
   it turns the more it reads as a mark rotating rather than a part spinning. */
#loading-overlay .spinner, #busy-mark .spinner {
  width: 72px;
  height: 72px;
  background: url("data:image/png;base64,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") center / contain no-repeat;
  animation: spin 3.2s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* Readers who ask for less motion get the mark, held still. The overlay is
   still the wait signal -- it is present or it is not. */
/* The download bar under the mark (app.js _renderProgress). Hidden until the
   wait has a known size; it fills to 95 % on bytes and the overlay's own
   hiding is the last step. Over the veil (a scenario switch) it sits on the
   dimmed map, so the track is opaque ground, not a tint. */
#loading-progress {
  width: 160px;
  height: 4px;
  border-radius: 2px;
  background: var(--n-dd);
  overflow: hidden;
}
#loading-progress[hidden] { display: none; }
#loading-progress > div {
  width: 0;
  height: 100%;
  background: var(--gold-deep);
  transition: width 0.2s linear;
}
@media (prefers-reduced-motion: reduce) {
  #loading-overlay .spinner, #busy-mark .spinner { animation: none; }
  #loading-progress > div { transition: none; }
}

/* ---- Measurements panel (bottom-docked, full width, fixed height) ----
   Replaces the floating #mp-body. The panel is always present; when no
   origin is pinned it shows #mp-placeholder instead of #mp-body.

   #mp-body uses CSS multi-column so the migrated hover-panel blocks fill the
   width instead of running off the bottom; overflow spills into further
   columns and is reachable by scrolling horizontally. The mode-choice panel
   opts out of the column flow (info-panel--mc) and scrolls vertically instead:
   at the panel's 180px it is a table plus one side block, and a column flow
   would push the side block off the right edge rather than let it scroll.
   The original brief said no scrolling at all; the dock is now firmly 180px,
   which makes a scrollbar the honest alternative to silent clipping. */
#measurements {
  /* Sized from the inside since PLAN-dock-controls S3 (ruling S3-b): the
     scroller under the header has the height (--mp-body-h, the old 180px
     dock less its header) and the header adds its own. --mp-body-h is what
     the resize grip moves (dock_resize.js, inline on <body>). The
     parameter strip sits in the header bar on desktop (R25, 2026-09-24),
     so it costs nothing until the header runs out of width and wraps to a
     second line -- and then that line costs map, not results (S3-b). */
  /* Fixed over the map's foot (G1): see "Map area". z 8, the phone sheet's:
     over MapLibre's controls (2) and the Flip frame (1), under the legend
     (900), the OD hint (950) and the menus (960). */
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 8;
  background: var(--ground-raised); border-top: 1px solid var(--n-cc);
  display: flex; flex-direction: column;
  font-size: 12px; color: var(--brand-ink);
}
/* The resize grip: a 7px band straddling the dock's top edge (desktop only —
   the phone stylesheet hides it, the sheet's drag row is the phone's), with a
   tab standing up from the top-middle (Taylor, 2026-10-22: "a little tab
   sticking up ... with a horizontal line like on mobile"). The tab (::before)
   is the dock's own ground on the dock's own edge colour, and carries the
   phone's gold pill (::after, .sheet-grip's 38×4) -- a pseudo-element is part
   of its element's hit area, so the tab is draggable without widening the
   7px band over the map. Lit on hover and while dragging. */
#mp-resize {
  position: absolute; left: 0; right: 0; top: -4px; height: 7px;
  cursor: ns-resize; z-index: 3; touch-action: none;
}
#mp-resize::before {
  content: ""; position: absolute; left: 50%; bottom: 3px; transform: translateX(-50%);
  width: 64px; height: 16px; box-sizing: border-box;
  border: 1px solid var(--n-cc); border-bottom: none; border-radius: 8px 8px 0 0;
  background: var(--ground-raised);
}
#mp-resize::after {
  content: ""; position: absolute; left: 50%; bottom: 9px; transform: translateX(-50%);
  width: 38px; height: 4px; border-radius: 2px; background: var(--gold-deep);
}
#mp-resize:hover, #mp-resize.dragging { background: var(--sel-border); opacity: 0.6; }
#mp-resize:hover::before, #mp-resize.dragging::before { border-color: var(--sel-border); }
body.mp-resizing { cursor: ns-resize; user-select: none; }

/* ---- The summary bar (desktop rendering) ----
   The sentence bar ("D · Sentence", Taylor 2026-10-19; subject first since
   G6, 2026-10-20): line 1 (subject, verb + indicator, value) · line 2
   (scenario) · below-the-fold (traveller class, filters) read left to right
   as one sentence -- "This zone has a population density ▾ of 9,230
   people/mi² under existing conditions ▾ for all travellers ▾". Every
   clause is its own flex item of #summary-bar (the containers between are
   `display: contents`), so the row wraps only BETWEEN clauses, never inside
   a chip or between a connective and its chip. The connective before each choice
   (.sb-conn, sentence.js) is Libre Caslon italic, the brand tagline's "for
   ‹word›" face; the choices are Overpass 600 in ink. No group rules: the
   connectives do the segmenting. The indicator name alone carries the gold
   underline (the lockup's device); every other choice has a thin slate one
   at rest that turns gold on hover / focus. The underlines are inset
   box-shadows on the TEXT spans, so they hug the word, never shift layout,
   and survive the spans' ellipsis clipping. The row wraps when it runs out
   of width. The phone stylesheet makes #summary-bar `display: contents` and
   lays its parts out as the sheet. */
#summary-bar {
  flex: 0 0 auto; display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px var(--sb-clause-gap);
  padding: 7px 16px 8px; background: var(--ground); border-bottom: 1px solid var(--n-e3);
  min-height: 52px; box-sizing: border-box;
}
#summary-bar .sheet-handle, #sb-flow { display: contents; }
/* The sentence's spacing (Taylor, 2026-10-20: "reads as one sentence"): a
   connective sits a word space from the choice it introduces
   (--sb-conn-gap), and one clause a little more than that from the next
   (--sb-clause-gap) -- 14px between clauses had read as separate
   controls. On a phone the 44px targets keep their size; only the space
   between them shrinks. */
:root { --sb-conn-gap: 4px; --sb-clause-gap: 9px; }
#summary-bar .sheet-grip { display: none; }
/* The connective words. 15px Caslon reads at about Overpass 14's x-height;
   brand slate is 7.1:1 on parchment. Never the st ligature ("destination"),
   which the italic carries in `liga` (brand reference § Type). */
#summary-bar .sb-conn {
  flex: none; font-family: var(--font-conn); font-style: italic; font-weight: 400;
  font-size: 15px; letter-spacing: 0; text-transform: none; color: var(--brand-slate);
  font-variant-ligatures: no-common-ligatures; white-space: nowrap;
}
#summary-bar .sb-conn:empty { display: none; }
/* The note line under the sentence (sentence.js NOTES; Taylor, 2026-10-22):
   its own full-width line of the flow, smaller and italic in the connective
   face, so it reads as an aside and never as a choice. */
#summary-bar .sb-note {
  flex: 1 0 100%; margin: -2px 0 0; font-family: var(--font-conn); font-style: italic;
  font-weight: 400; font-size: 13px; line-height: 1.3; color: var(--brand-ink);
  font-variant-ligatures: no-common-ligatures;
}
#summary-bar .sb-note[hidden] { display: none; }
/* The choices' underline: thin slate at rest, gold mass on hover / focus /
   while its menu is open. The indicator's is always gold (below). */
#summary-bar .scn-chip-text .scn-name, #profile-chip-text, .param-chip-text {
  box-shadow: inset 0 -1px 0 var(--brand-slate);
}
#summary-bar .scn-chip-btn:is(:hover, :focus-visible, [aria-expanded="true"]) .scn-chip-text .scn-name,
#profile-chip-btn:is(:hover, :focus-visible, [aria-expanded="true"]) #profile-chip-text,
.param-chip-btn:is(:hover, :focus-visible, [aria-expanded="true"]) .param-chip-text {
  box-shadow: inset 0 -3px 0 var(--gold);
}
/* A chip's value is a word in the sentence: "for all travellers", "by car".
   The scenario keeps its capital -- it is a name. */
#profile-chip-text::first-letter, .param-chip-text::first-letter { text-transform: lowercase; }
/* Line 1: its clauses are the bar's own flex items (see above). The verb
   phrase and the value sit a word space from what precedes them, not a
   clause gap (a negative margin against the bar's column gap). */
#mobile-summary { display: contents; }
/* After a scenario switch, until the full file lands: the line is one
   placeholder in the value slot (summary.js, Taylor 2026-10-24). */
#mobile-summary.ms-lagging > :not(#ms-val) { display: none; }
#ms-pred {
  display: flex; align-items: baseline; gap: var(--sb-conn-gap); flex: 0 1 auto; min-width: 0;
}
/* The scenario is the sentence's last clause on every row since 2026-10-22
   (a DOM move: #summary-line2 sits after #mp-header; a phone pulls it first
   again with a CSS order, scenario_results.mobile.css), so the access rows'
   "travellers-first" order (summary.js data-sb-order) needs no CSS of its own
   any more. (The benefit's figure-first order is a DOM move in summary.js, so
   it reads in order too.) */
#ms-ind {
  display: flex; align-items: baseline; flex: 0 1 auto; min-width: 0;
  margin: 0; padding: 0; border: 0;
  background: none; font: inherit; color: inherit; text-align: left; cursor: pointer;
}
#ms-ind-text {
  flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 17px; font-weight: 700; color: var(--brand-ink);
  box-shadow: inset 0 -3px 0 var(--gold);
}
#ms-ind::after {
  content: "\25BE"; flex: none; margin-left: 5px;
  font-size: 13px; line-height: 1; color: var(--brand-slate);
}
#ms-ind[aria-expanded="true"]::after { content: "\25B4"; }
#ms-ind:hover #ms-ind-text { color: var(--label-em); }
#ms-ind:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: 3px; }
/* The value: a figure and its unit (summary.js valueSpans), the figure the
   one large item in the bar. A value with no figure ("at class average")
   is set at the name's size, not the number's. */
#ms-val {
  flex: none; display: flex; align-items: baseline; gap: 5px;
  margin-left: calc(var(--sb-conn-gap) - var(--sb-clause-gap));
  color: var(--brand-ink); white-space: nowrap; font-variant-numeric: tabular-nums;
}
#ms-val .ms-fig { font-size: 24px; line-height: 1; font-weight: 700; color: var(--brand-ink); }
#ms-val .ms-fig--word { font-size: 17px; }
#ms-val .ms-unit { font-size: 15px; font-weight: 600; color: var(--brand-ink); }
#ms-val .ms-num { display: flex; align-items: baseline; gap: 5px; }
/* Line 1 as the compare benefit (summary.js renderBenefit, 2026-10-20):
   "13k person-h/yr | 5.1 min-eq/person/yr lost" in the benefit line's colours --
   text, so the darker benefit green (--pos-text, 5.3:1 on parchment), not the
   map's data green (2.6:1); --neg for lost; ink for no change. */
#ms-val.ms-val--pos .ms-fig, #ms-val.ms-val--pos .ms-unit { color: var(--pos-text); }
#ms-val.ms-val--neg .ms-fig, #ms-val.ms-val--neg .ms-unit { color: var(--neg); }
#ms-val .ms-sep { color: var(--n-aa); font-weight: 400; }
/* The benefit's figure groups (summary.js benefitGroups): the bar's own flex
   items, a figure's gap (5px) apart. The NEGATIVE RIGHT margin, not a left
   one, closes the clause gap: at the end of a line it falls in the ragged
   edge, so a group that wraps starts flush with the line above. */
#ms-val.ms-benefit { display: contents; }
#ms-val .ms-group {
  flex: none; display: flex; align-items: baseline; gap: 5px; white-space: nowrap;
  margin-right: calc(5px - var(--sb-clause-gap));
}
#ms-val .ms-group:last-child { margin-right: calc(var(--sb-conn-gap) - var(--sb-clause-gap)); }
/* Caslon italic's f overhangs ("of 4.73k"): its italic correction. */
#summary-bar .sb-conn--f { padding-right: 0.18em; }
#ms-val[title] { cursor: help; }
#ms-val:empty { display: none; }
/* summary.js tags the region figure "region" on a phone only; on a desktop
   the subject says it. */
#ms-val .ms-region, #ms-val .ms-side { display: none; }
/* Compare Outcome's second clause (G6 (c)), in line 2 after the scenario
   chip: ", up 0.4 from existing conditions", its figure a step below the
   main one, the comma tucked against the chip. */
#ms-delta {
  flex: none; display: flex; align-items: baseline; gap: var(--sb-conn-gap); white-space: nowrap;
  margin-left: -2px;
  font-size: 15px; color: var(--brand-ink); font-variant-numeric: tabular-nums;
}
#ms-delta[hidden] { display: none; }
#ms-delta .ms-fig { font-size: 17px; font-weight: 700; color: var(--brand-ink); }
#ms-delta .ms-unit { font-size: 15px; font-weight: 600; color: var(--brand-ink); }
#ms-delta .ms-from { font-weight: 600; }
/* The subject: the sentence's first words, in the connective face. What
   follows it (#ms-pred, or the benefit's first group) sits a word space away:
   a negative right margin here closes the clause gap (2026-10-28; a negative
   left margin on the next item had pulled a wrapped line 5px left). */
#sb-subject {
  display: flex; align-items: baseline; gap: var(--sb-conn-gap); flex: none; white-space: nowrap;
  margin-right: calc(var(--sb-conn-gap) - var(--sb-clause-gap));
}
#sb-subject-text:empty { display: none; }
/* A hover is a preview, not a choice: the subject a step quieter (still
   5.2:1 on parchment). */
#sb-subject.sb-hovered #sb-subject-text { color: var(--text-muted); }
/* A pinned subject explains on hover how to change or drop it (#subject-tip,
   summary.js): a dotted underline says there is something to read. */
#sb-subject[data-tip] #sb-subject-text {
  cursor: help; text-decoration: underline dotted var(--n-aa); text-underline-offset: 3px;
}
#subject-tip {
  position: fixed; z-index: 960; max-width: 340px; padding: 5px 10px; border-radius: 6px;
  background: var(--n-22); color: var(--n-ff); font-size: 12px; line-height: 1.35;
  box-shadow: 0 2px 8px var(--shade-15); pointer-events: none;
}
#subject-tip[hidden] { display: none; }
/* Below the fold: the traveller class and the mapped row's filters, each a
   clause of the bar's own row. */
#mp-header { display: contents; }
#mp-params:not([hidden]) { display: contents; }
/* "by the most likely mode (car)": the figure's mode after the top option. */
.param-chip-which { flex: none; font-weight: 600; }
.param-chip-which:empty { display: none; }

/* ---- The indicator menu (indicator_picker.js) ---- */
/* One listbox for both layouts, opened from #ms-ind (phone) or #mp-ind
   (desktop) and positioned by the script against whichever opened it --
   upward by preference, capped at 60% of the viewport so the map stays in
   view (a 26-row Advanced list scrolls inside). Above the sheets. Section
   headings are the registry's; the active row is ticked; a greyed row keeps
   its place with its reason under the label; a ⓘ opens the row's gloss. */
#ind-menu, #profile-menu, #scenario-menu, #param-menu {
  position: fixed; left: 8px; right: auto; bottom: auto; width: 400px;
  max-height: 60vh; max-height: 60dvh; overflow-y: auto; overscroll-behavior: contain;
  /* 960: over the desktop legend (900), which a menu opened from the summary
     bar can reach; under the loading overlay (1000). */
  z-index: 960; padding: 4px 0; background: var(--ground-raised); border: 1px solid var(--n-cc);
  border-radius: 10px; box-shadow: 0 6px 24px var(--shade-22); outline: none;
}
#ind-menu[hidden], #profile-menu[hidden], #scenario-menu[hidden], #param-menu[hidden] { display: none; }
.msm-head {
  padding: 8px 14px 3px; font-size: 11px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--text-muted);
}
.msm-head + .msm-head, .msm-opt + .msm-head { border-top: 1px solid var(--n-ee); margin-top: 4px; }
/* A bare row (no heading, R10) gets the same hairline a heading does, so it
   stands apart from the section above instead of reading as its child (R22).
   Not on the menu's first row: there is nothing above it to separate. */
.msm-opt + .msm-opt--bare, .msm-note + .msm-opt--bare { border-top: 1px solid var(--n-ee); margin-top: 4px; }
/* A section greyed wholesale, its reason under the heading. */
.msm-note { padding: 0 14px 4px 34px; font-size: 12px; line-height: 16px; color: var(--text-muted); }
.msm-opt {
  display: flex; align-items: flex-start; gap: 8px; min-height: 40px;
  padding: 6px 10px 6px 34px; font-size: 14px; color: var(--brand-ink); cursor: pointer; position: relative;
}
.msm-opt[aria-selected="true"] { font-weight: 600; color: var(--label-em); }
.msm-opt[aria-selected="true"]::before {
  content: "\2713"; position: absolute; left: 14px; color: var(--label-em); font-weight: 700;
}
.msm-opt.msm-cur, .msm-opt:active { background: var(--sel-soft); }
.msm-swatch { flex: none; width: 12px; height: 12px; border-radius: 3px; border: 1px solid var(--shade-15);
              margin-top: 5px; }
.msm-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.msm-label { line-height: 22px; }
/* The row names are the sentence's words, lower case (G6); a list
   capitalises them. */
#ind-menu .msm-opt .msm-label::first-letter { text-transform: uppercase; }
/* A greyed row: on offer, not selectable now, and it says why. The reason
   is the tooltip the panel shows on hover, printed -- a phone has no hover. */
.msm-opt.msm-off { cursor: default; }
.msm-opt.msm-off .msm-label { color: var(--text-muted); }
/* Unchanged across the compared pair (indicator_picker.js SAME_TEXT): the
   disabled rows' grey label, but a normal cursor -- it is still a pick. */
.msm-opt.msm-same .msm-label { color: var(--text-muted); }
.msm-why, .msm-gloss, .sm-note { font-size: 12px; line-height: 16px; font-weight: 400; color: var(--text-muted); }
.msm-gloss { color: var(--text-muted); padding: 2px 0 4px; }
.msm-gloss[hidden] { display: none; }
/* The ⓘ: a 24px target at the row's right edge, tab-skipped (the listbox
   owns focus; Right / Left open and close the current row's gloss). */
.msm-info {
  flex: none; width: 24px; height: 22px; margin: 0; padding: 0;
  border: 1px solid var(--n-cc); border-radius: 50%; background: var(--n-ff);
  font: 700 11px/1 Georgia, serif; font-style: italic; color: var(--text-muted); cursor: pointer;
}
.msm-info:hover, .msm-info[aria-expanded="true"] { background: var(--sel-soft); border-color: var(--sel-border); color: var(--label-em); }
/* The indicator menu's ⓘ (I1, review alpha-planner 2026-10-23): the button
   is a 32 px square hit box -- 44 px on a touch screen -- and the 22 px circle
   is drawn by its inner span, so the circle does not grow and neither does the
   row: negative margins fold the box into the row's padding, never past the
   row's edge (a click there would land in the next row's box). Clicking
   anywhere in the box opens the gloss and never picks the row
   (indicator_picker.js). The scenario menu's ⓘ (.sm-info) keeps the rule
   above. */
#ind-menu .msm-info {
  display: flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; margin: -5px -6px -5px 0;
  border: 0; border-radius: 6px; background: transparent;
}
#ind-menu .msm-info-dot {
  display: flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border: 1px solid var(--n-cc); border-radius: 50%; background: var(--n-ff);
  font: 700 11px/1 Georgia, serif; font-style: italic; color: var(--text-muted); pointer-events: none;
}
#ind-menu .msm-info:hover, #ind-menu .msm-info[aria-expanded="true"] { background: transparent; }
/* A press on the ⓘ must not flash its row's :active fill, which reads as the
   row being picked. */
#ind-menu .msm-opt:not(.msm-cur):has(.msm-info:active) { background: transparent; }
#ind-menu .msm-info:hover .msm-info-dot, #ind-menu .msm-info[aria-expanded="true"] .msm-info-dot {
  background: var(--sel-soft); border-color: var(--sel-border); color: var(--label-em);
}
@media (pointer: coarse) {
  /* A finger-height row (44 px) so the 44 px box fits inside it; the extra
     4 px sit under the label, which keeps its 22 px line centred on the dot. */
  #ind-menu .msm-opt { min-height: 44px; padding-top: 10px; padding-bottom: 12px; }
  #ind-menu .msm-info { width: 44px; height: 44px; margin: -10px -10px -12px 0; }
}
/* The scroll cue (I2): a fade at an edge where content is clipped, while
   indicator_picker.js syncScrollCue holds `msm-more-above` / `-below`. Sticky
   zero-height pseudo-elements, so the fade stays on the menu's edge as its
   content scrolls and paints over the rows (a background gradient would sit
   under the active row's fill). Not a target: clicks pass through. */
#ind-menu::before, #ind-menu::after {
  content: ""; display: block; position: sticky; z-index: 2; height: 22px;
  pointer-events: none; opacity: 0; transition: opacity .12s;
}
#ind-menu::before {
  top: 0; margin-bottom: -22px;
  background: linear-gradient(var(--ground-raised), transparent);
  box-shadow: inset 0 1px 0 var(--n-cc);
}
#ind-menu::after {
  bottom: 0; margin-top: -22px;
  background: linear-gradient(transparent, var(--ground-raised));
  box-shadow: inset 0 -1px 0 var(--n-cc);
}
#ind-menu.msm-more-above::before, #ind-menu.msm-more-below::after { opacity: 1; }
/* The profile menu (2026-09-21): #ind-menu's box, holding one block per
   modelled dimension -- a heading with its ⓘ, the gloss beneath when opened,
   and a wrapping row of option pills (role=radio buttons). The checked pill
   is filled; a pill no class would match is dimmed and inert. The footer is
   the live count, or the no-match sentence in the warning colour. */
#profile-menu { width: 420px; padding: 4px 0 0; }
/* The selection chips' menu (param_menu.js): #ind-menu's rows. A row under
   another -- a category under its purpose, a household option under Car --
   is indented one step; a pickable heading (a purpose group's "All <group>"
   mixture) keeps the heading's type but takes the row's tick and hover. */
#param-menu { width: 360px; }
.msm-opt--sub { padding-left: 52px; min-height: 34px; }
.msm-opt--sub[aria-selected="true"]::before { left: 34px; }
/* In a menu whose rows carry swatches the labels start 20 px further in, so
   a sub-row indents from there or it reads as outdented (the household rows
   under Car). */
#param-menu.pmn-swatched .msm-opt--sub { padding-left: 74px; }
#param-menu.pmn-swatched .msm-opt--sub[aria-selected="true"]::before { left: 56px; }
.msm-opt--head {
  min-height: 0; padding-top: 8px; padding-bottom: 3px; font-size: 11px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted);
}
.msm-opt--head .msm-label { line-height: 18px; }
.msm-opt--head[aria-selected="true"] { color: var(--label-em); }
.msm-opt + .msm-opt--head, .msm-head + .msm-opt--head { border-top: 1px solid var(--n-ee); margin-top: 4px; }
.msm-opt--head + .msm-head { border-top: 1px solid var(--n-ee); margin-top: 4px; }
.pm-dim[hidden] { display: none; }
.pm-dim + .pm-dim { border-top: 1px solid var(--n-ee); margin-top: 6px; }
.pm-head {
  display: flex; align-items: center; gap: 8px; padding: 8px 14px 4px;
  font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted);
}
.pm-head .pm-info { margin-left: auto; }
.pm-gloss { padding: 0 14px 6px; }
.pm-opts { display: flex; flex-wrap: wrap; gap: 6px; padding: 2px 14px 6px; }
/* The pill. The scenario menu shared these selectors while its options were
   pills too; since S5's second round (§0i: the matrix) it draws radios in a
   grid instead and the pill is the profile menu's own again. */
.pm-opt {
  flex: none; min-height: 32px; margin: 0; padding: 4px 12px;
  border: 1px solid var(--n-cc); border-radius: 999px; background: var(--n-ff);
  font: inherit; font-size: 13px; line-height: 20px; color: var(--brand-ink); cursor: pointer;
}
.pm-opt:hover { border-color: var(--sel-border); background: var(--sel-soft); }
.pm-opt[aria-checked="true"] {
  font-weight: 600; color: var(--label-em); background: var(--sel-soft); border-color: var(--sel-border);
}
.pm-opt[aria-disabled="true"] { color: var(--text-muted); border-style: dashed; background: none; cursor: default; }
.pm-opt:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
/* Sticky: the menu scrolls inside its 60 vh cap and the footer -- the count
   and Done -- must stay in view whatever the row above is doing. */
.pm-footer {
  position: sticky; bottom: 0; z-index: 1; background: var(--ground-raised);
  display: flex; align-items: center; gap: 10px; padding: 8px 14px 8px;
  border-top: 1px solid var(--n-ee); margin-top: 6px;
}
#pm-foot {
  flex: 1 1 auto; min-width: 0; font-size: 12px; line-height: 16px; color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
#pm-foot.pm-foot--none { color: var(--warn-text); }
/* Done closes; the choices are live already. Filled like a checked pill so it
   reads as the affirmative end of the menu. */
#pm-done {
  flex: none; margin: 0; padding: 4px 14px; min-height: 32px;
  border: 1px solid var(--sel-border); border-radius: 999px; background: var(--sel-soft);
  font: inherit; font-size: 13px; font-weight: 600; line-height: 20px; color: var(--label-em); cursor: pointer;
}
#pm-done:hover { background: var(--sel); color: var(--n-ff); }
#pm-done:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
/* The profile chip (S4-c; home settled 2026-09-20): one line after the
   indicator name -- who the map is about, as subsetLabel spells it, and a
   bare ✕ while narrowed. Ellipsis rather than wrap (the full label is the
   title). A subset in flight underlines the text with a progress bar; a
   combination no class matches is the label in the warning colour. Since
   2026-09-21 the label is a button with a ▾ (the #mp-ind convention beside
   it) opening #profile-menu; the button inherits the chip's type so the
   text is still one line at the chip's size. */
#mp-profile {
  position: relative; display: flex; align-items: baseline; gap: var(--sb-conn-gap);
  flex: 0 1 auto; min-width: 0;
  font-size: 15px; color: var(--brand-ink); white-space: nowrap;
}
#mp-profile[hidden] { display: none; }
#profile-chip-btn {
  display: flex; align-items: baseline; gap: 4px; flex: 0 1 auto; min-width: 0;
  margin: 0; padding: 0; border: 0; background: none; font: inherit; color: inherit;
  cursor: pointer; text-align: left;
}
#profile-chip-btn:hover #profile-chip-text, #profile-chip-btn[aria-expanded="true"] #profile-chip-text { color: var(--label-em); }
#profile-chip-btn:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; border-radius: 2px; }
.profile-chip-caret { flex: none; font-size: 11px; color: var(--brand-slate); }
#profile-chip-btn:hover .profile-chip-caret, #profile-chip-btn[aria-expanded="true"] .profile-chip-caret { color: var(--label-em); }
#profile-chip-text { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; font-weight: 600; }
.profile-chip-text--none { color: var(--warn-text); }
#profile-chip-bar {
  position: absolute; left: 0; right: 0; bottom: -3px; height: 2px;
  background: var(--hover-2); overflow: hidden;
}
#profile-chip-bar[hidden] { display: none; }
#profile-chip-bar > i { display: block; height: 100%; background: var(--sel); transition: width 120ms linear; }
/* The selection chips (applet-ui-cleanup rulings 7-8): the travel mode and
   the purpose / destination, in the traveller chip's idiom so the three read
   alike: a connective ("by", "on trips to"), the value, a caret. */
#mp-profile .param-chip-label { flex: none; }
.param-chip { color: var(--brand-ink); white-space: nowrap; min-width: 0; }
#mp-params .mp-param.param-chip:not([hidden]) { display: flex; align-items: baseline; gap: var(--sb-conn-gap); font-size: 15px; }
.param-chip-btn {
  display: flex; align-items: baseline; gap: 4px; flex: 0 1 auto; min-width: 0;
  margin: 0; padding: 0; border: 0; background: none; font: inherit; color: inherit;
  cursor: pointer; text-align: left;
}
.param-chip-text { flex: 0 1 auto; min-width: 0; max-width: 240px; overflow: hidden; text-overflow: ellipsis; font-weight: 600; }
.param-chip-caret { flex: none; font-size: 11px; color: var(--brand-slate); }
.param-chip-btn:hover .param-chip-text, .param-chip-btn[aria-expanded="true"] .param-chip-text,
.param-chip-btn:hover .param-chip-caret, .param-chip-btn[aria-expanded="true"] .param-chip-caret { color: var(--label-em); }
.param-chip-btn:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; border-radius: 2px; }
/* ---- The scenario chip and its menu (scenario_picker.js, PLAN-dock-controls S5) ---- */
/* The chip is map furniture, not dock chrome: what the map is OF belongs with
   the legend, and it is styled as the legend is -- the same veil ground, border,
   radius and shadow, and the legend's z-index in each layout (900 here, 5 on the
   phone, where the sheets sit at 8/9). Top-left is the corner the desktop legend
   leaves free; on a phone the two share it and the legend moves down by --scn-h.
   The label itself is #profile-chip-btn's idiom -- quiet text, a small caret,
   the full coordinate list in the title -- so the applet's two disclosure chips
   read alike. Ellipsis rather than wrap: a five-axis label is folded by the
   label rule, not by the box. */
/* Line 2 of the summary bar -- the sentence's LAST clause (desktop): "under
   existing conditions ▾ [+ compare to a different scenario]", or, comparing,
   "from <A> ▾ to <B> ▾" (idea A, 2026-10-22; scenario_picker.js). Each chip is
   plain text with a caret, not a floating box; the connectives are .sb-conn. */
#summary-line2 {
  display: flex; align-items: baseline; gap: var(--sb-conn-gap); flex: 0 1 auto; min-width: 0;
}
#summary-line2[hidden] { display: none; }
.scn-chip {
  display: flex; align-items: baseline; min-width: 0; max-width: 460px;
  font-size: 15px; color: var(--brand-ink);
}
.scn-chip[hidden] { display: none; }
.scn-chip-btn {
  display: flex; align-items: baseline; gap: 4px; max-width: 100%;
  margin: 0; padding: 0; border: 0; background: none; font: inherit; color: inherit;
  cursor: pointer; text-align: left;
}
.scn-chip-btn:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: 3px; }
.scn-chip-text {
  flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font-weight: 600; color: var(--brand-ink);
}
.scenario-chip-caret { flex: none; font-size: 11px; color: var(--brand-slate); }
.scn-chip-btn:is(:hover, [aria-expanded="true"]) :is(.scn-chip-text, .scenario-chip-caret) { color: var(--label-em); }
/* Compare in a cube: ", under <shared axes>" after the second chip (H7), set
   as the chips' names are, without a caret. */
#sb-scn-shared { display: flex; align-items: baseline; gap: var(--sb-conn-gap); min-width: 0; }
#sb-scn-shared[hidden] { display: none; }
.scn-shared-name {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 15px; font-weight: 600; color: var(--brand-ink);
}
/* Outcome's ", up 0.4 from" (#ms-delta) leads straight into A's chip; "vs"
   stands in only when it prints nothing. */
#ms-delta:not([hidden]) + #sb-scn-mid { display: none; }
/* "+ compare": an action, not a word of the sentence -- a dashed pill in the
   gold of the chips' hover, a clause gap from the chip before it. */
#compare-add {
  flex: none; align-self: center; margin: 0 0 0 calc(var(--sb-clause-gap) - var(--sb-conn-gap));
  padding: 0 10px; border: 1px dashed var(--gold-deep); border-radius: 999px; background: none;
  font: inherit; font-size: 13px; font-weight: 600; line-height: 20px; color: var(--brand-slate);
  white-space: nowrap; cursor: pointer;
}
#compare-add[hidden] { display: none; }
#compare-add:is(:hover, [aria-expanded="true"]) { border-style: solid; background: var(--gold-wash); color: var(--label-em); }
#compare-add:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* The compare sides as tags (idea C, Taylor 2026-10-22): while comparing, the
   reference's name is a dark tag with light text and the alternative's a light
   tag with an ink edge, in every view -- the same pair util.js sideTag draws in
   the dock, the mode bars, the scenario list and Flip's legend. The tags drop
   the other choices' rest underline (Taylor: tags accepted) and keep the gold
   bar on hover / focus / open. One scenario (data-side "active") stays an
   underlined word. */
#summary-bar .scn-chip-btn:is([data-side="A"], [data-side="B"]) .scn-chip-text .scn-name {
  padding: 0 6px; border-radius: 4px;
}
#summary-bar .scn-chip-btn[data-side="A"] .scn-chip-text .scn-name {
  background: var(--side-a); color: var(--side-a-on); box-shadow: none;
}
#summary-bar .scn-chip-btn[data-side="B"] .scn-chip-text .scn-name {
  background: var(--side-b); color: var(--side-b-on); box-shadow: inset 0 0 0 1px var(--side-b-edge);
}
#summary-bar .scn-chip-btn[data-side="A"]:is(:hover, :focus-visible, [aria-expanded="true"]) .scn-chip-text .scn-name {
  box-shadow: inset 0 -3px 0 var(--gold);
}
#summary-bar .scn-chip-btn[data-side="B"]:is(:hover, :focus-visible, [aria-expanded="true"]) .scn-chip-text .scn-name {
  box-shadow: inset 0 0 0 1px var(--side-b-edge), inset 0 -3px 0 var(--gold);
}
/* Compare's Flip view: the two chips are the toggle (D34). Both keep their
   side's tag; the shown side carries the gold bar (gold = selected), the
   other's caret greys, and the map frame says the same (below). The caret is
   a finger-sized target of its own -- it opens the list. */
#summary-line2[data-view="flip"] .scn-chip-btn[aria-pressed="false"] .scn-chip-text { color: var(--brand-ink); }
#summary-line2[data-view="flip"] .scn-chip-btn[aria-pressed="true"][data-side="A"] .scn-chip-text .scn-name {
  box-shadow: inset 0 -3px 0 var(--gold);
}
#summary-line2[data-view="flip"] .scn-chip-btn[aria-pressed="true"][data-side="B"] .scn-chip-text .scn-name {
  box-shadow: inset 0 0 0 1px var(--side-b-edge), inset 0 -3px 0 var(--gold);
}
#summary-line2[data-view="flip"] .scenario-chip-caret { padding: 2px 4px; margin: -2px -4px; }
/* --n-55: 6.4:1 on parchment, 5.9 inside the white kbd (--n-77 was 4.1). */
#flip-hint { flex: none; align-self: center; font-size: 10px; color: var(--brand-ink); white-space: nowrap; }
/* "Space flips" is a keyboard affordance: pointer-gated, not layout-gated
   (TABLE row 19). */
@media (pointer: coarse) { #flip-hint { display: none !important; } }
#flip-hint[hidden] { display: none; }
#flip-hint kbd {
  font: inherit; font-size: 9px; padding: 0 3px;
  border: 1px solid var(--n-cc); border-radius: 3px; background: var(--n-ff);
}
/* The side frame (D28; drawn as the side's tag since idea C): an inset border
   round the map -- solid ink for the reference, an ink-white-ink double line
   for the alternative, the light tag's edge at map scale. Above the canvas,
   below MapLibre's corner controls (z 2, in the same stacking context) so it
   never paints over a button, and it takes no events. It stops at the dock's
   top edge (--sheet-h, both layouts). */
#flip-frame {
  position: absolute; inset: 0 0 var(--sheet-h, 0px); z-index: 1; pointer-events: none;
  border: 2px solid var(--side-b-edge);
  box-shadow: inset 0 0 0 5px var(--side-b), inset 0 0 0 7px var(--side-b-edge);
}
#flip-frame[data-side="a"] { border: 4px solid var(--side-a); box-shadow: none; }
#flip-frame[hidden] { display: none; }

/* A compare side named anywhere but the sentence's chips (util.js sideTag):
   the dock's A / B column heads, the mode bars' labels, the scenario list's
   title, Flip's legend. Inline, so it sits in a line of text or a 1-letter
   table head alike. */
.side-tag {
  display: inline-block; border-radius: 3px; padding: 0 4px; line-height: 1.35;
  white-space: nowrap; font-weight: 600;
}
.side-tag[data-side="A"] { background: var(--side-a); color: var(--side-a-on); }
.side-tag[data-side="B"] { background: var(--side-b); color: var(--side-b-on); box-shadow: inset 0 0 0 1px var(--side-b-edge); }
/* The list's footer names the pair in full; a long cube label wraps inside its
   tag rather than pushing the footer wider than the list. */
#sm-summary .side-tag { white-space: normal; }

/* The menu shares #ind-menu's box (above). What is its own (S5 second round,
   PLAN §0i): the two segmented strips (mode, view), a column header naming the
   two compare sides, and -- per axis -- a RADIO
   MATRIX: one row per option, one radio column per side, both sides' picks
   visible at once. The footer carries two buttons rather than one, because the
   picks here are a DRAFT (S5-c) and there is something to cancel. */
/* --sm-lab / --sm-info / --sm-cell are the track widths the header row and
   every grid beneath it share, so a side word sits over the radios it names.
   The label track is CAPPED rather than 1fr (Taylor, 2026-09-22 -- "too much
   white space in the middle"): with 1fr the labels stretched to the menu's
   width and pushed the radio columns to the far edge, half a menu away from
   the words they answer -- worst in Explore, where one column sat alone at
   x=776 of an 838px menu. Capping the labels and parking the leftover in a
   trailing spacer track brings the columns back to the labels. The cap
   degrades gracefully: where the menu is narrower than the tracks want (the
   phone, edge to edge), the label track simply takes what is left, which is
   what 1fr gave it before. 420px, #profile-menu's width, now that the radio
   columns are 64px rather than 96 -- the fourth column no longer needs the
   extra 20. */
#scenario-menu {
  width: 420px; padding: 4px 0 0;
  --sm-lab: 190px; --sm-cell: 64px; --sm-info: 28px;
}
.sm-strip { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; padding: 8px 14px 6px; }
.sm-seg {
  flex: 0 1 auto; min-height: 32px; margin: 0; padding: 4px 12px;
  border: 1px solid var(--n-cc); border-radius: 999px; background: var(--n-ff);
  font: inherit; font-size: 13px; line-height: 20px; color: var(--brand-ink); cursor: pointer;
}
.sm-seg:hover:not(:disabled) { border-color: var(--sel-border); background: var(--sel-soft); }
.sm-seg[aria-pressed="true"] {
  font-weight: 600; color: var(--label-em); background: var(--sel-soft); border-color: var(--sel-border);
}
.sm-seg:disabled { color: var(--text-muted); border-style: dashed; background: none; cursor: default; }
.sm-seg:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }

/* The list's question (idea A, 2026-10-22): "Compare <A> with…" when
   starting a comparison, "Alternative scenario (compared with <A>)" in one. */
.sm-title { padding: 12px 14px 2px; font-size: 14px; line-height: 20px; color: var(--brand-ink); }
.sm-title .sm-conn { font-family: var(--font-conn); font-size: 15px; color: var(--brand-slate); }
.sm-title b { font-weight: 700; }
.sm-q { font-weight: 700; }
.sm-title-sub { font-size: 12px; color: var(--brand-ink); }
.sm-title-sub b { font-weight: 600; color: var(--brand-ink); }
/* The view (Change · Outcome | Flip) and "Cancel comparison", under the list. */
.sm-view-row { display: flex; align-items: center; gap: 4px; padding: 0 0 0 14px; border-top: 1px solid var(--n-ee); margin-top: 6px; }
.sm-view-label { font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted); }
.sm-view-row .sm-strip { padding-left: 6px; }
#sm-end {
  display: block; margin: 0 14px 8px; padding: 4px 0; border: 0; background: none;
  font: inherit; font-size: 13px; font-weight: 600; color: var(--brand-slate);
  text-decoration: underline; text-underline-offset: 3px; cursor: pointer;
}
#sm-end:hover { color: var(--label-em); }
#sm-end:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
/* A greyed row's printed reason ("shown now", "the reference" / "the alternative"). */
.sm-lab[data-note]::after { content: "\00a0· " attr(data-note); font-size: 11px; color: var(--text-muted); }

/* One block per axis, the #profile-menu .pm-dim rhythm. */
.sm-axis + .sm-axis, .sm-axis + .sm-strip, .sm-strip + .sm-axis, .sm-title + .sm-axis {
  border-top: 1px solid var(--n-ee); margin-top: 6px;
}
.sm-head {
  display: flex; align-items: center; gap: 8px; padding: 8px 14px 4px;
  font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted);
}
.sm-head .sm-info { margin-left: auto; }
/* A description, PRINTED rather than hovered (a phone has no hover) -- the
   axis's own behind the heading's ⓘ, a scenario's behind its row's -- in a
   popover over the list (S3, 2026-10-26), so opening it moves no row
   (scenario_picker.js openPop places it under its ⓘ, or above where the
   footer would cut it off). Over the sticky footer (z-index 1). */
.sm-pop {
  position: absolute; z-index: 2; box-sizing: border-box; padding: 8px 10px;
  background: var(--ground-raised); border: 1px solid var(--n-cc); border-radius: 8px;
  box-shadow: 0 4px 14px var(--shade-22);
  font-size: 12px; line-height: 16px; font-weight: 400; color: var(--text-muted);
}
.sm-pop[hidden] { display: none; }
/* The whole row picks (S3): the label and an empty ⓘ cell carry the radio's
   pointer, and pointing at them lights the radio's circle. */
.sm-lab, .sm-info-gap { cursor: pointer; }
.sm-grid > .sm-info-gap { align-self: stretch; }

/* The matrix. Every child is placed by explicit grid-row / grid-column from
   scenario_picker.js (its module header says why), so these tracks are the
   whole layout: label | ⓘ | one or two radio columns. */
.sm-grid {
  display: grid; align-items: center; padding: 0 14px 6px;
  grid-template-columns:
    minmax(0, var(--sm-lab)) var(--sm-info) var(--sm-cell) var(--sm-cell) minmax(0, 1fr);
}
.sm-grid[data-cols="1"] {
  grid-template-columns: minmax(0, var(--sm-lab)) var(--sm-info) var(--sm-cell) minmax(0, 1fr);
}
/* The radiogroups are per COLUMN; they draw no box of their own, so the cells
   they hold land on the coordinates above. */
.sm-group { display: contents; }
.sm-lab {
  font-size: 13px; line-height: 20px; color: var(--brand-ink);
  padding: 5px 8px 5px 0; min-height: 26px; display: flex; align-items: center;
}
.sm-info-gap { display: block; }
/* The radio: the whole cell is the target, the circle is 18px inside it. */
.sm-radio {
  display: flex; align-items: center; justify-content: center;
  margin: 0; padding: 0; min-height: 30px; border: 0; background: none;
  font: inherit; color: inherit; cursor: pointer;
}
.sm-radio::before {
  content: ""; display: block; box-sizing: border-box;
  width: 18px; height: 18px; border-radius: 50%;
  border: 1.5px solid var(--n-aa); background: var(--n-ff);
}
.sm-radio:is(:hover, .sm-row-hover):not([aria-disabled="true"])::before { border-color: var(--sel-border); background: var(--sel-soft); }
.sm-radio[aria-checked="true"]::before {
  border: 2px solid var(--sel-border);
  background: radial-gradient(circle, var(--sel) 0 5px, var(--n-ff) 5px);
}
/* A cell the study never ran, judged per side: dashed and inert in place, the
   reason as its title, rather than a select that silently reverts (what the
   panel's table did). */
.sm-radio.sm-off { cursor: default; }
.sm-radio.sm-off::before { border-style: dashed; border-color: var(--n-cc); background: none; }
.sm-radio:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; border-radius: 6px; }
.sm-note { padding: 0 14px 6px; color: var(--text-muted); }
/* Sticky, as #profile-menu's footer is: the menu scrolls inside its 60 vh cap
   and Apply must stay in view whatever the rows above are doing. */
.sm-foot {
  position: sticky; bottom: 0; z-index: 1; background: var(--ground-raised);
  display: flex; align-items: center; gap: 10px; padding: 8px 14px;
  border-top: 1px solid var(--n-ee); margin-top: 6px;
}
#sm-summary {
  flex: 1 1 auto; min-width: 0; font-size: 12px; line-height: 16px; color: var(--text-muted);
}
#sm-summary.sm-summary--none { color: var(--warn-text); }
#sm-cancel, #sm-apply {
  flex: none; margin: 0; padding: 4px 14px; min-height: 32px; border-radius: 999px;
  font: inherit; font-size: 13px; line-height: 20px; cursor: pointer;
}
#sm-cancel { border: 1px solid var(--n-cc); background: var(--n-ff); color: var(--brand-ink); }
#sm-cancel:hover { border-color: var(--sel-border); background: var(--sel-soft); color: var(--label-em); }
#sm-apply {
  border: 1px solid var(--sel-border); background: var(--sel-soft);
  font-weight: 600; color: var(--label-em);
}
#sm-apply:hover:not(:disabled) { background: var(--sel); color: var(--n-ff); }
/* Nothing to apply is not a button. */
#sm-apply:disabled { border-color: var(--n-dd); background: none; color: var(--n-aa); cursor: default; }
#sm-cancel:focus-visible, #sm-apply:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }

/* The settings panel's scenario picker is dev-only since PLAN-dock-controls S5
   (ruling S5-e's sibling to S3-e): the chip above is the reader's control, and
   the table survives as the second renderer the sweeps drive. Written as a
   `html:not(.dev)` rule rather than S3's `#sec-indicators` PAIR because the
   phone sheet re-declares `#scenario-picker { display: flex }` (applet-2's
   in-panel layout, mobile 273-335, left as it is): a plain
   `#scenario-picker { display: none }` here would lose to it on specificity
   ties, while this selector outranks both. initScenarioPicker's
   `style.display = ''` CLEARS the inline value rather than setting one, so the
   sheet still decides. Goes with the section at S6. */
html:not(.dev) #scenario-picker { display: none; }

/* The wrapper under the summary bar: holds the body at its own --mp-body-h
   height (the column flow fills by height). On the phone it is the sheet's
   one scroller. */
/* The body's tab strip (body_tabs.js), above the scroller: hidden while the
   mapped indicator has one tab. Overlapping trapezoid folder tabs (Taylor,
   2026-10-18; options in ives-notes applet-ui-cleanup/SKETCH-tab-polish):
   near-vertical sides, rounded top corners, a concave flare into the strip's
   rule at the foot, 11px overlap (Taylor, 2026-10-20, was 18). Inactive tabs are --ground-trim (Plate until the 2026-10-30 brand
   revision) with a soft slate edge; the active tab,
   the same size, sits in front with an Ink edge, the body's fill and a
   4px gold bar on top (gold as mass, per the brand reference), its open
   bottom joining the body over the strip's rule. A CSS border doesn't follow a
   curved outline, so each tab draws two masked layers: ::before the edge,
   ::after the fill inset 1px. The mask is three pieces: a 14px SVG cap each
   side (flare, slope, round top; stretched to the tab's height) and a plain
   middle. Tracked caps per the plate-caption style. Not the view
   pills -- these switch what the body shows of one subject, not the map.
   Stacking cascades from the selected tab (G5, Taylor 2026-10-20): the
   chosen tab in front, each tab one step away from it (either side) one
   layer further back. body_tabs.js writes each tab's z-index inline (number
   of tabs - distance from the active tab) when it draws the strip; the
   stylesheet sets none. */
#mp-tabs {
  --tab-cap-l: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 25' preserveAspectRatio='none'%3E%3Cpath d='M0 25C2.5 25 3.2 24 3.6 22L6.6 4.5C7.1 1.8 8.6 0 11.5 0H14V25Z'/%3E%3C/svg%3E");
  --tab-cap-r: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 25' preserveAspectRatio='none'%3E%3Cpath d='M14 25C11.5 25 10.8 24 10.4 22L7.4 4.5C6.9 1.8 5.4 0 2.5 0H0V25Z'/%3E%3C/svg%3E");
  display: flex; flex: none; align-items: flex-end; gap: 0; padding: 6px 14px 0 18px;
  border-bottom: 1px solid var(--brand-slate);
}
#mp-tabs[hidden] { display: none; }
/* An OD row with no origin picked maps nothing: no placeholder scale, range
   or swatches in the legend (indicators.js refreshDisplay sets the class). */
#legend.leg-od-empty #leg-bar, #legend.leg-od-empty .leg-labels,
#legend.leg-od-empty #leg-modes { display: none !important; }
/* A declared section (body_tabs.js showBodySections) is a wrapper for the
   sweep and the code, not a box: the body's own layout sees its content. */
.body-section { display: contents; }
.mp-tab {
  position: relative; margin: 0 -11px -1px 0;
  padding: 6px 24px 5px; border: 0; border-radius: 0; background: none;
  font: inherit; font-family: var(--font-ui); font-size: 11px; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase; color: var(--brand-slate); cursor: pointer;
}
.mp-tab::before, .mp-tab::after {
  content: ""; position: absolute; z-index: -1;
  -webkit-mask: var(--tab-cap-l) left / 14px 100% no-repeat, linear-gradient(#000, #000) center / calc(100% - 20px) 100% no-repeat,
    var(--tab-cap-r) right / 14px 100% no-repeat;
  mask: var(--tab-cap-l) left / 14px 100% no-repeat, linear-gradient(#000, #000) center / calc(100% - 20px) 100% no-repeat,
    var(--tab-cap-r) right / 14px 100% no-repeat;
}
.mp-tab::before { inset: 0 0 1px; background: var(--n-aa); }
.mp-tab::after { inset: 1px 1px 1px; background: var(--ground-trim); }
.mp-tab:hover:not(.active) { color: var(--brand-ink); }
.mp-tab:hover:not(.active)::after { background: var(--gold-wash); }
.mp-tab.active { color: var(--brand-ink); cursor: default; }
.mp-tab.active::before { inset: 0; background: var(--brand-slate); }
.mp-tab.active::after { inset: 1px 1px 0; background: linear-gradient(var(--gold) 0 4px, var(--ground-raised) 4px); }
.mp-tab:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
#mp-scroll {
  /* Capped at the body's height: anything under the body scrolls here rather
     than growing the dock into the map. */
  flex: 0 0 auto; min-height: 0; max-height: var(--mp-body-h); overflow-y: auto;
  display: flex; flex-direction: column;
}
/* The filters (below the fold, beside the traveller class) and the legend's
   scaling controls: the mapped row's parameters, labelled, wrapping.
   `hidden` when the row takes none. */
#mp-params { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px var(--sb-clause-gap); flex: 0 1 auto; min-width: 0; }
#mp-params[hidden], #leg-params[hidden] { display: none; }
#leg-params { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; margin: 4px 0 6px; }
#leg-params .mp-param-label { font-weight: 400; }
.mp-param { display: flex; align-items: center; gap: 6px; margin: 0; font-size: 11px; }
.mp-param[hidden] { display: none; }
.mp-param-label { font-weight: 600; color: var(--text-muted); white-space: nowrap; }
.mp-param .norm-toggle { margin: 0; }
.mp-param select {
  padding: 1px 4px; border: 1px solid var(--n-cc); border-radius: 3px;
  background: var(--n-ff); font: inherit; font-size: 11px; color: var(--brand-ink); cursor: pointer;
  max-width: 220px;
}
#mp-body {
  flex: 0 0 var(--mp-body-h); height: var(--mp-body-h); min-height: 0;
  /* Asymmetric bottom padding: at 180px the mode panel's formula line lands
     within ~2px of the fold, and a symmetric 8px was enough on its own to
     raise a 6px scrollbar on the common case. */
  padding: 6px 12px 4px;
  columns: 380px 3; column-gap: 22px; column-rule: 1px solid var(--n-ee);
  overflow-x: auto; overflow-y: auto;
}
#mp-body > * { break-inside: avoid; }
#mp-placeholder {
  flex: 0 0 var(--mp-body-h); height: var(--mp-body-h); display: none;
  align-items: center; justify-content: center;
  color: var(--text-muted); font-size: 12px; text-align: center; padding: 0 20px;
}
#measurements.mp-empty #mp-body { display: none; }
#measurements.mp-empty #mp-placeholder { display: flex; }
#mp-body .pn-name { font-weight: 700; margin-bottom: 5px; color: var(--brand-ink); font-size: 13px; }
.pn-row { display: flex; justify-content: space-between; gap: 8px; padding: 1px 0; }
.pn-label { color: var(--text-muted); }
.pn-val { font-weight: 600; color: var(--brand-ink); }
#mp-body .pn-row { display: flex; justify-content: space-between; gap: 8px; padding: 1px 0; }
#mp-body .pn-label { color: var(--text-muted); }
#mp-body .pn-val { font-weight: 600; color: var(--brand-ink); }
.pn-dens-swatch {
  display: inline-block; width: 10px; height: 10px;
  border-radius: 2px; margin-right: 5px;
}
#mp-body .pn-section { margin-top: 5px; padding-top: 4px; border-top: 1px solid var(--n-ee); }
#mp-body .pn-mode-table { width: 100%; border-collapse: collapse; }
#mp-body .pn-mode-table th {
  text-align: right; color: var(--text-muted); font-weight: 500; padding: 1px 4px 3px 4px;
  font-size: 10px; border-bottom: 1px solid var(--n-dd);
}
#mp-body .pn-mode-table th:first-child { text-align: left; }
#mp-body .pn-mode-table td { text-align: right; padding: 2px 4px; color: var(--brand-ink); font-size: 11px; }
#mp-body .pn-mode-table td:first-child { text-align: left; color: var(--brand-ink); }
#mp-body .pn-mode-table tr.pn-likeliest td { font-weight: 700; color: var(--brand-ink); }
/* `info-panel--wide` was the floating panel's 340px -> 560px widening. The
   measurements panel is always full-width, so the class is inert here; the
   JS still sets it and the rule is kept as the documented no-op. */
#mp-body.info-panel--wide { max-width: none; }
#mp-body .pn-util-purpose { font-weight: 600; color: var(--brand-ink); font-size: 11px; }
/* .od-purpose (the labelled select block that hosted Trip purpose,
   Destination category and Destination type) is gone as of session 27; the
   selects are in the dock's #mp-params strip since PLAN-dock-controls S3. */
/* The mode table's totals row closes its table and keeps the border-top
   above. (The population panel's user-class cross-tab and its .pn-uc-* rules
   were retired in session 31 — that panel carries the profile tree, .pdc-tbl.) */
#mp-body .pn-mode-table tr.pn-total-row td {
  font-weight: 600; border-top: 1px solid var(--n-dd); color: var(--brand-ink);
}
/* The trips dock's bar(s): the City Snapshot's rows, in the table column. */
#mp-body .cum-bars { margin: 2px 0 6px; max-width: 520px; }
#mp-body .cum-bars .snap-mode-lbl { width: 64px; }

/* ---- Logsums utility breakdown ---- */
#mp-body .pn-util { margin-top: 6px; }
#mp-body .pn-util-head { display: flex; align-items: center; gap: 8px; }
#mp-body .pn-util-toggle {
  font-weight: 700; color: var(--brand-ink); cursor: pointer; user-select: none; font-size: 12px;
}
#mp-body .pn-util-toggle:hover { color: var(--label-em); }
#mp-body .pn-util-group { font-size: 11px; padding: 1px 3px; }
#mp-body .pn-util-note { color: var(--text-muted); font-size: 10px; margin: 3px 0; line-height: 1.35; }
/* The OD family's regional stand-in (info_panel.js renderOdRegionStandIn):
   the one line that says the dock below is the region's figure, not a pair's.
   A flex child of the stats body, so it never shrinks under the columns. */
#mp-body .pn-standin { flex: 0 0 auto; font-size: 10.5px; color: var(--brand-ink); margin: 0 0 4px; }
/* Utility decomposition table. Numeric cells must not wrap — a wrapped numeric
   cell doubles the row height and the panel is already the tallest thing on the
   map. Headers are the exception: they are glossed ("Long-walk penalty (kink)",
   not "kink"), which makes them roughly twice as wide as bare model jargon, and
   left nowrap that one header alone eats ~150px. Two header lines cost ~11px
   once; a nowrap header costs width on every row. The mode column and the
   mode-specific chip cell wrap too. */
/* The decomposition table spans the whole panel rather than flowing inside one
   380px column. It is eight columns wide by design and cannot be squeezed into
   a column of the width the surrounding prose blocks want; left in the flow it
   fragments, and its last rows land in a neighbouring column detached from the
   header that names them — an unreadable table that still looks like a table. */
#mp-body .pn-decomp { margin-top: 0; width: auto; column-span: all; }
#mp-body .pn-decomp td { white-space: nowrap; }
#mp-body .pn-decomp th { white-space: normal; vertical-align: bottom; }
#mp-body .pn-decomp td:first-child { white-space: normal; }
/* The inline factors that are not shared across modes. Ragged by construction —
   car under PARKING_CHOICE carries the most — so it wraps within a bounded
   width rather than pushing the table into horizontal scroll. */
#mp-body .pn-decomp td.pn-decomp-chips {
  white-space: normal; max-width: 300px; text-align: left; line-height: 1.5;
}
#mp-body .pn-chip {
  display: inline-block; margin: 0 3px 1px 0; padding: 0 4px;
  border-radius: 3px; background: var(--veil-62); color: var(--brand-ink);
  font-size: 10px; white-space: nowrap; cursor: help;
}
/* The unexplained-residual tripwire. Styled as a defect, because that is what
   it is: on a complete build it never renders. */
#mp-body .pn-chip-warn {
  background: var(--warn-bg); color: var(--warn-text); border: 1px solid var(--warn-border);
}
/* ---- Mode-choice panel layout (table | model constants) ---- */
/* The column flow is off here: the two blocks are laid out explicitly, the
   table shrink-to-fit at its natural width and the constants taking what is
   left. `column-span: all` on .pn-decomp is inert under `columns: auto`. */
#mp-body.info-panel--mc { columns: auto; column-gap: normal; column-rule: none; }
.pn-mc-main { display: flex; gap: 22px; align-items: flex-start; }
.pn-mc-tbl { flex: 0 0 auto; }
.pn-mc-side { flex: 0 1 auto; min-width: 0; padding-top: 2px; }
#mp-body .pn-mc-side .pn-util-note { margin: 2px 0; }
/* The collapsed unavailable-mode row: one line for every mode the pair cannot
   use, grouped by reason. Left-aligned and wrapping, unlike every other cell
   in this table — it is a sentence, not a number. */
#mp-body .pn-mode-unavail td {
  text-align: left; white-space: normal; color: var(--text-muted); font-style: italic;
  font-size: 10px; padding-top: 3px; border-top: 1px solid var(--n-ee);
}
#mp-body .pn-decomp-formula {
  border-top: 1px solid var(--n-ee); padding-top: 2px; margin-top: 1px; color: var(--text-muted);
}
#mp-body .pn-util-sub { font-weight: 600; color: var(--brand-ink); font-size: 11px; margin-top: 5px; }
/* Destination-choice symbol · meaning · value rows */
#mp-body .pn-dc-tbl { margin-top: 2px; width: auto; }
#mp-body .pn-dc-tbl td { padding: 1px 6px 1px 0; font-size: 11px; }
#mp-body .pn-dc-tbl .pn-dc-sym { text-align: left; color: var(--brand-ink); font-weight: 600; white-space: nowrap; }
#mp-body .pn-dc-tbl .pn-dc-meaning { text-align: left; color: var(--text-muted); }
#mp-body .pn-dc-tbl .pn-dc-val { text-align: right; color: var(--brand-ink); white-space: nowrap; }
/* Operand cell: the arithmetic that produces the value, sitting just left of
   it — light, right-aligned, so each force stays one row and the value column
   stays the sum's spine. */
#mp-body .pn-dc-tbl .pn-dc-op { text-align: right; color: var(--text-muted); white-space: nowrap; }
#mp-body .pn-util-tbl th { font-size: 10px; }
#mp-body .pn-util-tbl td { font-size: 11px; }

/* ---- Destination-choice panel (dc_* indicators; plan session 4) ---- */
/* Flex replaces the base multicol: this panel manages its own three regions,
   and the category table scrolls INSIDE its region rather than fragmenting
   into columns. Click-to-pin is what makes an in-panel scrollbar usable --
   once the pair is frozen the list stops swapping under the cursor. */
#mp-body.info-panel--dc {
  columns: auto; column-gap: normal;
  display: flex; flex-direction: column;
  /* The category list is this panel's own scroller, so the outer box does not
     scroll horizontally. It does scroll VERTICALLY as a safety valve: at the
     dock's 180px the fixed regions (head + forces column) land within a few
     px of the fold, and clipping the last force silently is worse than a
     scrollbar that engages only when they genuinely overrun. */
  overflow: hidden auto;
}
/* .dcp-head is gone — it restated the dock header's O -> D slots, and what it
   did not restate moved to #mp-context (renderDestChoicePanel). */
.dcp-cols { display: flex; gap: 22px; flex: 1 1 auto; min-height: 0; }
.dcp-col-forces { flex: 0 0 auto; }
/* The outputs sit beside the forces, not under them: stacked they were the
   127px that overran a 120px box. The rule marks the seam between "what the
   sum is made of" and "what the sum produces". Under ~1500px of dock they
   stack again (.dcp-narrow, set by the renderer) — there the width matters
   more than the height, and the height is no longer the binding constraint. */
.dcp-col-sum { display: flex; gap: 22px; flex: 0 0 auto; }
.dcp-col-out { flex: 0 0 auto; border-left: 1px solid var(--n-ee); padding-left: 16px; }
.dcp-cols.dcp-narrow .dcp-col-sum { flex-direction: column; gap: 0; }
.dcp-cols.dcp-narrow .dcp-col-out {
  border-left: none; padding-left: 0;
  margin-top: 4px; padding-top: 3px; border-top: 1px solid var(--n-ee);
}
.dcp-col-mid { flex: 0 0 auto; max-width: 350px; }
/* Takes the width the outputs column freed, so a two-up category list has
   somewhere to go. Still scrolls, as the fallback past two columns' worth. */
.dcp-col-cats { flex: 1 1 auto; min-width: 0; overflow-y: auto; padding-right: 4px; }
/* Tight gap on purpose: two-up runs ~7px over the column at 1700px with the
   18px the other seams use, and the two sub-tables are one list. */
.dcp-cat-cols { display: flex; gap: 12px; align-items: flex-start; }
/* ---- Compare-mode destination-choice panel (compare pass 2) ---- */
/* Five columns instead of the explore panel's four: symbol, meaning, A, B, Δ.
   The three numeric columns share .pn-dc-val, so only the header row and the
   two summing rows need rules of their own. */
#mp-body .pn-dc-cmp th {
  font-size: 10px; color: var(--text-muted); font-weight: 600; text-align: right;
  padding: 0 6px 1px 0; white-space: nowrap;
}
#mp-body .pn-dc-cmp tr.pn-dc-strong td { font-weight: 700; color: var(--brand-ink); }
#mp-body .pn-dc-cmp tr.pn-dc-strong .pn-dc-meaning { font-weight: 400; color: var(--text-muted); }
/* A band constant that MOVED between the two scenarios: the recalibration
   case a single ladder would otherwise misreport silently. */
.dcp-band-moved { border-color: var(--n-99); background: var(--moved-bg); }
.dcp-band-dlt { font-size: 9px; font-weight: 600; }

.dcp-ladder { display: flex; gap: 3px; }
.dcp-band {
  border: 1px solid var(--n-dd); border-radius: 3px; padding: 1px 6px;
  text-align: center; font-size: 10px; color: var(--text-muted); cursor: help;
}
.dcp-band-val { font-weight: 600; color: var(--brand-ink); font-size: 11px; }
.dcp-band-cur { background: var(--sel-soft); border-color: var(--sel-border); }
.dcp-band-cur .dcp-band-val { color: var(--label-em); }
/* Sized to content, not stretched: `#mp-body .pn-mode-table { width: 100% }`
   out-specifies a bare `.dcp-cat-tbl`, and at 469px the four columns drifted
   so far apart the rows were hard to track across. */
#mp-body .dcp-cat-tbl { border-collapse: collapse; width: auto; }
/* Headers stay on one line: two-up, the narrower sub-table wrapped "Size here"
   and "Δ removed" onto a second line and stood taller than its twin. */
#mp-body .dcp-cat-tbl th { white-space: nowrap; }
/* Category labels too: content-sizing let "Shop" and its `map` tag split over
   two lines, which knocked the two-up sub-tables' rows out of alignment with
   each other. A long label widens the column instead of doubling a row. */
#mp-body .dcp-cat-tbl td:first-child { white-space: nowrap; }
/* Sticky header so the category list scrolls under its own labels. */
#mp-body .dcp-cat-tbl thead th { position: sticky; top: 0; background: var(--ground-trim); }
#mp-body .dcp-cat-tbl tr.dcp-cat-cur td { font-weight: 700; color: var(--brand-ink); background: var(--sel-soft); }

/* ---- Statistics panels (population / destination) ----
   Same shape as .dcp-cols: the column flow off, a leading column that
   decomposes the mapped number, and the rest of the width for the group's
   other indicators as one table. The multicol these panels used to ride ran
   the destination list across arbitrary breaks and the population panel's
   old cross-tab 343px off the right edge at 1280. */
#mp-body.info-panel--stats {
  columns: auto; column-gap: normal; column-rule: none;
  display: flex; flex-direction: column;
  /* The table column is this panel's own scroller, like the dc category list,
     so the outer box does not scroll horizontally. Vertically it still does,
     as the safety valve for a lead column that overruns 148px. */
  overflow: hidden auto;
}
/* Never shorter than its content (slice 2, 2026-10-19): shrinkable, the
   columns let a tall lead column (access Compare, pinned: ~143px in a 112px
   box) spill under the explainer row below it -- "What does this number
   mean?" printed over the last two stats. At its content height the body's
   own vertical scroll is the safety valve, as #mp-body.info-panel--stats
   intends; the phone sheet already sized it so (mobile.css). */
.stp-cols { display: flex; gap: 22px; flex: 1 0 auto; }
/* Shrinkable but not growable: the row labels set its natural width, and past
   ~300px it is only stretching a two-column key/value list. Never below its
   table's min-content, though: since every Compare change carries its % (numbers
   cluster 1, 2026-10-26; "1.1 pp (+35.3%) more by transit", the value cells
   nowrap) a 300px box let the Δ column run under the mode bars beside it. The
   labels still wrap; the column beside it gives way (it scrolls on its own). */
.stp-col-lead { flex: 0 1 300px; min-width: min-content; }
/* Scrolls on BOTH axes, and it is the table's own adjacent scroller rather
   than #mp-body's off-screen fourth column — which is what made the old
   343px overflow silent. The population tree's compare twin is the widest
   table here (~1,000px at burlington), so a study with a wider roster gets a
   visible scrollbar right under the table instead of a clip. */
.stp-col-tbl { flex: 1 1 auto; min-width: 0; overflow: auto; padding-right: 4px; }
/* Overall Access puts TWO tables in the table column: purposes and modes are
   two families that share no columns. Side by side rather than stacked --
   #mp-body is 148px tall and stacked they want ~218, where side by side the
   taller (the six-row mode table, measured at 128px) still fits. Below the
   width where both fit, .stp-col-tbl's own adjacent scrollbar takes over,
   which is the visible failure mode the silent clip was not. Gap matches
   .dcp-cat-cols: the two tables are one region. */
#mp-body .cum-tbls { display: flex; gap: 12px; align-items: flex-start; }
/* The mode table's rows carry their mode's hue inline (info_panel.js). The
   cell background .dcp-cat-cur paints for the active row would sit on top of
   it, so here the highlight is carried by the row's own darker tint plus the
   bold text the rule already sets. */
#mp-body .cum-mode-tbl tr.dcp-cat-cur td { background: transparent; }

/* Compare Zone Summary (session 34). The lead column is a four-column A/B/Δ
   table rather than the explore panel's label/value pairs, so its label cell
   has to be allowed to wrap -- .pn-dc-val keeps the three numeric columns
   nowrap, and without this the labels ("This zone vs. region") force the whole
   lead column past its 300px basis and push the tables into the ladder for a
   reason that is not the tables' width. */
#mp-body .pn-cum-lead .pn-dc-meaning { white-space: normal; line-height: 1.25; }
#mp-body .pn-cum-lead td { vertical-align: top; }
/* A per-zone change below the noise floor (compare-floor S2): muted, no sign
   colour, like the zero it stands for. */
.cmp-nochange { color: var(--text-muted); white-space: nowrap; }
/* Mode choice's Compare O→D lead (polish (k), 2026-10-02): with no tree
   beside it the 300px basis squeezed a four-column table into a fifth of
   the dock, its labels wrapping three and four lines. Sized to its content
   instead -- the A/B/Δ columns stay next to their labels rather than
   stretching to the dock's far edge -- with short one-line labels (the long
   forms are in the row titles). */
.stp-col-lead.stp-col-lead--fit { flex: 0 0 auto; max-width: 620px; }
#mp-body .pn-msplit-cmp { width: auto; }
#mp-body .pn-msplit-cmp .pn-dc-meaning { white-space: nowrap; padding-right: 12px; }
#mp-body .pn-msplit-cmp td.pn-dc-val { padding-left: 10px; }
/* The uncalibrated-NHB marker, rendered as a row rather than a title= for the
   same reason the explore panel renders it: it is why the row is safe to show. */
#mp-body tr.pn-cum-warn td { color: var(--cum-warn); font-size: 10px; }

.dcp-map-tag {
  display: inline-block; font-size: 9px; font-weight: 600;
  color: var(--on-accent); background: var(--sel); border-radius: 3px;
  padding: 0 3px; margin-left: 3px; vertical-align: middle;
}

/* ---- Legend ---- */
/* Top-left of the map (G2, Taylor 2026-10-20), the phone's corner, and the
   page's head since G7: its first row is the page head (lockup | Connectome |
   ☰), its last the Display fold. The max-height keeps the legend above
   the dock at any dock height (--sheet-h, the dock's cover). The width is
   FIXED (G7, ruled at about 248px): a legend sized to its content changed
   width with every title and every fold, and the page menu at its right
   edge jumped with it. Long titles wrap. The phone keeps its own placement,
   width and 70vh (mobile sheet). */
#legend {
  position: absolute; top: 10px; left: 10px;
  background: var(--veil-ground-94); border: 1px solid var(--n-cc);
  border-radius: 6px; padding: 8px 10px; z-index: 900;
  font-size: 11px; width: 248px;
  box-shadow: 0 2px 6px var(--shade-15);
  max-height: calc(100% - var(--sheet-h, 0px) - 20px); overflow-y: auto;
}
/* The label, under the ramp (geo-aggregation S1): "<Indicator>: <unit>",
   "Change in <indicator>: <unit>" (indicators.js legendLabel). */
#legend .leg-title { font-weight: 700; margin: 6px 0 0; line-height: 1.3; color: var(--brand-ink); font-size: 11px; }
/* A comparison with nothing to paint (sketch round 9): the line stands in
   for the ramp and its ends. */
#legend #leg-nochange { margin: 2px 0 0; font-size: 12px; color: var(--text-muted); }
#legend #leg-nochange[hidden], #legend #leg-ringkey[hidden] { display: none; }
/* What the rings mean (sketch rounds 10-11): a circle in each ring's colour,
   named by what it marks; only while a ring is drawn. */
#legend #leg-ringkey {
  display: flex; flex-wrap: wrap; gap: 4px 14px; margin: 6px 0 0;
  font-size: 11px; color: var(--brand-ink);
}
#legend .leg-ring { display: inline-flex; align-items: center; gap: 5px; }
#legend .leg-ring-swatch {
  box-sizing: border-box; display: inline-block; width: 12px; height: 12px; flex: none;
  border: 2.5px solid currentColor; border-radius: 50%; background: var(--n-f5);
}
#legend #leg-compare-view {
  font-size: 10px; color: var(--brand-ink); font-style: italic;
  margin: -3px 0 5px;
}
/* Flip and fold notices only, both layouts (TABLE row 28, Taylor 2026-10-18). */
#legend #leg-compare-view:not(.leg-flip):not(.leg-notice) { display: none !important; }
#leg-advanced-body { margin-top: 4px; }
#leg-scale-row {
  display: flex; align-items: center; gap: 6px; margin-bottom: 6px;
}
#leg-scale-row label { font-size: 11px; font-weight: 600; color: var(--label-em); }
/* The strip reuses .norm-toggle / .norm-btn so it reads as the same kind of
   control as the per-km²/total pills, but it is never auto-sized away from
   the legend's left edge the way those are inside the sub-control column. */
#leg-scale-toggle { margin: 0; }
#leg-scale-toggle .norm-btn:disabled {
  background: var(--n-f4); color: var(--n-bb); cursor: not-allowed;
}
#leg-opacity-row, #leg-height-row { margin-bottom: 6px; }
#leg-opacity-row label, #leg-height-row label {
  font-size: 11px; font-weight: 600; color: var(--label-em);
}
#leg-opacity-row #leg-opacity-slider,
#leg-height-row #height-slider {
  width: 100%; margin-top: 3px; cursor: pointer;
}
#leg-cap-slider-row {
  display: flex; align-items: center; gap: 4px;
  font-size: 10px; color: var(--brand-ink);
}
#leg-cap-slider-row label { white-space: nowrap; }
#leg-cap-slider { flex: 1; min-width: 60px; cursor: pointer; }
#leg-cap-value {
  font-variant-numeric: tabular-nums;
  width: 48px; text-align: right; color: var(--brand-ink);
  font-size: 10px; padding: 1px 2px;
  border: 1px solid var(--n-cc); border-radius: 2px;
  background: var(--n-ff);
}
#leg-cap-value:focus { outline: none; border-color: var(--focus); }
#leg-cap-unit { color: var(--brand-ink); font-size: 10px; }
.leg-cap-reset {
  background: none; border: none; padding: 0; cursor: pointer;
  font-size: 10px; color: var(--text-muted); text-decoration: underline;
}
.leg-cap-reset:hover { color: var(--brand-ink); }
#legend .leg-bar {
  height: 13px; border-radius: 3px; margin-bottom: 4px;
  background: linear-gradient(to right, var(--n-cc), var(--n-cc));
}
#legend .leg-labels { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; color: var(--brand-ink); }
#legend #leg-min { margin-right: auto; }
/* The diverging Explore ramps' centre (L4, Taylor 2026-10-23): a tick on the
   bar at the paper midpoint, its value on its own line under the ends. */
#legend .leg-bar.leg-bar-mid { position: relative; }
#legend .leg-bar.leg-bar-mid::after {
  content: ''; position: absolute; left: 50%; top: -3px; bottom: -3px;
  width: 2px; margin-left: -1px; background: var(--n-33); border-radius: 1px;
}
#legend #leg-mid { order: 3; flex-basis: 100%; text-align: center; margin-top: 1px; }
#legend .leg-divider { border-top: 1px solid var(--n-dd); margin: 5px 0 4px; }
#legend .leg-modes-row {
  display: flex; flex-wrap: wrap;
  gap: 4px 10px;
  padding: 1px 0;
}
#legend .leg-mode-chip {
  display: inline-flex; align-items: center; gap: 5px;
}
#legend .leg-mode-swatch {
  display: inline-block; width: 12px; height: 12px;
  border-radius: 2px; flex-shrink: 0;
}
#legend .leg-mode-label { color: var(--brand-ink); }

/* The 5-column `.cum-grid` table (Total / Per km² / Per person / % x one row
   per mode) and its `.ind-btn-sm` dots are gone (SPEC-s22 S4): the table's
   two axes are now the normalization strips (§5.3) and the shared mode chip
   row (§5.1), so the cumulative group is plain `.ind-btn` rows like every
   other group. */

/* ---- Modal-mix bars (mode_bars.js; the names are City Snapshot's, which is gone) ---- */
.snap-tooltip {
  position: absolute; background: var(--n-33); color: var(--on-dark); font-size: 10px;
  padding: 3px 6px; border-radius: 3px; pointer-events: none;
  display: none; white-space: nowrap; z-index: 10;
}
.snap-mode-rows { margin-bottom: 4px; position: relative; }
.snap-mode-row  { display:flex; align-items:center; gap:4px; margin-bottom:2px; }
.snap-mode-lbl  { font-size:9px; color: var(--text-muted); width:88px; flex-shrink:0;
                  text-align:right; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.snapshot-modal-bar {
  display: flex;
  height: 22px;
  border-radius: 4px;
  overflow: hidden;
  margin-bottom: 4px;
}
.snapshot-modal-bar div {
  transition: width 300ms ease;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  font-weight: 600;
  color: var(--on-dark);
  overflow: hidden;
  white-space: nowrap;
}
/* ---- The legend's folds (geo-aggregation S1) ---- */
/* Units, Geography and Display on one row under the label (sketch round 5),
   one open at a time, all closed at load (legend_folds.js); no rules
   between them (round 2). The open fold's heading takes the brand ink. */
#legend .leg-folds { display: flex; gap: 16px; align-items: baseline; margin: 7px 0 0; }
.leg-section-title {
  cursor: pointer; font-size: 11px; font-weight: 700; color: var(--brand-ink);
  padding: 0; user-select: none; white-space: nowrap;
}
.leg-section-title[hidden] { display: none; }
.leg-section-title:hover, .leg-section-title[aria-expanded="true"] { color: var(--brand-ink); }
.leg-section-title:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; border-radius: 2px; }
.collapse-arrow { font-size: 10px; color: var(--text-muted); margin-left: 4px; }
#legend .leg-fold-body { padding: 6px 0 2px; }
/* Units: every strip full width with equal buttons, its label above
   (sketch round 6), so all the strips' edges line up. */
#leg-units-body #leg-params { display: block; margin: 0; }
#leg-units-body .mp-param:not([hidden]) {
  display: flex; flex-direction: column; align-items: stretch; gap: 2px; margin: 0 0 6px;
}
#leg-units-body .mp-param:last-child { margin-bottom: 0; }
#leg-units-body .mp-param-label { font-weight: 400; color: var(--brand-ink); }
#leg-units-body .norm-toggle { margin: 0; width: auto; }
#leg-units-body .norm-btn { flex: 1 1 0; text-align: center; white-space: nowrap; }
/* Geography (geo-aggregation S2): the level strip, laid out as a Units strip. */
#leg-geo-body .mp-param { display: flex; flex-direction: column; align-items: stretch; gap: 2px; margin: 0; }
#leg-geo-body .mp-param-label { font-weight: 400; color: var(--brand-ink); }
#leg-geo-body .norm-toggle { margin: 0; width: auto; }
#leg-geo-body .norm-btn { flex: 1 1 0; text-align: center; white-space: nowrap; }
#leg-geo-body .norm-btn:disabled { opacity: 0.45; cursor: not-allowed; }
/* The dock at a geography unit (info_panel.js renderGeoUnitPanel). */
.geo-unit { padding: 4px 0; }
.geo-unit-name { font-weight: 600; font-size: 15px; color: var(--brand-ink); }
.geo-unit-meta { font-size: 12px; color: var(--brand-ink); margin: 2px 0 6px; }
.geo-unit-table { border-collapse: collapse; font-size: 13px; margin: 0 0 6px; }
.geo-unit-table th { text-align: left; font-weight: 400; color: var(--brand-ink); padding: 1px 12px 1px 0; }
.geo-unit-table td { text-align: right; font-variant-numeric: tabular-nums; padding: 1px 0 1px 14px; }
.geo-flow-bars { margin: 6px 0 8px; max-width: 460px; }
.geo-unit-note { font-size: 12px; color: var(--brand-ink); }
/* Display: Freeze scale first (Compare only, round 7), then the base map,
   opacity, the overlay rows and the Advanced drawer. */
#legend .leg-freeze { display: flex; align-items: center; gap: 6px; margin: 0 0 6px; font-size: 12px; color: var(--brand-ink); cursor: pointer; }
#legend .leg-freeze[hidden] { display: none; }
#legend .leg-freeze input { margin: 0; cursor: pointer; }
#legend-overlays-body #leg-opacity-row { margin: 0 0 6px; }
#legend-overlays-body #leg-advanced { margin: 6px 0 0; }
/* The Map | Satellite pair (basemap.js mountImageryToggle; G4's single
   preview tile until G7): two tiles, each a picture of the view it gives,
   its label over a dark foot so it reads on imagery and parchment alike. The
   active one carries a 2px gold ring and nothing else (no check mark; ruled
   on the G7 sketch) -- the selected-state gold of rung 4, as a shape around
   a picture rather than a fill. The pictures are background images the
   module sets once the fold first shows; until then (or if one fails) a
   tile is plain ground. 64x48 on a desktop; the phone's are 72x54. */
.legend-imagery[hidden] { display: none; }
.imagery-pair { display: flex; gap: 8px; padding: 0 0 8px; }
.imagery-tile {
  position: relative; display: block; width: 64px; height: 48px; margin: 0; padding: 0;
  border: 0; border-radius: 6px; overflow: hidden; cursor: pointer;
  background: var(--n-dd); box-shadow: 0 0 0 1px var(--n-cc);
}
.imagery-tile:hover { box-shadow: 0 0 0 1px var(--brand-slate), 0 1px 4px var(--shade-15); }
.imagery-tile[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--gold), 0 1px 4px var(--shade-15); }
.imagery-tile:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }
.imagery-thumb { position: absolute; inset: 0; background: center / cover no-repeat; }
.imagery-label {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 9px 2px 3px;
  background: linear-gradient(transparent, var(--shade-62));
  color: var(--on-dark); font-size: 10.5px; font-weight: 600; line-height: 1; text-align: center;
}
.imagery-tile[aria-pressed="true"] .imagery-label { font-weight: 800; }

/* ---- 3D toggle button ---- */
/* The legend's 3D button is the state's source (map.js setup3DToggle) but no
   longer a control: 2D/3D has one home on both layouts, the #ctrl-3d map
   control sheets.js relays to it (TABLE row 23). */
#btn-3d-toggle { display: none !important; }
#ctrl-3d button { font-size: 11px; font-weight: 700; color: var(--brand-ink); display: flex; align-items: center; justify-content: center; }
#ctrl-3d button:disabled { color: var(--n-bb); }

/* MapLibre zoom control margin */
.maplibregl-ctrl-top-right { margin-top: 10px; }

/* MapLibre's own furniture on the map -- the control group and the attribution
   bar -- ships white in maplibre-gl.css. They are chrome the reader sees inside
   our applet, so they take the same grounds as the legend beside them: the
   raised ground for the buttons, the raised veil for the attribution (which sits
   over the basemap and needs to stay translucent). Cartography pass, 2026-09-12. */
.maplibregl-ctrl-group { background: var(--ground-raised); }
.maplibregl-ctrl-attrib,
.maplibregl-ctrl-attrib.maplibregl-compact { background-color: var(--veil-ground-94); }
.maplibregl-ctrl-attrib a { color: var(--brand-slate); }

/* ---- Scenario picker ---- */
#scenario-picker {
  border-bottom: 1px solid var(--n-dd);
  padding: 7px 10px 8px;
  background: var(--ground-band);
}
.picker-label {
  font-size: 10px; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--text-muted); display: block; margin-bottom: 4px;
}
#scenario-picker select {
  width: 100%; padding: 4px 6px; border: 1px solid var(--n-cc);
  border-radius: 4px; background: var(--n-ff); font-size: 12px; cursor: pointer;
  text-overflow: ellipsis; overflow: hidden; white-space: nowrap;
  min-width: 0;
}

/* ---- Unified scenario table (axes + flat-scenario, Explore + Compare) ----
   CSS grid; axis-name column is capped + wraps so the dropdown columns get
   the bulk of the panel width. */
.scenario-table {
  display: grid;
  gap: 4px 6px;
  align-items: center;
  margin-top: 4px;
}
.scenario-table-explore {
  grid-template-columns: minmax(0, 110px) minmax(0, 1fr);
}
.scenario-table-compare {
  grid-template-columns: minmax(0, 110px) minmax(0, 1fr) minmax(0, 1fr);
}
.scenario-table .st-cell {
  min-width: 0;
}
.scenario-table .st-header {
  font-size: 9px; font-weight: 600; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--text-muted);
  text-align: center;
}
.scenario-table .st-header.st-axis-name { text-align: left; }
.scenario-table .st-axis-name {
  font-size: 11px; color: var(--brand-ink); font-weight: 600;
  line-height: 1.15;
  padding-right: 4px;
  word-wrap: break-word;
}
.scenario-table .st-header.st-ref {
  position: relative;
}
.scenario-table select {
  width: 100%; padding: 3px 4px; border: 1px solid var(--n-cc);
  border-radius: 4px; background: var(--n-ff); font-size: 10px; cursor: pointer;
  text-overflow: ellipsis; overflow: hidden; white-space: nowrap;
  min-width: 0;
}
.scenario-table select option:disabled { color: var(--n-bb); }

/* ---- Explore/Compare toggle strip ---- */
.mode-toggle-strip {
  display: flex; border: 1px solid var(--n-cc); border-radius: 4px;
  overflow: hidden;
}
.scenario-section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 4px;
}
.mode-btn {
  flex: 1; padding: 4px 8px; border: none;
  background: var(--n-ff); font-size: 11px; cursor: pointer; color: var(--brand-ink);
  font-weight: 600;
}
.mode-btn:hover { background: var(--hover); }
.mode-btn.active { background: var(--sel); color: var(--on-accent); }


/* ---- The aggregate dock's per-profile decomposition (PLAN-s37 S4) ----
   The map's number is a mixture over ~100 traveller profiles; this table is
   what it is a mixture OF, collapsed income → car ownership → real class. It
   rides the statistics panels' two-column shape (.stp-cols), so the tree gets
   the table column's own scroller — 100 leaves will not fit a 148px dock and
   the scroller is the visible failure mode a clip would not be. */
#mp-body .pdc-tbl { border-collapse: collapse; width: auto; }
#mp-body .pdc-tbl th { white-space: nowrap; font-weight: 600; }
#mp-body .pdc-tbl thead th { position: sticky; top: 0; background: var(--ground-trim); z-index: 1; }
#mp-body .pdc-tbl td { white-space: nowrap; }
/* The label column is the SECOND cell (the radio is the first), so the
   mode-table rule that right-aligns everything after the first cell was
   pushing the tree's names to the right and hiding the depth indent that is
   the tree's whole shape (found on the trips dock's copy, session 29). */
#mp-body .pdc-tbl thead tr:first-child th:nth-child(2),
#mp-body .pdc-tbl td:nth-child(2) { text-align: left; }
/* The one interactive control in the dock: a branch row toggles its children.
   Leaves are inert, and say so by not changing the cursor. */
#mp-body .pdc-tbl tr.pdc-open { cursor: pointer; }
#mp-body .pdc-tbl tr.pdc-open:hover td { background: var(--hover); }
#mp-body .pdc-tbl tr.pdc-total td {
  color: var(--brand-ink); border-bottom: 1px solid var(--n-bb);
}
/* The access tree in Compare (slice 5, the flat Per-profile change table
   folded in): a row whose gain share is under half its population share is
   tinted, the flat table's rule; the checked row's gold below still wins.
   The "Per period" header sorts the tree by that column. */
#mp-body .pdc-tbl tr.pdc-behind td { background: var(--neg-tint); }
#mp-body .pdc-tbl tr.pdc-behind.pdc-open:hover td { background: var(--neg-tint-2); }
#mp-body .pdc-tbl th.pdc-sort { cursor: pointer; text-decoration: underline dotted; text-underline-offset: 2px; }
#mp-body .pdc-tbl th.pdc-sort:hover, #mp-body .pdc-tbl th.pdc-sort:focus-visible { background: var(--hover); }
#mp-body .pdc-tbl th.pdc-sorted { color: var(--brand-ink); }
/* ---- The population selector (DESIGN-uc-radios, 2026-09-12) ----
   One radio per visible row, Total included: the tree is a control now, not
   only a readout. Its own narrow column, because selecting a row and expanding
   it are independent gestures and a shared hit target would make one of them
   unreachable. `line-height: 0` on the cell keeps a 13px control from setting
   the row height taller than the 9px mode bar beside it already does. */
#mp-body .pdc-tbl th.pdc-radio, #mp-body .pdc-tbl td.pdc-radio {
  width: 18px; padding: 0 3px; text-align: center; line-height: 0;
}
#mp-body .pdc-tbl .pdc-radio-in {
  margin: 0; cursor: pointer; vertical-align: middle;
  width: 12px; height: 12px;
}
/* The checked row is the population the map, the legend and the lead columns
   are showing, so it reads as selected in the brand's one selected idiom:
   a gold fill with the label's own ink on it (rung 4 — gold is a shape with
   mass, never text). Colour only, no weight: in this table bold belongs to
   the most likely mode of each row, and neither the checked row nor the
   Total row may take it (Taylor, 2026-09-14; supersedes the 2026-09-12
   "both" ruling). After the hover rule above, so a hovered checked row stays
   gold rather than dropping to the neutral hover wash — the two are the same
   family, and the selected state is the stronger statement. */
#mp-body .pdc-tbl tr.pdc-checked td { background: var(--sel-soft); }
#mp-body .pdc-tbl tr.pdc-checked:hover td { background: var(--sel-soft-hover); }
/* A COLLAPSED branch the checked row is folded inside. D1 says visibility is
   the reader's gesture, so the tree does not open itself to reveal the
   selection — but an unmarked fold would leave "no radio is checked" meaning
   two different things (it is hidden / the selection has no row at all). The
   expander triangle goes gold; a whole gold row would claim the branch IS the
   selection, and it only contains it. */
#mp-body .pdc-tbl tr.pdc-anc .pdc-exp { color: var(--gold-deep); }
.pdc-exp { display: inline-block; width: 11px; color: var(--text-muted); font-size: 9px; }
.pdc-exp--leaf { color: transparent; }
/* Member count / profile count — present but never competing with the label. */
.pdc-n { color: var(--text-muted); font-size: 9px; }
.pdc-nodata { color: var(--n-bb); font-size: 10px; }
/* Stacked mode-share bar. Fixed width so the rows read as a column of
   comparable bars rather than a ragged edge; the numbers are in the title. */
.pdc-bar {
  display: inline-flex; width: 84px; height: 9px; vertical-align: middle;
  border-radius: 2px; overflow: hidden; background: var(--n-f0);
}
.pdc-bar i { display: block; height: 100%; }
.pdc-hint, .pdc-note { font-size: 10px; color: var(--text-muted); line-height: 1.35; }
.pdc-hint { max-width: 320px; }
.pdc-note { margin-top: 4px; }

/* ---- ...and its compare twin: one tree, A | B | Δ per measure ----
   Same rows on both sides, so the two scenarios are merged into one table
   rather than juxtaposed — see `renderCompareProfileDecomp` for why. That buys
   row alignment and costs two header rows, which is the only thing here that
   needs its own CSS: the base rule sticks every `thead th` at `top: 0`, and
   with two rows the second one has to be offset by the first one's height.

   Measured against the scroller at three scroll positions, not guessed
   (`s45_compare_decomp_shot.mjs` prints it): the group row is 15.5px tall, so
   the second row pins at 15 and the two overlap by half a pixel. Deliberately
   a hair under rather than over — an overlap hides nothing, where a gap would
   show a sliver of a scrolling body row between the two header rows.

   ⚠ When re-measuring: "Who travels", "Mode split" and "Most likely" are
   `rowspan=2`, so the FIRST cell of the group row has the height of BOTH rows.
   Reading it instead of a `colspan` cell reports a 16px overlap that is not
   there, and sends you looking for a sticky bug that does not exist — it did,
   twice, while this was being built. The sticky is on the cells, like the
   explore twin's; it does not need to be on the rows. */
#mp-body .pdc-cmp thead tr.pdc-grp th { top: 0; }
#mp-body .pdc-cmp thead tr.pdc-sub th { top: 15px; }
#mp-body .pdc-cmp thead tr.pdc-grp th[colspan] {
  text-align: center; border-bottom: 1px solid var(--n-e8);
}
/* The sub-header names a side, not a quantity, so it reads quieter than the
   group label above it. */
#mp-body .pdc-cmp thead tr.pdc-sub th { color: var(--text-muted); font-weight: 500; }
/* One hairline at the end of each measure group: with twelve columns the eye
   needs to know where "Share of A→B" stops and "P(d|o)" starts. */
#mp-body .pdc-cmp .pdc-dcol { border-right: 1px solid var(--n-ee); padding-right: 6px; }
/* A and B's mode bars stacked in one cell. Two bars rather than two columns:
   a Δ of a stacked bar is not a stacked bar, and this costs one row of height
   instead of ~170px of width in a table that is already twelve columns wide. */
.pdc-bars {
  display: grid; grid-template-columns: auto auto;
  gap: 1px 3px; align-items: center;
}
.pdc-bars-k { color: var(--text-muted); font-size: 8px; line-height: 1; }
/* Half-height bars so a two-bar cell is not a two-line row. */
.pdc-bars .pdc-bar { height: 7px; }
/* A disagreement between the rows and the baked average they decompose is a
   defect, not a display state — it is loud on purpose. */
.pdc-warn {
  font-size: 10px; line-height: 1.35; margin-top: 4px;
  color: var(--warn-text-2); background: var(--warn-bg-2); border: 1px solid var(--warn-border-2);
  border-radius: 3px; padding: 3px 5px; max-width: 380px;
}

/* ---- The level roster's chip row (PROPOSAL-level-roster §4) ----
   Below the table, and quiet: this is a control over the VIEW, and the table
   above it is the thing being read. So it takes the note's ink and size rather
   than the label column's, and the only thing at full strength is the criterion
   name the reader is aiming at.

   The buttons are 20px square — the dock's touch target for a control that is
   not a row — around an 11px glyph, so the hit area is bigger than the mark.
   A disabled end button keeps its space rather than collapsing: the chips would
   otherwise change width as they moved past the ends, and a row of controls
   that reflows under your finger is the phone bug this workstream keeps
   finding. */
#mp-body .pdc-levels {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
  margin-top: 8px; padding-top: 6px; border-top: 1px solid var(--n-e3);
  font-size: 10px; color: var(--text-muted);
}
#mp-body .pdc-levels-cap { margin-right: 2px; }
#mp-body .pdc-chip {
  display: inline-flex; align-items: center;
  border: 1px solid var(--n-ee); border-radius: 3px; background: var(--n-f0);
}
#mp-body .pdc-chip-name {
  padding: 0 2px; font-size: 10px; color: var(--brand-ink); white-space: nowrap;
}
#mp-body .pdc-chip-btn {
  width: 20px; height: 20px; padding: 0; line-height: 1;
  border: 0; background: none; cursor: pointer;
  font-size: 11px; color: var(--text-muted);
}
#mp-body .pdc-chip-btn:hover:not(:disabled) { color: var(--brand-ink); background: var(--hover); }
/* Spacing kept, affordance withdrawn -- see the reflow note above. */
#mp-body .pdc-chip-btn:disabled { color: var(--n-ee); cursor: default; }
/* (A criterion with one value in this scope is left out of the row since
   2026-10-24, decompose.js levelChips; the greyed .pdc-chip--flat is gone.) */

/* ---- The why-table under the tree (Taylor, 2026-09-15) ----
   Same scroller as the tree, below it: the reader scrolls down to it at
   180px. A rule and a caption mark the switch from WHO to WHY. The single
   class's table is `.pn-decomp` as the retired mode panel rendered it; the
   mixture's is the same class with fewer columns. Its thead is NOT sticky —
   two sticky headers in one scroller would stack. */
#mp-body .pdc-why { margin-top: 10px; padding-top: 6px; border-top: 1px solid var(--n-e3); }
#mp-body .pdc-why-cap { font-size: 11px; color: var(--brand-ink); margin-bottom: 3px; }
#mp-body .pdc-why .pn-decomp thead th { position: static; }
#mp-body .pdc-why .pn-decomp { width: auto; }

/* Plain-language explainer for the deviation-painted access rows (s58/s59):
   rendered in the dock rather than hover-only, so phone readers get it too.
   Muted like the util note, a step larger for readability, capped so it stays
   a caption and never competes with the numbers. A one-row disclosure that is
   the LAST thing in the dock, zone or region alike (Taylor, 2026-09-18) —
   below the lead, the tables and the tree, so the numbers are never pushed
   apart by it. The dock body is a flex column, so the toggle row is always in
   view and the columns above it take what is left. */
#mp-body .pn-explain, #mp-placeholder .pn-explain {
  color: var(--text-muted); font-size: 10.5px; line-height: 1.4;
  margin: 6px 0 2px; max-width: 60em;
  border-top: 1px solid var(--n-dd); padding-top: 5px;
}
#mp-body .pn-explain, #mp-body .pn-explain-toggle { flex: 0 0 auto; }
#mp-placeholder .pn-explain {
  text-align: left; margin: 10px auto 0; border-top: none;
  max-width: 46em; color: var(--text-muted); font-size: 11px;
}
#mp-body .pn-explain-toggle {
  color: var(--text-muted); font-size: 10.5px; margin-top: 6px;
  cursor: pointer; user-select: none;
  border-top: 1px solid var(--n-dd); padding-top: 5px;
}
#mp-body .pn-explain-toggle:hover { color: var(--brand-ink); }
/* Under the toggle the body needs no second hairline. */
#mp-body .pn-explain-toggle + .pn-explain { border-top: none; padding-top: 0; margin-top: 3px; }

/* The traveller chip on a row that takes no class (D1, app.js syncSubControls). */
#mp-profile.mp-profile--row-off { display: none !important; }

/* The induced-demand note under BY PROFILE's "Grouped by" row and in the explainer
   (info_panel.js elasticBenefitNote; Taylor, 2026-10-24). Muted, like the other dock notes. */
.pn-elastic-note { font-size: 11px; line-height: 1.4; color: var(--brand-slate); margin: 4px 0 8px; }
