/* /map — slice 1. Full-bleed map under a slim bar. */
/* ⚠ --map-top IS OURS; --nav-h IS THE NAV'S. The controls below offset from
   the top of the map area, which is the nav's height normally and ZERO in full
   screen. That could not be done by setting --nav-h, and the note at
   .modeswitch says why in detail: --nav-h is the token css/nav.css sizes the
   NAV from, so writing it fed back — nav 68 -> map top 69 -> --nav-h 69 ->
   nav 69 -> … growing to 700px over successive renders. Read it, never write
   it. This is a separate variable that merely DEFAULTS to it. */
.map-body { margin: 0; height: 100dvh; display: flex; flex-direction: column; overflow: hidden;
  --map-top: var(--nav-h); }

/* ── full screen ────────────────────────────────────────────────────────────
   ⚠ IN-PAGE, NOT THE FULLSCREEN API — and the layout is most of the reason.
   .map-body is already a flex column with the nav in flow and #map at
   `flex: 1 1 auto`, so hiding the nav reflows the map to the whole viewport
   with no positioning arithmetic at all. What the API would add on top of that
   is an OS-level window takeover whose exit affordance is the BROWSER's, not
   ours, plus a user-gesture requirement and no support for non-video elements
   on iOS Safari — on a surface whose readers are largely on phones.

   The map still needs resize() when this class lands; see js/map.js. */
/* ⚠ THE STATES ARE ENUMERATED, NOT NEGATED — AND :not() SHIPPED THE NAV HIDDEN
   ON EVERY FRESH LOAD. This was `:not([data-chrome='none'])`, chosen so the
   rule would cover cinema without a second edit. It does something else too:
   an ABSENT attribute is also "not none", and data-chrome is absent until the
   first toggle. So a plain page load matched, the nav went, and the control
   stack sat at its full-screen offset (measured: options at y126 with nothing
   ever pressed).

   Enumerating both states is the fix, and the risk that motivated :not() —
   forgetting to add cinema here — is answered by adding it NOW, in the same
   commit that names the value, rather than by a selector that is true for
   states nobody has defined. */
.map-body[data-chrome='screen'] #nav,
.map-body[data-chrome='cinema'] #nav { display: none; }
.map-body[data-chrome='screen'],
.map-body[data-chrome='cinema'] { --map-top: 0px; }

.mapbar {
  display: flex; align-items: center; gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface); border-bottom: 1px solid var(--line);
  flex: 0 0 auto;
}

#map { flex: 1 1 auto; min-height: 0; }

.mapstatus {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  margin: 0; padding: var(--sp-3) var(--sp-4);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); color: var(--text-muted);
  font-size: var(--fs-sm); pointer-events: none;
}
.mapstatus:empty { display: none; }
/* The hint fades rather than vanishing; failures never reach this state
   because only hint() sets data-fading. */
.mapstatus { transition: opacity 320ms ease; }
.mapstatus[data-fading='1'] { opacity: 0; }
/* ⚠ NO TRANSITION UNDER REDUCED MOTION — the message still goes, it just goes
   at once. A fade is decoration here; the disappearance is the behaviour. */
@media (prefers-reduced-motion: reduce) {
  .mapstatus { transition: none; }
}

/* Shown when ?mode= names a map that is not live yet. Dismissible: it is an
   explanation, not an error, and it must not sit on the map forever. */
.mapnote {
  display: flex; align-items: center; gap: var(--sp-3);
  margin: 0; padding: var(--sp-3) var(--sp-4);
  background: var(--accent-tint); color: var(--accent-text);
  font-size: var(--fs-sm); flex: 0 0 auto;
  border-bottom: 1px solid var(--line);
}
.mapnote[hidden] { display: none; }
.mapnote__x {
  margin-left: auto; border: 0; background: none; cursor: pointer;
  color: inherit; font-size: 20px; line-height: 1; padding: 0 var(--sp-2);
}

/* The map paywall. NOT a modal that blocks the map — the field behind it is
   the offer, so the backdrop is light and the card sits centred over it. */
.paywall {
  position: absolute; inset: 0; display: grid; place-items: center;
  background: rgba(10, 22, 40, 0.45); z-index: 5;
}
.paywall[hidden] { display: none; }
.paywall__card {
  max-width: 27rem; margin: var(--sp-4);
  background: var(--surface); border-radius: var(--r-lg, 14px);
  padding: var(--sp-5, 24px); text-align: center;
  box-shadow: 0 12px 40px rgba(0,0,0,.28);
}
.paywall__title { margin: 0 0 var(--sp-2); font-size: var(--fs-lg, 1.25rem); }
.paywall__body { margin: 0 0 var(--sp-4); color: var(--text-secondary); }
.paywall__actions { display: flex; gap: var(--sp-3); justify-content: center; flex-wrap: wrap; }

/* ── in-map mode switcher ────────────────────────────────────────────────────
   Sits over the map rather than in the bar: the bar is fixed-height chrome and
   ten modes will not fit there on a phone. Horizontal scroll with the scrollbar
   hidden — the overflow is the design, not a fallback. */
.modeswitch {
  position: absolute;
  /* This read calc(var(--mapbar-h, 52px) + 8px), and --mapbar-h IS DEFINED
     NOWHERE — it was the height of a .mapbar header since deleted from
     map.html, so the row positioned itself against a phantom element and fell
     back to 52px while the nav is 68. The pills sat under it, clipped along
     their top edge.

     ⚠ --nav-h IS THE NAV'S OWN TOKEN (css/tokens.css) AND css/nav.css SETS
     THE NAV'S HEIGHT FROM IT. Read it; never write it. A first attempt at
     this fix measured #map's top in js and wrote it back to --nav-h, which
     made a feedback loop — nav 68 -> map top 69 -> --nav-h 69 -> nav 69 -> …
     — and the nav grew 68 -> 152 -> 262 -> 420 -> 700px over successive
     renders until the map was pushed off screen entirely. The variable
     already existed and already held exactly the right number; the whole bug
     was pointing at the wrong one. */
  top: calc(var(--map-top) + 8px);
  left: 8px;
  right: 8px;
  z-index: 3;
  display: flex;
  gap: 6px;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 2px;
}
.modeswitch::-webkit-scrollbar { display: none; }

