        /* =====================================================
           MAPBOX-ONLY TOKEN LAYER — shadcn-structured
           This sheet loads after tokens.css and core.css, so the
           :root re-points below win on this page only — leaflet.html
           never loads mapbox.css, so the shared sheets keep their
           original values there and the visual refresh stays
           Mapbox-only.

           Token semantics and component visual specs are adapted from
           shadcn/ui (MIT — https://github.com/shadcn-ui/ui), hand-ported
           to vanilla CSS carrying THIS project's palette; the tool-row
           icons in mapbox.html are Lucide (ISC — https://lucide.dev).
           No runtime dependency on either — values and markup are
           copied in.

           Dark values are the default; body[data-theme="light"]
           re-points the same tokens, so a rule written once with
           var(--token) themes itself. CAUTION: a custom property whose
           value is var(--other) is resolved where it is DECLARED, not
           where it is used — so every alias below is declared twice,
           once per theme block, or the :root copy would bake in the
           dark value and inherit it into light mode.

           Naming deviations from shadcn, both deliberate:
           - shadcn's --accent (hover-surface role) is NOT defined here:
             the shared tokens.css already owns --accent as the brand
             sky-blue and core.css consumes it as a text/badge colour,
             so that name keeps its brand meaning (aliased to
             --primary); hover surfaces use --secondary instead.
           - --foreground aliases the shared --text rather than the
             reverse, because core.css consumes --text directly.

           --text-muted and --text-dim keep their shared values in both
           themes: core.css's own light overrides consume them and were
           written against those values.

           NOTE: no backdrop-filter on any panel — blur over the WebGL
           map canvas stalled compositing (observed in embedded
           Chromium) so hard that Mapbox never completed its first
           frame, so map.once("load") — and the whole app init —
           never ran. Depth comes from shadows and near-opaque
           surfaces instead.
           ===================================================== */
        :root {
            /* shadcn-semantic core (dark) */
            --background:           #0f0f1a;
            --foreground:           var(--text);
            --card:                 rgba(16, 18, 38, 0.90);
            --popover:              rgba(30, 30, 50, 0.94);
            --primary:              #38bdf8;
            --primary-foreground:   #082f49;
            --secondary:            rgba(118, 124, 200, 0.22);
            --secondary-foreground: var(--text);
            --muted:                rgba(99, 99, 160, 0.15);
            --muted-foreground:     var(--text-muted);
            --destructive:          #ef4444;
            --border:               rgba(118, 124, 200, 0.38);
            --input:                rgba(99, 99, 160, 0.25);
            --ring:                 #7dd3fc;

            /* Radius scale: panels / popovers / controls+chips */
            --radius:    12px;
            --radius-md: 10px;
            --radius-sm: 6px;

            /* Brand accent keeps its shared meaning (see header note) */
            --accent: var(--primary);

            /* Legacy aliases — shared core.css reads these names */
            --panel-bg:           var(--card);
            --panel-bg-light:     rgba(255, 255, 255, 0.92);
            --panel-border:       var(--border);
            --panel-border-muted: rgba(118, 124, 200, 0.28);
            --panel-border-soft:  rgba(118, 124, 200, 0.20);
            --panel-border-faint: rgba(118, 124, 200, 0.15);

            /* Project extensions (roles shadcn doesn't carry) */
            --surface-opaque: rgb(20, 22, 45);   /* dropdowns that must not see through */
            --panel-hover:    rgba(36, 38, 72, 0.85);
            --focus-halo:     rgba(56, 189, 248, 0.22);
            --accent-bg:      rgba(56, 189, 248, 0.15);
            --accent-border:  rgba(56, 189, 248, 0.30);
            --ok-text:        #4ade80;
            --ok-bg:          rgba(74, 222, 128, 0.15);
            --idle-text:      #fbbf24;                    /* "Stopped" status chip — amber */
            --idle-bg:        rgba(251, 191, 36, 0.15);
            --fav:            #facc15;
            --history-accent: #f59e0b;
            --history-border: rgba(245, 158, 11, 0.5);
            --history-hover:  rgba(245, 158, 11, 0.35);

            --shadow-panel:   0 4px 24px rgba(0, 0, 0, 0.38), 0 1px 3px rgba(0, 0, 0, 0.30);
            --shadow-control: 0 2px 10px rgba(0, 0, 0, 0.30);
            --shadow-pop:     0 8px 32px rgba(0, 0, 0, 0.50);

            /* Contrast bump for the dimmest text tier on dark panels:
               the shared #64748b reads ~3.9:1 against --card; #75839a
               clears WCAG AA (≥4.5:1) at the small sizes this tier is
               used for. Light mode restores the shared value below —
               on white, #64748b is the better (4.8:1) choice. */
            --text-dim: #75839a;
        }

        body[data-theme="light"] {
            --text: var(--text-on-light);
            --text-dim: #64748b;   /* shared value — see the dark bump above */

            --background:           #f8fafc;
            --card:                 rgba(255, 255, 255, 0.92);
            --popover:              rgba(255, 255, 255, 0.95);
            --primary:              #0284c7;
            --primary-foreground:   #ffffff;
            --secondary:            rgba(0, 0, 0, 0.05);
            --muted:                rgba(0, 0, 0, 0.03);
            --muted-foreground:     var(--text-dim);
            --destructive:          #dc2626;
            --border:               rgba(0, 0, 0, 0.12);
            --input:                rgba(0, 0, 0, 0.10);
            --ring:                 #0284c7;

            /* Aliases MUST be re-declared here, not only at :root: a
               custom property containing var() is resolved where it is
               DECLARED, so the :root copies bake in the dark values and
               inherit them past this block. Declared on body, these
               resolve against the light re-points above. */
            --foreground:           var(--text);
            --secondary-foreground: var(--text);
            --accent:               var(--primary);
            --panel-bg:             var(--card);
            --panel-border:         var(--border);

            --panel-border-muted: rgba(0, 0, 0, 0.08);
            --panel-border-soft:  rgba(0, 0, 0, 0.06);
            --panel-border-faint: rgba(0, 0, 0, 0.04);

            --surface-opaque: rgb(255, 255, 255);
            --panel-hover:    rgba(241, 245, 249, 0.92);
            --focus-halo:     rgba(2, 132, 199, 0.16);
            --accent-bg:      rgba(56, 189, 248, 0.10);
            --accent-border:  rgba(56, 189, 248, 0.25);
            --ok-text:        #15803d;
            --ok-bg:          rgba(22, 163, 74, 0.10);
            --idle-text:      #b45309;                    /* darker amber for contrast on white */
            --idle-bg:        rgba(180, 83, 9, 0.10);
            --fav:            #eab308;
            --history-accent: #d97706;
            --history-border: rgba(217, 119, 6, 0.5);
            --history-hover:  rgba(217, 119, 6, 0.2);

            --shadow-panel:   0 4px 24px rgba(15, 23, 42, 0.14), 0 1px 3px rgba(15, 23, 42, 0.10);
            --shadow-control: 0 2px 10px rgba(15, 23, 42, 0.12);
            --shadow-pop:     0 8px 32px rgba(15, 23, 42, 0.22);
        }

        html, body {
            margin: 0;
            padding: 0;
            height: 100%;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
            background: #0f0f1a;
        }

        /* =====================================================
           UI PANELS — dark defaults, light values via the token
           flip above (per-rule light overrides only where the
           light design differs beyond colour tokens)
        ===================================================== */

        #status {
            position: absolute;
            bottom: 10px;
            left: 10px;
            background: var(--panel-bg);
            color: var(--text-muted);
            padding: 8px 14px;
            border-radius: var(--radius);
            font-size: 12px;
            z-index: 1000;
            border: 1px solid var(--panel-border);
            box-shadow: var(--shadow-panel);
            max-width: 600px;
            transition: background 0.2s, color 0.2s, border-color 0.2s;
            display: none;
        }

        /* =====================================================
           STYLE POPOVER — #map-controls, opened by the layers button
           ===================================================== */
        /* Overrides core.css's old bottom-left anchor: the panel is now a
           popover beside the bottom-right control column, hidden until the
           layers button (#style-toggle-btn) opens it. Opaque so the map
           never bleeds through the swatch names. */
        #map-controls,
        body[data-theme="light"] #map-controls {
            left: auto;
            right: 52px;
            bottom: 10px;
            display: none;
            flex-direction: column;
            gap: 3px;
            background: var(--surface-opaque);
            border: 1px solid var(--panel-border);
            border-radius: var(--radius-md);
            box-shadow: var(--shadow-pop);
            padding: 10px 12px;
            z-index: 1100;
        }
        /* Both selectors: the light-theme variant of the closed rule above
           is (1,1,1), which would outrank a bare #map-controls.open */
        #map-controls.open,
        body[data-theme="light"] #map-controls.open { display: flex; }

        .controls-heading {
            font-size: 10px;
            font-weight: 700;
            letter-spacing: 0.08em;
            text-transform: uppercase;
            color: var(--text-dim);
        }
        .light-heading { margin-top: 6px; }

        /* The Light section adapts to the selected style: time-of-day presets
           for Default (Standard), Dark/Light for 2D, hidden for Satellite. */
        #light-controls, #twod-controls { display: flex; }
        #map-controls[data-mode="default"] #twod-controls { display: none; }
        #map-controls[data-mode="twod"] #light-controls { display: none; }
        #map-controls[data-mode="sat"] #light-controls,
        #map-controls[data-mode="sat"] #twod-controls,
        #map-controls[data-mode="sat"] .light-heading { display: none; }

        /* Layers button: sky tint while its popover is open */
        #style-toggle-btn svg { display: block; margin: 0 auto; }
        #style-toggle-btn[aria-expanded="true"] { color: var(--accent); }
        /* NOTE: a `.light-btn { padding: 4px 8px }` rule lived here but was always
           overridden by the equal-specificity `.nav-btn, .tile-btn { padding: 4px 10px }`
           (now in core.css), so the light preset buttons rendered at 4px 10px. The dead
           declaration was removed during the CSS dedup to preserve that rendering. */

        /* =====================================================
           TOOL ROWS — desktop: two labelled rows grouped by domain
           ===================================================== */
        /* Bottom row: web-app tools (Favs · Measure · Labels · Viewport ·
           Flights). Row above: GTFS-data tools (Stops · Shapes · Trails ·
           History). The old style switcher no longer sits below, so the
           stack drops from 92px to 40px — clear of the #status bar (10px). */
        #tool-buttons {
            position: absolute;
            bottom: 40px;
            left: 10px;
            z-index: 1000;
            display: flex;
            flex-direction: column;
            gap: 6px;
            align-items: flex-start;
            transition: bottom 0.15s;
        }

        .tool-row { display: flex; gap: 4px; }

        /* The five-slot dock (with its More button) is mobile chrome */
        #tool-dock { display: none; }
        #tool-sheet, #tool-sheet-backdrop { display: none; }

        /* Lucide icon + label inside each tool button */
        #tool-buttons .tile-btn,
        #tool-sheet-body .tile-btn {
            display: inline-flex;
            align-items: center;
            gap: 5px;
        }

        #tool-buttons .tile-btn svg,
        #tool-sheet-body .tile-btn svg {
            flex-shrink: 0;
        }

        /* Desktop bottom-right control column. Mapbox stacks its controls in
           DOM order (compass, layers, scale), which floats the scale ruler
           on top; make the column a flex box and drop the ruler to the
           bottom (above the © attribution, which stays last per Mapbox ToS).
           Scoped to >768px so the confirmed mobile column is untouched. */
        @media (min-width: 769px) {
            .mapboxgl-ctrl-bottom-right {
                display: flex;
                flex-direction: column;
                align-items: flex-end;
            }
            .mapboxgl-ctrl-bottom-right .mapboxgl-ctrl-scale { order: 1; }
            .mapboxgl-ctrl-bottom-right .mapboxgl-ctrl-attrib { order: 2; }
        }

        .tile-btn.stops-active,
        .tile-btn.shapes-active,
        .tile-btn.trails-active,
        .tile-btn.labels-active,
        .tile-btn.flights-active,
        .tile-btn.cameras-active,
        .tile-btn.viewport-active {
            background: #22c55e !important;
            color: #ffffff !important;
        }

        .tile-btn.favs-active {
            background: var(--fav) !important;
            color: var(--text-on-light) !important;
        }

        /* =====================================================
           MAPBOX POPUP — themed via the token layer
        ===================================================== */

        .mapboxgl-popup-content {
            background: var(--popover);
            border: 1px solid var(--panel-border);
            color: var(--text);
            box-shadow: var(--shadow-pop);
            border-radius: var(--radius-md);
            padding: 10px 12px;
            font-size: 13px;
            min-width: 200px;
            max-width: 300px;
        }

        /* Tip colour per anchor side — Mapbox picks the anchor when the
           popup opens, so every direction needs the surface colour (the
           old sheet only themed the bottom-anchored tip; the other three
           showed Mapbox's default white triangle). */
        .mapboxgl-popup-tip {
            border-top-color: var(--popover) !important;
        }
        .mapboxgl-popup-anchor-top .mapboxgl-popup-tip,
        .mapboxgl-popup-anchor-top-left .mapboxgl-popup-tip,
        .mapboxgl-popup-anchor-top-right .mapboxgl-popup-tip {
            border-bottom-color: var(--popover) !important;
        }
        .mapboxgl-popup-anchor-left .mapboxgl-popup-tip {
            border-right-color: var(--popover) !important;
        }
        .mapboxgl-popup-anchor-right .mapboxgl-popup-tip {
            border-left-color: var(--popover) !important;
        }

        .mapboxgl-popup-close-button {
            color: var(--text-dim);
            font-size: 18px;
            padding: 2px 6px;
        }

        .mapboxgl-popup-close-button:hover {
            color: var(--text);
            background: transparent;
        }

        /* =====================================================
           VEHICLE DETAILS — dock button + docked panel
           ===================================================== */

        .vehicle-popup .popup-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 6px;
        }
        /* Docked details window — anchored under the Service Alerts row like
           #alert-panel (same top:100% drop zone; the transient alerts
           dropdown, z-index 1001, overlays this panel while it is open). */
        #vehicle-panel {
            position: absolute;
            top: 100%;
            right: 0;
            margin-top: 6px;
            display: none;
            width: 320px;
            max-width: calc(100vw - 20px);
            max-height: min(440px, calc(100vh - 160px));
            overflow-y: auto;
            /* Opaque, not the usual 90%-alpha panel surface: the details
               window overlays map markers (and, as a mobile bottom sheet,
               the tool row) — content ghosting through is noise here. */
            background: var(--surface-opaque);
            border: 1px solid var(--panel-border);
            border-radius: var(--radius-md);
            box-shadow: var(--shadow-panel);
            padding: 10px;
            font-size: 12px;
            color: var(--text);
            z-index: 1000;
        }
        /* Header action cluster shared by both details windows: the popup's
           dock+close (POPUP_ACTIONS_HTML) and the panel's undock+close
           (PANEL_ACTIONS_HTML). Sits inline at the header's right edge beside the
           entity name — identically styled in both so they read as one control. */
        .popup-panel-actions {
            display: flex;
            align-items: center;
            gap: 4px;
        }
        /* 30px square tap target — clears WCAG AA's 24px minimum (the old
           2px/4px padding round a 12px icon was ~16-20px and failed). 16px
           icon, 20px close glyph, borderless with a soft hover fill. */
        .popup-dock-btn,
        .popup-undock-btn,
        .popup-panel-close {
            width: 30px;
            height: 30px;
            flex: none;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            background: none;
            border: none;
            color: var(--text-muted);
            cursor: pointer;
            padding: 0;
            border-radius: var(--radius-sm);
        }
        .popup-dock-btn:hover,
        .popup-undock-btn:hover,
        .popup-panel-close:hover {
            color: var(--text);
            background: var(--secondary);
        }
        .popup-dock-btn svg,
        .popup-undock-btn svg { width: 16px; height: 16px; }
        .popup-panel-close { font-size: 20px; line-height: 1; }

        /* =====================================================
           TRANSIT-NATIVE popup (bus/train) — route badge leads,
           status chip + 2-column grid below. Emitted only by
           vehicles.js buildPopup for the transit types; ferries
           and vessels keep the name-first header above. Applies
           in both the map popup and the docked #vehicle-panel.
           ===================================================== */
        .vehicle-popup .popup-header--transit {
            justify-content: flex-start;
            align-items: center;
            gap: 9px;
        }
        .vehicle-popup .route-badge {
            flex: none;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            min-width: 40px;
            height: 34px;
            padding: 0 8px;
            background: var(--accent);
            color: var(--primary-foreground);
            border-radius: var(--radius-sm);
            font-weight: 700;
            font-size: 15px;
            letter-spacing: -0.01em;
            line-height: 1;
        }
        .vehicle-popup .popup-heading {
            display: flex;
            flex-direction: column;
            gap: 1px;
            min-width: 0;               /* let a long id ellipsize, not shove the actions off */
        }
        .vehicle-popup .popup-eyebrow {
            font-size: 10px;
            font-weight: 600;
            letter-spacing: 0.08em;
            text-transform: uppercase;
            color: var(--text-dim);
        }
        .vehicle-popup .popup-subtitle {
            font-size: 13px;
            font-weight: 600;
            color: var(--text);
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
        .vehicle-popup .popup-header--transit .popup-panel-actions {
            margin-left: auto;          /* dock + close pinned to the far right */
            align-self: flex-start;
        }

        /* =====================================================
           NAME-FIRST popup (ferries + non-AT vessels) — the vessel
           NAME leads as the hero with the type as an eyebrow above;
           no route badge and no status chip. Reuses .popup-heading /
           .popup-eyebrow from the transit block above; only the name
           line and the two-line action pinning are new. Emitted by
           buildPopup for the ferry/vessel types.
           ===================================================== */
        .vehicle-popup .popup-header--vessel {
            align-items: flex-start;    /* two-line heading; pin the actions to the top */
        }
        .vehicle-popup .popup-name {
            font-size: 16px;
            font-weight: 700;
            letter-spacing: -0.01em;
            line-height: 1.15;
            color: var(--accent);
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;         /* long names ellipsize, not wrap under the actions */
        }
        /* a nameless vessel promotes its MMSI link into the name slot */
        .vehicle-popup .popup-name a { color: inherit; text-decoration: none; }
        body[data-theme="light"] .vehicle-popup .popup-name { color: #1d4ed8; }

        /* Service / motion status chip */
        .vehicle-popup .popup-status {
            margin-top: 8px;
        }
        .vehicle-popup .status-chip {
            display: inline-flex;
            align-items: center;
            gap: 6px;
            padding: 3px 9px;
            border-radius: 999px;
            font-size: 11px;
            font-weight: 600;
            letter-spacing: 0.02em;
        }
        .vehicle-popup .status-chip .chip-dot {
            width: 6px;
            height: 6px;
            border-radius: 50%;
            background: currentColor;
        }
        .vehicle-popup .status-chip.moving  { color: var(--ok-text);   background: var(--ok-bg); }
        .vehicle-popup .status-chip.stopped { color: var(--idle-text); background: var(--idle-bg); }
        .vehicle-popup .status-chip.nis     { color: var(--text-dim);  background: var(--secondary); }

        /* 2-column field grid (transit body) */
        .vehicle-popup .data-grid {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 9px 12px;
            margin-top: 10px;
        }
        .vehicle-popup .data-cell {
            display: flex;
            flex-direction: column;
            gap: 1px;
            min-width: 0;
        }
        .vehicle-popup .data-cell .cell-label {
            font-size: 9.5px;
            font-weight: 600;
            letter-spacing: 0.08em;
            text-transform: uppercase;
            color: var(--text-dim);
        }
        .vehicle-popup .data-cell .cell-value {
            font-size: 13px;
            color: var(--text);
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
        .vehicle-popup .data-cell .cell-value a { color: var(--accent); }
        .vehicle-popup .data-cell--wide { grid-column: 1 / -1; }

        /* Route progress, transit variant — a clean open section matching the
           design study. The container drops the base .popup-progress class (in
           vehicles.js), so core.css's filled box + green left-rail no longer
           apply; here it is a hairline-separated section: an uppercase label +
           km on one row, a pill track with a position knob, and a plain-language
           read below. Fill/knob use --ok-text so they flip per theme; the label
           and fill nest one level deeper to win over core.css's
           body[data-theme="light"] .progress-* overrides. Ferry + off-route
           branches keep the base .popup-progress box untouched. */
        .popup-progress--transit {
            margin-top: 12px;
            padding-top: 11px;
            border-top: 1px solid var(--panel-border-soft);
            font-size: 11px;
            line-height: 1.5;
        }
        .popup-progress--transit .progress-top {
            display: flex;
            align-items: baseline;
            justify-content: space-between;
            gap: 8px;
            margin-bottom: 7px;
        }
        .popup-progress--transit .progress-top .progress-label {
            font-size: 9.5px;
            font-weight: 600;
            letter-spacing: 0.08em;
            text-transform: uppercase;
            color: var(--text-dim);
        }
        .popup-progress--transit .progress-km {
            color: var(--text);
            font-variant-numeric: tabular-nums;
            text-align: right;
        }
        .popup-progress--transit .progress-track {
            position: relative;
            height: 6px;
            border-radius: 999px;
            overflow: visible;          /* base track clips; the knob must show */
            margin: 0 0 8px;
        }
        .popup-progress--transit .progress-track .progress-fill {
            border-radius: 999px;
            background: var(--ok-text);
        }
        .popup-progress--transit .progress-knob {
            position: absolute;
            top: 50%;
            width: 11px;
            height: 11px;
            border-radius: 50%;
            background: var(--ok-text);
            border: 2px solid var(--popover);
            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
            transform: translate(-50%, -50%);
        }
        .popup-progress--transit .progress-detail {
            display: block;
            font-size: 10.5px;
        }

        .vehicle-popup .reference-heading {
            color: #22d3ee;
            font-size: 10px;
            font-weight: 700;
            letter-spacing: 0.05em;
            text-transform: uppercase;
            margin-bottom: 2px;
        }
        .vehicle-popup .reference-badge {
            display: inline-block;
            background: rgba(34, 211, 238, 0.18);
            color: #22d3ee;
            border-radius: 4px;
            padding: 0 6px;
            font-size: 11px;
            text-transform: capitalize;
        }
        .vehicle-popup .vessel-profile-caption {
            color: var(--text-muted);
            font-size: 10px;
            margin-top: 1px;
        }

        /* Traffic camera popup — still image under the shared popup header */
        .camera-popup .camera-popup-meta {
            color: var(--text-muted);
            font-size: 11px;
            margin: 2px 0 0;
        }
        /* The still sits in a fixed 16:9 frame that also holds a status overlay
           (spinner while loading, camera-off icon + label on failure). The
           <img> is shown only once a real blob loads (.is-loaded), so the
           browser's native broken-image glyph never appears for an empty or
           failed src. */
        .camera-popup-frame {
            position: relative;
            width: 100%;
            aspect-ratio: 16 / 9;
            margin-top: 6px;
            border-radius: var(--radius-sm);
            overflow: hidden;
            background: var(--secondary);
        }
        .camera-popup-image {
            display: none;
            width: 100%;
            height: 100%;
            object-fit: cover;
        }
        .camera-popup-frame.is-loaded .camera-popup-image {
            display: block;
            cursor: zoom-in;   /* loaded still is click-to-enlarge */
        }
        /* Loading / unavailable overlay, centred in the frame */
        .camera-popup-status {
            position: absolute;
            inset: 0;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            gap: 6px;
            color: var(--text-muted);
            font-size: 11px;
            text-align: center;
            padding: 8px;
        }
        .camera-popup-frame.is-loaded .camera-popup-status { display: none; }
        .camera-popup-status-icon { opacity: 0.55; }
        /* Spinner while loading; the camera-off icon (with its slash) on failure */
        .camera-popup-spinner {
            display: inline-block;
            width: 22px;
            height: 22px;
            border-radius: 50%;
            border: 2px solid var(--text-muted);
            border-top-color: transparent;
            opacity: 0.6;
            animation: camera-spin 0.8s linear infinite;
        }
        @keyframes camera-spin { to { transform: rotate(360deg); } }
        @media (prefers-reduced-motion: reduce) {
            .camera-popup-spinner { animation: none; }
        }
        .camera-popup-frame.is-loading .camera-popup-status-icon { display: none; }
        .camera-popup-frame.is-failed .camera-popup-spinner { display: none; }
        .camera-popup-status-icon .cam-slash { display: none; }
        .camera-popup-frame.is-failed .camera-popup-status-icon .cam-slash { display: inline; }
        /* Capture-time line under the still */
        .camera-popup-capture {
            color: var(--text-muted);
            font-size: 10px;
            margin-top: 4px;
            min-height: 12px;   /* reserve the row so the popup doesn't jump when it fills */
        }

        /* Click-to-enlarge overlay — above the map and its controls. Fixed
           dark scrim; the still displays at up to native size (capped in JS,
           never upscaled) and centred. */
        .camera-lightbox {
            position: fixed;
            inset: 0;
            z-index: 3000;
            display: flex;
            align-items: center;
            justify-content: center;
            background: rgba(0, 0, 0, 0.85);
            padding: 24px;
        }
        .camera-lightbox[hidden] { display: none; }
        .camera-lightbox-figure { margin: 0; text-align: center; }
        .camera-lightbox-image {
            display: block;
            width: auto;
            height: auto;
            border-radius: var(--radius-sm);
            box-shadow: 0 8px 40px rgba(0, 0, 0, 0.6);
        }
        .camera-lightbox-caption {
            color: #cbd5e1;
            font-size: 11px;
            margin-top: 8px;
        }
        .camera-lightbox-close {
            position: absolute;
            top: 16px;
            right: 20px;
            background: none;
            border: 0;
            color: #fff;
            font-size: 28px;
            line-height: 1;
            cursor: pointer;
        }

        /* Route focus chip — persistent top-centre indicator + exit while a
           route is focused. Tokens flip with the theme, so no light override. */
        .route-focus-chip {
            position: absolute;
            top: 12px;
            left: 50%;
            transform: translateX(-50%);
            z-index: 1000;
            display: flex;
            align-items: center;
            gap: 8px;
            max-width: calc(100vw - 24px);
            padding: 5px 6px 5px 12px;
            background: var(--surface-opaque);
            border: 1px solid var(--panel-border);
            border-radius: 999px;
            box-shadow: var(--shadow-control);
            font-size: 13px;
            font-weight: 600;
            color: var(--text);
        }
        .route-focus-chip[hidden] { display: none; }
        .route-focus-chip-label {
            display: inline-flex;
            align-items: center;
            gap: 6px;
            min-width: 0;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        .route-focus-chip-label::before {
            content: "";
            flex: none;
            width: 8px;
            height: 8px;
            border-radius: 50%;
            background: var(--accent);
        }
        .route-focus-chip-exit {
            flex: none;
            padding: 3px 10px;
            border: none;
            border-radius: 999px;
            background: var(--panel-border-muted);
            color: var(--text-muted);
            font: inherit;
            font-size: 12px;
            cursor: pointer;
            white-space: nowrap;
        }
        .route-focus-chip-exit:hover {
            color: var(--text);
            background: var(--panel-border);
        }

        .measure-label {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
            font-size: 11px;
            font-weight: 600;
            white-space: nowrap;
            padding: 2px 6px;
            border-radius: 3px;
            /* Subtle translucent chip so the text stays readable over any map
               background — including the shifting Standard 3D light presets. */
            background: rgba(0, 0, 0, 0.5);
            color: var(--text);
            text-shadow: 0 1px 3px rgba(0,0,0,0.7);
            pointer-events: none;
        }

        /* =====================================================
           BROWSE MODE
        ===================================================== */

        /* Browse icon inside search input */
        .search-input-wrapper {
            display: flex;
            align-items: center;
            gap: 4px;
        }

        .search-input-wrapper #route-search {
            flex: 1;
            min-width: 0;
        }

        .browse-icon-btn {
            background: none;
            border: none;
            cursor: pointer;
            color: var(--text-dim);
            padding: 4px;
            border-radius: 3px;
            line-height: 0;
            flex-shrink: 0;
            transition: color 0.15s, background 0.15s;
        }

        .browse-icon-btn:hover {
            color: var(--text);
            background: var(--secondary);
        }

        .browse-icon-btn.browse-active {
            color: var(--accent);
            background: var(--accent-bg);
        }

        body[data-theme="light"] .browse-icon-btn {
            color: var(--text-muted);
        }

        /* Browse bar — type filter tabs (shadcn Tabs: muted track,
           pill-shaped triggers) */
        #browse-bar {
            display: flex;
            flex-wrap: wrap;
            gap: 3px;
            margin: 6px 0 2px;
            padding: 3px;
            background: var(--muted);
            border-radius: calc(var(--radius-sm) + 2px);
        }

        /* Operator filter — full-width row below the type tabs (too many
           operators to fit as tabs); flex-basis 100% forces the wrap. */
        .browse-operator-select {
            flex: 0 0 100%;
            padding: 4px 6px;
            font-size: 11px;
            font-family: inherit;
            font-weight: 500;
            color: var(--text);
            background: var(--secondary);
            border: none;
            border-radius: var(--radius-sm);
            cursor: pointer;
        }

        .browse-operator-select:focus {
            outline: none;
        }

        .browse-tab {
            flex: 1;
            padding: 4px 0;
            font-size: 11px;
            font-family: inherit;
            font-weight: 500;
            background: transparent;
            border: none;
            border-radius: var(--radius-sm);
            cursor: pointer;
            color: var(--muted-foreground);
            transition: background 0.15s, color 0.15s;
        }

        .browse-tab:hover {
            background: var(--secondary);
            color: var(--text);
        }

        .browse-tab.active {
            background: var(--accent-bg);
            color: var(--accent);
        }

        /* Browse mode — taller dropdown */
        #search-results.browse-mode {
            max-height: 450px;
        }

        /* Search UX — match highlighting and keyboard-walked selection */
        #search-results mark {
            background: var(--accent-bg);
            color: var(--accent);
            border-radius: 2px;
            padding: 0 1px;
        }

        .search-result.kbd-selected {
            background: var(--secondary);
        }

        /* Vehicle count badge in browse list (shadcn Badge geometry) */
        .browse-vehicle-badge {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            min-width: 20px;
            height: 18px;
            padding: 0 5px;
            border-radius: var(--radius-sm);
            font-size: 10px;
            font-weight: 600;
            flex-shrink: 0;
        }

        .browse-vehicle-badge.has-vehicles {
            background: var(--ok-bg);
            color: var(--ok-text);
        }

        .browse-vehicle-badge.no-vehicles {
            background: rgba(100, 116, 139, 0.1);
            color: var(--text-dim);
        }

        body[data-theme="light"] .browse-vehicle-badge.no-vehicles {
            background: rgba(0, 0, 0, 0.04);
            color: var(--text-muted);
        }

        /* Route detail view */
        .browse-detail-back {
            background: none;
            border: none;
            cursor: pointer;
            color: var(--text-muted);
            font-size: 16px;
            padding: 4px 8px 4px 0;
            line-height: 1;
            transition: color 0.15s;
        }

        .browse-detail-back:hover {
            color: var(--text);
        }

        body[data-theme="light"] .browse-detail-back {
            color: var(--text-dim);
        }

        .browse-detail-header {
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 8px 8px 6px;
            border-bottom: 1px solid var(--panel-border-faint);
        }

        /* The route-id title is the only header child without its own themed
           colour; unscoped .search-route-id sets none, so give it the
           theme-aware --text (flips light/dark) instead of inheriting the
           browser default. */
        .browse-detail-header .search-route-id {
            color: var(--text);
        }

        .browse-detail-name {
            font-size: 13px;
            color: var(--text-muted);
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            flex: 1;
        }

        body[data-theme="light"] .browse-detail-name {
            color: var(--text-dim);
        }

        .browse-detail-section {
            padding: 6px 8px 3px;
            font-size: 10px;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.5px;
            color: var(--text-dim);
        }

        .browse-vehicle-row {
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 5px 8px;
            border-radius: var(--radius-sm);
            cursor: pointer;
            font-size: 12px;
            color: var(--text);
            transition: background 0.1s;
        }

        .browse-vehicle-row:hover {
            background: var(--secondary);
        }

        .browse-vehicle-label {
            font-weight: 500;
            flex-shrink: 0;
        }

        .browse-vehicle-info {
            color: var(--text-muted);
            font-size: 11px;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            flex: 1;
        }

        body[data-theme="light"] .browse-vehicle-info {
            color: var(--text-dim);
        }

        .browse-vehicle-speed {
            font-size: 10px;
            color: var(--text-dim);
            flex-shrink: 0;
        }

        .browse-empty {
            padding: 12px 8px;
            text-align: center;
            font-size: 12px;
            color: var(--text-dim);
            font-style: italic;
        }

        /* Favourites — star buttons and section */

        .fav-star {
            background: none;
            border: none;
            cursor: pointer;
            font-size: 14px;
            padding: 0 2px;
            line-height: 1;
            color: var(--text-dim);
            flex-shrink: 0;
            transition: color 0.15s;
        }

        .fav-star:hover { color: var(--fav); }
        .fav-star.favourited { color: var(--fav); }

        body[data-theme="light"] .fav-star { color: var(--text-muted); }

        .fav-section-label {
            padding: 6px 8px 3px;
            font-size: 10px;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.5px;
            color: var(--text-dim);
            display: flex;
            align-items: center;
            gap: 4px;
        }

        .fav-section-label .fav-icon { font-size: 11px; color: var(--fav); }

        .fav-empty {
            padding: 8px;
            font-size: 11px;
            color: var(--text-dim);
            text-align: center;
            font-style: italic;
        }

        .departure-fav {
            background: none;
            border: none;
            cursor: pointer;
            font-size: 16px;
            padding: 0 6px;
            line-height: 1;
            color: var(--text-dim);
            transition: color 0.15s;
        }

        .departure-fav:hover { color: var(--fav); }
        .departure-fav.favourited { color: var(--fav); }

        body[data-theme="light"] .departure-fav { color: var(--text-muted); }

        /* History playback */
        .tile-btn.history-active {
            background: var(--history-accent) !important;
            color: var(--text-on-light) !important;
        }

        #history-panel {
            position: absolute;
            bottom: 130px;
            left: 10px;
            background: var(--panel-bg);
            border: 1px solid var(--history-border);
            border-radius: var(--radius);
            box-shadow: var(--shadow-panel);
            padding: 10px 14px;
            z-index: 1000;
            min-width: 280px;
            max-width: 360px;
            transition: background 0.2s, border-color 0.2s;
        }

        .history-time {
            color: var(--history-accent);
            font-size: 14px;
            font-weight: 600;
            text-align: center;
            margin-bottom: 8px;
            font-variant-numeric: tabular-nums;
        }

        .history-controls {
            display: flex;
            align-items: center;
            gap: 6px;
            margin-bottom: 8px;
            justify-content: center;
        }

        .history-ctrl-btn {
            padding: 4px 10px;
            font-size: 12px;
            font-family: inherit;
            font-weight: 600;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            background: var(--panel-border-muted);
            color: var(--text);
            transition: background 0.15s;
        }

        .history-ctrl-btn:hover {
            background: var(--history-hover);
        }

        .history-speed-select {
            padding: 3px 6px;
            font-size: 11px;
            font-family: inherit;
            border: 1px solid var(--input);
            border-radius: var(--radius-sm);
            background: var(--muted);
            color: var(--text);
            cursor: pointer;
        }

        .history-slider {
            width: 100%;
            margin: 4px 0 8px;
            accent-color: var(--history-accent);
        }

        .history-live-btn {
            display: block;
            width: 100%;
            padding: 6px;
            font-size: 12px;
            font-family: inherit;
            font-weight: 600;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            background: #22c55e;
            color: #ffffff;
            transition: background 0.15s;
        }

        .history-live-btn:hover {
            background: #16a34a;
        }

        #legend.history-mode {
            border-color: var(--history-border) !important;
        }

        /* =====================================================
           LEGEND TOGGLE — mobile-only collapsed badge for #legend.
           Hidden on desktop; the media query below reveals it and
           hides the full #legend, which map-core.js then expands
           on tap (see the legend-toggle click listener).
        ===================================================== */

        #legend-toggle {
            display: none;
            align-items: center;
            gap: 6px;
            padding: 6px 12px;
            background: var(--panel-bg);
            border: 1px solid var(--panel-border);
            border-radius: var(--radius);
            box-shadow: var(--shadow-panel);
            cursor: pointer;
            font-size: 12px;
            font-family: inherit;
            color: var(--text);
            transition: background 0.2s, border-color 0.2s;
        }

        #legend-toggle:hover {
            background: var(--panel-hover);
        }

        #legend-toggle-count {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            min-width: 18px;
            height: 18px;
            padding: 0 5px;
            border-radius: 9px;
            background: var(--primary);
            color: var(--primary-foreground);
            font-size: 10px;
            font-weight: 700;
        }

        /* Nav hamburger in the search bar — mobile only, revealed by the
           media query below. Styled by .browse-icon-btn. */
        #nav-menu-btn {
            display: none;
        }

        /* Hide only Mapbox's "Improve this map" feedback link (its own
           .mapbox-improve-map class). The © Mapbox and © OpenStreetMap
           copyright links stay visible, so attribution remains compliant.
           Global (not mobile-only): the feedback link is unwanted on every
           viewport. */
        .mapboxgl-ctrl-attrib .mapbox-improve-map {
            display: none;
        }

        /* =====================================================
           SHARED-CHROME REFRESH (Mapbox page only)
           core.css owns these panels' base rules for both map
           renderers; the overrides below restyle them here without
           touching the shared sheet (same specificity, later load
           order wins). Light twins appear only where core.css's own
           body[data-theme="light"] rules would otherwise outrank a
           single-selector override.
        ===================================================== */

        #search-box, #map-controls, #nav-bar, #legend,
        #alert-toggle, #alert-panel, #departure-panel {
            border-radius: var(--radius);
            box-shadow: var(--shadow-panel);
        }

        /* shadcn button geometry for the compact HUD controls (heights
           deliberately unchanged — the 92px/128px bottom-stack offsets
           assume them) */
        .nav-btn, .tile-btn {
            border-radius: var(--radius-sm);
        }

        /* core.css hardcodes these two surfaces per theme instead of
           reading the tokens — re-point both themes at the panel token. */
        #alert-panel, #departure-panel,
        body[data-theme="light"] #alert-panel,
        body[data-theme="light"] #departure-panel {
            background: var(--panel-bg);
        }

        #alert-toggle:hover,
        body[data-theme="light"] #alert-toggle:hover {
            background: var(--panel-hover);
        }

        /* Floating tool buttons get control-scale depth */
        #tool-buttons .tile-btn {
            border-radius: var(--radius-sm);
            box-shadow: var(--shadow-control);
        }

        /* Measure stays a destructive-coloured toggle (shadcn
           destructive token; !important to match core.css's own) */
        .tile-btn.measure-active {
            background: var(--destructive) !important;
        }

        /* Alert cards — shadcn Alert geometry over core.css's base
           (left severity border kept; that's load-bearing meaning) */
        .alert-card,
        body[data-theme="light"] .alert-card {
            background: var(--muted);
            border-radius: var(--radius-sm);
        }

        /* Legend filter rows are real <button>s (keyboard + screen
           readers); strip the UA button chrome so they render exactly
           as the spans they replaced, and add a hover surface so the
           click-to-filter affordance is visible before you commit. */
        button.legend-mode, button.legend-cell, button.legend-all {
            background: none;
            border: none;
            padding: 0;
            margin: 0;
            font: inherit;
            color: inherit;
        }

        button.legend-all {
            width: 100%;      /* keep the total right-aligned via its auto margin */
            font-size: 11px;  /* re-state core.css's type — the font: inherit
                                 reset above outranks it (0,1,1 vs 0,1,0) */
            font-weight: 500;
        }

        .legend-mode:hover, .legend-cell:hover, .legend-all:hover {
            background: var(--secondary);
            border-radius: var(--radius-sm);
        }

        .legend-hint {
            margin-top: 4px;
            font-size: 9px;
            color: var(--text-dim);
            text-align: center;
            line-height: 1.4;
        }

        /* Inputs — shadcn Input: token surfaces plus a visible focus
           ring. The light twins outrank core.css's explicit light
           input rules. */
        #route-search, #departure-filter-input,
        body[data-theme="light"] #route-search,
        body[data-theme="light"] #departure-filter-input {
            background: var(--muted);
            border: 1px solid var(--input);
            border-radius: var(--radius-sm);
            color: var(--text);
        }

        #route-search:focus, #departure-filter-input:focus,
        body[data-theme="light"] #route-search:focus,
        body[data-theme="light"] #departure-filter-input:focus {
            border-color: var(--ring);
            box-shadow: 0 0 0 3px var(--focus-halo);
        }

        /* Keyboard focus — visible ring on every control; mouse clicks
           stay ring-free via :focus-visible. */
        button:focus-visible,
        input:focus-visible,
        select:focus-visible,
        a.nav-btn:focus-visible {
            outline: 2px solid var(--ring);
            outline-offset: 1px;
        }

        /* Thin themed scrollbars inside the floating panels */
        #search-results, #alert-panel, #departure-list {
            scrollbar-width: thin;
            scrollbar-color: var(--panel-border) transparent;
        }

        #search-results::-webkit-scrollbar,
        #alert-panel::-webkit-scrollbar,
        #departure-list::-webkit-scrollbar {
            width: 8px;
        }

        #search-results::-webkit-scrollbar-track,
        #alert-panel::-webkit-scrollbar-track,
        #departure-list::-webkit-scrollbar-track {
            background: transparent;
        }

        #search-results::-webkit-scrollbar-thumb,
        #alert-panel::-webkit-scrollbar-thumb,
        #departure-list::-webkit-scrollbar-thumb {
            background: var(--panel-border);
            border-radius: 4px;
        }

        @media (max-width: 768px) {
            #legend {
                display: none;
            }

            #legend.legend-expanded {
                display: block;
            }

            #legend-toggle {
                display: flex;
            }

            /* Search box spans the full width; hamburger appears inside it.
               Raised above #right-panel (z 1000) so an open dropdown paints
               over the Vehicles/Service Alerts badges instead of under them,
               and made opaque so nothing bleeds through the dropdown. */
            #search-box {
                right: 10px;
                width: auto;
                z-index: 1002;
                background: var(--surface-opaque);
                /* The 44px touch targets (.browse-icon-btn below) make the
                   buttons the tallest thing in the bar; drop the vertical
                   padding so the bar stays ~46px and doesn't grow down over
                   the Tracked/Alerts row (#right-panel, top:61px) or the nav
                   dropdown (top:54px), both anchored to the bar's height. */
                padding-top: 0;
                padding-bottom: 0;
            }

            body[data-theme="light"] #search-box {
                background: var(--surface-opaque);
            }

            #nav-menu-btn {
                display: block;
            }

            /* Touch targets (H3): the hamburger, browse, and clear buttons
               live in the search bar and are the primary mobile controls.
               Give each a 44x44 hit area — the glyph keeps its size, flex
               just centres it — instead of the ~20px the desktop padding
               leaves. */
            .browse-icon-btn {
                min-width: 44px;
                min-height: 44px;
                display: inline-flex;
                align-items: center;
                justify-content: center;
            }

            /* Route-focus chip (H2): drop it below the full-width search bar
               and the Tracked/Alerts row so its "Show all" exit stays visible
               and tappable, rather than hiding under the opaque search box
               (which sits above it at z 1002). */
            .route-focus-chip {
                top: 96px;
                z-index: 1001;
            }

            /* iOS Safari auto-zooms the page when a focused control's font
               size is under 16px. Meeting the threshold on phones kills the
               zoom at its cause (and reads better at this size anyway);
               desktop keeps the smaller sizes from core.css. */
            #route-search,
            #departure-filter-input {
                font-size: 16px;
            }

            /* Right panel drops below the search box, so nothing competes
               with the search bar for the top row. Tracked and Service
               Alerts share one horizontal line (the same 6px gap the old
               column used); the alert/vehicle panels still hang off the
               row via their own top:100% anchors. */
            #right-panel {
                top: 61px;
                right: auto;
                left: 10px;
                flex-direction: row;
                align-items: flex-start;
            }

            /* Nav collapses behind the hamburger; map-core.js toggles
               .nav-open (same pattern as .legend-expanded) */
            #nav-bar {
                display: none;
            }

            /* Float the open menu top-right (dropping from the hamburger)
               instead of stacking it in #right-panel's flex column, so it
               doesn't push the Vehicles / Service Alerts badges down.
               Fixed (not absolute) because #right-panel — the only positioned
               ancestor — is left-anchored and narrow, so it can't provide a
               screen-right anchor. No scroll here, so fixed == viewport. */
            #nav-bar.nav-open {
                display: flex;
                flex-direction: column;
                position: fixed;
                top: 54px;
                right: 10px;
                min-width: 140px;
                z-index: 1002;
            }

            #nav-bar .nav-btn {
                padding: 8px 12px;
            }

            /* Alert panel hangs off the (now left-anchored) right panel;
               clamp it to the viewport instead of its fixed 380px */
            #alert-panel {
                left: 0;
                right: auto;
                width: calc(100vw - 20px);
                max-width: 380px;
                box-sizing: border-box;
            }

            /* Departure board becomes a bottom sheet — overrides the
               desktop-only left: 360px anchor in core.css */
            #departure-panel {
                top: auto;
                left: 0;
                right: 0;
                bottom: 0;
                width: auto;
                max-height: 60vh;
                border-radius: 14px 14px 0 0;
                border-bottom: none;
            }

            /* Vehicle-details window becomes the same bottom sheet as the
               departure board. Its desktop anchor (top:100% off the
               right-panel stack) lands off-screen on narrow viewports.
               position:fixed (not absolute) because the element lives
               INSIDE #right-panel — fixed escapes that containing block
               and pins to the viewport bottom like #departure-panel,
               which is a direct child of <body>. */
            #vehicle-panel {
                position: fixed;
                top: auto;
                left: 0;
                right: 0;
                bottom: 0;
                width: auto;
                max-width: none;
                max-height: 60vh;
                margin-top: 0;
                border-radius: 14px 14px 0 0;
                border-bottom: none;
                z-index: 1001;   /* above map chrome, same tier as departures */
            }

            /* Tool dock: layoutTools() moves Shapes/Stops/Measure/Flights in
               beside the More button — one opaque five-slot bar pinned to
               the bottom edge. The desktop rows are empty here; hide them. */
            #tool-buttons {
                left: 8px;
                right: 8px;
                bottom: 10px;
                flex-direction: row;
            }

            #tool-row-gtfs, #tool-row-app { display: none; }

            #tool-dock {
                display: grid;
                grid-template-columns: repeat(5, 1fr);
                flex: 1;
                background: var(--surface-opaque);
                border: 1px solid var(--panel-border);
                border-radius: 14px;
                padding: 4px;
            }

            /* Dock/sheet buttons: icon above a small label, flat inside the
               shared panel (active-state colours still apply via the
               .*-active rules, which carry !important). */
            #tool-dock .tile-btn,
            #tool-sheet-body .tile-btn {
                flex-direction: column;
                justify-content: center;
                gap: 3px;
                padding: 7px 2px;
                font-size: 9.5px;
                background: none;
                border: none;
            }

            /* More sheet — same bottom-sheet pattern as the departure board */
            #tool-sheet {
                display: block;
                position: fixed;
                left: 0;
                right: 0;
                bottom: 0;
                z-index: 1300;
                background: var(--surface-opaque);
                border-top: 1px solid var(--panel-border);
                border-radius: 14px 14px 0 0;
                padding: 10px 14px calc(16px + env(safe-area-inset-bottom, 0px));
                transform: translateY(105%);
                transition: transform 0.28s cubic-bezier(0.32, 0.72, 0.22, 1);
            }
            #tool-sheet.open { transform: none; }

            #tool-sheet-backdrop {
                display: block;
                position: fixed;
                inset: 0;
                background: rgba(5, 8, 20, 0.4);
                opacity: 0;
                pointer-events: none;
                transition: opacity 0.25s;
                z-index: 1250;
            }
            #tool-sheet-backdrop.open { opacity: 1; pointer-events: auto; }

            .tool-sheet-grab {
                width: 34px;
                height: 4px;
                border-radius: 2px;
                background: var(--panel-border);
                margin: 0 auto 10px;
            }
            .tool-sheet-heading {
                font-size: 10px;
                font-weight: 700;
                letter-spacing: 0.08em;
                text-transform: uppercase;
                color: var(--text-dim);
                margin-bottom: 6px;
            }
            #tool-sheet-body {
                display: grid;
                grid-template-columns: repeat(5, 1fr);
                gap: 2px;
            }

            /* Style popover opens above the dock, beside the control column */
            #map-controls,
            body[data-theme="light"] #map-controls {
                right: 50px;
                bottom: 72px;
            }

            /* Mapbox native controls: compass with the layers button below
               it, as a column raised above the dock; scale and attribution
               stay visible beneath them (Mapbox ToS). */
            .mapboxgl-ctrl-bottom-right {
                bottom: 64px;
                display: flex;
                flex-direction: column;
                align-items: flex-end;
            }

            /* Element+class needed to outrank mapbox-gl.css's
               `.mapboxgl-ctrl-group button { display: block }` */
            .mapboxgl-ctrl button.mapboxgl-ctrl-zoom-in,
            .mapboxgl-ctrl button.mapboxgl-ctrl-zoom-out {
                display: none;
            }

            /* Compass matches the app's panel chrome, not Mapbox white —
               themed via the token layer (light values flip with the
               data-theme attribute). */
            .mapboxgl-ctrl-bottom-right .mapboxgl-ctrl-group {
                background: var(--panel-bg);
                border: 1px solid var(--panel-border);
                box-shadow: var(--shadow-control);
                border-radius: var(--radius-sm);
            }

            /* The hidden zoom buttons still count as siblings, so kill
               mapbox-gl.css's `button + button { border-top }` divider
               that would otherwise streak across the compass */
            .mapboxgl-ctrl-bottom-right .mapboxgl-ctrl-group button {
                border-top: none;
            }

            /* Layers button icon tracks the themed control background here
               (this block themes the bottom-right controls; desktop keeps
               Mapbox's white control + dark icon, so this is mobile-only).
               The icon is an inline SVG using stroke:currentColor; --text
               flips with the theme — near-white on the dark control in dark
               mode, dark on the light control in light mode. Without this it
               inherited a fixed dark colour and vanished in dark mode. The
               open state still wins via #style-toggle-btn[aria-expanded]. */
            #style-toggle-btn { color: var(--text); }

            /* Compass-rose icon (ring, cardinal ticks, N, red north
               needle) replacing Mapbox's plain grey arrow. Mapbox rotates
               the icon span inline with map bearing, so the rose tracks
               north automatically. Selector mirrors mapbox-gl.css's own
               (.mapboxgl-ctrl button.mapboxgl-ctrl-compass .mapboxgl-ctrl-icon)
               to outrank it. */
            .mapboxgl-ctrl button.mapboxgl-ctrl-compass .mapboxgl-ctrl-icon {
                background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" fill="none" stroke="%2394a3b8" stroke-width="1.3"/><line x1="12" y1="20.6" x2="12" y2="22" stroke="%2394a3b8" stroke-width="1.2"/><line x1="1.4" y1="12" x2="2.8" y2="12" stroke="%2394a3b8" stroke-width="1.2"/><line x1="21.2" y1="12" x2="22.6" y2="12" stroke="%2394a3b8" stroke-width="1.2"/><text x="12" y="7.6" text-anchor="middle" font-family="-apple-system,Segoe UI,sans-serif" font-size="6" font-weight="700" fill="%23e2e8f0">N</text><polygon points="12,8.8 14.1,13.5 12,12.6 9.9,13.5" fill="%23ef4444"/><polygon points="12,18.2 9.9,13.5 12,14.4 14.1,13.5" fill="%23e2e8f0"/></svg>');
            }
            body[data-theme="light"] .mapboxgl-ctrl button.mapboxgl-ctrl-compass .mapboxgl-ctrl-icon {
                background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" fill="none" stroke="%2364748b" stroke-width="1.3"/><line x1="12" y1="20.6" x2="12" y2="22" stroke="%2364748b" stroke-width="1.2"/><line x1="1.4" y1="12" x2="2.8" y2="12" stroke="%2364748b" stroke-width="1.2"/><line x1="21.2" y1="12" x2="22.6" y2="12" stroke="%2364748b" stroke-width="1.2"/><text x="12" y="7.6" text-anchor="middle" font-family="-apple-system,Segoe UI,sans-serif" font-size="6" font-weight="700" fill="%23334155">N</text><polygon points="12,8.8 14.1,13.5 12,12.6 9.9,13.5" fill="%23ef4444"/><polygon points="12,18.2 9.9,13.5 12,14.4 14.1,13.5" fill="%2364748b"/></svg>');
            }

            /* Slim, panel-toned scale so it reads as chrome, not map
               content. Pinned to the bottom-left (out of the right control
               column) and lifted clear of the tool dock — dock top sits
               ~66px from the bottom, so 72px keeps a small gap. position:
               fixed anchors to the viewport, matching the sheet/nav
               overlays; it drops the ruler out of the column so compass
               and layers close up. */
            .mapboxgl-ctrl-bottom-right .mapboxgl-ctrl-scale {
                background: rgba(20, 20, 45, 0.75);
                color: #cbd5e1;
                border-color: rgba(203, 213, 225, 0.5);
                font-size: 10px;
                padding: 1px 6px;
                position: fixed;
                left: calc(10px + env(safe-area-inset-left, 0px));
                bottom: calc(72px + env(safe-area-inset-bottom, 0px));
            }
            body[data-theme="light"] .mapboxgl-ctrl-bottom-right .mapboxgl-ctrl-scale {
                background: rgba(255, 255, 255, 0.75);
                color: #334155;
                border-color: rgba(51, 65, 85, 0.5);
            }

            /* Notched phones: keep the floating chrome clear of the home
               indicator, notch, and rounded corners. env() resolves to 0
               on plain screens; viewport-fit=cover in mapbox.html is what
               activates the insets on iOS. In a browser without env()
               each declaration is simply invalid and the earlier plain
               values above stand. */
            #search-box {
                left: calc(10px + env(safe-area-inset-left, 0px));
                right: calc(10px + env(safe-area-inset-right, 0px));
            }

            #right-panel {
                left: calc(10px + env(safe-area-inset-left, 0px));
            }

            #map-controls,
            body[data-theme="light"] #map-controls {
                right: calc(50px + env(safe-area-inset-right, 0px));
                bottom: calc(72px + env(safe-area-inset-bottom, 0px));
            }

            #status {
                left: calc(10px + env(safe-area-inset-left, 0px));
                bottom: calc(10px + env(safe-area-inset-bottom, 0px));
            }

            #tool-buttons {
                left: calc(8px + env(safe-area-inset-left, 0px));
                right: calc(8px + env(safe-area-inset-right, 0px));
                bottom: calc(10px + env(safe-area-inset-bottom, 0px));
            }

            #history-panel {
                left: calc(10px + env(safe-area-inset-left, 0px));
                bottom: calc(130px + env(safe-area-inset-bottom, 0px));
            }

            #departure-panel {
                padding-bottom: env(safe-area-inset-bottom, 0px);
            }

            /* Same home-indicator clearance for the vehicle bottom sheet;
               added to the existing 10px so content never sits under the
               indicator. */
            #vehicle-panel {
                padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
            }

            .mapboxgl-ctrl-bottom-right {
                bottom: calc(64px + env(safe-area-inset-bottom, 0px));
            }
        }

        /* Vestibular safety: collapse UI transitions when the user asks
           for reduced motion. The marker glide makes the same check in
           JS (map-core.js, prefers-reduced-motion) — this covers the
           CSS transitions on panels, buttons, and hovers. */
        @media (prefers-reduced-motion: reduce) {
            *, *::before, *::after {
                transition-duration: 0.01ms !important;
                animation-duration: 0.01ms !important;
            }
        }
