/* ── Reset & full-screen map ──────────────────────────────────────────── */
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
/* #157: visible keyboard-focus indicator — the stylesheet had NONE, so keyboard/AT users had
   no idea where focus was across the whole settings panel + controls. :focus-visible only shows
   for keyboard/assistive focus (not mouse clicks), so it adds no rings to pointer interactions. */
:focus-visible { outline: 2px solid #4fc3f7; outline-offset: 2px; }
/* #158: visually-hidden but screen-reader-exposed. For SR-only live regions
   (e.g. #location-status) — clipped to 1px so it's off-screen yet still read. */
.sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
/* #84: safe-area insets as vars (0 when unsupported / no notch). Edge-anchored controls use
   max(designGap, var(--sa-*)) on the gap axis so they never sit UNDER the notch/home-indicator
   AND never stack a second gap on top of the design gap (the old calc(base+env) double-counted,
   which read as a "big gap at the bottom"). In landscape the cutout is on a SIDE, so the
   right-hand control row also offsets by --sa-right. Requires viewport-fit=cover (set in HTML). */
:root {
    --sa-top:    env(safe-area-inset-top, 0px);
    --sa-right:  env(safe-area-inset-right, 0px);
    --sa-bottom: env(safe-area-inset-bottom, 0px);
    --sa-left:   env(safe-area-inset-left, 0px);
}
/* Full-bleed map: fill the standalone viewport edge-to-edge (100dvh handles iOS
   home-screen chrome). The previous safe-area PADDING on body inset the map away from
   the edges, so the body bg showed through as a bottom gap (#74). Map goes under the
   notch/home bar; only the floating UI (controls/HUD) offsets for the safe area. */
/* body is position:relative + 100dvh so the absolute-positioned floating controls anchor to
   the DYNAMIC viewport box, not the layout viewport. In Safari's browser mode that keeps the
   bottom controls (HUD / settings / pause / theme) ABOVE the URL toolbar instead of hidden
   behind it (env(safe-area-inset-bottom) doesn't cover the Safari toolbar). (#84/#2b) */
html, body { width: 100%; height: 100%; height: 100dvh; overflow: hidden; background: #0d1117; position: relative; }
#map { width: 100%; height: 100%; height: 100dvh; }
/* Dark tile background: during a view transition, not-yet-loaded tiles show this
   instead of Leaflet's default light-gray, so the gap reads as a brief darkening
   rather than a gray checkerboard. */
.leaflet-container { background: #0d1117; }

/* #42 interaction-pause indicator — distinct from the manual PAUSED badge so it
   reads as automatic ('you're exploring, the tour stopped'). Tappable to resume. */
#explore-badge {
    display: none; position: absolute; top: 12px; left: 50%;
    transform: translateX(-50%); z-index: 1000;
    background: rgba(79,195,247,0.92); color: #06121f;
    font-size: 12px; font-weight: 600; padding: 6px 14px; border-radius: 16px;
    cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,0.4); white-space: nowrap;
}
/* Dim the light (Voyager) basemap so the radar palette — tuned for a dark
   map — stays readable. Applied to the basemap layer's OWN container via the
   tileLayer className, so radar tiles in the same pane are NOT affected. */
.basemap-dim { filter: brightness(0.72) saturate(0.9) contrast(1.03); }
/* Lift the dark (Carto dark_all/nolabels) basemap so its near-black roads and
   labels are actually visible without washing out the dark theme. */
.basemap-boost { filter: brightness(1.35) contrast(1.08); }
/* #81 satellite: dim imagery a touch so radar + temp stamps stay legible over bright terrain */
.basemap-sat { filter: brightness(0.78) saturate(0.95); }

/* #87 minimal mode: fade the floating chrome out when idle (radar = hero). pointer-events:none
   while hidden so a wake-tap reaches the map, where revealChrome() handles it (no tooltip). */
#hud, #loc-card, #theme-btn, #pause-btn, #settings-btn, #legend-stack, .leaflet-control-attribution {
    transition: opacity 0.45s ease;
}
body.chrome-hidden #hud,
body.chrome-hidden #loc-card,
body.chrome-hidden #theme-btn,
body.chrome-hidden #pause-btn,
body.chrome-hidden #settings-btn,
body.chrome-hidden #legend-stack,
body.chrome-hidden .leaflet-control-attribution {
    opacity: 0;
    pointer-events: none;
}

/* ── Overlay legends (#202 smoke, #185 AQI) — one shared flex stack, bottom-LEFT above
   the HUD (#215: information lives on the left — loc-card, HUD, keys — controls on the
   right; the old bottom-right slot was covered by the expanded settings panel). The
   bottom offset is set dynamically by positionLegendStack() (HUD height varies with its
   status lines); the calc() here is only the no-JS/HUD-hidden fallback. Multiple
   contextual legends coexist without colliding; each controls its own display. */
#legend-stack {
    position: absolute;
    left: max(10px, var(--sa-left, 10px));
    bottom: calc(max(10px, var(--sa-bottom, 10px)) + 8px);   /* fallback; JS positions above the HUD */
    z-index: 1000;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
    pointer-events: none;   /* the stack stays transparent to the map... */
}
#smoke-legend, #aqi-legend {
    pointer-events: auto;   /* ...but the keys take hover so the #213 cadence title shows
                               on desktop (touch keeps the visible 'Refreshed' line) */
    font-family: 'JetBrains Mono', 'Fira Code', 'SF Mono', 'Consolas', monospace;
    font-size: 11px;
    color: #e0e0e0;
    background: rgba(10, 10, 30, 0.85);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 6px;
    padding: 6px 9px 5px;
    line-height: 1.5;
    backdrop-filter: blur(4px);
}
#legend-stack .legend-title { font-weight: 700; letter-spacing: 0.04em; margin-bottom: 2px; }
#legend-stack .legend-swatch {
    display: inline-block; width: 10px; height: 10px; border-radius: 2px;
    margin-right: 6px; vertical-align: -1px;
}

