/* Morning brief: reading takes precedence over squeezing a whole city into one frame.
   Maps, HUDs, charts and tickers are retained; evidence expands in place. */
.dashboard-shell { font-size: 16px; line-height: 1.5; }
.dashboard-shell .brief-item { font-size: 16px; line-height: 1.55; }
.dashboard-shell .brief-card { padding: 16px; }
.dashboard-shell .brief-disclosure { display: block; min-height: 0; padding-top: 8px; padding-bottom: 8px; }
.dashboard-shell .brief-kicker { font-size: 16px; line-height: 1.4; font-weight: 700; }
.brief-disclosure > summary, #insightsBar > summary {
  cursor: pointer; min-height: 44px; padding: 10px 0; display: list-item;
}
.brief-disclosure[open] > summary { margin-bottom: 10px; }
#insightsBar > summary { padding: 12px 16px; }
.dashboard-shell .brief-list { gap: 12px; }
.dashboard-shell .brief-item { grid-template-columns: 24px minmax(0, 1fr); overflow-wrap: anywhere; }
.dashboard-shell .ctrl-btn, .dashboard-shell .lang-btn,
.dashboard-shell .scope-btn, .dashboard-shell .dim-btn,
.dashboard-shell .segmented-control button, .dashboard-shell .action-btn,
.dashboard-shell .tray-tab, .dashboard-shell .web-app-button {
  min-height: 44px; font-size: 14px; padding: 8px 12px; line-height: 1.35;
}
.dashboard-shell .scope-toggle, .dashboard-shell .dim-toggle { height: auto; min-height: 44px; }
.dashboard-shell .controls-row, .dashboard-shell .runtime-badges { flex-wrap: wrap; }
.dashboard-shell .today-brief { display: none; } /* stale payload-derived duplicate; current brief is below */
.dashboard-shell .verdict-verb { font-size: 24px; white-space: normal; }
.dashboard-shell .verdict-why { font-size: 16px; line-height: 1.5; display: block; overflow: visible; }
.dashboard-shell .verdict-chip { font-size: 14px; padding: 6px 8px; }
.dashboard-shell .metric-band { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.building-tools { flex: 0 0 auto; padding: 12px; border-top: 1px solid var(--line-strong); background: var(--hud-bg); }
.building-buttons { display: flex; flex-wrap: wrap; gap: 8px; }
.building-tools button, .building-tools .building-buttons a {
  font: 600 14px/1.4 var(--font-sans); min-height: 44px; padding: 10px 12px;
  color: var(--ink); background: var(--card-bg); border: 1px solid var(--line-strong);
  cursor: pointer; text-decoration: none;
}
.building-tools button[aria-pressed="true"] { border-width: 2px; border-color: var(--cyan); }
.building-tools button:disabled { opacity: .6; cursor: default; }
.building-tools p, .building-tools summary { font-size: 14px; line-height: 1.5; margin: 8px 0; }
.building-tools summary { cursor: pointer; min-height: 44px; padding: 10px 0; }
.building-tools a { color: var(--cyan); font-size: 14px; margin-right: 12px; overflow-wrap: anywhere; }
#buildingSelection { display: flex; gap: 12px; flex-wrap: wrap; border: 2px solid var(--cyan); padding: 10px; }
#buildingSelection[hidden] { display: none; }
.building-tools a:focus-visible { outline: 2px solid var(--cyan); outline-offset: 3px; }
.dashboard-shell .map-3d .cesium-viewer-bottom,
.dashboard-shell .map-3d .cesium-widget-credits { display: block !important; }
.dashboard-shell #metricBand .metric-card { grid-column: auto; }
.dashboard-shell .metric-label, .dashboard-shell .metric-context { font-size: 14px; white-space: normal; }
.dashboard-shell .metric-value { font-size: 24px; }
.dashboard-shell .map-summary { font-size: 14px; white-space: normal; }
.dashboard-shell .map-controls { flex-wrap: wrap; overflow-x: auto; }
.dashboard-shell .map-controls .segmented-control { flex-wrap: wrap; }
.dashboard-shell .rail-disclosure-summary, .dashboard-shell .tray-tab { min-height: 44px; font-size: 14px; }
.dashboard-shell .posture-detail, .dashboard-shell .fa-reason, .dashboard-shell .hg-name,
.dashboard-shell .hg-brief, .dashboard-shell .insights-bar-text { font-size: 16px; line-height: 1.5; }
.dashboard-shell .hg-name small, .dashboard-shell .hg-observed, .dashboard-shell .hg-band,
.dashboard-shell .hg-live, .dashboard-shell .pulse-note { font-size: 14px; line-height: 1.5; }
.hg-observed { margin-top: 6px; color: var(--soft); }
.hg-observed:not([data-status="recent"]) { color: var(--amber); font-weight: 700; }
.cascade-stale { padding: 12px; font-size: 16px; color: var(--amber); border: 2px solid var(--amber); }
.dashboard-shell .hg-level { font-size: 20px; }
.dashboard-shell summary:focus-visible, .dashboard-shell button:focus-visible {
  outline: 2px solid var(--cyan); outline-offset: 3px;
}
html[data-text-scale="large"] .dashboard-shell .brief-item,
html[data-text-scale="large"] .dashboard-shell .verdict-why,
html[data-text-scale="large"] .dashboard-shell .hg-name,
html[data-text-scale="large"] .dashboard-shell .hg-brief { font-size: 20px; }
html[data-text-scale="large"] .dashboard-shell .brief-kicker,
html[data-text-scale="large"] .dashboard-shell .hg-observed,
html[data-text-scale="large"] .dashboard-shell .metric-context { font-size: 18px; }
html[data-text-scale="large"] .dashboard-shell .controls-row button { font-size: 16px; min-height: 48px; }
#briefNow:focus-visible { outline: 2px solid var(--cyan); outline-offset: 3px; }
@media screen and (min-width: 1041px) {
  /* One-screen command board (28 Sep brief): the page never scrolls, each
     column scrolls inside itself. styles.css owns the frame; this block only
     keeps the reading-scale chrome that fits inside it. */
  .dashboard-shell .masthead { grid-template-columns: minmax(0, 1fr); }
  .dashboard-shell .verdict-strip { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .dashboard-shell .verdict-chips { grid-column: 1 / -1; justify-content: flex-start; flex-wrap: wrap; }
  .dashboard-shell .operator-tools { flex-wrap: wrap; }
  .dashboard-shell .col-map .map-panel { flex: 1 1 auto; min-height: 0; height: auto; }
  .dashboard-shell .col-map.hero-map-active .building-tools { display: none !important; }
  .dashboard-shell .col-map.hero-map-active .deep-tray { flex-basis: 32px !important; max-height: 32px !important; min-height: 32px !important; }
}
@media (max-width: 1040px) {
  .dashboard-shell .masthead { grid-template-columns: minmax(0, 1fr); }
  .dashboard-shell .insights-bar-inner { grid-template-columns: minmax(0, 1fr); }
  .dashboard-shell .brief-strip { grid-template-columns: minmax(0, 1fr); }
  .dashboard-shell .map-panel { min-width: 0; }
}
@media (max-width: 720px) {
  .dashboard-shell .metric-band { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dashboard-shell .controls-row .ctrl-btn, .dashboard-shell .controls-row .lang-btn { padding: 8px 2px; }
  .dashboard-shell .verdict-strip { padding: 16px; gap: 12px; }
}

/* Map-first workbench. This final layer owns chrome across all breakpoints;
   no small-type shortcut: secondary tools expand only when requested. */
html, body { overflow-x: clip; }
.dashboard-shell .masthead {
  display:flex; align-items:center; justify-content:space-between; gap:8px;
  min-height:48px; padding:0 8px; position:relative; z-index:1002; box-sizing:border-box;
  border-bottom:2px solid var(--line-strong);
}
.dashboard-shell .masthead .title-block { display:block; min-width:0; padding:0; flex:1; }
.dashboard-shell .masthead .title-block h1 {
  font-size:16px; line-height:1.25; margin:0; white-space:nowrap; text-overflow:ellipsis;
  overflow:hidden; letter-spacing:0; text-shadow:none;
}
.dashboard-shell .masthead .eyebrow, .dashboard-shell .masthead .summary-lead,
.dashboard-shell .masthead .ioc-brand-mark { display:none; }
.view-tools { flex:none; }
.view-tools > summary, .map-options > summary {
  cursor:pointer; font:600 14px/1.4 var(--font-sans); min-height:44px;
  padding:12px 8px; box-sizing:border-box; color:var(--ink);
}
.view-tools-body {
  position:absolute; top:100%; right:0; width:min(540px,100%);
  max-height:calc(100dvh - 60px); overflow-y:auto; background:var(--card-bg);
  padding:12px; border:2px solid var(--line-strong); box-sizing:border-box;
}
.view-tools:not([open]) > .view-tools-body { display:none; }
.dashboard-shell .view-tools .status-block { display:flex; flex-direction:column; align-items:stretch; gap:12px; padding:0; }
.dashboard-shell .view-tools .controls-row { display:flex; flex-wrap:wrap; gap:8px; }
.dashboard-shell .view-tools .controls-row > button { width:auto; flex:none; min-width:44px; }
.dashboard-shell .view-tools .segmented-control { display:flex; flex-wrap:wrap; min-height:44px; height:auto; }
.dashboard-shell .view-tools button, .dashboard-shell .map-options button,
.dashboard-shell .map-options .building-buttons a {
  font:600 14px/1.35 var(--font-sans); min-height:44px; min-width:44px; padding:8px 12px;
}
.dashboard-shell .view-tools .partner-row { display:flex; flex-wrap:wrap; gap:8px; }
.dashboard-shell .view-tools .runtime-detail { display:block; font-size:14px; white-space:normal; }
.dashboard-shell .view-tools .operator-tools {
  display:flex; gap:8px; flex-wrap:wrap; padding:12px 0 0; margin:12px 0 0;
  border-top:1px solid var(--line-strong); border-bottom:0;
}
.dashboard-shell .view-tools .operator-tools p { font:14px/1.5 var(--font-sans); }
.dashboard-shell .view-tools .operator-manual-button { margin:0; }
.dashboard-shell .view-tools .web-app-button { width:auto; font-size:14px; }
.map-entry { display:flex; gap:8px; padding:4px 8px; align-items:center; flex-wrap:wrap; flex:none; border-bottom:1px solid var(--line-strong); }
.map-entry > button {
  background:var(--card-bg); color:var(--ink); border:1px solid var(--line-strong);
  font:600 14px/1.35 var(--font-sans); min-height:44px; padding:8px; cursor:pointer;
}
.map-entry > button[aria-pressed="true"] { border:2px solid var(--cyan); }
.map-entry > button:disabled { opacity:.6; }
.map-entry .map-options { margin-left:auto; }
.map-options[open] { flex:1 0 100%; margin:0; }
.dashboard-shell .map-options .map-head { padding:8px 0; display:flex; flex-direction:column; align-items:stretch; gap:8px; }
.dashboard-shell .map-options .map-controls, .dashboard-shell .map-options .segmented-control { display:flex; flex-wrap:wrap; gap:8px; height:auto; }
.dashboard-shell .map-options .building-tools { display:block !important; padding:8px 0; }
.dashboard-shell .col-map.hero-map-active .map-options .building-tools { display:block !important; }
.dashboard-shell .map-options .building-tools p, .dashboard-shell .map-options .building-tools summary { font-size:14px; }
.regional-status { font:14px/1.4 var(--font-sans); padding:4px 8px; margin:0; flex:none; }
.regional-selection { font:16px/1.5 var(--font-sans); margin:0; padding:8px; border-left:3px solid var(--cyan); overflow-wrap:anywhere; }
.regional-selection a { color:var(--cyan); padding:8px; display:inline-block; }
.regional-attribution { display:block; font:12px/1.5 var(--font-sans); color:var(--ink); white-space:normal; overflow-wrap:anywhere; max-width:100%; }
.dashboard-shell .map-footer { flex-wrap:wrap; }
.dashboard-shell .map-footer .regional-attribution { flex:1 0 100%; }
.dashboard-shell .map-footer .regional-attribution[hidden], .regional-selection[hidden] { display:none; }
@media(min-width:1041px) {
  .dashboard-shell .masthead { min-height:46px; padding:0 12px; }
  .view-tools > summary { min-height:44px; padding:12px 8px; }
  .dashboard-shell .verdict-strip { padding:6px 12px; gap:6px; }
  .dashboard-shell .verdict-verb { font-size:20px; }
  .dashboard-shell .verdict-why { font-size:14px; }
}
/* Three named areas need three explicit tracks at every desktop width; with
   two, the rail took an auto track and squeezed the map to 0px (16 Oct). */
@media(min-width:1041px) {
  .dashboard-shell .board-grid.war-room { grid-template-columns:clamp(250px,19vw,340px) minmax(0,1fr) clamp(290px,24vw,420px); }
}
@media(max-width:1040px) {
  .dashboard-shell { display:flex; flex-direction:column; height:auto; overflow:visible; }
  .dashboard-shell .masthead { order:0; }
  .dashboard-shell .board-grid.war-room { order:1; display:flex; flex-direction:column; min-height:0; }
  .dashboard-shell .col-now, .dashboard-shell .col-map { display:contents; }
  .dashboard-shell .map-panel { order:0; height:auto; min-height:0; }
  .dashboard-shell #metricBand { order:1; }
  .dashboard-shell .brief-strip { order:2; }
  .dashboard-shell .insights-bar { order:3; }
  .dashboard-shell .situation-rail { order:4; }
  .dashboard-shell .news-rail { order:5; }
  .dashboard-shell .deep-tray { order:6; }
  .dashboard-shell .verdict-strip { order:2; }
  .dashboard-shell .ticker-bar { order:3; }
  .dashboard-shell .telemetry-strip, .dashboard-shell .bottom-bar { order:4; }
  .dashboard-shell .map-frame { height:clamp(380px,calc(100dvh - 170px),720px); min-height:380px; flex:none; }
  .dashboard-shell .map-footer .watchpoint-list { display:none; }
  .dashboard-shell .map-entry { gap:8px; }
  .dashboard-shell .map-entry > button { font-size:13px; }
  .map-options > summary { font-size:13px; padding:12px 0; }
}
html:lang(zh) .view-tools, html:lang(zh) .regional-status { letter-spacing:0; line-height:1.6; }

/* ======================================================================
   RAMS PASS (18 Oct 2026). One owner for type, colour and line, last in
   the cascade on purpose. Earlier layers in styles.css and this file came
   from several agents and left 14 text sizes, three gradients, rounded
   chips, glows, italics and 14 coloured side bars. This block replaces all
   of that with one rule set:
   - three sizes: display (hero numbers, the verdict), body, micro (labels)
   - one accent (--cyan: civic navy in light, cyan in dark) for interaction
   - severity colour only on the words that carry severity, never as fills,
     glows or bars
   - lines by role: hairline between items, a stronger rule between zones
   ====================================================================== */
:root { --t-display: 22px; --t-body: 14px; --t-micro: 11px; --font-display: var(--font-sans); }
html[data-text-scale="large"] { --t-display: 26px; --t-body: 17px; --t-micro: 13px; }

.dashboard-shell :where(:not(svg, svg *, .leaflet-container *, .map-3d *)) {
  font-size: var(--t-body) !important;
  font-family: var(--font-sans) !important;
  font-style: normal !important;
  text-shadow: none !important;
  border-radius: 0 !important;
  background-image: none !important;
}
.dashboard-shell :where(:not(svg, svg *, .leaflet-container *, .map-3d *, .tray-tab, :focus-visible)) { box-shadow: none !important; }
.dashboard-shell :where(.legend-dot, .ward-cat-dot, .wr-dot, .pulse-indicator) { border-radius: 50% !important; }
.dashboard-shell .pulse-indicator { animation: none !important; }

/* Lines by role. Side bars were severity-as-decoration; severity now lives
   in the verb and kicker text. */
.dashboard-shell :where(.verdict-strip, .gp-lane, .urban-toggle, .rain-regime, .cascade-stale, .flood-action-card,
  .posture-block, .operation-card, .signal-card, .councillor-card, .flight-row, .regional-selection, .brief-card) {
  border-left: 0 !important;
}
.dashboard-shell :where(.verdict-strip, .insights-bar-text, .panel-head, .news-intake-card, .news-intake-item, .gp-lane,
  .tray-tabs, .forecast-card, .flood-action-card, .hydro-gauge-panel, .posture-block, .operation-card, .cr-empty,
  .signal-card, .official-pulse-block, .mpp-contact-card, .map-head, .col-now, .ward-category-bar) {
  background-color: transparent !important;
}
.dashboard-shell :where(.map-entry > button, .building-buttons button, .building-buttons a, .scope-btn, .ward-cat-chip,
  .segmented-control button, .verdict-chip, .action-btn) {
  background-color: var(--card-bg) !important;
  border: 1px solid var(--line-strong) !important;
}
/* Active state is a border-colour change, not a second ring. The previous
   rule also set an inset 1px shadow on the same edge the border already
   occupies, which drew a 2px edge and read as fake depth (rule 5 / 7). */
.dashboard-shell :where(.map-entry > button[aria-pressed="true"], .scope-btn.active, .ward-cat-chip.active,
  .segmented-control button.active, .building-buttons button[aria-pressed="true"]) {
  border-color: var(--cyan) !important;
  color: var(--cyan) !important;
}
.dashboard-shell .verdict-chip { color: var(--ink) !important; font-weight: 500; }
.dashboard-shell :where(.hg-observed, .hg-observed:not([data-status="recent"])) { color: var(--soft) !important; font-weight: 400 !important; }
.dashboard-shell .cascade-stale { border: 1px solid var(--line-strong) !important; color: var(--ink) !important; }

/* Zone rules: one strong line where one region of the board meets another. */
.dashboard-shell .masthead, .dashboard-shell .ticker-bar { border-bottom: 1px solid var(--line-strong) !important; }
.dashboard-shell .verdict-strip { border-bottom: 2px solid var(--ink) !important; }

/* Labels read as labels: micro, tracked, uppercase only where they already were. */
.dashboard-shell :where(.kicker, .section-label, .fa-kicker, .hg-title, .gp-kicker, .rr-kicker) { color: var(--soft) !important; letter-spacing: 0.06em; }

/* Rule 3 (readable line length). Measured 18 Oct 2026 at 1024 px: the brief
   column goes full-bleed on tablet and its prose ran to 137 characters —
   well past the 75-char ceiling. At 1440 px the verdict reason and the 3D
   status line ran 95–117ch. Cap the measure instead of adding a column, so
   the map keeps its width. 65ch at 14px body ≈ 100 characters, which is the
   top of the band; tighter would fight the three-column grid. */
.dashboard-shell :where(.brief-item, .verdict-why, .regional-status, .operation-card p,
  .flood-action-card p, .posture-block p, .signal-card p, .gp-item p, .news-intake-item p,
  .insights-bar-label, .ward-risk-note, .brief-kicker, .metric-context, .metric-label) {
  max-width: 68ch !important;
}
/* Prose inside a narrow rail already fits; don't let the cap letterbox it.
   Scoped to the wide layout on purpose: at <=1040px the left column becomes
   `display: contents` and the brief runs full-bleed, which is exactly the
   case the cap exists for. An unscoped `.col-now` opt-out silently disabled
   the measure on tablet — measured 137ch before this was scoped. */
@media screen and (min-width: 1041px) {
  .dashboard-shell :where(.situation-rail, .col-now) :where(.brief-item, .verdict-why, .regional-status) {
    max-width: none !important;
  }
}
/* Generated 18 Oct 2026 from the rendered page: every text style that was
   >=18px is display, every one that averaged <=11.5px is micro, the rest is body. */
.dashboard-shell .verdict-verb,
.dashboard-shell .metric-value,
.dashboard-shell .flood-value,
.dashboard-shell .fa-watch-head strong,
.dashboard-shell .hg-level,
.dashboard-shell .val,
.dashboard-shell .stat-val,
.dashboard-shell .g-val { font-size: var(--t-display) !important; line-height: 1.15 !important; }
.dashboard-shell .glyph,
.dashboard-shell .runtime-badge span,
.dashboard-shell .section-stamp,
.dashboard-shell .lang-badge,
.dashboard-shell .kicker,
.dashboard-shell .news-intake-badge,
.dashboard-shell .news-intake-card span,
.dashboard-shell .news-intake-note,
.dashboard-shell .news-intake-item-head strong,
.dashboard-shell .news-intake-item-head span,
.dashboard-shell .news-intake-title,
.dashboard-shell .rail-disclosure-summary span,
.dashboard-shell .news-digest-tab,
.dashboard-shell .news-digest-badge,
.dashboard-shell .news-digest-title,
.dashboard-shell .news-digest-time,
.dashboard-shell .gp-kicker,
.dashboard-shell .gp-totals,
.dashboard-shell .gp-lane-label,
.dashboard-shell .gp-lane-count,
.dashboard-shell .gp-unit,
.dashboard-shell .gp-narrative,
.dashboard-shell .gp-badge,
.dashboard-shell .metric-unit,
.dashboard-shell .metric-label,
.dashboard-shell .metric-context,
.dashboard-shell .src-name,
.dashboard-shell .src-fresh,
.dashboard-shell .ward-cat-count,
.dashboard-shell .map-compass-label,
.dashboard-shell .coord-label,
.dashboard-shell .coord-value,
.dashboard-shell .coord-copy,
.dashboard-shell .legend-item,
.dashboard-shell .watchpoint-list span,
.dashboard-shell .rr-kicker,
.dashboard-shell .rr-method summary,
.dashboard-shell .rr-method p,
.dashboard-shell .rr-method a,
.dashboard-shell .rr-radar span,
.dashboard-shell .hz-counts span,
.dashboard-shell .hz-counts a,
.dashboard-shell .hz-note,
.dashboard-shell .hz-note a,
.dashboard-shell .delta-stamp,
.dashboard-shell .delta-since,
.dashboard-shell .cascade-title,
.dashboard-shell .cascade-explain,
.dashboard-shell .cascade-fan-caption span,
.dashboard-shell .cascade-table th,
.dashboard-shell .cascade-table td,
.dashboard-shell .c-lag,
.dashboard-shell .c-muted,
.dashboard-shell .cascade-band,
.dashboard-shell .section-label,
.dashboard-shell .forecast-badge,
.dashboard-shell .forecast-sub,
.dashboard-shell .fc-card-label,
.dashboard-shell .fc-unit,
.dashboard-shell .fc-outlook,
.dashboard-shell .fc-glyph,
.dashboard-shell .fc-range,
.dashboard-shell .flood-station,
.dashboard-shell .flood-unit,
.dashboard-shell .flood-day-label,
.dashboard-shell .flood-day-val,
.dashboard-shell .flood-model,
.dashboard-shell .fa-kicker,
.dashboard-shell .fa-watch-head span,
.dashboard-shell .fa-watch-head small,
.dashboard-shell .fa-watch-head em,
.dashboard-shell .fa-story-row span,
.dashboard-shell .fa-path-button,
.dashboard-shell .fa-path-button span,
.dashboard-shell .fa-meta strong,
.dashboard-shell .fa-meta span,
.dashboard-shell .fa-details summary,
.dashboard-shell .fa-reality,
.dashboard-shell .fa-reality strong,
.dashboard-shell .fa-reality div,
.dashboard-shell .fa-reality a,
.dashboard-shell .fa-hotline strong,
.dashboard-shell .fa-hotline span,
.dashboard-shell .fa-shelter span,
.dashboard-shell .fa-shelter strong,
.dashboard-shell .fa-source,
.dashboard-shell .fa-source a,
.dashboard-shell .hg-title,
.dashboard-shell .ward-risk-note,
.dashboard-shell .wr-ward,
.dashboard-shell .wr-area,
.dashboard-shell .wr-props,
.dashboard-shell .wr-props sup,
.dashboard-shell .wr-gauge,
.dashboard-shell .directive-status,
.dashboard-shell .ws-head span,
.dashboard-shell .ws-head a,
.dashboard-shell .ws-meta,
.dashboard-shell .ws-note,
.dashboard-shell .cr-empty,
.dashboard-shell .cr-empty a,
.dashboard-shell .val sup,
.dashboard-shell .meta,
.dashboard-shell .meta span,
.dashboard-shell .pulse-label,
.dashboard-shell .pulse-stat span,
.dashboard-shell .pd-name,
.dashboard-shell .pulse-district strong,
.dashboard-shell .mpp-contact-header strong,
.dashboard-shell .mpp-hq-badge,
.dashboard-shell .mpp-contact-label,
.dashboard-shell .mpp-contact-link,
.dashboard-shell .mpp-contact-val,
.dashboard-shell .mpp-service-badge,
.dashboard-shell .mpp-service-arrow,
.dashboard-shell .mpp-service-title,
.dashboard-shell .mpp-service-desc,
.dashboard-shell .councillor-role,
.dashboard-shell .councillor-name,
.dashboard-shell .councillor-scope,
.dashboard-shell .councillor-phone,
.dashboard-shell .ward-chip-cat-icon,
.dashboard-shell .ward-chip-area,
.dashboard-shell .ward-chip-stats span,
.dashboard-shell .ward-hint,
.dashboard-shell .councillor-term,
.dashboard-shell .fm-amc-label,
.dashboard-shell .fm-amc-val,
.dashboard-shell .fm-amc-mm,
.dashboard-shell .fm-col-label,
.dashboard-shell .fm-horizons span,
.dashboard-shell .fm-station,
.dashboard-shell .fm-row-amc,
.dashboard-shell .fm-imp,
.dashboard-shell .fm-band,
.dashboard-shell .fm-stale,
.dashboard-shell .airport-feed-status,
.dashboard-shell .stat-label,
.dashboard-shell .flight-callsign,
.dashboard-shell .flight-type,
.dashboard-shell .flight-dist,
.dashboard-shell .flight-eta,
.dashboard-shell .flight-feed-tag,
.dashboard-shell .event-time,
.dashboard-shell .event-type,
.dashboard-shell .event-text,
.dashboard-shell .tlm-time,
.dashboard-shell .tlm-sep,
.dashboard-shell .tlm-entry span,
.dashboard-shell .bottom-disclaimer,
.dashboard-shell .help-hint,
.dashboard-shell .help-hint kbd,
.dashboard-shell .growth-lead,
.dashboard-shell .growth-lead strong,
.dashboard-shell .g-label,
.dashboard-shell .gz-name,
.dashboard-shell .gz-pct,
.dashboard-shell .gz-gauge,
.dashboard-shell .growth-foot,
.dashboard-shell .growth-foot strong,
.dashboard-shell .locality-reset,
.dashboard-shell .locality-summary span,
.dashboard-shell .locality-num,
.dashboard-shell .locality-ward-header span,
.dashboard-shell .ward-locality-count,
.dashboard-shell .locality-code,
.dashboard-shell .locality-name,
.dashboard-shell .locality-const,
.dashboard-shell .c-res,
.dashboard-shell .econ-pill-value,
.dashboard-shell .econ-pill-label,
.dashboard-shell .cov-count,
.dashboard-shell .cov-hint,
.dashboard-shell .cov-note,
.dashboard-shell .source-matrix-card span,
.dashboard-shell .source-chip,
.dashboard-shell .source-status,
.dashboard-shell .source-updated,
.dashboard-shell .zone-num { font-size: var(--t-micro) !important; line-height: 1.35 !important; }

/* The map wins on desktop: KPIs sit in one row, watchpoint chips stay on the
   map's own labels, and the tray takes a fixed share below. */
@media (min-width: 1041px) {
  .dashboard-shell #metricBand { grid-template-columns: repeat(6, minmax(0, 1fr)) !important; }
  .dashboard-shell .map-footer .watchpoint-list { display: none; }
  .dashboard-shell .col-map .deep-tray { flex: 0 0 30%; min-height: 0; }
}

/* Decoration removed: a 40px grid drawn over the whole page (it showed as
   seams across the map) and 2-3px colour bars on brief and pulse cards. */
body::before { display: none !important; }
.dashboard-shell :where(.brief-card, .official-pulse-block, .mpp-contact-card)::before { display: none !important; }

/* Phone: the verdict is the first sentence, right under the title. */
@media (max-width: 1040px) { .dashboard-shell .verdict-strip { order: 0; } }

@media (min-width: 1041px) {
  .dashboard-shell .map-entry { padding: 4px 8px; }
  .dashboard-shell .map-entry > button, .dashboard-shell .map-options > summary { min-height: 32px; padding: 4px 10px; }
}

/* Verdict chips are labels, not buttons: no touch-target height. */
.dashboard-shell .verdict-chip { min-height: 0 !important; padding: 3px 8px !important; line-height: 1.35 !important; }
@media (min-width: 1041px) { .dashboard-shell .col-map .deep-tray { flex-basis: 26%; } }

/* ======================================================================
   ONE-LINE COMMAND BAR (18 Oct 2026). Measured at 1440x900: masthead 46
   + ticker 27 + verdict 72 = 145px of chrome stacked ABOVE the map, which
   was 821x311 — 19.7% of the first screen, out-massed 2.7:1 by its own
   furniture. Three stacked bands for one line of reading each.

   One row now: title+tools | verdict verb + reason | chips | headlines.
   The map gets the 109px back. Layout is grid, so nothing reflows inside
   the bands; each band shrinks to its own content height and the row
   takes the tallest.
   ====================================================================== */
@media screen and (min-width: 1041px) {
  /* head + ticker + verdict share row 1; board takes everything left.
     The telemetry heartbeat and the disclaimer each get a FULL-width row:
     in a third column the footer was 374px, and at 1366 it wrapped the
     disclaimer to 163px tall and stole 118px of map height. Chrome at the
     foot of the board should not be rationed by a track meant for news. */
  .dashboard-shell {
    grid-template-rows: auto minmax(0, 1fr) auto auto;
    grid-template-areas:
      "head  head  head"
      "board board board"
      "tele  tele  tele"
      "foot  foot  foot";
    grid-template-columns: auto minmax(0, 1fr) minmax(0, 26vw);
  }
  /* Explicit placement, NOT the old named areas: styles.css still assigns
     .ticker-bar{grid-area:ticker} and .verdict-strip{grid-area:verdict},
     and those names no longer exist in the template above — leaving them
     to auto-placement dropped both into implicit rows at the bottom of the
     shell and collapsed the map column to 0px (same class of bug as the
     1600px grid defect in b8f67f5). Name every zone explicitly. */
  .dashboard-shell .masthead     { grid-area: head;  grid-column: 1; grid-row: 1; }
  .dashboard-shell .verdict-strip { grid-area: head; grid-column: 2; grid-row: 1; }
  .dashboard-shell .ticker-bar   { grid-area: head; grid-column: 3; grid-row: 1; }
  /* Row 1 is one flex line: title+tools | verdict | headlines. */
  .dashboard-shell .masthead,
  .dashboard-shell .ticker-bar,
  .dashboard-shell .verdict-strip {
    align-self: stretch;
    border-bottom: 0 !important;
    margin: 0;
    padding: 0 10px;
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 34px;
    max-height: 34px;
    overflow: hidden;
  }
  /* Zone rules read as hairlines BETWEEN zones on one line, not under each. */
  .dashboard-shell .ticker-bar { border-left: 1px solid var(--line-strong) !important; }
  .dashboard-shell .verdict-strip { border-left: 2px solid var(--ink) !important; flex: 1 1 auto; min-width: 0; }

  /* Masthead keeps the title and Tools; the eyebrow/lead are already off. */
  .dashboard-shell .masthead { flex: 0 0 auto; padding-left: 10px; }
  .dashboard-shell .masthead .title-block h1 { font-size: 15px; line-height: 1.2; }
  .dashboard-shell .view-tools > summary { min-height: 32px; padding: 4px 8px; }

  /* Ticker: stamp + summary + window, all one row. The window's marquee is
     nowrap and max-content is enormous, so the COLUMN is capped (34vw) —
     an `auto` track let the ticker eat the verdict down to 22px. */
  .dashboard-shell .ticker-bar { flex: 1 1 auto; min-width: 0; }
  .dashboard-shell .section-stamp { line-height: 1; padding: 0 8px; flex: 0 0 auto; }
  /* The summary preview had ~30px and rendered as "Gr…", which is worse than
     nothing: a truncated word reads as a bug. The verdict and the brief both
     carry the summary in full, so the crawl takes the whole column. */
  .dashboard-shell .ticker-summary { display: none; }
  .dashboard-shell .ticker-window { flex: 1 1 auto; min-width: 0; overflow: hidden; }

  /* Verdict: the verb is the display headline and the chips are status
     tokens. Chips must NEVER be clipped — a status you cannot read is
     worse than no status — so they are flex:0 0 auto and the verb is the
     only thing allowed to shrink. (Measured: the chips needed 588px in a
     212px slot and the tail was silently cut.) */
  .dashboard-shell .verdict-verb {
    flex: 0 1 auto; min-width: 0;
    /* !important matches the Rams display rule above; without it this clamp
       is silently overridden and the headline never scales down. */
    font-size: clamp(17px, 1.45vw, 20px) !important; line-height: 1.1 !important;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .dashboard-shell .verdict-chips {
    flex: 0 0 auto; flex-wrap: nowrap; overflow: visible; gap: 6px;
  }
}

/* Below ~1400px there is not enough width for headline + four status
   chips + a news crawl on one line, and cutting a chip is not an option.
   The crawl yields first: headlines still run in the left-column news rail
   and the Economy tray tab, so nothing is lost. */
@media screen and (min-width: 1041px) and (max-width: 1399px) {
  /* Two columns only: the crawl yields, and the verdict takes the slack.
     The track is dropped, not just zeroed — a 0-width third track still
     forced the footer into it. */
  .dashboard-shell {
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
      "head  head"
      "board board"
      "tele  tele"
      "foot  foot";
  }
  .dashboard-shell .ticker-bar { display: none; }
  .dashboard-shell .masthead { grid-column: 1; grid-row: 1; }
  .dashboard-shell .verdict-strip { grid-column: 2; grid-row: 1; border-left: 2px solid var(--ink) !important; }
}

/* The KPI card measured 117px because .metric-label and .metric-context were
   never named in the micro list above, so both rendered at 14px body and the
   context wrapped to two lines. They are in the list now; the band is ~67px.
   These width rules stop a long context from wrapping it back open. */
@media screen and (min-width: 1041px) {
  .dashboard-shell #metricBand .metric-card { padding: 4px 8px 3px; }
  .dashboard-shell #metricBand .metric-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .dashboard-shell #metricBand .metric-context { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  /* Kept as one line after the 22 Oct MSH pass tried and reverted a two-line
     wrap. tests/anti-slop.test.mjs records why: the band once regrew to 118px
     when the context wrapped, which took 118px off the map. The map is the
     product; a clipped qualifier is not worth it. The full text is in the
     element's title attribute (added 22 Oct), so hover recovers it. */
  /* The whole point of the band is a scannable row of readings; six cards
     side by side at ~46px beats three rows of tall ones. */
  .dashboard-shell #metricBand { gap: 1px; }
}

/* The map is the hero: it takes the height the chrome gave back, and the
   evidence tray keeps a fixed share so the map cannot be squeezed by tray
   content (measured: the map's area swung 23% with tray content). */
@media screen and (min-width: 1041px) {
  .dashboard-shell .col-map .deep-tray { flex: 0 0 150px; min-height: 150px; max-height: 150px; }
  .dashboard-shell .col-map.hero-map-active .deep-tray { flex-basis: 32px !important; min-height: 32px !important; max-height: 32px !important; }
}
/* The idle 3D hint is instructional filler that sat as its own 28px row
   between the controls and the map. It names a control that is already
   on screen, so it rides at the end of the controls row instead. */
@media screen and (min-width: 1041px) {
  .dashboard-shell .regional-status {
    position: absolute; left: 0; right: 0; bottom: 24px;
    padding: 2px 8px; font-size: var(--t-micro); line-height: 1.25;
    background: var(--hud-bg); color: var(--soft);
    pointer-events: none; z-index: 3;
  }
  .dashboard-shell .map-panel { position: relative; }
  /* The overlay must never sit on top of the legend row: 24px clears the
     22px footer measured at this width. */
  .dashboard-shell .map-footer { position: relative; z-index: 4; }
}
/* Source citations and news headlines were rendering in UA-default
   rgb(0,0,238), which is not in this palette. Scoped to the board chrome
   and deliberately NOT to Leaflet, which owns its own attribution colour. */
.dashboard-shell :where(.tray-pane, .news-rail, .situation-rail, .deep-tray, .source-matrix, .lower-grid) a {
  color: var(--cyan);
}
.dashboard-shell :where(.tray-pane, .news-rail, .situation-rail, .deep-tray, .source-matrix, .lower-grid) a:visited {
  color: var(--cyan);
}
/* Two spans in one summary read as one run-together string without a
   separator ("News digestheadlines & language lanes"). */
.dashboard-shell .rail-disclosure-summary span + span::before { content: " — "; color: var(--soft); }

/* One display headline per screen: the page verdict. Panel headlines that
   echo it are bold body text, so the verdict is not repeated at full size. */
.dashboard-shell :where(.fa-verb, .posture-title) { font-size: var(--t-body) !important; font-weight: 700; line-height: 1.35 !important; }

/* Forecast ranges are an ink tint, the same everywhere; no second accent. */
.dashboard-shell :where(.fc-band, .flood-spark-band, .forecast-card[data-tone="warn"] .fc-band) { fill: var(--ink) !important; fill-opacity: 0.08 !important; }
.dashboard-shell .forecast-card { border-color: var(--line) !important; }
/* A normal reading is a word, not a box; only non-normal bands keep a frame. */
.dashboard-shell .cascade-band[data-tone="normal"], .dashboard-shell .cascade-band[data-tone="na"] { border: 0 !important; background: none !important; color: var(--soft) !important; }
/* Collapsed rows are as tall as their touch target, no more. */
.dashboard-shell .brief-card { padding: 12px 16px !important; }
.dashboard-shell .brief-disclosure { padding-top: 0 !important; padding-bottom: 0 !important; }
.dashboard-shell .brief-disclosure:not([open]) > summary { margin: 0; }
@media (max-width: 720px) { .dashboard-shell .help-hint { display: none; } }
/* A white legend swatch (ward hairlines) needs an edge to be seen on paper. */
.dashboard-shell .legend-dot { outline: 1px solid var(--line-strong); }

/* ---- Haze now (19 Oct 2026) -------------------------------------------
   Malaysia's API colours carry meaning here, so they are allowed as small
   swatches and station discs. Motion has one job each: the station halo
   says "this reading is Unhealthy or worse", the moving dashes say "air is
   arriving from this direction". Reduced motion keeps both still. */
.hz-st-icon { background: none; border: 0; }
.hz-st {
  position: relative; display: grid; place-items: center; width: 40px; height: 40px;
  border-radius: 50%; background: var(--hz); color: #111; border: 2px solid #fff;
  font: 700 13px/1 var(--font-sans);
}
.hz-st[data-level="3"], .hz-st[data-level="4"] { color: #fff; }
.hz-st::after {
  content: ""; position: absolute; inset: -2px; border-radius: 50%;
  border: 2px solid var(--hz); opacity: 0; pointer-events: none;
}
.hz-st[data-level="2"]::after, .hz-st[data-level="3"]::after, .hz-st[data-level="4"]::after {
  animation: hz-breathe 3.2s cubic-bezier(0.22, 1, 0.36, 1) infinite;
}
@keyframes hz-breathe { 0% { transform: scale(1); opacity: 0.85; } 100% { transform: scale(2.4); opacity: 0; } }
.hz-st-name {
  position: absolute; left: 50%; top: 44px; transform: translateX(-50%); white-space: nowrap;
  font: 600 11px/1.2 var(--font-sans); color: #fff; text-shadow: 0 0 3px #000, 0 0 2px #000;
}
.haze-flow { animation: hz-flow 1.6s linear infinite; }
@keyframes hz-flow { to { stroke-dashoffset: -32; } }
@media (prefers-reduced-motion: reduce) {
  .hz-st::after, .haze-flow { animation: none !important; }
  .hz-st[data-level="2"]::after, .hz-st[data-level="3"]::after, .hz-st[data-level="4"]::after { opacity: 0.6; transform: scale(1.5); }
}

.dashboard-shell .haze-card {
  position: absolute; top: 8px; left: 8px; z-index: 900; width: min(300px, calc(100% - 16px));
  max-height: calc(100% - 56px); overflow-y: auto; box-sizing: border-box;
  background: var(--bg); color: var(--ink); border: 1px solid var(--line-strong);
  border-top: 4px solid var(--hz); padding: 10px 12px;
}
.dashboard-shell .haze-card[hidden] { display: none; }
.dashboard-shell .haze-card .hc-head { display: flex; justify-content: space-between; align-items: center; }
.dashboard-shell .haze-card .hc-kicker, .dashboard-shell .haze-card .hc-label { font-size: var(--t-micro) !important; text-transform: uppercase; letter-spacing: 0.06em; color: var(--soft); font-weight: 700; }
.dashboard-shell .haze-card .hc-close { min-width: 44px; min-height: 44px; margin: -12px -12px -12px 0; background: none !important; border: 0 !important; color: var(--ink); cursor: pointer; }
.dashboard-shell .haze-card .hc-reading { display: flex; align-items: baseline; gap: 8px; margin: 2px 0 4px; }
.dashboard-shell .haze-card .hc-swatch { width: 12px; height: 12px; background: var(--hz); align-self: center; }
.dashboard-shell .haze-card .hc-api { font-size: var(--t-display) !important; line-height: 1 !important; }
.dashboard-shell .haze-card .hc-band { font-weight: 700; }
.dashboard-shell .haze-card .hc-source, .dashboard-shell .haze-card .hc-others { font-size: var(--t-micro) !important; color: var(--soft); }
.dashboard-shell .haze-card .hc-source a { color: var(--cyan); }
.dashboard-shell .haze-card .hc-group { margin-top: 8px; border-top: 1px solid var(--line); padding-top: 6px; }
.dashboard-shell .haze-card ul { margin: 4px 0 0; padding-left: 16px; }
.dashboard-shell .haze-card li { margin: 2px 0; }
.dashboard-shell .haze-card summary { cursor: pointer; min-height: 32px; display: list-item; }
.dashboard-shell .haze-card .hc-scale { display: grid; grid-template-columns: repeat(5, 1fr); margin-top: 8px; }
.dashboard-shell .haze-card .hc-scale span { border-top: 4px solid var(--hz); font-size: var(--t-micro) !important; color: var(--soft); padding-top: 2px; text-align: right; opacity: 0.55; }
.dashboard-shell .haze-card .hc-scale span[data-on="true"] { opacity: 1; color: var(--ink); font-weight: 700; }
@media (max-width: 720px) { .dashboard-shell .haze-card { width: calc(100% - 16px); max-height: 52%; } }

/* The verdict is the one sentence that must read whole; the headline crawl
   gives way to it on the one-line command bar. */
@media (min-width: 1041px) {
  .dashboard-shell .ticker-bar { flex: 0 1 24vw; }
  .dashboard-shell .verdict-strip { flex: 1 0 auto; }
}
/* Phone: the haze card opens to one reading line so the map stays visible;
   the advice is one tap away. */
.dashboard-shell .haze-card .hc-more > summary { cursor: pointer; min-height: 44px; display: list-item; font-weight: 700; }
@media (min-width: 721px) { .dashboard-shell .haze-card .hc-more > summary { display: none; } }
@media (min-width: 1400px) {
  .dashboard-shell { grid-template-columns: auto minmax(0, 1fr) minmax(0, 18vw); }
}
/* Below 1400 the verb and four chips do not fit one 34px line; the chips
   drop to a second line rather than cutting the sentence off. */
@media (min-width: 1041px) and (max-width: 1399px) {
  .dashboard-shell .verdict-strip { flex-wrap: wrap; max-height: none; row-gap: 2px; padding-top: 4px; padding-bottom: 4px; }
  .dashboard-shell .verdict-verb { flex: 1 1 100%; white-space: normal; }
}
.tray-tablist { display: contents; }

/* --- Research tab ------------------------------------------------------------
   The blueprint pane. Same rules as the rest of the board: square corners,
   no glow, quiet chrome, and the limitation column given the visual weight
   because that is the column a sceptic actually needs. Laid out to survive a
   30%-height tray pane, so it scrolls rather than compressing the type. */
.dashboard-shell .research-panel-body { font-size: var(--t-micro, 11px); line-height: 1.45; }

.dashboard-shell .sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* Build record — the facts that make this screen reproducible. */
.dashboard-shell .rp-record {
  display: flex; flex-wrap: wrap; gap: 4px 14px; align-items: center;
  padding: 7px 9px; margin-bottom: 10px;
  border: 1px solid var(--line); background: var(--card-bg);
  font-family: var(--font-mono); font-size: 11px; color: var(--soft);
}
.dashboard-shell .rp-record b { color: var(--ink); font-weight: 700; }
.dashboard-shell .rp-copy {
  margin-inline-start: auto; min-height: 22px; padding: 2px 8px;
  border: 1px solid var(--line-strong); background: transparent;
  color: var(--ink); font-family: var(--font-mono); font-size: 11px;
  cursor: pointer;
}
.dashboard-shell .rp-copy:hover { background: var(--card-hover); }
.dashboard-shell .rp-copy:focus-visible { outline: 2px solid var(--cyan, #1f4e79); outline-offset: 1px; }

/* Architecture Blueprint — 4 operational pillars & anti-hallucination laws */
.dashboard-shell .rp-blueprint { margin-bottom: 12px; }
.dashboard-shell .rp-blueprint-lead { font-size: 12px; color: var(--soft); margin-bottom: 8px; line-height: 1.4; }
.dashboard-shell .rp-subhead {
  font-family: var(--font-mono); font-size: 10px; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--soft); margin: 8px 0 4px;
}
.dashboard-shell .rp-schematic-wrap { margin-bottom: 8px; }
.dashboard-shell .rp-schematic {
  margin: 0; padding: 8px 10px; background: var(--card-bg);
  border: 1px solid var(--line); font-family: var(--font-mono);
  font-size: 10px; line-height: 1.35; color: var(--ink);
  overflow-x: auto;
}
.dashboard-shell .rp-pillars-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 8px; margin-bottom: 8px;
}
.dashboard-shell .rp-pillar-card {
  padding: 8px 10px; background: var(--card-bg); border: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 4px;
}
.dashboard-shell .rp-pillar-head { display: flex; gap: 8px; align-items: baseline; }
.dashboard-shell .rp-pillar-badge {
  font-family: var(--font-mono); font-size: 10px; font-weight: 700;
  color: var(--cyan, #1f4e79); background: var(--tint, rgba(31,78,121,0.08));
  padding: 1px 4px; border-radius: 2px;
}
.dashboard-shell .rp-pillar-head strong { font-size: 12px; color: var(--ink); }
.dashboard-shell .rp-pillar-role { display: block; font-family: var(--font-mono); font-size: 10px; color: var(--soft); }
.dashboard-shell .rp-pillar-desc { margin: 0; font-size: 11px; color: var(--soft); line-height: 1.35; }
.dashboard-shell .rp-pillar-rule {
  margin-top: auto; padding-top: 4px; border-top: 1px dashed var(--line);
  font-size: 10px; color: var(--ink);
}
.dashboard-shell .rp-laws-list {
  margin: 0 0 6px; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 4px;
}
.dashboard-shell .rp-law-item {
  font-size: 11px; line-height: 1.35; color: var(--soft);
  padding: 4px 6px; background: var(--card-bg); border-left: 2px solid var(--cyan, #1f4e79);
}
.dashboard-shell .rp-law-item strong { color: var(--ink); font-weight: 700; }

/* Pipeline — four stages, one line each, numbered. */
.dashboard-shell .rp-pipeline {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 8px; margin: 0 0 4px; padding: 0; list-style: none;
}
.dashboard-shell .rp-step { display: flex; gap: 7px; align-items: flex-start; }
.dashboard-shell .rp-step-n {
  flex: 0 0 auto; width: 17px; height: 17px; border: 1px solid var(--line-strong);
  display: grid; place-items: center; font-family: var(--font-mono); font-size: 10px;
  color: var(--ink);
}
.dashboard-shell .rp-step-body { display: flex; flex-direction: column; gap: 2px; }
.dashboard-shell .rp-step-body strong { color: var(--ink); font-size: 12px; }
.dashboard-shell .rp-step-body span { color: var(--soft); }

/* Signal dictionary. The limit column is the point of the table. */
.dashboard-shell .rp-table {
  width: 100%; border-collapse: collapse; margin-bottom: 4px;
  font-size: 11px;
}
.dashboard-shell .rp-table th, .dashboard-shell .rp-table td {
  text-align: start; vertical-align: top; padding: 5px 7px;
  border-bottom: 1px solid var(--line);
}
.dashboard-shell .rp-table thead th {
  font-family: var(--font-mono); font-size: 10px; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--soft); border-bottom-color: var(--line-strong);
}
.dashboard-shell .rp-table tbody th { color: var(--ink); font-weight: 700; white-space: nowrap; }
.dashboard-shell .rp-table td { color: var(--soft); }
.dashboard-shell .rp-table .rp-limit { color: var(--ink); }
.dashboard-shell .rp-table .rp-state { width: 26px; text-align: center; }
.dashboard-shell .rp-glyph { font-family: var(--font-mono); font-size: 12px; }
.dashboard-shell .rp-glyph[data-tone="ok"] { color: var(--green); }
.dashboard-shell .rp-glyph[data-tone="alert"] { color: var(--red); }
.dashboard-shell .rp-glyph[data-tone="warn"] { color: var(--amber); }
.dashboard-shell .rp-glyph[data-tone="muted"] { color: var(--soft); }

/* Recipes — "I want to X" beside the file to open. */
.dashboard-shell .rp-recipes { margin: 0 0 4px; padding: 0; list-style: none; }
.dashboard-shell .rp-recipe {
  display: grid; grid-template-columns: minmax(150px, 34%) 1fr; gap: 10px;
  padding: 5px 0; border-bottom: 1px solid var(--line); align-items: baseline;
}
.dashboard-shell .rp-want { color: var(--ink); font-weight: 700; }
.dashboard-shell .rp-do { color: var(--soft); font-family: var(--font-mono); font-size: 11px; }

/* Omissions — the sceptic's section. Deliberately plain. */
.dashboard-shell .rp-omissions { margin: 0; padding-inline-start: 16px; color: var(--soft); }
.dashboard-shell .rp-omissions li { padding: 2px 0; }

@media (max-width: 720px) {
  .dashboard-shell .rp-recipe { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  .dashboard-shell .rp-record { gap: 3px 10px; }
  .dashboard-shell .rp-copy { margin-inline-start: 0; }
  .dashboard-shell .rp-table { font-size: 11px; }
  .dashboard-shell .rp-table th, .dashboard-shell .rp-table td { padding: 5px 5px; }
}

/* --- News rail fills its reserved column ------------------------------------
   The rail is flex:1 in a one-screen column, but nothing inside it grew: four
   lane cards, a note and three headlines came to 474px against a ~1164px rail,
   leaving a large empty plane under "Ground pulse". Measured 20 Oct 2026.
   The panel now absorbs that space and shows more of the window's headlines.

   One scroll container, not two. The first attempt also gave the inner list its
   own overflow and let the panel shrink (min-height:0); on a short column the
   panel then collapsed to 72px while its fixed children overflowed to 992px and
   painted over the sibling disclosures. Now panel-inner is the only scroller and
   the panel grows but never below its own content. */
.dashboard-shell .news-rail .panel-inner {
  display: flex; flex-direction: column;
  overflow-y: auto; overscroll-behavior: contain;
}
.dashboard-shell .sentiment-panel {
  flex: 1 0 auto; min-height: min-content;
  display: flex; flex-direction: column;
}
.dashboard-shell .news-intake-grid,
.dashboard-shell .news-intake-note { flex: 0 0 auto; }
.dashboard-shell .news-intake-list { flex: 0 0 auto; }

/* --- Left column: the brief yields, the rail gets a floor --------------------
   Measured 20 Oct 2026. The left column is a fixed-height flex stack —
   brief → insights → news — and .brief-strip was content-sized with
   `flex: 0 0 auto`, so flex-shrink: 0 meant it could not yield. At 1440x900
   it took 736px of an 813px column, the insights bar took 69px, and
   .news-rail computed to ZERO height: fourteen real headlines in the DOM
   with no visible window at all, and no page scroll to reach them.

   This is the wasted space in the left bar. It is vertical, not horizontal:
   across 1100→2560px the rail is 250-340px wide and 92% full, so there is no
   half-width to reclaim, and narrowing it would wrap the brief taller and
   starve the rail further.

   Let the brief shrink and scroll inside itself, and give the rail a real
   floor so it is always operable. 40% basis: with room to spare the rail
   takes 40% of the column, and when there is not, the brief absorbs the
   deficit instead of the rail disappearing. */
.dashboard-shell .col-now .brief-strip {
  flex: 0 1 auto; min-height: 0;
  overflow-y: auto; overscroll-behavior: contain;
}
.dashboard-shell .col-now .news-rail {
  /* 168px, not a rounder number: the panel head is ~51px and one headline is
     ~67px, so anything under ~160 leaves a window too short to read a single
     story in. The brief strip shrinks further to pay for it rather than
     letting the digest collapse again. */
  flex: 1 1 40%; min-height: 168px;
}

/* --- MSH, 22 Oct 2026: five clipped rows, repaired at the cause -------------
   The spatial gate measured 9 "clip" findings across 26 widths (390 to 1501).
   Every one was the same mistake: a single-line ellipsis in a box the layout
   had already made too small, with no tooltip and therefore no way to read the
   rest. Three of them sit inside collapsed disclosures, so the gate measured
   them closed — re-measured with all 27 disclosures OPEN they are still wrong,
   and that is the state a reader reaches by clicking.

   1. Ground pulse — three language lanes across a 254-340px rail. Each lane got
      ~80px, and inside a lane the `auto 1fr` item grid left the headline 5.6px
      wide. A headline rendered six pixels across is not a headline. The phone
      layout already stacks the lanes at <=1040px; the narrow desktop rail now
      does the same, because the lane has to be able to hold a sentence. */
.dashboard-shell .col-now .gp-lanes { grid-template-columns: 1fr; }

/* The lane is readable now, so the headline inside it has to wrap — 642px of
   text will not sit on one line in a 179px lane. Same two-line clamp the
   board already uses for this element at wider breakpoints. */
.dashboard-shell .gp-title {
  white-space: normal;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  line-height: 1.3;
}
.dashboard-shell .gp-meta { white-space: normal; }

/* 2. A headline IS the news. The digest gave the title 111px of a 234px row and
      hid 393px of a 99-character headline, with no title attribute to recover
      it. Two lines, using the same clamp the ground pulse already uses. */
.dashboard-shell .news-digest-title {
  white-space: normal;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  line-height: 1.3;
}

/* 3. The gauge name lost its tail in a 56px column of a five-column row.
      "Aman 5 Bridge" rendered as "Aman 5 Bri…". */
.dashboard-shell .ward-risk-row .wr-gauge {
  white-space: normal; overflow: visible; text-overflow: clip;
}

/* 4. Leaflet's own attribution was ellipsised from 111px to 77px, cutting
      "Leaflet | Tiles © Esri" mid-credit. An attribution you cannot read is
      not an attribution. The row is revealed on hover by design, so only the
      revealed state matters here — and there the name lost half its text.
      Keep the source identity whole; a timestamp may abbreviate. */
.dashboard-shell .metric-card .metric-source .src-name {
  flex: 0 1 auto; white-space: nowrap; overflow: visible; text-overflow: clip;
}
.dashboard-shell .metric-card .metric-source .src-fresh {
  flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
}

/* 5. The corner credit is not a headline and does not need to share a line.
      Let it wrap instead of ellipsing. */
.dashboard-shell .leaflet-control-attribution {
  max-width: 90%; white-space: normal; overflow: visible; text-overflow: clip;
}

/* --- The three the first pass left behind ----------------------------------
   Keeping the source name whole pushed the timestamp into the last few pixels
   at 390px, so the two now wrap onto their own lines instead of competing for
   one. The revealed row is 30px tall and two 8px lines fit inside it. */
.dashboard-shell .metric-card .metric-source {
  /* At the narrowest band (a 90px KPI tile at 1041px) the source name and the
     timestamp cannot share one line. They stack instead of one cutting the
     other off. The row is revealed on hover inside a 30px box, and two 8px
     lines fit in it. */
  flex-wrap: wrap; row-gap: 0;
}
.dashboard-shell .metric-card .metric-source .src-name { flex: 0 0 auto; }

/* The ward area name hid 89px of itself at 390px for the same reason the
   headline did. Two lines, same clamp. */
.dashboard-shell .ward-risk-row .wr-area {
  white-space: normal;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  line-height: 1.25;
}

/* "2 councillors" and "20 localities" wrapped into a 53px chip and left one
   alone on the last row — the orphan row-wrap the law names as an absolute
   failure. Stacked, each is its own row and there is no orphan. */
.dashboard-shell .ward-chip-stats { flex-direction: column; align-items: center; }

/* --- 22 Oct, round two: what the first repairs exposed ---------------------
   Gate re-run at 26 widths. Nine clip findings gone, three gone; these are the
   ones the changed geometry revealed underneath them. */

/* The metric card is six equal columns of a ~115px band, and both its label
   and its context were single-line ellipses: "Rain next 6h" lost 12px and
   "1.5 mm expected today" lost 20px. A KPI's unit and its qualifier are the
   reading, not decoration — let them wrap onto the second line the card
   already has room for. */
.dashboard-shell .metric-card .metric-label {
  /* A label is a fixed i18n string ("Rain next 6h"), so its width is stable and
     one line with a title attribute is the honest answer. */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dashboard-shell .metric-card .metric-context {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* The flood matrix header wrapped and left one span alone on the last row.
   Stacked, each part is its own row and there is no orphan. */
.dashboard-shell .fm-amc { flex-wrap: nowrap; align-items: flex-start; }
.dashboard-shell .fm-amc-label { flex: 1 1 100%; }

/* The district strip wrapped four chips into 3 + 1 and left one alone. Four
   chips at their own 56px minimum fit a 251px row with room to spare, so the
   strip was wrapping for no reason: a flexible grid fills the row it has
   instead of stacking to leave an orphan. */
.dashboard-shell .pulse-districts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(56px, 1fr));
}
/* Evidence is requested at the reading, not another permanent chrome band.
   Civic register: map first, Helvetica retained, content-sized controls. */
.metric-card { position: relative; }
.metric-heading { grid-area: label; display: flex; align-items: center; gap: 4px; min-width: 0; }
.metric-heading .metric-label { flex: 1 1 auto; min-width: 0; }
.metric-evidence-open { flex: 0 0 24px; width: 24px; height: 24px; padding: 0; background: var(--bg); color: var(--text); border: 1px solid currentColor; cursor: pointer; }
.metric-evidence-open:focus-visible, .locality-evidence-open:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }
.locality-evidence-open { justify-self: start; padding: .3em .5em; font: inherit; color: var(--text); background: transparent; border: 1px solid currentColor; cursor: pointer; }
.evidence-dialog { box-sizing: border-box; width: min(42rem, calc(100vw - 24px)); max-height: calc(100svh - 32px); overflow: auto; padding: 16px; border: 2px solid var(--cyan); border-radius: 0; background: var(--bg); color: var(--text); font: 16px/1.5 var(--font-sans); }
.evidence-dialog a { color: var(--cyan); text-underline-offset: .15em; }
.evidence-dialog header { display: flex; align-items: start; gap: 12px; }
.evidence-dialog h2 { flex: 1; margin: 0; font-size: 24px; }
.evidence-dialog h3 { font-size: 16px; margin: 1em 0 .25em; }
.evidence-dialog p, .evidence-dialog li { max-width: 65ch; overflow-wrap: anywhere; }
.evidence-dialog header button { padding: .4em .6em; min-height: 32px; font: inherit; }
.water-evidence { margin: .75em 0; border-block-start: 1px solid var(--line); padding-block-start: .5em; }
.research-method > summary { cursor: pointer; padding: .4em 0; }
.evidence-funnel { display: block !important; list-style: none; padding: 0; }
.evidence-funnel .rp-step { display: grid; grid-template-columns: 1.5em minmax(0, 1fr); gap: .5em; margin: .75em 0; }
.evidence-funnel .rp-step-body { font-size: 16px; line-height: 1.5; }
.evidence-funnel .rp-step-body > strong, .evidence-funnel .rp-step-body > span { display: block; }
@media (max-width: 720px) {
  .locality-evidence-open { min-height: 32px; margin-block: 4px; }
}

/* WCAG 2.2 target-size floor (24px), MSH-compliant: label plus padding, never a
   slab. Measured on the live board at 1440px: the coordinate buttons were 21px,
   the keyboard-help hint 20px, the news-language tabs 20px, the disclosure
   summaries 15-20px, and the tap-to-call links 15px. Touch (<=720px) already
   clears 24px; this closes the desktop gap. */
.dashboard-shell .coord-copy,
.dashboard-shell .help-hint,
.dashboard-shell .news-digest-tab { min-height: 24px; display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; }
.dashboard-shell summary { padding-block: 5px; }
.dashboard-shell .councillor-phone,
.dashboard-shell .mpp-contact-link { display: inline-block; padding-block: 5px; }
