    /* ── Type: Public Sans, self-hosted (P2.22 step 1) ──────────────── */
    /* Variable weight (100-900), latin subset — covers Irish names'
       á/é/í/ó/ú (U+0000-00FF, verified against the file's own cmap).
       font-display: swap avoids an invisible-text flash on a slow link;
       system-ui shows until the font arrives, same as design.md asks. */
    @font-face {
      font-family: 'Public Sans Variable';
      font-style: normal;
      font-display: swap;
      font-weight: 100 900;
      /* No ?v= here: unlike app.html, a CSS file is served as a plain
         /static/* file (web/app.py's static handler), never through
         render_template — a literal __ASSET_VER__ placeholder would reach
         the browser unsubstituted. The font is a new file, so the first
         fetch is a cache miss regardless; a future font swap would need a
         new filename (or a cache-bust query added by hand) to beat the
         one-day /static/* cache-control. */
      src: url('/static/fonts/public-sans-latin-wght-normal.woff2') format('woff2-variations');
    }

    * { box-sizing: border-box; margin: 0; padding: 0; }
    html, body { height: 100%; }
    body {
      font-family: 'Public Sans Variable', 'Public Sans', system-ui, -apple-system, sans-serif;
      background: var(--page); color: var(--ink); overflow: hidden;
    }
    /* Times use tabular numerals so a countdown ticking down doesn't jump
       (docs/design.md §Type). */
    .pill-time, .tl-time, .veh-stop-time,
    .stop-row-time, #depart-time-input {
      font-variant-numeric: tabular-nums;
    }

    /* The navbar/panel shell (nav, #sidebar's positioning, #map's own
       position, map controls) and the stop search/header/rows moved to
       shell.css and stop-board.css in P2.22 step 2 — see those files'
       headers; both are new 600px-breakpoint builds, and this file was
       already past docs/architecture.md's 800-line must-split threshold. */

    /* The planner's own search form and results — .input-layout,
       .stop-field/.stop-input (the From/To fields), .dropdown, .when-pill,
       #swap-btn, #plan-btn, .results-header, .route-pill/.pill-*, .tl-*
       (the timeline) — moved to plan-board.css in P2.22 step 3, redrawn
       from literal hex to docs/design.md's Search/Planner-results
       components (this file's P2.22 step 1 "As built" flagged this exact
       block as pending that step). .plan-bus-icon/.plan-bus-pulse (the
       live-tracking bus's pulse halo) moved there too — plan-only, like
       the rest of this block. */

    /* ── Live bus marker (plan) ───────────────────────────────────── */
    .bus-marker { position: relative; width: 22px; height: 22px; }
    .bus-dot  { position: absolute; inset: 4px; border-radius: 50%; background: var(--bus-c); border: 2px solid #fff; box-shadow: 0 1px 5px rgba(0,0,0,.45); }
    .bus-ring { position: absolute; inset: 0; border-radius: 50%; background: var(--bus-c); opacity: .35; animation: bus-ring 1.6s ease-out infinite; }
    @keyframes bus-ring { 0% { transform: scale(.55); opacity: .5; } 100% { transform: scale(1.7); opacity: 0; } }

    /* ── Empty-state message (shared: stop/plan/vehicle-sheet/favs) ──
       .skeleton/.skel-pill (plan's own loading placeholder) moved to
       plan-board.css with the rest of that view, P2.22 step 3; .empty-msg
       stays here, used by every mode's own error/empty states. */
    .empty-msg { text-align: center; padding: 24px 10px; color: var(--ink-muted); font-size: 12px; line-height: 1.6; }
    .empty-msg strong { color: var(--ink-secondary); display: block; margin-bottom: 4px; font-size: 13px; }


    /* The bus sheet itself (header, stop rows, Track and close) moved to
       bus-sheet.css in P2.22 step 4. Only the status chips stay here: the
       header's delay chip shares the cascade css_contrast.test.mjs checks. */
    .veh-chip {
      display: inline-block; font-size: 11px; font-weight: 700;
      padding: 2px 8px; border-radius: var(--radius-pill); white-space: nowrap; flex-shrink: 0;
      background: rgba(255,255,255,.18); color: #fff;
    }
    .veh-chip.late  { background: var(--chip-late-bg); color: var(--chip-late-ink); }
    .veh-chip.early { background: var(--chip-ok-bg); color: var(--chip-ok-ink); }
    .veh-chip.ontime { background: var(--chip-ok-bg); color: var(--chip-ok-ink); }

    /* .plan-bus-icon/.plan-bus-pulse (the live-tracking bus's pulse halo)
       moved to plan-board.css, P2.22 step 3 — plan-only. */

    /* ── Toast (bottom-center) ───────────────────────────────────── */
    #toast-host {
      position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
      z-index: 2000; pointer-events: none;
      display: flex; flex-direction: column; align-items: center; gap: 8px;
    }
    .toast {
      background: rgba(20,24,30,.92); color: #fff;
      padding: 10px 18px; border-radius: 22px; font-size: 13px;
      box-shadow: 0 4px 16px rgba(0,0,0,.3);
      opacity: 0; transform: translateY(10px);
      transition: opacity .25s ease, transform .25s ease;
      pointer-events: auto;
    }
    .toast.show { opacity: 1; transform: translateY(0); }
    .toast b { font-weight: 700; color: #fff; }

    /* ── Dim background layers while plan results are showing ────── */
    /* (handled via setStyle/setOpacity in JS, no CSS rule needed)   */

    /* ── Stop markers (small dot inside a generous click target) ──── */
    .stop-marker {
      background: transparent !important; border: none !important;
      cursor: pointer;
    }
    .stop-marker .stop-dot {
      position: absolute; left: 50%; top: 50%;
      width: 7px; height: 7px; border-radius: 50%;
      background: var(--stop-dot); opacity: 1;   /* design.md: 7px, stop-dot (was a 5px green) */
      transform: translate(-50%, -50%);
      transition: opacity .15s, transform .15s;
    }
    .stop-marker:hover .stop-dot {
      opacity: 1; transform: translate(-50%, -50%) scale(1.6);
    }
    /* In stop view, other stop dots stay visible but are not interactive. */
    .stop-marker.disabled { pointer-events: none; cursor: default; }
    .stop-marker.disabled:hover .stop-dot { opacity: 1; transform: translate(-50%, -50%); }

    .star-toggle {
      background: none; border: 1.5px solid rgba(255,255,255,.35);
      color: #fff; cursor: pointer; font-size: 14px; line-height: 1;
      padding: 4px 8px; border-radius: 6px; transition: all .15s;
    }
    .star-toggle:hover { background: rgba(255,255,255,.15); }
    .star-toggle.on    { background: var(--accent); color: #78350f; border-color: var(--accent); }
    /* .star-toggle-light/.share-btn (the old plan results header's save/
       share buttons) removed P2.22 step 3 — the header is now .plan-head's
       own .plan-icon-btn (plan-board.css), with no separate share control
       (that step's "As built" says why). */

    /* ── Map context menu (long-press / right-click) ─────────────── */
    #map-ctx {
      position: fixed; z-index: 1500; display: none;
      background: var(--surface); border-radius: 8px; min-width: 180px;
      box-shadow: 0 6px 24px rgba(0,0,0,.22);
      overflow: hidden;
    }
    #map-ctx .ctx-header {
      padding: 8px 12px; font-size: 11px; color: var(--ink-muted);
      background: var(--page); border-bottom: 1px solid var(--line);
      white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
      max-width: 240px;
    }
    #map-ctx .ctx-header b { color: var(--ink); font-weight: 600; }
    #map-ctx button {
      display: block; width: 100%; padding: 10px 14px; text-align: left;
      background: var(--surface); border: none; cursor: pointer; font-size: 13px;
      color: var(--ink); transition: background .12s;
    }
    #map-ctx button:hover { background: var(--surface-raised); }
    #map-ctx button.disabled { color: var(--ink-muted); cursor: default; }
    #map-ctx button.disabled:hover { background: var(--surface); }

    /* ── Dark mode overrides ─────────────────────────────────────── */
    /* Most of this block is gone (P2.22 step 1): once a rule above reads a
       var(--token), body.dark's redefinition of that same token (tokens.css)
       already gives the right value, with no selector-specific override
       needed. What's left is either (a) a value no token covers (an rgba
       tint, a decorative line, a planner control still on its own literal
       colours pending step 3), or (b) a genuine exception: navy reads fine
       as a *background* in both themes, but not as a hover/focus
       *foreground* sitting directly on a surface that turns dark — those
       keep their own lighter-blue override, as they already did. */
    /* The planner's own dark-mode overrides moved with the rest of that
       view to plan-board.css (P2.22 step 3) — nearly all of them simply
       don't exist any more: every rule there reads a var(--token) that
       already resolves correctly under body.dark (tokens.css), the same
       convergence step 1 describes above for this file. The two genuine
       exceptions (the "awaiting a map tap" amber tint, the segmented
       switch's active-state blue) kept their own body.dark rule there,
       next to the selector they override. */


    /* Leaflet ships its own light-mode popup/attribution styling (not ours
       to tokenise); dark only overrides what it hardcodes. */
    body.dark .leaflet-popup-content-wrapper,
    body.dark .leaflet-popup-tip {
      background: #25272f; color: #e6e8eb; box-shadow: 0 3px 14px rgba(0,0,0,.6);
    }
    body.dark .leaflet-popup-content a { color: #93c5fd; }
    body.dark .leaflet-container .leaflet-control-attribution {
      background: rgba(28,30,36,.85); color: #c6cad0;
    }
    body.dark .leaflet-container .leaflet-control-attribution a { color: #93c5fd; }
    body.dark .leaflet-bar a, body.dark .leaflet-bar a:hover {
      background-color: #25272f; color: #e6e8eb; border-bottom-color: #34373f;
    }
    body.dark #map-ctx { box-shadow: 0 6px 24px rgba(0,0,0,.6); }   /* a deeper dark-mode shadow; background is var(--surface) */

    /* The navbar/sidebar mobile layout (bottom sheet, grab handle, nav-pill
       sizing) moved to shell.css's mobile-first rules in P2.22 step 2 —
       see that file's header. */

    /* Respect users who prefer reduced motion: kill the pulsing/looping
       animations (live dots, bus rings, skeleton shimmer) but keep the
       short slide/transition affordances. */
    @media (prefers-reduced-motion: reduce) {
      .live-dot, .due-dot, .bus-ring, #stale-pill .dot,
      .skeleton, .sk-block, .pill-live .live-dot {
        animation: none !important;
      }
    }

/* ── Ride mode ──────────────────────────────────────────────────────────────
   The ride sheet and the pill that brings it back are ride-sheet.css. */
/* The location-sharing consent dialog (the ride sheet itself is ride-sheet.css) */
.ride-modal {
  position: fixed; inset: 0; z-index: 1800;
  background: rgba(0, 0, 0, .55);
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
}
.ride-modal.hidden { display: none; }
.ride-modal-body {
  background: var(--surface); border-radius: 14px;
  max-width: 420px; width: 100%;
  max-height: 80vh; overflow-y: auto;
  padding: 18px 20px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, .35);
}
.ride-modal-body h3 {
  font-size: 16px; font-weight: 700; color: var(--ink); margin: 0;
}
.ride-modal-body p {
  font-size: 13px; line-height: 1.5; color: var(--ink-secondary); margin: 8px 0 14px;
}
.ride-modal-actions {
  display: flex; flex-direction: column; gap: 8px; margin-top: 14px;
}
.ride-modal-actions button {
  padding: 11px 14px; border: none; border-radius: var(--radius-button);
  font-size: 14px; font-weight: 700; cursor: pointer;
  transition: background .15s;
}
.ride-consent-yes {
  background: var(--due-early); color: #fff;
}
.ride-consent-yes:hover { background: #086a4f; }
.ride-consent-no {
  background: var(--page); color: var(--ink);
}
.ride-consent-no:hover { background: #e2e5ea; }

/* Dark variants */
/* Most of this section's dark pairing is now tokenised (var(--surface),
   var(--ink) and friends already flip) — only the hover shades below
   (no token for a hover state) still need their own dark value. */
body.dark .ride-consent-no:hover { background: #3d4049; }