/* #255e: fully themed slider (ticks dropped — the live colored label carries position).
   Track = the toggle-ON blue wash; thumb = solid accent blue with a white border (white
   is the theme's border language — panels/pills use rgba(255,255,255,…) edges). */
#set-aqi-hide {
    -webkit-appearance: none; appearance: none;
    width: 100%; height: 6px; border-radius: 3px;
    background: rgba(79, 195, 247, 0.3);
    outline: none; margin: 8px 0 2px;
}
#set-aqi-hide::-webkit-slider-thumb {
    -webkit-appearance: none; appearance: none;
    width: 16px; height: 16px; border-radius: 50%;
    background: #4fc3f7; border: 2px solid #fff; cursor: pointer;
}
#set-aqi-hide::-moz-range-thumb {
    width: 16px; height: 16px; border-radius: 50%;
    background: #4fc3f7; border: 2px solid #fff; cursor: pointer;
}
#set-aqi-hide::-moz-range-track { height: 6px; border-radius: 3px; background: rgba(79, 195, 247, 0.3); }
#set-aqi-hide:focus-visible { outline: 2px solid #4fc3f7; outline-offset: 2px; }   /* #157 parity */

/* .aqi-marker (#185): filled pill chip — dark text on the EPA/EAQI band color.
   #209 provenance: measured AirNow areas render solid (default, subtle white ring);
   model grid estimates render muted + dashed so interpolation is visible at a glance. */
.aqi-marker {
    display: inline-block; min-width: 24px; text-align: center;
    font-family: 'JetBrains Mono', 'Fira Code', 'SF Mono', 'Consolas', monospace;
    font-size: 10px; font-weight: 700; color: #111;
    border-radius: 8px; padding: 1px 4px;
    /* #233: provenance is DASH PATTERN ONLY (Charles: station vs model shouldn't differ
       beyond dashed-vs-solid). Identical weight + color both kinds; no ring, no opacity
       fade — those stacked extra differences that read as inconsistency. */
    border: 1px solid rgba(0, 0, 0, 0.5);
}
.aqi-marker--model {
    border-style: dashed;   /* the ONLY difference from a station pill */
}

/* #96 flight-track: ✈ button, typeahead search overlay, followed-flight HUD */
#flight-btn {
    position: absolute; top: max(10px, var(--sa-top)); right: calc(10px + var(--sa-right));
    z-index: 1000; width: 32px; height: 32px; border-radius: 6px; line-height: 1;
    background: rgba(10, 10, 30, 0.7); color: #cfe0ff; border: 1px solid rgba(255, 255, 255, 0.12);
    font-size: 16px; cursor: pointer;
}
#flight-btn:hover { color: #fff; border-color: rgba(120, 170, 255, 0.6); }
/* persistent flight-mode chip — sits just left of the ✈ button while following */
#flight-active { display: none; position: absolute; z-index: 1001; align-items: center; gap: 6px;
    top: max(10px, var(--sa-top)); right: calc(48px + var(--sa-right)); height: 32px; padding: 0 4px 0 10px;
    background: rgba(79, 195, 247, 0.92); color: #06243a; border-radius: 6px; font-size: 13px; font-weight: 700;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); white-space: nowrap;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
#flight-active.on { display: inline-flex; }
#flight-active::before { content: '\2708\FE0E'; font-weight: 400; }
#flight-exit { background: rgba(6, 36, 58, 0.15); border: none; color: #06243a; cursor: pointer;
    width: 22px; height: 22px; border-radius: 4px; font-size: 16px; line-height: 1; padding: 0; }
#flight-exit:hover { background: rgba(6, 36, 58, 0.3); }
/* multi-modal HUD: in flight mode the loc-card shows plane telemetry — keep the card, just hide the
   weather forecast chip (the flight readout has no forecast). */
body.flight-on #loc-card .loc-forecast { display: none !important; }
/* #47: airport endpoint labels on the flight route line (origin/destination IATA codes) */
.route-ap { color: #fff; font: 700 10px/14px -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    text-align: center; text-shadow: 0 0 3px #000, 0 0 3px #000; pointer-events: none; white-space: nowrap; }
/* #47: the route progress bar lives in .loc-station, which is dim grey (#555) by default — brighten
   it in flight mode so the bar/%/ETA read clearly. */
body.flight-on #loc-card .loc-station { color: #cfe0ff; font-size: 11px; margin-top: 3px; }
#flight-search { display: none; position: absolute; top: max(10px, var(--sa-top)); left: 50%;
    transform: translateX(-50%); z-index: 1003; width: min(340px, 82vw); }
#flight-search.open { display: block; }
#flight-input {
    width: 100%; box-sizing: border-box; padding: 9px 30px 9px 12px;   /* #157: no focus suppression — let :focus-visible show the ring */
    background: rgba(10, 10, 30, 0.95); color: #fff; border: 1px solid rgba(120, 170, 255, 0.5);
    border-radius: 8px; font-size: 14px;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
#flight-stop { position: absolute; top: 6px; right: 8px; background: none; border: none;
    color: #8a93a6; font-size: 18px; cursor: pointer; line-height: 1; }
#flight-suggest { margin-top: 4px; background: rgba(10, 10, 30, 0.97); border-radius: 8px;
    overflow: hidden; border: 1px solid rgba(255, 255, 255, 0.1); }
#flight-suggest:empty { display: none; }
.fs-item { padding: 8px 12px; cursor: pointer; color: #e0e0e0; font-size: 13px;
    display: flex; justify-content: space-between; align-items: center; gap: 10px; }