.modeswitch__btn {
  flex: 0 0 auto;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(10, 22, 40, 0.72);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  color: var(--text-on-dark-2);
  font: 500 13px/1 var(--font-sans, system-ui, sans-serif);
  padding: 8px 12px;
  border-radius: 999px;
  cursor: pointer;
  white-space: nowrap;
  transition: background 120ms ease, border-color 120ms ease;
}
/* the app's layer-picker icon before the label (js/material-symbols.js) */
.modeswitch__btn:has(.modeswitch__ic) { display: inline-flex; align-items: center; gap: 6px; }
.modeswitch__ic { font-size: 16px; }
.modeswitch__btn:hover { background: rgba(20, 40, 68, 0.82); }
.modeswitch__btn:focus-visible {
  outline: 2px solid var(--brand-teal-bright, #3DE0C4);
  outline-offset: 2px;
}
.modeswitch__btn[aria-current='true'] {
  background: var(--brand-teal-bright, #3DE0C4);
  border-color: var(--brand-teal-bright, #3DE0C4);
  color: var(--ink-on-bright);
}
/* Not-yet-live modes stay VISIBLE and disabled rather than being hidden: the
   ten-mode set is the product, and a switcher that silently shows five reads as
   a smaller product rather than as an in-progress one. */
.modeswitch__btn[disabled] {
  opacity: 0.42;
  cursor: not-allowed;
}
/* While a switch is in flight the old field stays on screen — see the swap
   comment in js/map.js. This only dims the control. */
.modeswitch[data-busy='1'] .modeswitch__btn { opacity: 0.6; pointer-events: none; }

/* ── timeline ────────────────────────────────────────────────────────────────
   Sits over the map, anchored to the bottom, for the same reason the mode
   switcher sits over it: #map is flex:1 and the bar is fixed-height chrome, so
   anything added to the column steals map. Overlaying costs nothing and keeps
   the field full-bleed. */
.timeline {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 4;
  display: flex; flex-direction: column; gap: var(--sp-2);
  /* ⚠ SAFE AREA ON THE BOTTOM. On a phone the home indicator occupies the
     last ~34px of the viewport, so a bar at bottom:0 has its last row — here
     the day ticks — underneath it. Desktop is unaffected: env() resolves to 0
     where there is no inset, so this is additive rather than a breakpoint. */
  padding: var(--sp-3) var(--sp-4) calc(var(--sp-4) + env(safe-area-inset-bottom, 0px));
  background: linear-gradient(to top, var(--surface) 62%, transparent);
  color: var(--text);
}
.timeline[hidden] { display: none; }

.timeline__head { display: flex; align-items: center; gap: var(--sp-3); }
.timeline__when {
  font-size: var(--fs-sm); font-weight: var(--fw-label);
  font-variant-numeric: tabular-nums;
}
.timeline__now {
  margin-left: auto; border: 1px solid var(--line); background: var(--surface);
  color: var(--accent-text); border-radius: var(--r-md);
  padding: 2px var(--sp-3); font-size: var(--fs-sm); cursor: pointer;
}
.timeline__now:hover { background: var(--accent-tint); }

/* The range and the locked tail share one row and are sized by flex-grow, set
   in js from the slot counts — so the tail is literally in proportion to the
   days it withholds rather than a decorative stub. */
.timeline__track { display: flex; align-items: center; gap: 2px; }

/* The scrubber takes ALL the leftover width; the locked strip is sized to its
   own content and capped below. This replaced a proportional split driven by
   flex-grow from the slot counts — see the note in js/map.js's renderTimeline
   for why proportionality lost. */

/* Busy is a whole-control state, not a spinner: the field is still the previous
   hour until the new pack decodes, and dimming the control says "not yet"
   without covering the map that is the reason anyone is here. */
.timeline[data-busy] .timeline__track { opacity: .55; }

@media (max-width: 640px) {
  .timeline { padding: var(--sp-2) var(--sp-3) calc(var(--sp-3) + env(safe-area-inset-bottom, 0px)); }
  /* ⚠ NOTHING IS HIDDEN AT 390 ANY MORE. This used to drop every other label —
     the honest fix at the time, when the strip could not scroll and five labels
     in 175px genuinely collided. The strip scrolls now (see .timeline__strip),
     so hiding half the week is crunching a problem that no longer exists, and
     Grant's standing instruction is to scroll instead. A 16-day horizon makes
     it worse either way: alternating would leave eight of sixteen days
     unnamed. */
}

/* ⚠ ATTRIBUTION MUST STAY VISIBLE — the timeline is a full-width bottom
   overlay and MapLibre puts the Stadia / OpenMapTiles / OSM credit bottom-right
   by default, so it would sit underneath. --timeline-h is measured in js
   (ResizeObserver) rather than hard-coded, because the control's height moves
   with the note bar, the tick row, and the 640px breakpoint. */
.map-body .maplibregl-ctrl-bottom-left { bottom: var(--timeline-h, 0px); }
/* The right column also clears the MODEL / LAYER pills (W2a, .mappills: 10 px
   over the timeline, their rendered height measured by js/map-stack.js into
   --pills-clear = height + 14): its last group ends 12 px above the top pill —
   the 14 less the pills' 10, plus the group's own 8 px bottom margin. Both
   numbers come from the page; nothing here guesses the pills' size. */
.map-body .maplibregl-ctrl-bottom-right {
  bottom: calc(var(--timeline-h, 0px) + var(--pills-clear, 0px));
}

/* ── play / pause ────────────────────────────────────────────────────────────
   One button, two glyphs, swapped by [data-state]. Both live in the markup and
   CSS chooses — building the icon in js would put the control's appearance in
   a second place from the control's behaviour. */
.timeline__play {
  flex: 0 0 auto; display: grid; place-items: center;
  width: 30px; height: 30px; padding: 0;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--surface); color: var(--accent-text); cursor: pointer;
}
.timeline__play:hover { background: var(--accent-tint); }
.timeline__play:focus-visible { outline: 2px solid var(--accent-text); outline-offset: 2px; }
.timeline__playicon { width: 16px; height: 16px; display: block; }

/* Paused shows PLAY (the action available), playing shows PAUSE. The state
   named by data-state is what the timeline IS doing, so the icon is the
   opposite of it — a transport control offers the next action, it does not
   report the current one. */
.timeline__play .timeline__bars { display: none; }
.timeline__play[data-state="playing"] .timeline__tri  { display: none; }
.timeline__play[data-state="playing"] .timeline__bars { display: inline; }

/* Warming is a REAL state and it gets its own look, because it is the one
   moment the button is pressed and nothing is moving yet. Without it the
   pre-load reads as a dead button. */
.timeline__play[data-state="warming"],
.timeline__play[data-state="buffering"] { opacity: .6; }
.timeline__play[data-state="warming"] .timeline__tri,
.timeline__play[data-state="buffering"] .timeline__tri  { display: none; }
.timeline__play[data-state="warming"] .timeline__bars,
.timeline__play[data-state="buffering"] .timeline__bars { display: inline; }
/* W2b §3: BUFFERING is the PLAY buffer filling (before the first beat, or a
   dry beat mid-run) — the same pressed-but-not-moving look as warming, with
   a spinner ring so it reads as "loading", not "stuck". */
.timeline__play[data-state="buffering"] { position: relative; }
.timeline__play[data-state="buffering"]::after {
  content: ''; position: absolute; inset: -3px; border-radius: 50%;
  border: 2px solid transparent; border-top-color: var(--accent-text, currentColor);
}
@media (prefers-reduced-motion: no-preference) {
  .timeline__play[data-state="warming"] { animation: tl-pulse 1s ease-in-out infinite; }
  .timeline__play[data-state="buffering"]::after { animation: tl-spin 0.9s linear infinite; }
  @keyframes tl-pulse { 50% { opacity: .3; } }
  @keyframes tl-spin { to { transform: rotate(360deg); } }
}

/* ── map options: one button, two pages ───────────────────────────────────
   ⚠ THE MODE ROW IS INSET TO CLEAR THIS BUTTON rather than the button being
   placed inside it. .modeswitch scrolls horizontally — ten modes do not fit a
   phone — so anything inside it scrolls away, and this is the control you
   most want reachable when the row is longest. */
.modeswitch { right: 54px; }

/* ⚠ THE BUTTON AND MapLibre's ZOOM STACK SHARE THIS CORNER, so they are
   stacked rather than placed independently. Measured before the fix: the
   button sat at top 76-110 / right 1394-1432 and the zoom control at 69-137 /
   1401-1440 — fully overlapping, at 1440, 1024, 768 AND 390. Both offsets now
   derive from --mapopt-btn-h so moving one cannot leave the other behind. */
:root { --mapopt-btn-h: 34px; }
.mapopt__btn {
  position: absolute;
  /* slot 4 = layers (#map-options-btn), which uses this base; every other
     .mapopt__btn overrides its own slot below. */
  top: var(--stack-4);
  right: 8px;
  z-index: 4;
  width: 38px; height: var(--mapopt-btn-h);
  display: grid; place-items: center;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(10, 22, 40, 0.72);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  color: var(--text-on-dark-2); border-radius: 999px; cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease;
}
.mapopt__btn:hover { background: rgba(20, 40, 68, 0.82); }
.mapopt__btn:focus-visible {
  outline: 2px solid var(--brand-teal-bright, #3DE0C4); outline-offset: 2px;
}
.mapopt__btn[aria-expanded='true'] {
  background: var(--brand-teal-bright, #3DE0C4);
  border-color: var(--brand-teal-bright, #3DE0C4);
  color: var(--ink-on-bright);
}

/* ── ⚠ THE PANEL IS TALLER THAN THE SCREEN, AND HAD NO SCROLLER AT ALL ─────
   Not the timeline's mistake (a scroller with its bar hidden) and not
   .dr__scroll's (a flex item that shrank so its overflow clipped). A third
   kind: overflow-y visible, max-height none, scrollHeight === clientHeight, so
   the panel simply grew past the viewport and the surplus spilled out of the
   document with nothing to scroll. Measured on Overlays: 738 px of content
   anchored at y 228 in an 844 px viewport — bottom at 966, the last two rows
   at y 826 and 883, unreachable. It overflowed at 1440 too, by 74 px.

   ⚠ THE CEILING IS DERIVED FROM THE ANCHOR, NOT A FIXED NUMBER. This panel
   hangs off the layers button, and that button moved when the right stack was
   reordered on 2026-08-26 — a max-height written against the old y would have
   been silently wrong the moment it did. --mapopt-top is the single expression
   both `top` and `max-height` read, so they cannot disagree and both follow
   the stack.

   ⚠ AND IT IS SIZED BY THE VIEWPORT, NOT BY TODAY'S ROW COUNT. The panel is
   expected to grow — cinema mode brings the mode switcher into it, and Basemap
   gains a row per new style. A ceiling counted in rows would need editing
   every time; 100dvh minus the anchor never does. */
.map-body {
  /* Each panel hangs off ITS button's slot — layers at 4, legend at 6 now. */
  --mapopt-top: calc(var(--stack-4) + var(--mapopt-btn-h) + var(--stack-gap));
  --maplegend-top: calc(var(--stack-6) + var(--maplegend-btn-h) + var(--stack-gap));
}
.mapopt {
  position: absolute;
  /* Hangs off the layers button it belongs to — same reason as .maplegend. */
  top: var(--mapopt-top);
  /* 16px keeps it clear of the viewport's bottom edge rather than flush to it. */
  max-height: calc(100dvh - var(--mapopt-top) - 16px);
  /* The tabs stay put; only the page under them scrolls. */
  display: flex; flex-direction: column;
  right: 8px;
  z-index: 5;
  width: min(268px, calc(100vw - 16px));
  padding: 10px;
  border-radius: 14px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: rgba(10, 22, 40, 0.94);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
  color: var(--text-on-dark-2);
  font: 400 13px/1.4 var(--font-sans, system-ui, sans-serif);
}

/* ⚠ min-height: 0 IS LOAD-BEARING, NOT TIDINESS. A flex item's default
   min-height is auto — it refuses to shrink below its content — so overflow-y
   here would never engage and the panel would grow past its own max-height
   exactly as before. This one declaration is the difference between a scroller
   and a taller box. */
.mapopt__page {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  /* ⚠ A VISIBLE BAR. scrollbar-width: none on a 12px lane is what made the
     timeline's tick row "technically scrollable, practically not"; a panel
     that scrolls without saying so is the same defect with more rows behind
     it. Thin, and coloured so it reads on the panel's dark ground. */
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.28) transparent;
}
.mapopt__page::-webkit-scrollbar { width: 8px; }
.mapopt__page::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.28); border-radius: 4px;
}
.mapopt__page::-webkit-scrollbar-track { background: transparent; }
.mapopt__cap {
  margin: 0 0 6px; font-size: 10.5px; letter-spacing: 0.08em;
  text-transform: uppercase; opacity: 0.66;
}
/* ⚠ LOCKED IS DIMMED, NOT DISABLED. A disabled chip cannot be tapped, and the
   tap is what opens the offer — the app's own rule (requirePremium on tap). */
.mapopt__note { margin: 8px 0 0; font-size: 11.5px; line-height: 1.45; opacity: 0.74; }
.mapopt__row {
  width: 100%; display: flex; align-items: center; justify-content: space-between;
  gap: 10px; padding: 7px 2px; background: none; border: 0; cursor: pointer;
  color: inherit; font: inherit; text-align: left;
}
.mapopt__rowlbl { display: block; font-size: 13px; }
.mapopt__row:focus-visible { outline: 2px solid var(--brand-teal-bright, #3DE0C4); outline-offset: 2px; }

/* ⚠ pointer-events:none — the particle canvas sits over the map and must never
   eat a drag, a tap or a scroll. It is decoration on top of an interactive
   surface. */
.flowparticles, .flowarrows {
  position: absolute; inset: 0; z-index: 2;
  pointer-events: none;
}
.flowarrows { z-index: 1; }   /* W2b: the arrow canvas, under the particles */

/* MapLibre's top-right stack drops below the options button — same corner,
   stacked, which is the conventional arrangement and keeps both reachable.

   ⚠ AND BELOW THE LEGEND BUTTON TOO, WHICH IT DID NOT. #map-legend-btn arrived
   in 055e240 and landed in this same column: measured 1349..1432 against the
   zoom stack's 1401..1440, an overlap of 29x23 px AT EVERY WIDTH, with the
   zoom buttons sitting on the legend button's right end. Neither map-clip nor
   a11y could see it — both assert that a control renders and is reachable, and
   neither compares one control's box to another's. tools/map-controls.mjs is
   the sweep that does, and it is what found this.

   --maplegend-btn-h is the button (34) plus the gap (8); the stack clears
   both chips rather than one. */
/* ── ⚠ THE RIGHT-HAND STACK, DECLARED ONCE AS A CHAIN ──────────────────────
   Top to bottom (2026-08-29): FULL SCREEN, cinema, search, layers, zoom,
   legend. Each slot is the previous one plus its height plus the gap, so an
   order change is an edit to the chain block and the six reads that follow —
   and nowhere else.

   ⚠ THE ZOOM IS MAPLIBRE'S NavigationControl AND WE DO NOT OWN ITS LAYOUT. It
   lives in .maplibregl-ctrl-top-right, which MapLibre positions; ours are
   absolutely positioned siblings. So the stack is ordered by arithmetic around
   it, and its HEIGHT is the one number in the chain that belongs to somebody
   else. It used to be hard-coded here at 68px — correct for two 29px buttons
   and their borders today, and silently wrong the day MapLibre changes them,
   which would slide layers and legend under or over the zoom with nothing to
   catch it. js/map.js now measures that container and publishes --mapzoom-h;
   the 68px here is only the value used before the first measurement lands.

   The alternative — mounting ours INTO MapLibre's container as custom
   controls — would let MapLibre stack all four and remove the arithmetic
   entirely. It is the more durable shape and a bigger change than this
   reorder: the legend and layers PANELS anchor to their buttons' positions,
   and both buttons are static markup that the panels' CSS reads. Worth doing
   if this corner is touched again; recorded here rather than left implicit. */
.map-body {
  --maplegend-btn-h: 42px;
  --mapzoom-h: 68px;                /* replaced at runtime by measurement */
  --stack-gap: 8px;
  /* ⚠ NEW ORDER 2026-08-29: full screen, cinema, search, layers, zoom, legend.
     The tall MEASURED zoom (--mapzoom-h) moved from slot 2 to slot 5, so the
     one place its height enters the chain moved with it — from --stack-3 to
     --stack-6. Every other slot adds --mapopt-btn-h; only the slot AFTER the
     zoom adds --mapzoom-h. Reorder = edit these six lines and the six reads
     below, nothing else. */
  --stack-1: calc(var(--map-top) + var(--stack-gap));                        /* full screen */
  --stack-2: calc(var(--stack-1) + var(--mapopt-btn-h) + var(--stack-gap));  /* cinema      */
  --stack-3: calc(var(--stack-2) + var(--mapopt-btn-h) + var(--stack-gap));  /* search      */
  --stack-4: calc(var(--stack-3) + var(--mapopt-btn-h) + var(--stack-gap));  /* layers      */
  --stack-5: calc(var(--stack-4) + var(--mapopt-btn-h) + var(--stack-gap));  /* zoom        */
  --stack-6: calc(var(--stack-5) + var(--mapzoom-h) + var(--stack-gap));     /* legend      */
}

/* Slot 6. Chains off --mapopt-btn-h like every slot after the legend — see the
   note at slot 5, where reaching for --maplegend-btn-h double-counted the gap
   and put the tile 8px low. */
#map-search-btn { top: var(--stack-3); }   /* slot 3 */

/* ⚠ SLOT 5 CHAINS OFF --mapopt-btn-h LIKE EVERY OTHER SLOT. It was written
   against --maplegend-btn-h first, on the theory that the legend is a taller
   pill needing extra clearance. It is not — it renders 34px like the rest, and
   --maplegend-btn-h is ALREADY the button plus the gap (see the note at the
   token). Adding --stack-gap to it double-counted the gap and pushed cinema
   8px low: measured 16px where every other gap is 8.

   ⚠ AND map-controls.mjs PASSED ON IT, at three widths in both themes. It
   asserts no-overlap and on-screen; a tile that is too far DOWN violates
   neither. The stack's rhythm is not currently under test — only its
   collisions are. Measure the gaps when adding slot 6. */
#map-cinema-btn { top: var(--stack-2); }   /* slot 2 */

#map-full-btn { top: var(--stack-1); }
/* One button, two glyphs: which one shows is the pressed state, so the icon
   cannot fall out of step with what the press will do. */
#map-full-btn .mapfull__out { display: none; }
#map-full-btn[aria-pressed='true'] .mapfull__in { display: none; }
#map-full-btn[aria-pressed='true'] .mapfull__out { display: block; }
.map-body .maplibregl-ctrl-top-right {
  /* margin-top is measured from #map's top, so the slot has --map-top removed.
     ⚠ slot 5 since the 2026-08-29 reorder — was slot 2. The measured
     --mapzoom-h still enters the chain at --stack-6 (the slot below zoom). */
  margin-top: calc(var(--stack-5) - var(--map-top));
}

/* ── radar page ─────────────────────────────────────────────────────────────
   ⚠ THE SITE LABEL IS NOT DECORATION. docs/map-radar.md records the app's
   model — "the active site changes ONLY via the site picker or the locate
   arrow — panning does NOT change it" — and this page deliberately does the
   other thing, choosing the nearest site to the map centre. A page that
   silently switches antennas while you pan would be worse than either model,
   so which site is drawn is stated permanently and prominently. */
.radarbar {
  position: absolute; top: 12px; left: 12px; z-index: 5;
  display: flex; flex-direction: column; gap: 2px;
  padding: 8px 12px; border-radius: 10px;
  background: var(--surface); color: var(--text);
  box-shadow: 0 2px 10px rgb(0 0 0 / 18%);
  font: 600 13px/1.3 var(--font-sans, system-ui, sans-serif);
  max-width: min(70vw, 320px);
}

/* CC-BY-4.0 obligation — see radar.html. Sits with the basemap attribution so
   it cannot be scrolled away or covered by the status line. */
.radarattrib {
  position: absolute; right: 8px; bottom: 26px; z-index: 5; margin: 0;
  padding: 3px 7px; border-radius: 6px;
  background: var(--surface); color: var(--text-muted);
  font: 400 11px/1.35 var(--font-sans, system-ui, sans-serif);
}
.radarattrib a { color: inherit; }

/* ── radar loop controls ────────────────────────────────────────────────────
   Sits where the map's timeline does, so the two pages feel like one product
   even though their axes are different — the map scrubs 5 days of forecast,
   this scrubs the last ~25 minutes of observation. */

/* ── the legend, above the row, per the app's card ─────────────────────────
   ⚠ THE BAR IS PAINTED FROM THE LUT, not described in CSS. See paintLegend:
   the gradient is sampled out of the same table the shader uses, so the key
   cannot drift from the picture it explains. */
.radarleg { display: flex; flex-direction: column; gap: 4px; padding: 2px 2px 8px; }
.radarleg__title {
  font: 600 12px/1.3 var(--font-sans, system-ui, sans-serif);
  color: var(--text-muted); letter-spacing: 0.03em;
}
.radarleg__bar { display: block; height: 10px; border-radius: 3px; }
.radarleg__ticks { position: relative; display: block; height: 14px; }
.radarleg__tick {
  position: absolute; transform: translateX(-50%);
  font: 500 11px/1.2 var(--font-sans, system-ui, sans-serif);
  color: var(--text-muted); font-variant-numeric: tabular-nums;
}
/* ⚠ NOTHING IN THIS ROW MAY SHRINK. Flex items shrink by default, and a
   38x38 button with only `width` set is not a 38px button — it is a button
   that is 38px when there is room. Measured at 390: the locate and settings
   buttons rendered 5x38, i.e. a 5-pixel-wide tap target, because the row
   overflowed and flexbox took the difference out of whatever would give. The
   clock may shrink; the controls may not. */
.radarctl__icon,
.radarctl__site,
.radarctl__stale { flex: 0 0 auto; }
.radarctl__site {
  display: flex; align-items: center; gap: 6px;
  padding: 7px 12px; border: 1px solid var(--line); border-radius: 999px;
  background: none; color: var(--text); cursor: pointer;
  font: 600 13px/1.2 var(--font-sans, system-ui, sans-serif);
}
.radarctl__site[aria-pressed="true"] {
  background: var(--accent-tint); color: var(--accent-text); border-color: transparent;
}
.radarctl__icon {
  width: 38px; height: 38px; display: grid; place-items: center; padding: 0;
  border: 1px solid var(--line); border-radius: 50%;
  background: none; color: var(--text); cursor: pointer;
}
.radarctl__icon[aria-pressed="true"] {
  background: var(--accent-tint); color: var(--accent-text); border-color: transparent;
}

.radarctl__stale {
  padding: 2px 8px; border-radius: 999px;
  background: var(--warn-tint, var(--line)); color: var(--text-muted);
  font: 600 11px/1.5 var(--font-sans, system-ui, sans-serif);
}

/* ── radar mode selector ────────────────────────────────────────────────────
   The app puts Mode behind a settings sheet; on the web it is one tap from the
   map, because the web has no bottom tab bar competing for that corner. */
.radarmodes {
  position: absolute; inset: 0; z-index: 20;
  display: flex; align-items: flex-end; justify-content: center;
  background: rgb(0 0 0 / 32%);
}
.radarmodes__sheet {
  width: min(560px, 100%); max-height: 82%; overflow-y: auto;
  border-radius: 16px 16px 0 0; padding: 16px 16px 24px;
  background: var(--surface); color: var(--text);
}
.radarmodes__head { display: flex; align-items: center; justify-content: space-between; }
.radarmodes__title { margin: 0; font: 700 20px/1.3 var(--font-sans, system-ui, sans-serif); }
.radarmodes__x {
  border: 0; background: none; cursor: pointer; color: var(--text-muted);
  font-size: 26px; line-height: 1; padding: 0 4px;
}
.radarmodes__group {
  margin: 18px 0 6px; font: 600 12px/1.4 var(--font-sans, system-ui, sans-serif);
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-muted);
}
.radarmode {
  display: flex; align-items: flex-start; gap: 12px; width: 100%;
  padding: 10px 8px; border: 0; border-radius: 10px; cursor: pointer;
  background: none; color: inherit; text-align: left;
}
.radarmode:hover { background: var(--accent-tint); }
.radarmode[data-on="true"] { background: var(--accent-tint); }
.radarmode[data-on="true"] .radarmode__name { color: var(--accent-text); }
/* Unavailable rows stay READABLE — they are information, not noise. Dimming
   them to illegibility would be the same as hiding them. */
.radarmode[aria-disabled="true"] { cursor: default; }
.radarmode[aria-disabled="true"]:hover { background: none; }
.radarmode[aria-disabled="true"] .radarmode__name,
.radarmode[aria-disabled="true"] .radarmode__desc { color: var(--text-muted); }
.radarmode__text { display: flex; flex-direction: column; gap: 2px; flex: 1 1 auto; }
.radarmode__name { font: 600 15px/1.3 var(--font-sans, system-ui, sans-serif); }
.radarmode__desc { font: 400 13px/1.35 var(--font-sans, system-ui, sans-serif); color: var(--text-muted); }
.radarmode__chip {
  flex: 0 0 auto; align-self: center; white-space: nowrap;
  padding: 3px 10px; border: 1px solid var(--line); border-radius: 999px;
  font: 600 11px/1.5 var(--font-sans, system-ui, sans-serif); color: var(--text-muted);
}
/* A feed that may come back reads differently from one that never will. */
.radarmode__chip[data-reason="no-data"] { border-style: dashed; }

/* ── radar settings sheet ───────────────────────────────────────────────── */
.radarsettings__btn {
  position: absolute; top: 12px; right: 12px; z-index: 5;
  width: 36px; height: 36px; display: grid; place-items: center;
  border: 0; border-radius: 10px; cursor: pointer;
  background: var(--surface); color: var(--text);
  box-shadow: 0 2px 10px rgb(0 0 0 / 18%);
}
.radarsheet {
  position: absolute; inset: 0; z-index: 20;
  display: flex; align-items: flex-end; justify-content: center;
  background: rgb(0 0 0 / 32%);
}
.radarsheet__panel {
  width: min(560px, 100%); max-height: 86%; overflow-y: auto;
  border-radius: 16px 16px 0 0; padding: 16px 16px 28px;
  background: var(--surface); color: var(--text);
}
.radarsheet__head { display: flex; align-items: center; justify-content: space-between; }
.radarsheet__title { margin: 0; font: 700 20px/1.3 var(--font-sans, system-ui, sans-serif); }
.radarsheet__x {
  border: 0; background: none; cursor: pointer; color: var(--text-muted);
  font-size: 26px; line-height: 1; padding: 0 4px;
}
.radarsheet__group {
  margin: 18px 0 6px; font: 600 12px/1.4 var(--font-sans, system-ui, sans-serif);
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-muted);
}
/* Stated once, above the rows it explains — not repeated on each. */
.radarsheet__note {
  margin: 0 0 8px; padding: 8px 10px; border-radius: 8px;
  background: var(--accent-tint); color: var(--accent-text);
  font: 400 13px/1.4 var(--font-sans, system-ui, sans-serif);
}
.radarrow {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 10px 8px; border: 0; border-radius: 10px;
  background: none; color: inherit; text-align: left; cursor: pointer;
  font: inherit;
}
.radarrow:hover { background: var(--accent-tint); }
.radarrow__text { display: flex; flex-direction: column; gap: 2px; flex: 1 1 auto; }
.radarrow__name { font: 600 15px/1.3 var(--font-sans, system-ui, sans-serif); }
.radarrow__desc { font: 400 13px/1.35 var(--font-sans, system-ui, sans-serif); color: var(--text-muted); }
.radarrow__value { font: 400 14px/1.3 var(--font-sans, system-ui, sans-serif); color: var(--text-muted); }
.radarrow__chev { color: var(--text-muted); font-size: 18px; }
/* ⚠ A REAL PILL SWITCH, NOT A CHECKBOX. The two products should read as one
   product, and the app's Radar Settings uses a pill. The input stays a real
   <input type=checkbox> underneath — it keeps the keyboard behaviour, the
   focus ring and the screen-reader semantics that a div-with-a-click throws
   away — and is painted rather than replaced. */
.radarrow__switch {
  appearance: none; -webkit-appearance: none;
  position: relative; flex: 0 0 auto; cursor: pointer;
  width: 46px; height: 27px; margin: 0;
  border: 0; border-radius: 999px;
  background: var(--line);
  transition: background-color 140ms ease;
}
.radarrow__switch::after {
  content: ''; position: absolute; top: 3px; left: 3px;
  width: 21px; height: 21px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 3px rgb(0 0 0 / 28%);
  transition: transform 140ms ease;
}
.radarrow__switch:checked { background: var(--accent); }
.radarrow__switch:checked::after { transform: translateX(19px); }
.radarrow__switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
  .radarrow__switch, .radarrow__switch::after { transition: none; }
}
.radarrow--toggle { cursor: default; }
.radarrow--toggle:hover { background: none; }
/* A disabled toggle stays legible: the row is telling you something. */
.radarrow__switch:disabled { opacity: 0.4; cursor: not-allowed; }
/* A locked Fetch+ row stays readable and focusable — it is an offer, not noise. */
.radarrow--style[data-locked="true"] .radarrow__name { color: var(--text-muted); }
.radarrow--style[data-locked="true"] .radarrow__desc {
  color: var(--accent-text); font-weight: 600; letter-spacing: 0.04em;
}
.radarrow--style[data-on="true"] { background: var(--accent-tint); }
.radarrow--style[data-on="true"] .radarrow__name { color: var(--accent-text); }

/* ⚠ BELOW THE CROSSOVER THE ROW REFLOWS — TAKING THE FULL WIDTH IS NOT ENOUGH.
   The measured minimum is 456px WITH the stale badge and 375 without; a 390px
   phone gives the bar 366. So the two things that can yield, yield: the gap
   tightens, and the badge — the only item that appears unpredictably — moves
   to its own line instead of squeezing the controls off the end.

   With a 6px gap the row needs 36+124+75+38+38 + four gaps + 24 padding =
   359px, inside 366. The buttons keep their full tap targets at every width,
   which is the part that was actually broken. */
@media (max-width: 560px) {
  .radarctl__stale { order: 9; flex-basis: 100%; align-self: flex-start; }
  /* ⚠ THE SPACER GOES AWAY, AND IT IS WORTH TWO PIXELS. An empty flex item
     with base 0 still sits between two others, so it costs a GAP even when it
     costs no width. Measured at 390: the row needed 344px against 342
     available and the settings button wrapped to a second line — then the
     spacer grew into the hole it had just made, which is why the row looked
     like it had room. Removing it removes one 6px gap and the row fits with
     4px to spare; `margin-left: auto` does the same pushing without being an
     element. */
  .radarctl__site { margin-left: auto; }
}

/* ── Map legend ───────────────────────────────────────────────────────────
   ⚠ THE PILL REUSES .mapopt__btn RATHER THAN RESTATING IT. The app's legend
   deliberately shares the station rail's tile treatment "so the two controls
   can't drift apart"; the web's equivalent neighbour is the options button, so
   the legend takes its class and overrides only width and content. One hover
   state, one focus ring, one radius, one place to change them. */
.maplegend__btn {
  /* ⚠ --stack-4, AND IT USED TO READ --nav-h. That was missed when full screen
     introduced --map-top: in full screen --nav-h keeps the nav's real height
     (it is the nav's own token and must never be written), so the legend
     button and its panel stayed 69px down while the rest of the stack rose to
     the top of the viewport. Nothing caught it — map-controls only sweeps the
     normal state, and the full-screen harness asserted the OPTIONS button had
     moved without checking this one.
     ⚠ slot 6 (bottom) since the 2026-08-29 reorder — was slot 4. */
  top: var(--stack-6);
  width: auto;
  gap: 4px;                       /* the app's kLegendLabelGap */
  grid-auto-flow: column;
  padding: 0 10px;
  font: 600 12px/1 var(--font-sans, system-ui, sans-serif);
  letter-spacing: 0.02em;
}
.maplegend__word { pointer-events: none; }
/* 180° on expand, matching the app's 0 -> 0.5-turn controller. */
.maplegend__chev { transition: transform 200ms ease; }
.maplegend__btn[aria-expanded='true'] .maplegend__chev { transform: rotate(180deg); }

.maplegend {
  top: var(--maplegend-top);
  /* Same ceiling rule, its own anchor — the legend is the taller of the two on
     a 14-stop ramp and would have overflowed next, not eventually. */
  max-height: calc(100dvh - var(--maplegend-top) - 16px);
  width: max-content;
  min-width: 132px;
  max-width: min(200px, calc(100vw - 16px));
}
.maplegend__title {
  margin: 0 0 8px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.75;
}
/* ⚠ VERTICAL, one row per stop — see the note on the markup. */
.maplegend__lbl { font-variant-numeric: tabular-nums; font-size: 12px; }

@media (max-width: 460px) {
  /* The panel is narrow already; keep it clear of the zoom control. */
  .maplegend { max-width: calc(100vw - 16px); }
}

/* ══ CINEMA ════════════════════════════════════════════════════════════════
   All of Fetch's chrome hidden — just the field. The third value of
   data-chrome; 'screen' hides only the site nav.

   ⚠ display: none, NEVER opacity: 0. An opacity-0 control keeps its box and
   stays in the tab order, so a "hidden" chrome would still be reachable by
   Tab and still announced — hidden to the eye and present to everything else.

   ⚠ AND .maplibregl-ctrl-bottom-right IS NOT IN THIS LIST. The Stadia /
   OpenMapTiles / OpenStreetMap credit is a LICENCE REQUIREMENT — covered
   attribution is absent attribution, which css/map.css and js/map.js both
   already say in capitals. It measures 332.5 x 44, about 85% of a 390px bottom
   strip, so it is not a detail that hides quietly. It stays. */
.map-body[data-chrome='cinema'] #map-modeswitch,
.map-body[data-chrome='cinema'] #map-options-btn,
.map-body[data-chrome='cinema'] #map-options,
.map-body[data-chrome='cinema'] #map-legend-btn,
.map-body[data-chrome='cinema'] #map-legend,
.map-body[data-chrome='cinema'] .maplibregl-ctrl-top-right,
/* ⚠ THE FULL-SCREEN TOGGLE GOES TOO, and it was missed on the first pass —
   the derived chrome roster caught it. Cinema already hides more than screen
   does, so a control offering to enter screen is both redundant and a second
   way out that says the wrong thing; the exit chip is the one visible
   control. */
.map-body[data-chrome='cinema'] #map-full-btn,
/* The way in is not needed once you are in — and leaving it would offer to
   enter a state the reader is already in. */
.map-body[data-chrome='cinema'] #map-cinema-btn,
.map-body[data-chrome='cinema'] #map-search-btn,
.map-body[data-chrome='cinema'] #map-note { display: none; }

/* ⚠ THE TIMELINE COLLAPSES, IT DOES NOT HIDE. Hiding it would take
   --timeline-h to zero and drop the attribution to the viewport floor; the
   ResizeObserver on #map-timeline republishes the height either way, so
   collapsing to the nub keeps the credit correctly lifted in BOTH states with
   no new code. */
.map-body[data-chrome='cinema'] .timeline__head,
.map-body[data-chrome='cinema'] .timeline__track { display: none; }
.map-body[data-chrome='cinema'] .timeline { padding: 0; background: none; border: 0; }

/* Panel up: the timeline returns, and brings the mode switcher with it —
   the mode row is hidden in cinema, so the panel is the only place mode can
   live, which is the real reason for the panel to exist beyond the timeline. */
.map-body[data-chrome='cinema'][data-cinema-panel='up'] .timeline__head,
.map-body[data-chrome='cinema'][data-cinema-panel='up'] .timeline__track { display: flex; }
.map-body[data-chrome='cinema'][data-cinema-panel='up'] .timeline {
  padding: 0 10px 8px; background: var(--timeline-bg, rgba(8, 18, 32, 0.86));
}
/* Stage 6 QA: the raised row is NOT `position: static` any more. .map-body is
   a flex column and, with the nav hidden, a static row became its FIRST item
   at y = 0 — flush against the screen edge, buttons' tops cut off — spanning
   the full width under the Exit chip. It is placed with the cinema layout
   block at the end of this file. */

/* ── the pull-up nub ─────────────────────────────────────────────────────── */
.timeline__nub { display: none; }
.map-body[data-chrome='cinema'] .timeline__nub {
  display: flex; align-items: center; gap: 6px;
  /* ⚠ CENTRED — both bottom CORNERS belong to the attribution. */
  margin: 0 auto; padding: 6px 12px;
  border: 1px solid rgba(255, 255, 255, 0.22);
  background: rgba(10, 22, 40, 0.72);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  color: var(--text-on-dark-2); border-radius: 999px;
  font: 600 11px/1 var(--font-sans, system-ui, sans-serif);
  cursor: pointer;
  /* ⚠ touch-action: none ON THE NUB ONLY. The tick strip is 2,352px in a 262px
     window at 390 — nine screens through a porthole, the most gesture-hungry
     surface on the page. Claiming the vertical axis here and leaving pan-x to
     the scroller keeps the two orthogonal: a drag-anywhere-to-dismiss design
     would fight the scroll on every horizontal gesture. */
  touch-action: none;
}
.timeline__nubbar {
  width: 26px; height: 3px; border-radius: 2px;
  background: currentColor; opacity: 0.75;
}
/* The word shows on first entry then the pill shrinks to the bar alone. */
.timeline__nub[data-labelled='0'] .timeline__nubword { display: none; }

/* ── the exit chip ───────────────────────────────────────────────────────── */
.mapexit {
  position: absolute; top: var(--stack-1); right: 8px; z-index: 6;
  display: flex; align-items: center; gap: 5px;
  height: var(--mapopt-btn-h); padding: 0 11px;
  border: 1px solid rgba(255, 255, 255, 0.22);
  background: rgba(10, 22, 40, 0.72);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  color: var(--text-on-dark-2); border-radius: 999px;
  font: 600 11px/1 var(--font-sans, system-ui, sans-serif); cursor: pointer;
  transition: opacity 240ms ease;
}
.mapexit[hidden] { display: none; }
/* Fades back rather than away — it must never become unfindable. */
.mapexit[data-idle='1'] { opacity: 0.35; }
.mapexit:hover, .mapexit:focus-visible { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .mapexit { transition: none; } }

/* ── the search pin's label ─────────────────────────────────────────────────
   The app's shape (lib/map/swell_map_page.dart): a rounded plate carrying the
   NAME and a dismiss ×, sitting above the dot.

   ⚠ THE NAME IS A BUTTON, NOT A DIV WITH A HANDLER. The pin used to be
   aria-hidden with its name in title= only — visible to a sighted reader and
   absent to a screen reader, the same gap as an unresolvable × in the other
   direction. Real buttons are focusable and announced; nothing here relies on
   a title attribute to carry meaning. */
.mapsearch__pin { display: flex; flex-direction: column; align-items: center; }
.mapsearch__chip {
  display: flex; align-items: center; gap: 4px;
  max-width: 220px;
  padding: 5px 6px 5px 9px;
  border-radius: 10px;
  background: var(--surface, #fff);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35);
}
.mapsearch__name {
  border: 0; background: none; padding: 0;
  font: 600 12px/1.25 var(--font-sans, "Inter", system-ui, sans-serif);
  color: var(--text, #16202C);
  text-align: left; cursor: pointer;
  /* Two lines then ellipsis, as the app does — a long spot name must not turn
     the chip into a billboard over the field it is pinned to. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.mapsearch__name:hover { text-decoration: underline; }
.mapsearch__x {
  flex: 0 0 auto;
  width: 22px; height: 22px;          /* the app's 22px dismiss */
  border: 0; border-radius: 6px; background: none;
  font-size: 15px; line-height: 1;
  color: var(--text-secondary, #5B6B7C);
  cursor: pointer;
}
.mapsearch__x:hover { background: var(--surface-sunken, rgba(0,0,0,.06)); }
.mapsearch__dot {
  width: 10px; height: 10px; margin-top: 3px;
  border-radius: 50%;
  background: var(--accent, #12A594);
  box-shadow: 0 0 0 2px var(--surface, #fff);
}

/* The sheet's conditional action. Built only when the report is for a resolved
   entity — see setSheetAction; never present-and-hidden. */
.drsheet__full {
  margin-left: auto; margin-right: var(--sp-3, 12px);
  font: 600 13px/1 var(--font-sans, "Inter", system-ui, sans-serif);
  color: var(--accent-text, #0E7A66);
  text-decoration: none; white-space: nowrap;
}
.drsheet__full:hover { text-decoration: underline; }

/* ── storm-cell callout (radar tap-to-reveal) ──────────────────────────────
   Deliberately dark in both themes: it floats over the radar's own hard-coded
   semantic palette (near-white tracks, dark halos), not over page chrome. */
.stormpop__wrap .maplibregl-popup-content {
  /* The reference callout: translucent dark card, well rounded, the echo
     reading through it. */
  background: rgba(14, 18, 14, .78);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  color: #fff; border-radius: 14px;
  padding: 12px 14px; box-shadow: 0 4px 14px rgba(0, 0, 0, .45);
}
.stormpop__wrap .maplibregl-popup-tip {
  border-top-color: rgba(14, 18, 14, .78); border-bottom-color: rgba(14, 18, 14, .78);
  border-left-color: rgba(14, 18, 14, .78); border-right-color: rgba(14, 18, 14, .78);
}
.stormpop { display: flex; align-items: center; gap: 14px;
  font-size: 14px; line-height: 1.4; }
.stormpop__title { margin: 0 0 2px; font-size: 17px; font-weight: 700; }
.stormpop__clock { margin: 0; }
.stormpop__row { margin: 0; }
.stormpop__row b { font-weight: 700; }
.stormpop__info { color: #4FC3F7; display: inline-flex; }

/* ═══ Maps A §1 — map-first chrome (web redesign) ═══════════════════════════
   Parity reference: the app's Stage 6 map chrome (lib/map/map_chrome.dart):
   round controls on one translucent material with a hairline, the legend
   top-left, ONE right-hand control column (zoom · locate · measure) with the
   attribution under it, all sitting above the time band. The web keeps the
   desktop layer ROW the brief asks for; below ~900 px it is the app's Layers
   popover. Light and dark: the material follows the site theme.
   The Overlays / Basemap / Legend PANELS are Maps B and are untouched here. */
.map-body {
  --mc-bg: rgba(255, 255, 255, 0.9);
  --mc-bg-hover: #fff;
  --mc-panel: rgba(255, 255, 255, 0.96);
  --mc-ink: #16202C;
  --mc-line: rgba(16, 32, 44, 0.14);
  --mc-shadow: 0 2px 10px rgba(9, 19, 34, 0.16);
  --mc-accent: var(--brand-teal, #0E7A66);
  --mc-on: #fff;
  --modes-h: 34px;
  --layers-btn-w: 96px;
  /* the legend now hangs top-left, under the layer row */
  --maplegend-top: calc(var(--map-top) + 12px + var(--modes-h) + 8px + var(--mapopt-btn-h) + 8px);
}
:root[data-theme='dark'] .map-body {
  --mc-bg: rgba(30, 38, 46, 0.75);          /* the app's band material 0xBF1E262E */
  --mc-bg-hover: rgba(40, 50, 60, 0.88);
  --mc-panel: rgba(24, 31, 38, 0.94);
  --mc-ink: #fff;
  --mc-line: rgba(255, 255, 255, 0.2);      /* 0x33FFFFFF hairline */
  --mc-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
}

/* the layer row */
.modeswitch { top: calc(var(--map-top) + 12px); left: 12px; right: 58px; }
.modeswitch__btn {
  border: 1px solid var(--mc-line); background: var(--mc-bg); color: var(--mc-ink);
  box-shadow: var(--mc-shadow);
  font: 600 13px/1 var(--font-sans, system-ui, sans-serif); padding: 9px 13px;
}
.modeswitch__btn:hover { background: var(--mc-bg-hover); }
.modeswitch__btn[aria-current='true'] { background: var(--mc-accent); border-color: var(--mc-accent); color: var(--mc-on); }
.modeswitch__btn:focus-visible { outline: 2px solid var(--mc-accent); outline-offset: 2px; }

/* round controls (full screen · cinema · search · options · legend · layers) */
.mapopt__btn {
  border: 1px solid var(--mc-line); background: var(--mc-bg); color: var(--mc-ink);
  box-shadow: var(--mc-shadow);
}
.mapopt__btn:hover { background: var(--mc-bg-hover); }
.mapopt__btn[aria-expanded='true'] { background: var(--mc-accent); border-color: var(--mc-accent); color: var(--mc-on); }
.mapopt__btn:focus-visible { outline: 2px solid var(--mc-accent); }

/* legend: TOP-LEFT, under the row (app: left 12, top safe + 12) */
.maplegend__btn { top: calc(var(--map-top) + 12px + var(--modes-h) + 8px); left: 12px; right: auto; }
.maplegend { left: 12px; right: auto; }

/* Layers button: only below the breakpoint */
.maplayers__btn { display: none; }
@media (max-width: 900px) {
  .map-body { --maplegend-top: calc(var(--map-top) + 12px + var(--mapopt-btn-h) + 8px); }
  .maplayers__btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    top: calc(var(--map-top) + 12px); left: 12px; right: auto;
    width: var(--layers-btn-w); padding: 0 12px;
    font: 600 13px/1 var(--font-sans, system-ui, sans-serif);
  }
  .maplegend__btn { top: calc(var(--map-top) + 12px); left: calc(12px + var(--layers-btn-w) + 8px); }
  .modeswitch { display: none; }
  /* the SAME buttons, as a compact two-column panel under the button */
  .map-body[data-layers-open='1'] .modeswitch {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px;
    top: calc(var(--map-top) + 12px + var(--mapopt-btn-h) + 8px); left: 12px; right: auto;
    width: min(300px, calc(100vw - 24px));
    max-height: calc(100dvh - var(--map-top) - var(--mapopt-btn-h) - var(--timeline-h, 0px) - 40px);
    overflow-x: hidden; overflow-y: auto; padding: 8px; z-index: 6;
    background: var(--mc-panel); border: 1px solid var(--mc-line); border-radius: 14px;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
    -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  }
  .map-body[data-layers-open='1'] .modeswitch__btn {
    width: 100%; text-align: left; border-radius: 10px; box-shadow: none; padding: 10px 12px;
  }
}

/* the right-hand control column: zoom · locate · measure, attribution under */
.map-body .maplibregl-ctrl-bottom-right .maplibregl-ctrl-group {
  margin: 0 12px 8px 0; border-radius: 12px; overflow: hidden;
  background: var(--mc-bg); border: 1px solid var(--mc-line); box-shadow: var(--mc-shadow);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
}
.map-body .maplibregl-ctrl-bottom-right .maplibregl-ctrl-group button { width: 38px; height: 38px; }
.map-body .maplibregl-ctrl-bottom-right .maplibregl-ctrl-group button + button { border-top-color: var(--mc-line); }
.map-body .maplibregl-ctrl-bottom-right .maplibregl-ctrl-attrib { margin: 0 12px 8px 0; }
:root[data-theme='dark'] .map-body .maplibregl-ctrl-group .maplibregl-ctrl-icon { filter: invert(1) brightness(1.4); }
.mapmeasure__btn { display: grid; place-items: center; color: var(--mc-ink); cursor: pointer; }
.mapmeasure__btn[aria-pressed='true'] { background: var(--mc-accent); color: var(--mc-on); }
.map-body[data-chrome='cinema'] .maplibregl-ctrl-bottom-right .maplibregl-ctrl-group,
.map-body[data-chrome='cinema'] #map-layers-btn { display: none; }
/* ── the right column's overflow (js/map-stack.js) ──────────────────────────
   When the column cannot fit between the top-right chrome and the pills, the
   lowest-priority tools fold into the one More button: level 1 = measure +
   locate, level 2 = zoom too. Overlays and the attribution never fold. The
   folded buttons stay in the DOM (display:none) — the More rows click them. */
.map-body:not([data-right-stack='1']):not([data-right-stack='2']) .mapstack__more { display: none; }
.map-body[data-right-stack='1'] :is(.mapstack__measure, .mapstack__geo) { display: none; }
.map-body[data-right-stack='2'] :is(.mapstack__measure, .mapstack__geo, .mapstack__zoom) { display: none; }
.mapmore__row[aria-disabled='true'] { opacity: 0.5; cursor: default; }

/* ═══ Maps A §2 — the time band + draggable ruler ═══════════════════════════
   The app's band (lib/map/time_band.dart): full width, translucent, blurred
   (σ 4), row 1 = play · the selected time · Now (32), row 2 = the ruler (36).
   The ruler paints on a canvas (js/time-ruler.js) and reads its colours from
   the --ruler-* tokens below, so both themes are CSS. The old native-range
   rules above are dead with it (Stage 6 cleanup). */
.map-body {
  --band-bg: rgba(255, 255, 255, 0.86);
  --ruler-ink: #16202C;
  --ruler-muted: rgba(22, 32, 44, 0.6);
  --ruler-rule: rgba(22, 32, 44, 0.28);
  --ruler-lock: rgba(16, 32, 44, 0.09);
  --ruler-accent: #0E7A66;                  /* kFetchBrandTeal, the app's chromeAccent */
  --ruler-now: #00A846;
  --ruler-font: var(--font-sans, "Inter", system-ui, sans-serif);
}
:root[data-theme='dark'] .map-body {
  --band-bg: rgba(30, 38, 46, 0.75);        /* 0xBF1E262E */
  --ruler-ink: #fff;
  --ruler-muted: rgba(255, 255, 255, 0.7);  /* 0xB3FFFFFF */
  --ruler-rule: rgba(255, 255, 255, 0.35);
  --ruler-lock: rgba(0, 0, 0, 0.2);         /* 0x33000000 */
  --ruler-accent: #3DE0C4;
  --ruler-now: #00C853;
}
.timeline {
  gap: 0;
  padding: 4px 0 calc(4px + env(safe-area-inset-bottom, 0px));
  background: var(--band-bg);
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  border-top: 1px solid var(--mc-line);
  color: var(--ruler-ink);
}
.timeline__head { min-height: 32px; padding: 0 12px; gap: 10px; }
.timeline__when { font: 600 14px/1.2 var(--ruler-font); color: var(--ruler-ink); font-variant-numeric: tabular-nums; }
.timeline__frame { font: 500 11px/1 var(--ruler-font); color: var(--ruler-muted); }
.timeline__frame:empty { display: none; }
.timeline__play {
  width: 36px; height: 30px; border-color: var(--mc-line);
  background: transparent; color: var(--ruler-ink);
}
.timeline__play:hover { background: var(--mc-bg-hover); }
.timeline__now {
  margin-left: auto; border: 1px solid var(--mc-line); background: transparent;
  color: var(--ruler-ink); border-radius: 999px; padding: 5px 12px;
  font: 600 12px/1 var(--ruler-font);
}
.timeline__now:hover { background: var(--mc-bg-hover); }
.timeline__track { display: block; }
.timeline[data-busy] .timeline__track { opacity: 1; }   /* the ruler never dims mid-drag */
.timeline__ruler {
  position: relative; height: 36px; overflow: hidden;
  touch-action: none; user-select: none; -webkit-user-select: none; cursor: grab;
  outline: none;
}
.timeline__ruler:active { cursor: grabbing; }
.timeline__ruler:focus-visible { box-shadow: inset 0 0 0 2px var(--ruler-accent); border-radius: 6px; }
.ruler__canvas { display: block; }
/* the FIXED centre marker — 2 px, the accent, radius 1 (time_band.dart:445) */
.ruler__marker {
  position: absolute; top: 0; bottom: 0; left: calc(50% - 1px); width: 2px;
  border-radius: 1px; background: var(--ruler-accent); pointer-events: none;
}
.map-body[data-chrome='cinema'][data-cinema-panel='up'] .timeline__track { display: block; }
/* Maps B §0: Radar, last in the row, leaves the map — a small ↗ says so */
.modeswitch__out { display: inline-block; vertical-align: -1px; margin-left: 3px; opacity: 0.8; }

/* ═══ Maps B §1 — anchored popovers (the app's MapPopoverSlot) ══════════════
   Overlays and Basemap hang off the right control column: right-aligned just
   left of it, bottom-aligned above the time band, scrolling within the map
   area. The Legend hangs off its top-left button. ONE open at a time
   (js/map-popovers.js). The panel material follows the site theme, like the
   rest of the Maps A chrome. */
.map-body .mappop {
  top: auto; right: 62px;
  bottom: calc(var(--timeline-h, 0px) + var(--pills-clear, 0px) + 8px);
  max-height: calc(100dvh - var(--map-top) - var(--timeline-h, 0px) - var(--pills-clear, 0px) - 24px);
  width: min(300px, calc(100vw - 74px));
}
.map-body .mapopt {
  background: var(--mc-panel); color: var(--mc-ink); border-color: var(--mc-line);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.22);
}
.map-body .mapopt__page { scrollbar-color: var(--mc-line) transparent; }
.mappop__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin: -2px 0 8px; flex: 0 0 auto; }
.mappop__title { margin: 0; font: 700 15px/1.2 var(--font-sans, system-ui, sans-serif); }
.mappop__x {
  width: 32px; height: 32px; border: 0; border-radius: 8px; background: none; cursor: pointer;
  color: var(--mc-ink); font-size: 20px; line-height: 1;
}
.mappop__x:hover { background: color-mix(in srgb, var(--mc-ink) 8%, transparent); }
.mappop__link { margin-top: 6px; border-top: 1px solid var(--mc-line); padding-top: 10px; flex: 0 0 auto; }
:where(.mappop__x, .mapthumb, .mappanel__btn):focus-visible { outline: 2px solid var(--mc-accent); outline-offset: 2px; }

/* Basemap: the app's MapBasemapThumb row — 80×60, radius 10, accent ring */
.mapthumbs { display: flex; justify-content: space-between; gap: 8px; }
.mapthumb {
  display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 0;
  background: none; border: 0; color: inherit; cursor: pointer; font: 600 12px/1.2 var(--font-sans, system-ui, sans-serif);
}
.mapthumb__img {
  width: 80px; height: 60px; object-fit: cover; border-radius: 10px; display: block;
  border: 2px solid transparent; box-shadow: 0 0 0 1px var(--mc-line);
}
.mapthumb[aria-checked='true'] .mapthumb__img { border-color: var(--mc-accent); }
.mapthumb[data-locked] { opacity: 0.6; }

/* the panel buttons, heading the column */
.mappanel__btn { display: grid; place-items: center; color: var(--mc-ink); cursor: pointer; }
.mappanel__btn[aria-expanded='true'] { background: var(--mc-accent); color: var(--mc-on); }

/* Legend: compact — a 12×150 bar, high at the top, labels beside it */
.maplegend__scale { display: flex; gap: 8px; align-items: stretch; }
.maplegend__bar { width: 12px; height: 150px; border-radius: 3px; flex: 0 0 auto; box-shadow: inset 0 0 0 1px var(--mc-line); }
.maplegend__labels { display: flex; flex-direction: column; justify-content: space-between; height: 150px; }
.maplegend__labels .maplegend__lbl { font: 500 10px/1 var(--font-sans, system-ui, sans-serif); font-variant-numeric: tabular-nums; white-space: nowrap; }
.map-body .maplegend { min-width: 0; width: max-content; padding: 8px 10px 8px 8px; }

.map-body[data-chrome='cinema'] #map-overlays,
.map-body[data-chrome='cinema'] #map-more { display: none; }

/* ═══ Maps B §3 — the radar page in the map's chrome ════════════════════════
   Same layout as /map: the Legend popover top-left (the site label beside
   it), the right control column bottom-right (zoom · locate · measure, with
   site · settings above) and the attribution under it, the time band with
   the ruler along the bottom. Material and ruler colours are the shared
   --mc-* / --ruler-* tokens, so light and dark follow the site theme. */
#radar-legend-btn { top: calc(var(--map-top) + 12px); left: 12px; right: auto; width: 88px; }
#radar-legend-panel {
  top: calc(var(--map-top) + 12px + var(--mapopt-btn-h) + 8px); left: 12px; right: auto;
  width: min(260px, calc(100vw - 24px)); max-width: none;
}
#radar-legend-panel .radarleg__title { color: var(--mc-ink); }
#radar-legend-panel .radarleg__tick { color: var(--mc-ink); opacity: 0.8; }
.map-body .radarbar {
  top: calc(var(--map-top) + 12px); left: calc(12px + 88px + 8px);
  background: var(--mc-bg); color: var(--mc-ink); border: 1px solid var(--mc-line);
  box-shadow: var(--mc-shadow); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  max-width: min(calc(100vw - 132px), 320px);
}
.map-body .radarattrib {
  left: 12px; right: auto; bottom: calc(var(--timeline-h, 0px) + 8px);
  background: var(--mc-bg); color: var(--mc-ink);
}
.radarband .radarctl__stale { margin-left: 4px; }
.radarband .timeline__play:disabled { opacity: 0.45; cursor: default; }
.radarband .timeline__play[data-state='playing'] .timeline__tri { display: none; }
.radarband .timeline__play[data-state='playing'] .timeline__bars { display: inline; }
/* the column's radar buttons: the site code widens its button */
.map-body .maplibregl-ctrl-bottom-right .maplibregl-ctrl-group button#radar-site-btn {
  width: auto; min-width: 38px; padding: 0 8px; display: flex; align-items: center; gap: 4px;
  background: transparent; border: 0; border-radius: 0; box-shadow: none; color: var(--mc-ink);
  font: 600 11px/1 var(--font-sans, system-ui, sans-serif);
}
.map-body .maplibregl-ctrl-bottom-right .maplibregl-ctrl-group .mappanel__btn {
  background: transparent; border: 0; border-radius: 0; box-shadow: none; color: var(--mc-ink);
}
.map-body .maplibregl-ctrl-bottom-right .maplibregl-ctrl-group .mappanel__btn[aria-pressed='true'],
.map-body .maplibregl-ctrl-bottom-right .maplibregl-ctrl-group .mappanel__btn[aria-expanded='true'] {
  background: var(--mc-accent); color: var(--mc-on);
}

/* Maps B rev B §3: the legend's unit segment (the phone's MapLegendScale) */
.maplegend__units { display: flex; gap: 2px; padding: 2px; margin: 0 0 8px; border-radius: 8px; background: rgba(255, 255, 255, 0.08); }
.maplegend__unit {
  flex: 1 1 0; border: 0; border-radius: 6px; padding: 4px 6px; background: none; cursor: pointer;
  color: var(--mc-ink); font: 600 11px/1 var(--font-sans, system-ui, sans-serif); white-space: nowrap;
}
.maplegend__unit[aria-pressed='true'] { background: var(--mc-accent); color: var(--mc-on); }
.maplegend__unit:focus-visible { outline: 2px solid #6FE3CC; outline-offset: 1px; }

/* ═══ Maps B rev B §4 — THE PHONE'S CHROME: dark glass on every basemap ═════
   lib/map/map_chrome.dart:139-235 + time_band.dart:50-60. One material for
   every map control, independent of basemap AND of the site theme (the app's
   chrome never follows either): fill kTimeBandColor 0xBF1E262E (rgba 30,38,
   46 @ 0.75) over a σ4 backdrop blur, a 1 px kMapChromeHairline 0x33FFFFFF,
   white ink (kMapChromeInk), 40 px round buttons (44 hit), a 40 px / radius 20
   layer pill, accent 0xFF0E7A66 when active. The band is the same material
   with the phone's text shadow (0x99000000, blur 3, y 1). This overrides the
   theme-following tokens of Maps A / B; the panels take the glass too (the
   phone's popover cards follow the app theme — Grant asked for glass). */
.map-body,
:root[data-theme='dark'] .map-body {
  --mc-bg: rgba(30, 38, 46, 0.75);
  --mc-bg-hover: rgba(46, 56, 66, 0.82);
  --mc-panel: rgba(30, 38, 46, 0.75);
  --mc-ink: #FFFFFF;
  --mc-muted: rgba(255, 255, 255, 0.7);
  --mc-line: rgba(255, 255, 255, 0.2);
  --mc-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
  --mc-accent: #0E7A66;
  --mc-on: #FFFFFF;
  --mc-focus: #6FE3CC;
  --mc-blur: blur(4px);
  --mapopt-btn-h: 40px;
  --modes-h: 40px;
  --band-bg: rgba(30, 38, 46, 0.75);
  --ruler-ink: #FFFFFF;
  --ruler-muted: rgba(255, 255, 255, 0.7);     /* 0xB3FFFFFF */
  --ruler-rule: rgba(255, 255, 255, 0.35);     /* kTimeBandDim 0x59FFFFFF */
  --ruler-lock: rgba(0, 0, 0, 0.2);            /* 0x33000000 */
  --ruler-accent: #3DE0C4;
  --ruler-now: #00C853;
}
/* the glass, on every control surface */
.map-body :is(.modeswitch__btn, .mapopt__btn, .mapopt, .timeline, .radarbar, .radarattrib,
  .maplibregl-ctrl-bottom-right .maplibregl-ctrl-group, .maplibregl-ctrl-attrib) {
  background: var(--mc-bg); color: var(--mc-ink); border: 1px solid var(--mc-line);
  -webkit-backdrop-filter: var(--mc-blur); backdrop-filter: var(--mc-blur);
}
.map-body .timeline { border-width: 1px 0 0; }
.map-body .mapopt { background: var(--mc-panel); box-shadow: var(--mc-shadow); }
.map-body[data-layers-open='1'] .modeswitch { background: var(--mc-panel); border-color: var(--mc-line);
  -webkit-backdrop-filter: var(--mc-blur); backdrop-filter: var(--mc-blur); }
/* round buttons 40 / layer pill 40, radius 20 */
.map-body .mapopt__btn { width: 40px; height: 40px; border-radius: 20px; }
.map-body .maplegend__btn, .map-body .maplayers__btn, .map-body #radar-legend-btn { width: auto; min-width: 40px; padding: 0 12px; border-radius: 20px; }
.map-body .modeswitch__btn { height: 40px; padding: 0 12px; border-radius: 20px; font: 700 13px/1 var(--font-sans, "Inter", system-ui, sans-serif); }
.map-body :is(.modeswitch__btn, .mapopt__btn):hover { background: var(--mc-bg-hover); }
.map-body :is(.modeswitch__btn[aria-current='true'], .mapopt__btn[aria-expanded='true']) {
  background: var(--mc-accent); border-color: var(--mc-accent); color: var(--mc-on);
}
.map-body .maplibregl-ctrl-bottom-right .maplibregl-ctrl-group { border-radius: 20px; box-shadow: none; }
.map-body .maplibregl-ctrl-bottom-right .maplibregl-ctrl-group button { width: 40px; height: 40px; }
.map-body .maplibregl-ctrl-group button + button { border-top: 1px solid var(--mc-line); }
.map-body .maplibregl-ctrl-group .maplibregl-ctrl-icon { filter: invert(1); }   /* MapLibre's dark glyphs -> white ink */
.map-body .maplibregl-ctrl-attrib { border-radius: 12px; }
.map-body .maplibregl-ctrl-attrib a { color: var(--mc-ink); }
.map-body .maplibregl-ctrl-attrib-button { filter: invert(1); background-color: transparent; }
/* panel text on glass */
.map-body .mapopt :is(.mapopt__cap, .mapopt__note) { color: var(--mc-muted); opacity: 1; }
.map-body .mappop__x:hover { background: rgba(255, 255, 255, 0.1); }
.map-body .maplegend__labels .maplegend__lbl,
.map-body .maplegend__title { color: var(--mc-ink); }
/* the band's ink and the phone's text shadow */
.map-body .timeline :is(.timeline__when, .timeline__frame, .timeline__now, .timeline__play) {
  color: var(--ruler-ink); text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
}
.map-body .timeline__frame { color: var(--ruler-muted); }
.map-body .timeline :is(.timeline__now, .timeline__play) { border-color: var(--mc-line); background: transparent; }
.map-body .timeline :is(.timeline__now, .timeline__play):hover { background: rgba(255, 255, 255, 0.1); }
/* focus stays visible on glass */
.map-body :is(.modeswitch__btn, .mapopt__btn, .mappanel__btn, .mapthumb, .mappop__x, .timeline__now,
  .timeline__play, .mapmeasure__btn, .maplibregl-ctrl-group button, .mapopt__row):focus-visible {
  outline: 2px solid var(--mc-focus); outline-offset: 2px;
}
.map-body .timeline__ruler:focus-visible { box-shadow: inset 0 0 0 2px var(--mc-focus); }
/* the two pills other offsets are computed from keep a fixed width */
.map-body .maplayers__btn { width: var(--layers-btn-w); }
.map-body #radar-legend-btn { width: 88px; }

/* ═══ Maps B rev C §1 — the Overlays / Basemap panels as squarer tile cards ═
   ≤ 360 × 420 at every width; the header (title + ×) stays, the body
   scrolls. Sections with ALL-CAPS headings; tiles in a 3-column grid (2 on
   a narrow screen), icon + label (+ where it applies), ≥ 44 px, the on state
   in the accent. Dark glass as before. */
.map-body .mappop {
  width: min(360px, calc(100vw - 74px));
  max-height: min(420px, calc(100dvh - var(--map-top) - var(--timeline-h, 0px) - 24px));
}
.maptiles__sec + .maptiles__sec { margin-top: 12px; }
.maptiles__sec .mapopt__cap { margin: 0 0 6px; font-size: 10.5px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; }
.maptiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; }
@media (max-width: 360px) { .maptiles { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.maptile {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  min-height: 64px; padding: 8px 4px; border-radius: 12px; cursor: pointer;
  border: 1px solid var(--mc-line); background: rgba(255, 255, 255, 0.06); color: var(--mc-ink);
  font: 600 12px/1.15 var(--font-sans, "Inter", system-ui, sans-serif); text-align: center;
}
.maptile:hover { background: rgba(255, 255, 255, 0.12); }
.maptile__ic svg { display: block; }
.maptile__sub { font-size: 10px; font-weight: 500; color: var(--mc-muted); }
.maptile[aria-pressed='true'], .maptile[aria-checked='true'] {
  background: var(--mc-accent); border-color: var(--mc-accent); color: var(--mc-on);
}
.maptile[aria-pressed='true'] .maptile__sub, .maptile[aria-checked='true'] .maptile__sub { color: rgba(255, 255, 255, 0.85); }
.maptile[data-inapplicable] { opacity: 0.55; }
.maptile[data-locked] { opacity: 0.75; }
.maptile:focus-visible { outline: 2px solid var(--mc-focus, #6FE3CC); outline-offset: 2px; }
/* Basemap: the same tile frame around each thumbnail */
.map-body .mapthumbs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; }
.map-body .mapthumb {
  padding: 6px; border-radius: 12px; border: 1px solid var(--mc-line); background: rgba(255, 255, 255, 0.06);
  min-height: 44px; color: var(--mc-ink);
}
.map-body .mapthumb:hover { background: rgba(255, 255, 255, 0.12); }
.map-body .mapthumb__img { width: 100%; height: auto; aspect-ratio: 4 / 3; }
.map-body .mapthumb[aria-checked='true'] { border-color: var(--mc-accent); box-shadow: inset 0 0 0 1px var(--mc-accent); }

/* ═══ Maps B rev D §1 — WINDOWS ARE SOLID; only the always-on controls are
   glass. Every window that opens from a control — Overlays, Basemap, Legend,
   the narrow Layers panel (and on /radar its Legend; the Radar Settings /
   Mode sheets and the Daily Report sheet were already solid) — takes the
   site theme's ordinary card: opaque, no backdrop blur, the theme's border
   and the phone's popover shadow (mapPickerCardDecoration: card, radius 14,
   1 px line, black .35 / blur 12 / y 4). The phone's popovers follow the
   app theme the same way. Their inner tokens are re-pointed at the theme so
   tiles, chips and toggles keep their contrast in light AND dark. */
.map-body .mapopt,
.map-body[data-layers-open='1'] .modeswitch {
  --mc-bg: var(--surface-raised, var(--bg));
  --mc-bg-hover: var(--surface-sunken, var(--surface));
  --mc-panel: var(--bg);
  --mc-ink: var(--text);
  --mc-muted: var(--text-secondary);
  --mc-line: var(--line-strong, var(--line));
  --mc-focus: var(--accent);
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--line-strong, var(--line));
  border-radius: 14px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
  -webkit-backdrop-filter: none; backdrop-filter: none;
}
.map-body .mapopt :is(.maptile, .mapthumb) { background: var(--surface-sunken, var(--surface)); }
.map-body .mapopt :is(.maptile, .mapthumb):hover { background: var(--accent-tint); }
.map-body .mapopt .maplegend__units { background: var(--surface-sunken, var(--surface)); }
.map-body .mapopt .mappop__x:hover { background: var(--accent-tint); }
.map-body .mapopt .mapopt__page { scrollbar-color: var(--line-strong, var(--line)) transparent; }
/* the narrow Layers panel's buttons, on the solid card */
.map-body[data-layers-open='1'] .modeswitch__btn {
  background: var(--surface-sunken, var(--surface)); color: var(--text);
  border-color: var(--line-strong, var(--line)); text-shadow: none;
}
.map-body[data-layers-open='1'] .modeswitch__btn:hover { background: var(--accent-tint); }
.map-body[data-layers-open='1'] .modeswitch__btn[aria-current='true'] { background: var(--mc-accent, #0E7A66); color: #fff; }
/* the radar legend's text on the card */
#radar-legend-panel :is(.radarleg__title, .radarleg__tick) { color: var(--text); }

/* ═══ Maps B rev D §2 — EVERY MAP POP-UP FITS THE VISIBLE MAP AREA ══════════
   Between the header (--map-top) and the time band (--timeline-h, measured
   live by a ResizeObserver on both pages), with a margin: never under the
   band, never off-screen. Taller content scrolls in the window's BODY while
   its title + × stay put. All in CSS on live variables + dvh, so a resize
   (or the band changing height) recomputes it. */
/* Radar Settings + Select mode: anchored in the map area, not a bottom sheet
   that ran under the band (its last rows — Loop, Basemap — were hidden) */
.map-body :is(.radarsheet, .radarmodes) {
  inset: var(--map-top) 0 var(--timeline-h, 0px) 0;
  padding: 8px 12px; box-sizing: border-box;
  align-items: flex-end;
}
.map-body :is(.radarsheet__panel, .radarmodes__sheet) {
  max-height: 100%; box-sizing: border-box; border-radius: 16px;
  border: 1px solid var(--line-strong, var(--line)); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
  overscroll-behavior: contain;
}
/* the title + × stay fixed while the body scrolls */
.map-body :is(.radarsheet__head, .radarmodes__head) {
  position: sticky; top: -16px; z-index: 1;
  margin: -16px -16px 0; padding: 16px 16px 8px;
  background: var(--surface); border-radius: 16px 16px 0 0;
}
/* Legend (both pages): from its anchor down to the band */
.map-body .maplegend {
  max-height: calc(100dvh - var(--maplegend-top) - var(--timeline-h, 0px) - 16px);
  overflow-y: auto;
}
.map-body #radar-legend-panel {
  max-height: calc(100dvh - var(--map-top) - var(--mapopt-btn-h) - 20px - var(--timeline-h, 0px) - 16px);
}
/* the Daily Report (point forecast) sheet stops above the band */
.map-body .drsheet {
  bottom: var(--timeline-h, 0px);
  max-height: calc(100dvh - var(--map-top) - var(--timeline-h, 0px) - 16px);
}
@media (min-width: 900px) {
  .map-body .drsheet { bottom: calc(var(--timeline-h, 0px) + 8px); }
}

/* ═══ Maps B rev E §1 — TILE STATES READABLE ON THE SOLID PANELS ════════════
   Rev D made the windows the theme card but the states kept glass colours,
   and rev D's own "tiles on the card surface" rule outranked the selected
   rule — so a SELECTED tile kept the pale surface under its white text. The
   states are re-stated here, specific enough to win, for both themes:
     selected      SOLID TEAL #0E7A66 + white icon and label — 5.2:1 (white
                   on #0E7A66), the same in light and dark; its border and
                   bold label make "on" obvious at a glance
     unselected    the theme's text on the card's sunken surface, a subtle
                   strong-line border
     not here      no opacity: the label AND its "Only …" caption in the
                   theme's secondary text (light #44555E on #FFFFFF 7.8:1;
                   dark #93A6B6 on #0F1C2C 6.9:1) on a transparent tile with a
                   dashed border; aria-disabled, not clickable, a tooltip
     locked        full-strength text with its 🔒 (opens Fetch+)
     hover / focus a teal border + tint / a 2 px --accent-text ring */
.map-body .mapopt .maptile,
.map-body .mapopt .mapthumb,
.map-body .mapopt .maplegend__unit,
.map-body[data-layers-open='1'] .modeswitch .modeswitch__btn {
  background: var(--surface-sunken, var(--surface)); color: var(--text);
  border: 1px solid var(--line-strong, var(--line)); opacity: 1;
}
.map-body .mapopt .maplegend__unit { border-color: transparent; }
.map-body .mapopt .maptile .maptile__sub { color: var(--text-secondary); }
.map-body .mapopt :is(.maptile, .mapthumb, .maplegend__unit):hover,
.map-body[data-layers-open='1'] .modeswitch .modeswitch__btn:hover {
  background: var(--accent-tint); border-color: var(--accent-text);
}
.map-body .mapopt :is(.maptile[aria-pressed='true'], .maptile[aria-checked='true'], .maplegend__unit[aria-pressed='true']),
.map-body[data-layers-open='1'] .modeswitch .modeswitch__btn[aria-current='true'] {
  background: #0E7A66; border-color: #0E7A66; color: #FFFFFF;
}
.map-body .mapopt :is(.maptile[aria-pressed='true'], .maptile[aria-checked='true']) .maptile__lbl { font-weight: 800; }
.map-body .mapopt :is(.maptile[aria-pressed='true'], .maptile[aria-checked='true']) .maptile__sub { color: #FFFFFF; }
.map-body .mapopt :is(.maptile[aria-pressed='true'], .maptile[aria-checked='true'], .maplegend__unit[aria-pressed='true']):hover,
.map-body[data-layers-open='1'] .modeswitch .modeswitch__btn[aria-current='true']:hover { background: #0B6655; border-color: #0B6655; }
.map-body .mapopt .maptile[data-inapplicable],
.map-body .mapopt .maptile[data-inapplicable]:hover {
  opacity: 1; background: transparent; border-style: dashed; color: var(--text-secondary); cursor: not-allowed;
}
.map-body .mapopt .maptile[data-inapplicable][aria-pressed='true'],
.map-body .mapopt .maptile[data-inapplicable][aria-checked='true'] {
  /* on, but not on THIS layer: say both — a teal outline, readable text */
  background: transparent; color: var(--text-secondary); border: 1px dashed var(--accent-text);
}
.map-body .mapopt .maptile[data-locked] { opacity: 1; }
.map-body .mapopt .mapthumb[aria-checked='true'] { border-color: #0E7A66; box-shadow: inset 0 0 0 1px #0E7A66; }
.map-body .mapopt .mapthumb[aria-checked='true'] .mapthumb__lbl { color: var(--accent-text); font-weight: 800; }
.map-body .mapopt :is(.maptile, .mapthumb, .maplegend__unit, .mappop__x, .mapopt__row):focus-visible,
.map-body[data-layers-open='1'] .modeswitch .modeswitch__btn:focus-visible {
  outline: 2px solid var(--accent-text); outline-offset: 2px;
}
/* Radar Settings: the chosen row is unmistakable — the same solid teal */
.radarrow--style[data-on="true"], .radarmode[data-on="true"] { background: #0E7A66; color: #FFFFFF; }
.radarrow--style[data-on="true"] .radarrow__name, .radarmode[data-on="true"] .radarmode__name { color: #FFFFFF; }
.radarrow--style[data-on="true"]:hover, .radarmode[data-on="true"]:hover { background: #0B6655; }
.radarrow--style[data-on="true"] :is(.radarrow__desc, .radarrow__value, .radarrow__chev),
.radarmode[data-on="true"] * { color: #FFFFFF; }
.radarrow__switch:disabled { opacity: 0.6; }
.radarrow:focus-visible, .radarmode:focus-visible { outline: 2px solid var(--accent-text); outline-offset: 2px; }

/* ═══ Maps B rev E §2 — the header never covers the body's first row ═══════
   Every window opens scrolled to the top (js/map-popovers.js); the fixed
   header is separated from the body by its own padding and a divider, with
   no negative margins, and the body has room at its top edge for the first
   row's focus ring. */
.map-body .mappop__head { margin: 0; padding: 0 0 8px; border-bottom: 1px solid var(--line); }
.map-body .mappop .mapopt__page { padding: 10px 2px 4px; }
.map-body :is(.radarsheet__panel, .radarmodes__sheet) { padding-top: 0; }
.map-body :is(.radarsheet__head, .radarmodes__head) {
  top: 0; margin: 0 -16px 4px; padding: 16px 16px 10px;
  border-bottom: 1px solid var(--line);
}

/* ═══ Stage 6 QA — cinema layout: nothing clipped, nothing overlapping ═════
   Cinema hides the nav, so --map-top is the notch/status-bar inset instead
   of the header (it was 0px, which put everything against the glass on a
   notched phone). Exit owns a fixed slot at the top right; the raised layer
   row sits at normal mode's top margin and STOPS before that slot, scrolling
   horizontally when the modes do not fit (the Layers button is hidden in
   cinema, so the row scrolls at every width instead of collapsing). The
   timeline, the nub and the attribution keep their existing bottom anchoring
   (--timeline-h); Legend, Overlays/Basemap and the right control column are
   hidden in cinema by the rules above. Leaving cinema drops every rule here
   — they are all keyed on data-chrome='cinema'. */
.map-body[data-chrome='cinema'] {
  --map-top: env(safe-area-inset-top, 0px);
  --cin-l: calc(12px + env(safe-area-inset-left, 0px));
  --cin-r: calc(12px + env(safe-area-inset-right, 0px));
  --cin-exit-w: 76px;
}
.map-body[data-chrome='cinema'] #map-exit-btn {
  top: calc(var(--map-top) + 12px); right: var(--cin-r);
  width: var(--cin-exit-w); height: 40px; box-sizing: border-box;
  justify-content: center;
}
.map-body[data-chrome='cinema'][data-cinema-panel='up'] #map-modeswitch {
  display: flex; flex-direction: row; flex-wrap: nowrap; align-items: center;
  position: absolute; z-index: 5; margin: 0;
  top: calc(var(--map-top) + 12px);
  left: var(--cin-l);
  right: calc(var(--cin-r) + var(--cin-exit-w) + 8px);
  width: auto; max-height: none;
  padding: 0 0 2px; background: none; border: 0; box-shadow: none;
  overflow-x: auto; overflow-y: hidden;
}
.map-body[data-chrome='cinema'][data-cinema-panel='up'] #map-modeswitch .modeswitch__btn {
  flex: 0 0 auto; width: auto; height: 40px; padding: 0 12px; border-radius: 20px;
  justify-content: center; white-space: nowrap;
}

/* ═══ Timeline style: "Timeline: Ruler | Buttons" (js/map-timeline-style.js)
   The map chrome's segment (the legend's unit segment), at the foot of the
   Basemap panel and — below the 900 px breakpoint — of the Layers panel. */
.tlstyle { display: flex; align-items: center; gap: 10px; }
.tlstyle--panel { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--mc-line); }
.tlstyle__lbl { font: 600 12px/1 var(--font-sans, system-ui, sans-serif); color: var(--mc-muted); }
.tlstyle__seg { display: flex; gap: 2px; padding: 2px; border-radius: 8px; background: rgba(255, 255, 255, 0.08); flex: 1 1 auto; }
.tlstyle__opt {
  flex: 1 1 0; border: 0; border-radius: 6px; padding: 6px 8px; background: none; cursor: pointer;
  color: var(--mc-ink); font: 600 12px/1 var(--font-sans, system-ui, sans-serif); white-space: nowrap;
}
.tlstyle__opt[aria-checked='true'] { background: var(--mc-accent); color: var(--mc-on); }
.tlstyle__opt:focus-visible { outline: 2px solid var(--mc-focus, #6FE3CC); outline-offset: 1px; }
.modeswitch__tlstyle { display: none; }
@media (max-width: 900px) {
  .map-body[data-layers-open='1'] .modeswitch__tlstyle {
    display: flex; grid-column: 1 / -1; margin-top: 4px; padding-top: 8px;
    border-top: 1px solid var(--mc-line);
  }
}

/* ═══ The BUTTONS timeline (js/time-buttons.js) — the app's MapTimeButtons
   in the SAME band as the ruler: the band, its glass and its top row are
   shared; only the 36 px strip under the row differs, so both styles are
   the same height (32 + 36) and nothing above the band re-flows on a swap.
   Strip: 2 pad + 26 cell (15 px number over 10 px am/pm) + 4 dot + 4 bar. */
.tbtn { height: 36px; position: relative; }
.tbtn[hidden], .timeline__ruler[hidden] { display: none; }
.tbtn__strip {
  height: 36px; display: flex; align-items: stretch; padding: 0 10px;
  overflow-x: auto; overflow-y: hidden; scrollbar-width: none;
  touch-action: pan-x; overscroll-behavior-x: contain; user-select: none; -webkit-user-select: none;
  position: relative;
}
.tbtn__strip::-webkit-scrollbar { display: none; }
.tbtn__day { display: flex; flex: 0 0 auto; }
.tbtn__div {
  flex: 0 0 8px; align-self: stretch; position: relative;
}
.tbtn__div::after {
  content: ''; position: absolute; top: 0; bottom: 0; left: 3.5px; width: 1px;
  background: rgba(255, 255, 255, 0.35);
}
.tbtn__cell {
  flex: 0 0 34px; width: 34px; height: 36px; padding: 2px 0 0; margin: 0;
  border: 0; border-radius: 8px; background: transparent; cursor: pointer;
  display: flex; flex-direction: column; align-items: center;
  color: var(--ruler-ink); font-family: var(--ruler-font, var(--font-sans, system-ui, sans-serif));
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
}
.tbtn__cell:focus-visible { outline: 2px solid var(--ruler-accent); outline-offset: -2px; }
.tbtn__cell--sel { background: rgba(255, 255, 255, 0.18); }
.tbtn__num { font: 600 15px/15px var(--ruler-font, system-ui, sans-serif); font-variant-numeric: tabular-nums; }
.tbtn__ap { font: 500 10px/11px var(--ruler-font, system-ui, sans-serif); color: var(--ruler-muted); }
.tbtn__cell[data-daily] .tbtn__num { font-size: 13px; line-height: 26px; }
.tbtn__cell[data-daily] .tbtn__ap { display: none; }
.tbtn__dot { height: 4px; display: flex; align-items: center; justify-content: center; margin-top: 0; }
.tbtn__cell[data-now] .tbtn__dot::after {
  content: ''; width: 4px; height: 4px; border-radius: 50%; background: var(--ruler-ink);
}
.tbtn__bar {
  align-self: stretch; height: 4px; margin-top: auto; margin-bottom: 0;
  background: color-mix(in srgb, var(--ruler-accent) 70%, transparent);
}
.tbtn__cell--first .tbtn__bar { border-radius: 2px 0 0 2px; }
.tbtn__cell--last .tbtn__bar { border-radius: 0 2px 2px 0; }
.tbtn__cell--first.tbtn__cell--last .tbtn__bar { border-radius: 2px; }
.tbtn__cell[data-locked] { color: rgba(255, 255, 255, 0.35); }
.tbtn__cell[data-locked] .tbtn__ap { color: rgba(255, 255, 255, 0.35); }
.tbtn__cell[data-locked] .tbtn__bar { background: rgba(255, 255, 255, 0.35); }
.tbtn__cell[data-lockmark] .tbtn__num::before { content: '🔒'; font-size: 8px; margin-right: 1px; vertical-align: 2px; }
/* the top label's cross-fade while the strip scrolls (js/map.js) */
@keyframes tl-when-fade { from { opacity: 0.25; } to { opacity: 1; } }
.timeline__when--fade { animation: tl-when-fade 140ms ease-out; }
@media (prefers-reduced-motion: reduce) { .timeline__when--fade { animation: none; } }

/* ═══ Station markers (js/station-markers.js, 2026-10-05) ══════════════════
   The app's _pinMarker / _clusterMarker: "data colour" (the layer's, or the
   stale grey), exempt from the theme tokens like the field ramps. A pin is a
   26 px disc, 1.5 px white rim, soft shadow, a 15 px white Material icon; a cluster
   is its count on the layer colour at alpha 0.88 (#…E0), 2 px rim, and lets clicks
   through to the map (the app's cluster has no tap). */
.stmark {
  width: 26px; height: 26px; padding: 0; border-radius: 50%;
  display: grid; place-items: center; cursor: pointer;
  border: 1.5px solid rgba(255, 255, 255, 0.9);
  box-shadow: 0 0 3px rgba(0, 0, 0, 0.33);
}
.stmark:focus-visible { outline: 2px solid var(--mc-accent, #12A594); outline-offset: 2px; }
/* the app's Material icons (js/material-symbols.js) — markers + Overlays */
.msym {
  font-family: 'Material Symbols Outlined'; font-weight: normal; font-style: normal;
  font-size: 20px; line-height: 1; letter-spacing: normal; text-transform: none;
  white-space: nowrap; direction: ltr; -webkit-font-smoothing: antialiased;
  font-feature-settings: 'liga'; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
  display: inline-block; overflow: hidden; width: 1em; height: 1em;
}
.msym--fill { font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24; }
.stmark__ic { font-size: 15px; color: #fff; display: block; }
.stmark-cluster {
  display: grid; place-items: center; border-radius: 50%; pointer-events: none;
  border: 2px solid rgba(255, 255, 255, 0.9);
  color: #fff; font: 700 12px/1 var(--font-sans, system-ui, sans-serif); font-variant-numeric: tabular-nums;
}

/* ── the station quick-look (js/station-sheet.js), the app's
   ndbc_detail_sheet: name, the station line, one inset card per role, the
   model card, the full-details action, the source line ── */
.stsheet { display: grid; gap: 10px; }
.stsheet__name { margin: 0; font-size: 19px; font-weight: 700; color: var(--text); }
.stsheet__sub { margin: -6px 0 0; font-size: 12.5px; color: var(--text-secondary); }
.stsheet__secs { display: grid; gap: 10px; }
.stsheet__loading { margin: 0; padding: 20px 0; text-align: center; font-size: 13px; color: var(--text-muted); }
.stsheet__sec { padding: 14px; border: 1px solid var(--line); border-radius: 10px; background: var(--surface-sunken); }
.stsheet__head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.stsheet__title { margin: 0; font-size: 14px; font-weight: 700; color: var(--text); }
.stsheet__badge { padding: 2px 7px; border: 1px solid var(--line); border-radius: 6px;
  font-size: 10px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-secondary); }
.stsheet__stamp { margin-left: auto; font-size: 10px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-secondary); }
.stsheet__fields { display: flex; flex-wrap: wrap; gap: 8px 18px; }
.stsheet__f { display: grid; gap: 2px; }
.stsheet__v { font-size: 17px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
.stsheet__sec.is-dim .stsheet__v { color: var(--text-secondary); }
.stsheet__k { font-size: 10px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-muted); }
.stsheet__none { margin: 0; font-size: 13px; color: var(--text-secondary); }
.stsheet__go { display: block; padding: 12px; text-align: center; border-radius: 10px; font-weight: 700; font-size: 14px;
  color: var(--accent-text, var(--accent)); border: 1px solid var(--accent); text-decoration: none; }
.stsheet__go:hover { text-decoration: underline; }
.stsheet__go:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.stsheet__src { margin: 0; font-size: 10px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-muted); }



/* ── W2a (app 2.0.3): the MODEL + LAYER pills, the loading bar ──────────────
   The top mode row and its Layers button are superseded by the LAYER pill
   (hidden, not removed: the pill selects a layer by clicking the row's own
   button, so the charge / paywall / radar rules stay one path). */
.modeswitch, #map-layers-btn { display: none !important; }
.mappills {
  position: absolute; right: 8px; bottom: calc(var(--timeline-h, 0px) + 10px); z-index: 5;
  display: flex; flex-direction: column; align-items: flex-end; gap: 6px;
}
.mpill__host { position: relative; }
.mpill__host[hidden] { display: none; }
.mpill {
  display: inline-flex; align-items: center; gap: 6px; height: 34px; padding: 0 10px 0 12px;
  border: 1px solid rgba(255, 255, 255, 0.18); border-radius: 999px;
  background: rgba(10, 22, 40, 0.72); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  color: var(--text-on-dark-2); cursor: pointer; white-space: nowrap;
  font: 600 13px/1 var(--font-sans, "Inter", system-ui, sans-serif);
}
.mpill:hover { background: rgba(20, 40, 68, 0.82); }
.mpill:focus-visible { outline: 2px solid var(--brand-teal-bright, #3DE0C4); outline-offset: 2px; }
.mpill--model { font-weight: 700; color: var(--brand-teal-bright, #3DE0C4); }
.mpill svg:last-child { transition: transform 160ms ease; }
.mpill[aria-expanded='true'] svg:last-child { transform: rotate(180deg); }
.mpill[data-busy] svg:last-child {
  width: 12px; height: 12px; border-radius: 50%; border: 2px solid rgba(61, 224, 196, 0.25); border-top-color: var(--brand-teal-bright, #3DE0C4);
  animation: fcm-spin 0.8s linear infinite; stroke: transparent;
}
@keyframes fcm-spin { to { transform: rotate(360deg); } }
.mpill__ic, .mpill__sym { display: inline-flex; width: 16px; height: 16px; }
.mpill__panel {
  position: absolute; right: 0; bottom: calc(100% + 6px); z-index: 6;
  width: max-content; min-width: 240px; max-width: min(280px, calc(100vw - 16px)); box-sizing: border-box;
  max-height: calc(100dvh - var(--map-top, 0px) - var(--timeline-h, 0px) - 80px); overflow-y: auto;
  padding: 6px; border-radius: 14px; border: 1px solid rgba(255, 255, 255, 0.16);
  background: rgba(10, 22, 40, 0.94); -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4); color: var(--text-on-dark-2);
  font: 400 13px/1.3 var(--font-sans, "Inter", system-ui, sans-serif);
}
.mpill__panel[hidden] { display: none; }
.mpill__panel--layer { min-width: 280px; }
.mpill__cap { margin: 6px 10px 4px; font: 700 11px/1.2 var(--font-sans, "Inter", system-ui, sans-serif); letter-spacing: 0.06em; text-transform: uppercase; color: rgba(255, 255, 255, 0.6); text-align: left; }
.mpill__rule { border: 0; border-top: 1px solid rgba(255, 255, 255, 0.14); margin: 6px 4px; }
.mpill__list, .mpill__toggles { display: flex; flex-direction: column; gap: 1px; }
.mpill__row {
  display: flex; align-items: center; gap: 10px; width: 100%; min-height: 40px; box-sizing: border-box;
  padding: 5px 10px; border: 0; border-radius: 10px; background: none; text-align: left; cursor: pointer;
  color: inherit; font: inherit;
}
.mpill__row:hover, .mpill__row:focus-visible { background: rgba(255, 255, 255, 0.1); }
.mpill__row:focus-visible { outline: 2px solid var(--brand-teal-bright, #3DE0C4); outline-offset: -2px; }
.mpill__row[aria-disabled='true'] { opacity: 0.5; cursor: default; }
.mpill__rowtext { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1 1 auto; }
.mpill__rowname { font: 500 14px/1.25 var(--font-sans, "Inter", system-ui, sans-serif); color: #fff; white-space: nowrap; }
.mpill__row--layer .mpill__rowname, .mpill__row--style .mpill__rowname { flex: 1 1 auto; }
.mpill__rowsub { font: 500 12px/1.25 var(--font-sans, "Inter", system-ui, sans-serif); color: rgba(255, 255, 255, 0.62); white-space: nowrap; }
.mpill__check, .mpill__tick { visibility: hidden; flex: none; color: var(--brand-teal-bright, #3DE0C4); font-weight: 700; }
.mpill__row[aria-selected='true'], .mpill__row[aria-checked='true'] { background: rgba(61, 224, 196, 0.16); }
.mpill__row[aria-selected='true'] .mpill__rowname, .mpill__row[aria-checked='true'] .mpill__rowname { color: var(--brand-teal-bright, #3DE0C4); font-weight: 600; }
.mpill__row[aria-selected='true'] .mpill__check, .mpill__row[aria-checked='true'] .mpill__check, .mpill__row[aria-checked='true'] .mpill__tick { visibility: visible; }
.mpill__row--toggle .mpill__tick { visibility: hidden; }
.mpill__row--toggle[aria-checked='true'] { background: none; }
.mpill__row--toggle[aria-checked='true'] .mpill__rowname { color: #fff; font-weight: 500; }
.mpill__lock { display: inline-flex; color: rgba(255, 255, 255, 0.7); }
.maplegend__notes { margin-top: 8px; display: flex; flex-direction: column; gap: 3px; }
.maplegend__note { margin: 0; font: 500 11px/1.35 var(--font-sans, "Inter", system-ui, sans-serif); color: rgba(255, 255, 255, 0.72); }
.maplegend__note--horizon, .maplegend__note--note { color: #fff; }
.maplegend__note--attribution { font-size: 10px; color: rgba(255, 255, 255, 0.55); }
.maploading {
  position: absolute; top: calc(var(--map-top) + 8px); left: 50%; transform: translateX(-50%); z-index: 5;
  display: inline-flex; align-items: center; gap: 8px; height: 30px; padding: 0 10px; box-sizing: border-box;
  border: 1px solid rgba(255, 255, 255, 0.18); border-radius: 999px;
  background: rgba(10, 22, 40, 0.72); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  color: var(--text-on-dark-2); white-space: nowrap; overflow: hidden;
  font: 600 12px/1.2 var(--font-sans, "Inter", system-ui, sans-serif);
}
.maploading[hidden] { display: none; }
.maploading__text[hidden], .maploading__pct[hidden] { display: none; }
.maploading__track { position: relative; display: inline-block; width: 36px; height: 4px; border-radius: 2px; background: rgba(255, 255, 255, 0.2); overflow: hidden; }
.maploading__fill { position: absolute; left: 0; top: 0; bottom: 0; width: 0; background: var(--brand-teal-bright, #3DE0C4); transition: width 160ms ease; }
.maploading__pct { font-variant-numeric: tabular-nums; min-width: 30px; text-align: right; }
.maploading[data-state='failed'] .maploading__fill { background: #ff8a5b; }
@media (max-width: 899px) { .maploading { top: calc(var(--map-top) + 8px); } }