.fs-item:hover { background: rgba(120, 170, 255, 0.15); }
.fs-meta { color: #8a93a6; font-size: 11px; white-space: nowrap; }
.fs-none, .fs-none:hover { color: #8a93a6; cursor: default; background: none; }

/* #88: Resume overlay shown when returning to a focus-paused (backgrounded) tab */
#resume-cta {
    display: none; position: absolute; inset: 0; z-index: 1500;
    flex-direction: column; align-items: center; justify-content: center;
    text-align: center; padding: 24px;
    background: rgba(5, 8, 16, 0.55);
    -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;   /* else .resume-hint falls back to serif */
}
#resume-cta.show { display: flex; }
#resume-btn {
    font: 600 18px -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    color: #fff; background: rgba(20, 30, 55, 0.96);
    border: 1px solid rgba(120, 170, 255, 0.5); border-radius: 12px;
    padding: 14px 30px; cursor: pointer;
}
#resume-btn:hover { background: rgba(30, 45, 80, 0.96); border-color: rgba(120, 170, 255, 0.8); }
.resume-hint { margin-top: 14px; font-size: 11px; color: #8a93a6; max-width: 300px; line-height: 1.45; }
.resume-hint code { color: #aab4c8; }

/* === #87 mobile-trim: condense the always-on chrome on phones so the radar reads cleaner.
   REVERT: set CONFIG.mobileTrim=false (drops body.mobile-trim) — or delete this block. === */
@media (max-width: 600px) {
    body.mobile-trim #hud-product,
    body.mobile-trim #hud-frames { display: none; }              /* drop technical product (dBZ·km) + frame dots */
    body.mobile-trim #loc-detail,
    body.mobile-trim #loc-station { display: none; }             /* drop wind/humidity + station line */
    /* #143: compact forecast — drop the "Forecast" header + short-forecast blurb and
       stack today's high (red) over low (blue), so the chip reads as two big temps on a phone. */
    body.mobile-trim #loc-card .loc-fc-label,
    body.mobile-trim #loc-card .loc-fc-text { display: none; }
    body.mobile-trim #loc-card .loc-forecast { width: auto; }    /* two short lines no longer need the 90px blurb width */
    body.mobile-trim #loc-card .loc-hilo { font-size: 14px; line-height: 1.25; }
    body.mobile-trim #loc-card .loc-hilo .hi,
    body.mobile-trim #loc-card .loc-hilo .lo { display: block; } /* stack: ↑high over ↓low */
    body.mobile-trim .leaflet-control-attribution {
        max-width: 62vw; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
}

/* ── HUD: bottom-left monospace info panel ────────────────────────────── */
#hud {
    position: absolute;
    bottom: max(10px, var(--sa-bottom));            /* tight to lower-left; attribution is bottom-RIGHT */
    left: max(10px, var(--sa-left));
    z-index: 1000;
    font-family: 'JetBrains Mono', 'Fira Code', 'SF Mono', 'Consolas', monospace;
    font-size: 13px;
    color: #e0e0e0;
    background: rgba(10, 10, 30, 0.85);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 6px;
    padding: 10px 14px 7px;
    line-height: 1.6;
    pointer-events: none;
    min-width: 260px;
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
}

/* #71: subtle load-status line at the top of the HUD — narrates what's happening
   under the covers during a (cold, HTTP/1.1-serialized) load. Collapses to zero height
   when empty so it never adds a gap; a tiny pulsing dot signals "working". */
/* #71: minimal floating DEBUG hint above the HUD — plain text, no box/ring/dot. Just
   signals (especially on slow devices) that something is happening during load. A faint
   text-shadow keeps it readable on either basemap without needing a background. */
#hud-status {
    position: absolute;
    bottom: calc(100% + 5px);          /* float just above the HUD, where it already sat */
    left: 2px;
    max-width: 280px;
    font-size: 10px;
    line-height: 1.3;
    color: #e0e0e0;                    /* coordinate with the location card's primary text */
    text-shadow: 0 0 3px rgba(0, 0, 0, 0.85), 0 1px 2px rgba(0, 0, 0, 0.6);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.25s;
}
#hud-status.show { opacity: 0.75; }
/* light mode: the near-white text washes out on the light basemap — flip to dark text
   with a light halo so it's legible in BOTH themes (the bug the light-mode test missed). */
body.theme-light #hud-status {
    color: #12283b;
    text-shadow: 0 0 3px rgba(255, 255, 255, 0.95), 0 1px 2px rgba(255, 255, 255, 0.8);
}
/* ── Location card: upper-left, current conditions at user's position ── */
#loc-card {
    position: absolute;
    top: max(10px, var(--sa-top));
    left: max(10px, var(--sa-left));
    z-index: 1000;
    font-family: 'JetBrains Mono', 'Fira Code', 'SF Mono', 'Consolas', monospace;
    font-size: 12px;
    color: #e0e0e0;
    background: rgba(10, 10, 30, 0.85);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 6px;
    padding: 8px 12px;
    line-height: 1.5;
    pointer-events: none;
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
}
#loc-card { min-width: 156px; }   /* fits temp (left) + the fixed ~102px forecast chip (right) */
/* loc-card data-source tooltips: enable hover on the two info zones (native title), the
   rest of the card stays click-through so it doesn't block the map. */
#loc-card .loc-main, #loc-card .loc-forecast { pointer-events: auto; cursor: help; }

/* Temp-stamp source/site tooltip (#66): one reusable element, follows cursor/tap, never
   blocks the map. Auto-collapses (JS timer) on tap or when the cursor stops moving. */
#temp-tip {
    position: fixed; z-index: 1200; pointer-events: none;
    transform: translate(-50%, -100%);
    background: rgba(10, 10, 30, 0.92); color: #e0e0e0;
    border: 1px solid rgba(255,255,255,0.15); border-radius: 5px;
    padding: 4px 8px; font-family: 'JetBrains Mono','Fira Code',monospace; font-size: 11px;
    line-height: 1.35; white-space: nowrap; max-width: 240px;
    opacity: 0; transition: opacity 0.15s; backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
}
#temp-tip.show { opacity: 0.96; }
#temp-tip .tip-src { color: #888; font-size: 9px; }

/* Unify ALL map tooltips (the location dot's "Browser/IP: …", the WWA / DWD-hazard / SPC overlay
   hovers) on the #temp-tip look — dark, blurred, monospace — instead of Leaflet's default white box. */
.leaflet-tooltip {
    background: rgba(10, 10, 30, 0.92); color: #e0e0e0;
    border: 1px solid rgba(255, 255, 255, 0.15); border-radius: 5px;
    padding: 4px 8px; box-shadow: none;
    font-family: 'JetBrains Mono','Fira Code',monospace; font-size: 11px; line-height: 1.35;
    backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
}
.leaflet-tooltip::before { display: none; }   /* drop Leaflet's default arrow for the clean temp-tip look */

/* Light mode ("respectively"): both #temp-tip and the leaflet tooltips flip to a light card.
   (theme-light is only set on the flat light map — satellite stays dark, so its tooltips stay dark.) */
body.theme-light #temp-tip,
#map.theme-light .leaflet-tooltip {
    background: rgba(252, 252, 250, 0.96); color: #1c2533;
    border-color: rgba(0, 0, 0, 0.18);
}
body.theme-light #temp-tip .tip-src { color: #667085; }

/* #83 live aircraft markers — ▲ rotated to heading (inline transform), altitude-colored
   (inline), with a dark halo so they read on any basemap. Non-interactive (shared tooltip). */
.aircraft-marker {
    font-size: 14px; line-height: 14px; text-align: center; pointer-events: none;
    text-shadow: 0 0 2px rgba(0,0,0,0.95), 0 0 4px rgba(0,0,0,0.7);
}
/* plane = triangle head + trailing tail (heading); drop-shadow for legibility on any basemap */
.aircraft-svg { overflow: visible; pointer-events: none; filter: drop-shadow(0 0 1.5px rgba(0,0,0,0.9)); }

/* "Temperature stations" disclosure: a standard expand control. The triangle is hidden
   until temp is on (.shown), then reads as a prominent accent-colored caret that spins
   ▸→▾ (.open) when the options expand. The label only takes on pointer + hover affordance
   when the caret is present (:has) — so it's not a misleading "clickable" while temp is
   off and there's nothing to expand. */
.temp-disclosure {
    display: inline-flex; align-items: center; gap: 6px; cursor: default;
    border-radius: 6px; padding: 2px 6px; margin: -2px -6px;
    transition: background 0.15s ease, color 0.15s ease;
}
.temp-disclosure:has(.tcaret.shown) { cursor: pointer; }
.temp-disclosure:has(.tcaret.shown):hover { background: rgba(79, 195, 247, 0.12); color: #dcefff; }
.tcaret {
    display: inline-block; font-size: 26px; line-height: 1; color: #4fc3f7;
    visibility: hidden; transition: transform 0.2s ease;
}
.tcaret.shown { visibility: visible; }
.tcaret.open { transform: rotate(90deg); }

#loc-card .loc-temp { font-size: 22px; font-weight: 700; }
#loc-card .loc-desc { color: #aaa; }
#loc-card .loc-detail { color: #888; font-size: 11px; }
#loc-card .loc-station { color: #555; font-size: 10px; margin-top: 2px; }
/* #66: today's high/low + short forecast tucked into the card's upper-right CORNER
   (absolute — #loc-card is already position:absolute, so it anchors this) so it doesn't
   widen the card into a separate pane. */
/* FIXED-WIDTH corner chip: width reserves the worst-case high/low line ("↑-99° ↓-99°"),
   balanced padding + centered so left/right margins read even; flush to the card's inner
   top-right so the L-bracket's open ends meet the card's top & right edges. */
#loc-card .loc-forecast { position: absolute; top: 0; right: 0; width: 90px; text-align: center;
                          border-left: 1px solid rgba(255,255,255,0.15);
                          border-bottom: 1px solid rgba(255,255,255,0.15);
                          border-bottom-left-radius: 4px;
                          padding: 7px 6px 5px 6px; }   /* tight, even L/R margins */
#loc-card .loc-fc-label { font-size: 7px; text-transform: uppercase; letter-spacing: 0.4px;
                          color: #555; margin-bottom: 1px; }   /* match the loc-station grey */
#loc-card .loc-hilo { font-size: 12px; font-weight: 700; white-space: nowrap; }
#loc-card .loc-hilo .hi { color: #ef7a6d; }   /* warm */
#loc-card .loc-hilo .lo { color: #6db3f2; }   /* cool */
/* blurb: clipped to the reserved width; banner-scrolls only if it overflows */
#loc-card .loc-fc-text { font-size: 9px; color: #999; margin-top: 2px; line-height: 1.3; overflow: hidden; }
#loc-card .loc-fc-text .fc-scroll { display: inline-block; white-space: nowrap; }
#loc-card .loc-fc-text.marquee { text-align: left; }
#loc-card .loc-fc-text.marquee .fc-scroll { animation: fc-marquee 9s ease-in-out infinite alternate; }
@keyframes fc-marquee { 0%, 12% { transform: translateX(0); }
                        88%, 100% { transform: translateX(var(--fc-scroll, 0)); } }
@media (prefers-reduced-motion: reduce) {
    #loc-card .loc-fc-text.marquee .fc-scroll { animation: none; }   /* static clip fallback */
}

#hud-label    { color: #4fc3f7; font-weight: 600; font-size: 14px; }
#hud-product  { color: #aaa; }
#hud-time     { color: #81c784; }
.hud-rel      { color: #888; font-size: 0.82em; font-weight: 400; }   /* #relative-age of the radar frame */
#hud-frames   { color: #666; letter-spacing: 2px; font-size: 11px; min-height: 1.6em; }   /* #273: reserve the row —
                 updateHudTime empties it on every view change until frames load; collapsing shrank the whole HUD mid-slide */

/* #37 support links now live IN the Leaflet attribution bar (set via
   attributionControl.setPrefix in initMap) so they inherit the attribution styling. */

/* #43 view filmstrip — current view bright, upcoming views dimmed (foreshadow) */
/* #43 filmstrip — combined into #hud-label: current bright/normal (inherits the label
   style), upcoming inline at small size + low opacity (size = the current/next cue).
   Subtle slide on each view change, disabled under prefers-reduced-motion. */
/* #273: FIXED width (not max-width) — the HUD's width was content-driven, so the #268
   ghost insertion (+46px) and per-view label-length churn (−77px on settle) resized the
   whole box and visually swallowed the conveyor slide. min() keeps phones fitting. */
#hud-label { white-space: nowrap; overflow: hidden; text-overflow: clip; width: min(340px, calc(100vw - 68px)); max-width: 100%; }   /* max-width:100%: respect #hud's own 62vw phone cap (review) */
#hud-label .fs-next { font-weight: 400; }   /* size + opacity set inline per item (recede) */
#hud-label .fs-sep  { font-size: 9px; font-weight: 400; opacity: 0.3; margin: 0 3px; }

/* ── #267: tap-to-advance — the HUD is pointer-events:none; ONLY these tour controls
   accept input. The CHEVRON gets a ≥44px padded hit area (glyph stays small) and is the
   primary touch affordance; the label targets' vertical slop is capped by #hud-label's
   overflow:hidden clip (hit-testing honors the clip — bleed beyond the line box is dead),
   so on touch they're convenience targets, not the primary path. Chevron visible at rest
   (no hover on touch devices). */
#hud-next {
    position: absolute; right: 4px; top: 14px;
    color: #4fc3f7; opacity: 0.5; font-size: 15px; font-weight: 700; line-height: 1;
    pointer-events: auto; cursor: pointer;
    padding: 15px 12px; margin: -15px -8px;                /* ≥44px effective target */
    -webkit-tap-highlight-color: transparent;
}
#hud-next:hover, #hud-next:active { opacity: 1; }
#hud-label { padding-right: 16px; }                        /* strip text never runs under the chevron */
#hud-label .fs-next[data-view-idx] {
    pointer-events: auto; cursor: pointer;
    padding: 14px 2px; margin: -14px -2px;                 /* slop capped by the #hud-label clip; see block comment */
    -webkit-tap-highlight-color: transparent;
}
#hud-label .fs-next[data-view-idx]:active { opacity: 1 !important; }
/* flight-follow / ?pin: the tour is locked — dim + fully disable the targets */
body.tour-locked #hud-next,
body.tour-locked #hud-label .fs-next[data-view-idx] {
    opacity: 0.15 !important; pointer-events: none; cursor: default;
}
/* #273: below ~446px the bottom-right cluster (theme-btn at right:94px of the VIEWPORT)
   intrudes over the HUD's right edge (min-width:260 + 10 left) — the #267 chevron was fully
   occluded there (elementFromPoint = theme-btn). Park it left of the intrusion and reserve
   strip padding so text can't run under it. MUST sit AFTER the base #hud-next rule — equal
   specificity, source order decides (first attempt sat in the early mobile-trim block and
   silently lost the cascade). Broader cluster-vs-HUD overlap tracked in #276. */
@media (max-width: 600px) {
    #hud-next { right: 40px; padding: 15px 10px; margin: -15px -6px; }
    #hud-label { padding-right: 46px; }
}
/* Reel advances right-to-left: the new current enters from the right (clipped by the
   #hud-label overflow), so it reads like a filmstrip looping behind the interface. */
/* #268: conveyor slide — one slot left per view change, transform-only (no layout
   thrash); the landing current label grows/brightens via its own transition. The
   ghost pair is transient (removed on transitionend / 500ms fallback). */
#hud-label .fs-track { display: inline-block; transition: transform 0.4s ease-out; will-change: transform; }
#hud-label .fs-ghost { opacity: 0.9; }
/* #273: grow via transform scale, NOT font-size — font-size transition reflowed layout
   every frame (measured: HUD height dipped 107.6→90px mid-slide). inline-block makes the
   span transformable; origin left-center so it grows out of its slot. */
#hud-label .fs-cur   { display: inline-block; transform-origin: 0 60%; transition: transform 0.4s ease-out, opacity 0.4s ease-out; }
/* reduced-motion: no translation (JS also skips the ghost), but a gentle FADE of the
   landing label so the change is still perceptible (fades are motion-safe). Without
   this, the label snapped instantly = 'too fast'. */
@keyframes fs-fade { from { opacity: 0.15; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
    #hud-label .fs-track, #hud-label .fs-cur { transition: none; }
    #hud-label.fs-swap .fs-cur { animation: fs-fade 0.5s ease; }
}

/* #34 — when the settings panel is open, HIDE the bottom-right controls so the full-height
   panel can't occlude them. Hiding (the issue's preferred fix) is deterministic — no fragile
   "relocate beside a fixed-width panel" math that breaks if the panel width changes — and it
   matches the phone behavior below, so desktop and mobile are consistent. The panel's own
   close affordance replaces the '?'; pause/theme are a panel-close away. */
body.settings-open #settings-btn,
body.settings-open #pause-btn,
body.settings-open .theme-toggle { display: none; }

/* #34b — phone layout: full-width panel, hide the now-redundant bottom controls while
   it's open, and tighten the HUD/loc-card so they don't crowd a small screen. */
@media (max-width: 600px) {
    #settings-panel { width: 100%; border-left: none; }
    body.settings-open .theme-toggle,
    body.settings-open #pause-btn,
    body.settings-open #settings-btn { display: none; }
    #hud { font-size: 11px; max-width: 62vw; }
    #hud-label { font-size: 12px; }
    #loc-card { transform: scale(0.9); transform-origin: top left; }
    /* Credit/sponsor bar scales down so the longer 'Sponsor | Coffee | Leaflet | Radar… |
       © CARTO' bar doesn't crowd a phone (icons shrink with it). */
    .leaflet-control-attribution { font-size: 9px !important; line-height: 1.3; }
    .leaflet-control-attribution svg { width: 9px; height: 9px; }
}
/* ── Day/night sliding toggle (sun ⇆ moon, knob marks active mode) ────── */
.theme-toggle {
    position: absolute;
    bottom: max(28px, var(--sa-bottom));
    right: calc(94px + var(--sa-right));
    z-index: 1000;
    width: 56px; height: 32px;
    padding: 0;
    background: rgba(10, 10, 30, 0.85);
    border: 1px solid rgba(255,255,255,0.1);
    border-radius: 16px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: space-between;
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
}
.theme-toggle .tt-icon {
    display: flex; align-items: center; justify-content: center;
    flex: 1; height: 100%;          /* each icon fills HALF the track → centers at 1/4 & 3/4 */
    z-index: 2; pointer-events: none;
    transition: color 0.2s;
}
.theme-toggle .tt-icon svg { width: 14px; height: 14px; }
/* The cyan knob slides under the active side; the icon it covers goes dark
   for contrast, the inactive icon stays grey. Default state = day (sun). */
.theme-toggle .tt-knob {
    position: absolute; top: 3px;
    left: 25%; margin-left: -12px;     /* center at 25% of the track — matches the flex:1 icon
                                          center exactly, independent of box-sizing/border width */
    width: 24px; height: 24px; border-radius: 50%;
    background: #4fc3f7; opacity: 0.9;
    transition: left 0.2s;
    z-index: 1;
}
.theme-toggle .tt-sun  { color: #0d1117; }
.theme-toggle .tt-moon { color: #888; }
.theme-toggle.night .tt-knob { left: 75%; }  /* center at 75% of the track (matches moon-icon center) */
/* No load-time slide: gate all toggle transitions until the resolved theme is applied
   (body.theme-ready, set after the initial updateThemeButton). Knob then only animates on
   a real user toggle, not on the initial light→resolved-theme sync. */
body:not(.theme-ready) #theme-btn * { transition: none !important; }
.theme-toggle.night .tt-sun  { color: #888; }
.theme-toggle.night .tt-moon { color: #0d1117; }

/* ── Pause/play button (bottom-right, left of settings) ──────────────── */
#pause-btn {
    position: absolute;
    bottom: max(28px, var(--sa-bottom));
    right: calc(52px + var(--sa-right));
    z-index: 1000;
    width: 32px; height: 32px;
    background: rgba(10, 10, 30, 0.85);
    border: 1px solid rgba(255,255,255,0.1);
    border-radius: 6px;
    color: #888;
    font-size: 16px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    transition: color 0.2s;
}
#pause-btn:hover { color: #4fc3f7; }
#pause-btn.paused { color: #ef5350; }

/* ── Settings trigger button (bottom-right) ──────────────────────────── */
#settings-btn {
    position: absolute;
    bottom: max(28px, var(--sa-bottom));
    right: calc(10px + var(--sa-right));
    z-index: 1000;
    width: 32px; height: 32px;
    background: rgba(10, 10, 30, 0.85);
    border: 1px solid rgba(255,255,255,0.1);
    border-radius: 6px;
    color: #888;
    font-size: 18px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    transition: color 0.2s;
}
#settings-btn:hover { color: #4fc3f7; }

/* ── Settings panel (slide-in from right) ────────────────────────────── */
#settings-panel {
    position: absolute;
    top: 0; right: 0;
    width: 280px; height: 100%;
    z-index: 1002;
    background: rgba(10, 10, 30, 0.97);   /* #51: bumped 0.95→0.97 to compensate for the removed blur */
    border-left: 1px solid rgba(255,255,255,0.1);
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    font-size: 13px;
    color: #e0e0e0;
    /* #84: keep panel content clear of the notch/home-indicator (esp. full-width on phones
       and landscape side cutouts); never less than the 20px design padding. */
    padding: max(20px, var(--sa-top)) max(20px, var(--sa-right)) max(20px, var(--sa-bottom)) max(20px, var(--sa-left));
    /* #92: GPU-promote (translate3d + will-change) so the slide composites off the main thread;
       ease-out reads snappier than plain ease. */
    transform: translate3d(100%, 0, 0);
    transition: transform 0.24s cubic-bezier(0.2, 0.8, 0.2, 1);
    will-change: transform;
    overflow-y: auto;
    /* #51: NO backdrop-filter — Firefox re-rasterizes an 8px backdrop blur every frame during the
       slide (Chrome composites it cheaply), which made the panel janky on FF. At 0.97 opacity the
       blur was nearly invisible anyway, so dropping it is smooth everywhere for ~no visual cost. */
}
#settings-panel.open { transform: translate3d(0, 0, 0); }
/* #51: Firefox janks the transform slide (it repaints the panel content every frame). FF composites
   opacity fades cheaply, so on FF keep the panel in place and fade it in/out instead of sliding. */
body.is-ff #settings-panel { transform: none; will-change: opacity; opacity: 0; visibility: hidden;
    transition: opacity 0.16s ease, visibility 0s 0.16s; }
body.is-ff #settings-panel.open { opacity: 1; visibility: visible; transition: opacity 0.16s ease; }
#settings-panel h3 { color: #4fc3f7; margin-bottom: 16px; font-size: 16px; }
#settings-panel h4 { color: #888; font-size: 11px; text-transform: uppercase;
                     letter-spacing: 1px; margin: 16px 0 8px; }
.setting-row {
    display: flex; align-items: center; justify-content: space-between;
    padding: 8px 0; border-bottom: 1px solid rgba(255,255,255,0.05);
}
.setting-row > span { flex: 1; }
.setting-row select {
    background: rgba(255,255,255,0.1); color: #e0e0e0; border: 1px solid rgba(255,255,255,0.2);
    border-radius: 4px; padding: 4px 8px; font-size: 12px;
}
/* Toggle switch */
.toggle { position: relative; width: 36px; height: 20px; flex-shrink: 0; margin-left: 8px; }
.toggle input { opacity: 0; width: 0; height: 0; }
.toggle .slider {
    position: absolute; inset: 0; background: rgba(255,255,255,0.15);
    border-radius: 10px; cursor: pointer; transition: background 0.2s;
}
.toggle .slider::before {
    content: ''; position: absolute; left: 2px; top: 2px;
    width: 16px; height: 16px; background: #888;
    border-radius: 50%; transition: transform 0.2s, background 0.2s;
}
.toggle input:checked + .slider { background: rgba(79,195,247,0.3); }
.toggle input:checked + .slider::before { transform: translateX(16px); background: #4fc3f7; }
/* #157: the real checkbox is visually hidden (opacity:0), so route its keyboard-focus ring
   onto the visible .slider — else tabbing through the whole settings panel shows nothing. */
.toggle input:focus-visible + .slider { outline: 2px solid #4fc3f7; outline-offset: 2px; }
.shortcut-table { width: 100%; font-size: 12px; }
.shortcut-table td { padding: 4px 0; }
.shortcut-table td:first-child {
    color: #4fc3f7; font-family: monospace; font-weight: 600;
    width: 55px; padding-right: 12px;
}
#settings-reset {
    margin-top: 16px; width: 100%; padding: 8px;
    background: rgba(239,83,80,0.15); color: #ef5350;
    border: 1px solid rgba(239,83,80,0.3); border-radius: 4px;
    cursor: pointer; font-size: 12px;
}
#settings-reset:hover { background: rgba(239,83,80,0.25); }

/* ── Stale data indicator — applied when serving cached tiles during block ── */
#stale-badge {
    position: absolute;
    top: 10px;
    right: 52px;
    z-index: 1000;
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    color: #ffa726;
    background: rgba(10, 10, 30, 0.85);
    border: 1px solid rgba(255, 183, 77, 0.4);
    border-radius: 4px;
    padding: 4px 10px;
    pointer-events: none;
    display: none;
}
/* Desaturate the radar layer when stale */
.leaflet-tile-pane.stale {
    filter: saturate(0.4) brightness(0.8);
}

/* ── Loading overlay — shown during first animation pass while tiles cache ── */
#loading-overlay {
    position: absolute;
    inset: 0;
    z-index: 999;
    background: rgba(13, 17, 23, 0.6);
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: 'JetBrains Mono', 'Fira Code', monospace;
    font-size: 14px;
    color: #4fc3f7;
    pointer-events: none;
    transition: opacity 0.8s ease;
}
#loading-overlay.hidden { opacity: 0; pointer-events: none; }
#loading-overlay .spinner {
    width: 20px; height: 20px;
    border: 2px solid rgba(79,195,247,0.3);
    border-top-color: #4fc3f7;
    border-radius: 50%;
    animation: spin 1s linear infinite;
    margin-right: 10px;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* Reduced-motion: don't rotate (vestibular safety), but DON'T leave a frozen
   highlighted segment that looks like a stalled spinner. Use a uniform ring with a
   gentle opacity pulse — motion-safe (no movement/rotation) and clearly "working". */
@media (prefers-reduced-motion: reduce) {
    #loading-overlay .spinner {
        animation: spinner-pulse 1.2s ease-in-out infinite;
        border-top-color: rgba(79,195,247,0.3);   /* uniform — no false "frozen top" */
    }
}
@keyframes spinner-pulse { 50% { opacity: 0.3; } }

/* ── Paused indicator ─────────────────────────────────────────────────── */
#hud-paused {
    color: #ef5350;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 1px;
    display: none;
}
#hud-progress.paused { animation-play-state: paused; }

/* ── Temperature markers on map ──────────────────────────────────────── */
.temp-marker {
    font-family: 'JetBrains Mono', 'Fira Code', monospace;
    font-size: 11px;
    font-weight: 700;
    opacity: var(--temp-opacity, 0.9);
    /* Full dark outline + glow so colored stamps stay legible on ANY basemap,
       light or dark (the colors alone wash out on the light/Voyager map). */
    text-shadow:
        -1px -1px 0 rgba(0,0,0,0.95),  1px -1px 0 rgba(0,0,0,0.95),
        -1px  1px 0 rgba(0,0,0,0.95),  1px  1px 0 rgba(0,0,0,0.95),
         0    0    4px rgba(0,0,0,0.9);
    white-space: nowrap;
    text-align: center;
}
/* Light basemap: the dark-tuned stamp colors have almost no contrast on a
   ~145/255 map (audit measured median 1.34:1). Put each stamp on a translucent
   dark pill so the bright text reads at high contrast — same trick the dark
   map gets for free from its near-black background. Dark mode keeps no pill. */
#map.theme-light .temp-marker {
    display: inline-block;
    background: rgba(8, 12, 24, 0.62);
    border-radius: 3px;
    padding: 0 4px;
    opacity: 1;
    /* the dark pill already gives contrast on the light basemap — drop the heavy black
       text-outline that otherwise reads as an ugly dark shadow/halo in light mode. */
    text-shadow: none;
}
/* #91 satellite: green stamps wash out on green terrain — same dark-pill trick as the light
   map, but keep a light outline too since imagery brightness varies wildly (desert/snow/sea). */
#map.basemap-satellite .temp-marker {
    display: inline-block;
    background: rgba(6, 10, 20, 0.62);
    border-radius: 3px;
    padding: 0 4px;
    opacity: 1;
    text-shadow: 0 0 3px rgba(0,0,0,0.95), 0 1px 2px rgba(0,0,0,0.85);
}

/* ── Countdown progress bar: drains left→right over the dwell period ─── */
#hud-progress {
    height: 2px;
    margin-top: 5px;
    background: rgba(79, 195, 247, 0.45);  /* subtle — matches the filmstrip's upcoming-item
                                              opacity for a consistent 'quiet cue' language */
    border-radius: 1px;
    transform-origin: left center;
    transform: scaleX(0);                  /* resting empty; restartCountdown() arms the
                                              fill per view (it sets the full shorthand
                                              WITH duration — a bare CSS animation here
                                              defaults to 0s and just snaps to full). */
}
/* Fills LEFT→RIGHT over cycleSeconds so the bar reads as "time until the next view"
   (empty = just switched, full = about to advance). */
@keyframes countdown-fill {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
}

/* ── Toast: top-center transient notifications ────────────────────────── */
#toast {
    position: absolute;
    /* #84/#1: clear the Dynamic Island / notch — it sits top-center where toasts appear */
    top: calc(var(--sa-top) + 16px);
    left: 50%;
    transform: translateX(-50%) translateY(-100px);
    z-index: 1001;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    font-size: 14px;
    color: #fff;
    background: rgba(10, 10, 30, 0.92);
    border: 1px solid rgba(255, 255, 255, 0.15);
    border-radius: 8px;
    padding: 10px 20px;
    transition: transform 0.4s ease;
    pointer-events: none;
    white-space: nowrap;
}
#toast.visible { transform: translateX(-50%) translateY(0); }
#toast.warn    { border-color: rgba(255, 183, 77, 0.5); }
#toast.error   { border-color: rgba(239, 83, 80, 0.5); }

/* #120: non-disruptive "new version" prompt — replaces the forced location.reload() that yanked
   active sessions (and caused the FF reload-supersede hang). Persistent + clickable; user reloads
   when ready, keeping their place until then. */
#update-banner {
    position: absolute;
    top: calc(var(--sa-top) + 16px);
    left: 50%;
    transform: translateX(-50%) translateY(-120px);
    z-index: 1002;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    font-size: 13px; font-weight: 600;
    color: #0b0e17;
    background: #4fc3f7;
    border: 1px solid rgba(255, 255, 255, 0.3);
    border-radius: 8px;
    padding: 9px 16px;
    cursor: pointer;
    transition: transform 0.4s ease;
    white-space: nowrap;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
}
#update-banner.show { transform: translateX(-50%) translateY(0); }
#update-banner:hover { filter: brightness(1.08); }

/* ── Pulsing user-location dot (pure CSS, no image assets) ────────────── */
.user-dot {
    position: relative;
    width: var(--dot-size, 14px);
    height: var(--dot-size, 14px);
}
.user-dot-core {
    position: absolute;
    top: calc(var(--dot-size, 14px) * 0.214);
    left: calc(var(--dot-size, 14px) * 0.214);
    width: calc(var(--dot-size, 14px) * 0.571);
    height: calc(var(--dot-size, 14px) * 0.571);
    background: var(--dot-color, #ef5350);
    border-radius: 50%;
    border: 1.5px solid #fff;
    z-index: 2;
}
.user-dot-pulse {
    position: absolute;
    top: 0; left: 0;
    width: var(--dot-size, 14px);
    height: var(--dot-size, 14px);
    border-radius: 50%;
    background: var(--dot-pulse-color, rgba(239, 83, 80, 0.4));
    animation: pulse-ring 2s ease-out infinite;
    z-index: 1;
}
@keyframes pulse-ring {
    0%   { transform: scale(1); opacity: 0.6; }
    100% { transform: scale(3); opacity: 0; }
}
/* Disable the expanding pulse ring when the user prefers reduced motion */
@media (prefers-reduced-motion: reduce) {
    .user-dot-pulse { animation: none; opacity: 0.3; }
}
/* Vague location (IP-geo / hardcoded default): replace the crisp pinpoint with a large soft halo
   centred on the point — honest that we only know the area, not the spot. Precise sources (GPS,
   searched place) keep the sharp pulsing dot above. */
.loc-vague .user-dot-core {
    /* keep a faint centre point inside the area circle */
    opacity: 0.75;
    border-color: rgba(255, 255, 255, 0.6);
}
.loc-vague .user-dot-pulse {
    /* "approximate area": translucent fill + a DASHED coloured border with a clear edge */
    width: 48px; height: 48px;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    background: var(--dot-pulse-color);
    border: 2px dashed var(--dot-color);
    border-radius: 50%;
    animation: none;
    opacity: 0.9;
}

/* ── Leaflet overrides for dark theme ─────────────────────────────────── */
.leaflet-control-attribution {
    background: rgba(10, 10, 30, 0.7) !important;
    color: #888 !important;
    font-size: 10px !important;
    /* #84: clear the home-indicator + landscape right-side cutout (bottom-right control) */
    margin-bottom: var(--sa-bottom) !important;
    margin-right: var(--sa-right) !important;
}
.leaflet-control-attribution a { color: #4fc3f7 !important; }
.leaflet-control-zoom { display: none; }   /* hide for wall display */
