/* The app's shell (P2.22 step 2): the navigation, the panel/sheet wrapper,
   the map's own position, and Leaflet's zoom/locate controls — everything
   docs/design.md's "Layout" table governs. Split out of unified.css in the
   same commit (docs/architecture.md: unified.css was already past the
   800-line must-split threshold before this step added anything).

   Mobile-first: the rules below with no media query are the "under 600 px"
   column (phone — the map full screen, a floating pill, a bottom sheet);
   `@media (min-width: 600px)` is the "600 px and wider" column (a fixed
   ~400 px left panel). One breakpoint, matching docs/design.md's own
   table — every other file in this app that cared about a breakpoint used
   640px; this step moves all of them (map.js's _centerForSheet, ui.js's
   sheet-drag isMobile()) to the same 600px, named here once. */

/* ── The map ──────────────────────────────────────────────────────────────
   Phone: full screen — the navigation and the board float/sheet over it.
   Panel: starts right of the fixed-width panel, fills the rest. */
#map { position: fixed; inset: 0; z-index: 1; }

/* ── #panel: a plain wrapper, inert on phones ───────────────────────────
   On phones it has no layout rules of its own — #navbar and #sidebar stay
   position:fixed (viewport-relative; a static ancestor with no transform
   doesn't break that) and float independently over the map. From 600px it
   becomes the actual fixed, sized panel, and #navbar/#sidebar turn into
   plain static flex children stacked inside it — this is what lets one
   markup serve both the floating-pill-over-sheet layout and the
   nav-card-over-board panel without a JS-driven layout switch. */
#panel { display: block; }

@media (min-width: 600px) {
  /* Both #panel and #map read the same clamp() — #panel's own width and
     the left edge Leaflet's #map div (and so its own internal coordinate
     system, tile grid and control corners) starts from — but only once
     #sidebar actually has something open (stop/plan/favourites, or a
     tapped bus). The *default* (no #sidebar.open — map mode, including
     the very first paint, since the implicit map-mode load with no URL
     params never calls setMode()) keeps #map full width and shrinks
     #panel to just the floating navy nav card: until step 4 ("Map and
     list") gives this panel real content, reserving a permanent ~400px
     blank white rectangle under the card would cost every desktop/tablet
     rider a third of their map for nothing (review round 1, finding #3).
     Keyed off #panel's own child via :has(), not a JS-toggled class, so
     it's correct before any JS has run; ui.js's _sidebarOpen() still
     calls map.invalidateSize() on every #sidebar.open toggle, since a
     CSS width change alone doesn't fire Leaflet's own resize listener —
     without it the tile grid and the zoom/locate controls stay sized for
     whichever width was current when the map was first measured. */
  #map { left: 0; }
  #panel {
    position: fixed; top: 0; left: 0; bottom: 0;
    width: auto;
    display: flex; flex-direction: column;
    background: transparent;
    box-shadow: none;
    z-index: 1000;
    overflow: hidden;
  }
  #panel:has(#sidebar.open) {
    width: clamp(360px, 26vw, 420px);
    background: var(--surface);
    box-shadow: var(--shadow-panel);
  }
  /* P2.22h: in map mode #panel is a transparent, full-height column (kept
     that tall so #nav-dropdown has room under overflow:hidden), and it sat
     over the map swallowing drags, wheel and taps. Let the mouse through
     the empty column; the card and its dropdown (children) keep it. With a
     panel open the :not(:has()) no longer matches and #panel is solid. */
  #panel:not(:has(#sidebar.open)) { pointer-events: none; }
  #panel:not(:has(#sidebar.open)) > * { pointer-events: auto; }
  /* #map isn't inside #panel (it comes first in the DOM, so a sibling
     combinator can't reach it from #panel either way) — keyed off body
     instead, same #sidebar.open condition. */
  body:has(#sidebar.open) #map { left: clamp(360px, 26vw, 420px); }
}

/* ── Navigation ───────────────────────────────────────────────────────────
   Phone: a floating navy pill, 12px from the top, inset 16px each side.
   Panel: the top of the panel — a navy card (mark, title, tabs) on a plain
   white strip, so it reads as the panel's own header rather than a second
   floating element glued above it. */
#navbar {
  position: fixed; top: 12px; left: 16px; right: 16px;
  background: var(--navy);
  border-radius: var(--radius-pill);
  padding: 4px;
  z-index: 1000;
}
.nav-card { display: flex; align-items: center; gap: 8px; }
.nav-brand { display: flex; align-items: center; gap: 8px; flex-shrink: 0; margin-left: 4px; }
.nav-mark { width: 28px; height: 28px; border-radius: 6px; flex-shrink: 0; }
.nav-title { display: block; margin: 0; color: #fff; font-size: 14px; font-weight: 800; }   /* P5.9b: the name beside the tabs on phones too */
/* P5.9f: the small Offline pill is gone; offline is a state of the load banner (load-error.js), so the header never changes. */
/* Theme toggle + the stale-feed pill: not drawn on the canvas's nav (it
   shows only the mark and the four tabs), but dark mode and the staleness
   notice are both still load-bearing (dark mode is in the browser tier's
   checklist at every viewport) — kept reachable as a small trailing
   cluster rather than invented a new home mid-step. Neither is in the
   44px GATE list (map controls / nav tabs / stop header buttons /
   departure rows) — same INFO-only treatment step 1 already reported them
   under. */
.nav-utility { display: flex; align-items: center; gap: 2px; margin-left: auto; flex-shrink: 0; }
.nav-tabs { display: flex; gap: 4px; flex: 1; min-width: 0; }

.nav-tab, .nav-tab.active {
  flex: 1; text-align: center;
  font-size: 12.5px; font-weight: 600;
  border: none; border-radius: var(--radius-pill);
  min-height: 44px; display: flex; align-items: center; justify-content: center;
  cursor: pointer; background: transparent; color: var(--nav-tab-ink);
  transition: background .15s, color .15s;
}
.nav-tab.active { font-weight: 700; color: var(--navy); background: #fff; }
.nav-tab:hover:not(.active) { background: rgba(255,255,255,.12); color: #fff; }
/* PC-09: at 360 px the active "Saved" pill was exactly its label's width (44 px, nothing either side), so the
   pill's round ends cut the label; Map, Stop and Plan were 42 px. Under 380 px tighter gaps and a 5 px side padding
   give 54 px for Saved and 44.7 px for the others at 360. Under 360 px (a 344 px fold phone, a 320 px SE) that no
   longer fits beside the name, so the name goes there; screen readers still read the h1. */
@media (max-width: 379px) {
  .nav-card { gap: 4px; }
  .nav-brand { gap: 6px; margin-left: 0; }
  .nav-tabs { gap: 2px; }
  .nav-tab, .nav-tab.active { padding: 0 5px; }
}
@media (max-width: 359px) {
  .nav-title { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
}

@media (min-width: 600px) {
  #navbar {
    position: static; background: transparent; box-shadow: none; border-radius: 0;
    /* Map mode (no panel): 16px in, top and left only. Panel mode
       (below): the usual 14px panel gutter on every side but the
       bottom. Either way #navbar itself still just wraps .nav-card at
       that card's own width (below) — #panel's width:auto in map mode
       no longer has anything to do with the card's size (review round
       1's second finding: it used to, and the card collapsed to its
       own min-content, squashing "Map" into a circle and running
       "Stop Plan Saved" together with no pill spacing). */
    padding: 16px 0 0 16px; flex-shrink: 0;
  }
  .nav-card {
    flex-direction: column; align-items: stretch; gap: 10px;
    background: var(--navy); border-radius: var(--radius-card); padding: 14px;
    /* Review round 1, finding #2: in map mode nothing upstream of here
       gives the card a real width any more (#panel is width:auto, see
       above), so without this it shrinks to its own min-content — "Map"
       becomes a tiny circle, the other three tabs run together with no
       pill spacing. Match the panel's own content width exactly: the
       same clamp() #panel/#map use, minus the 14px+14px #navbar side
       padding panel mode gives it (body:has(#sidebar.open) below reverts
       to auto, where panel mode's own stretch-to-#panel's-width already
       produces this identical number — this is only needed as an
       explicit value where that stretch has nothing to size against). */
    width: calc(clamp(360px, 26vw, 420px) - 32px);
  }
  /* The card does not move or resize between modes (owner, review r1): the
     same 16px inset and the same explicit width with the panel open or shut. */
  /* P2.22g: the navigation has no shadow at any width or state. */
  .nav-brand { margin-left: 0; }
  .nav-mark { width: 26px; height: 26px; }
  .nav-title { display: block; color: #fff; font-size: 15px; font-weight: 800; }
  .nav-tabs { background: rgba(255,255,255,.1); border-radius: var(--radius-pill); padding: 4px; }
}

/* The quick nav-search dropdown: superseded, for now, by the Stop tab's own
   search (#stop-search) — there's no room for it in the exact floating
   pill the canvas draws (mark + tabs only), and the full-screen Search view
   that would give it a proper home is step 6. Elements kept in the DOM
   (ui.js's setupNavSearch() wires them unconditionally at load time; an
   absent #nav-search would throw there, taking down every other module
   that imports ui.js) — just never shown. */
#nav-search-wrap { display: none; }
#nav-dropdown {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0;
  background: var(--surface); border: 1.5px solid var(--line); border-radius: 8px;
  max-height: 280px; overflow-y: auto; display: none;
  box-shadow: 0 6px 20px rgba(0,0,0,.2); z-index: 1100;
}
.nav-drop-item {
  padding: 9px 12px; cursor: pointer; font-size: 13px; color: var(--ink);
  display: flex; justify-content: space-between; align-items: center;
  border-bottom: 1px solid var(--line);
}
.nav-drop-item:last-child { border-bottom: none; }
.nav-drop-item:hover, .nav-drop-item.active { background: var(--surface-raised); }
.nav-drop-item .nm { font-weight: 500; }
.nav-drop-item .cd { font-size: 11px; color: var(--ink-muted); background: var(--surface-raised); padding: 2px 6px; border-radius: 4px; white-space: nowrap; }
#nav-search-clear {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  width: 20px; height: 20px; border-radius: 50%; border: none;
  background: rgba(255,255,255,.15); color: #cbd5e1;
  font-size: 14px; line-height: 1; cursor: pointer;
  display: none; padding: 0;
}

/* Dark-mode toggle (the stale-feed pill that sat beside it went in P5.9f). */
/* P5.9c fix: one button at every width. P5.9x (PB-12): the button itself is 44 x 44 px; the 32 px ring
   (drawn in the icon's SVG) sits at its centre and the hover wash is a 32 px ::before circle. */
#theme-toggle {
  position: relative; box-sizing: border-box;
  width: 44px; height: 44px; padding: 0; flex-shrink: 0; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; color: var(--nav-tab-ink);
  border: 0; border-radius: 50%;   /* the ring is a circle in the icon's own SVG (theme.js) */
  transition: color .15s;
}
#theme-toggle::before { content: ""; position: absolute; top: 50%; left: 50%; width: 32px; height: 32px; margin: -16px 0 0 -16px; border-radius: 50%; transition: background .15s; }
#theme-toggle svg { position: relative; display: block; overflow: visible; }   /* the ring reaches the box edge: never clip its antialiased rim at a fractional position */
#theme-toggle:hover { color: #fff; }
#theme-toggle:hover::before { background: rgba(255,255,255,.1); }
body.dark #theme-toggle:hover::before { background: rgba(255,255,255,.05); }
#theme-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── The board: a bottom sheet on phones, the rest of the panel from
   600px ──────────────────────────────────────────────────────────────── */
#sidebar {
  position: fixed; left: 0; right: 0; bottom: 0; top: auto;
  width: 100%; max-width: 100%;
  height: 45vh; max-height: 92vh;
  background: var(--surface); color: var(--ink);
  border-radius: var(--radius-sheet) var(--radius-sheet) 0 0;
  box-shadow: var(--shadow-sheet);
  transform: translateY(100%);
  transition: transform .25s ease;
  z-index: 1000;
  display: flex; flex-direction: column; overflow: hidden;
}
#sidebar.open { transform: translateY(0); }
/* The map-overlay is never shown: the sheet leaves the map visible above
   it and it stays fully pannable/zoomable — dismissal is the handle, the
   back arrow or dragging down, not a tap-anywhere scrim. */
#map-overlay { display: none !important; }

/* A real 44px button (design.md: "a 40×4 px handle inside a 44 px
   button"), not the old decorative ::before — tappable for keyboard/click
   users, and still the touch-drag start zone ui.js's setupSheetDrag()
   already detects by touch Y position, unchanged. */
.sheet-handle {
  display: flex; align-items: center; justify-content: center;
  width: 100%; height: 44px; flex-shrink: 0;
  border: none; background: transparent; cursor: pointer; padding: 0;
}
.sheet-handle span {
  display: block; width: 40px; height: 4px; border-radius: 2px;
  background: rgba(0,0,0,.18);
}
body.dark .sheet-handle span { background: rgba(255,255,255,.22); }

/* P5.9q: on a phone the trip view has no pull tab and no band above its header: the coloured
   header is the sheet's top (the sheet's 24px corners clip it). The header's top 44px stay the
   touch-drag zone (ui.js reads them) and the handle stays in the page for keyboards and screen
   readers: visually hidden, but focusable and still the expand/collapse button; focused, it shows
   as a 44px square at the header's centre, clear of Track and close. Keyed on the header being in the sheet, so any other view keeps its tab. */
@media (max-width: 599px) {
  #sidebar.vehicle-open:has(.veh-header) .sheet-handle {
    position: absolute; top: 0; left: 0; z-index: 2; width: 1px; height: 1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  #sidebar.vehicle-open:has(.veh-header) .sheet-handle:focus-visible {
    left: 50%; margin-left: -22px; width: 44px; height: 44px; clip-path: none;   /* centred: clear of Track and close */
  }
}

.sb-header {
  padding: 12px 14px; border-bottom: 1px solid var(--line);
  display: flex; align-items: center; justify-content: space-between;
  background: var(--surface); flex-shrink: 0;
}
.sb-title { font-size: 14px; font-weight: 700; color: var(--ink); }
.sb-close {
  background: none; border: none; cursor: pointer; color: var(--ink-muted);
  font-size: 20px; padding: 0 var(--space-8); line-height: 1; border-radius: 5px;
}
.sb-close:hover { background: var(--surface-raised); color: var(--ink); }
.sb-body { flex: 1; padding: var(--sb-gutter); overflow-y: auto; }

/* The vehicle popup and (new) the stop board replace the generic header
   with their own — same pattern, two consumers. */
.sb-body.vehicle-mode { display: flex; flex-direction: column; padding: 0; overflow: hidden; }
.sb-body.vehicle-mode .veh-header { margin: 0; flex-shrink: 0; }
.sb-body.vehicle-mode .veh-stops-scroll { flex: 1; overflow-y: auto; padding: 8px 8px 16px; }
#sidebar.vehicle-open .sb-header { display: none; }
/* From 600px the bus sheet leaves the same 16px gap under the navigation card
   as the stop board (the Bus_* boards), instead of sitting flush. */
/* From 600px the tapped bus keeps the panel's gutter like every other view
   (owner, 2026-10-06): the route-coloured header spans gutter to gutter
   exactly as the stop header does (its own 16px padding puts the text on the
   shared content line), and the stop rows' 8px padding does the same. */
@media (min-width: 600px) { .sb-body.vehicle-mode { margin-top: 16px; padding: 0 var(--sb-gutter); } }
#sidebar.stop-board-open .sb-header { display: none; }

@media (min-width: 600px) {
  /* #sidebar without .open: collapsed, not just off-screen. On phones the
     base rule's transform:translateY(100%) already hides a closed sheet;
     naively resetting that to transform:none here (as an earlier draft of
     this rule did) made the panel permanently show whatever #sb-body last
     held — "Map" mode calls _sidebarOpen(false) (ui.js) and never touches
     #sb-body again, so the panel was left showing a stale "Stop
     departures" header with nothing under it, found by screenshotting
     this step's own 844x390/map run. #navbar (the nav card) stays outside
     this rule and is always visible, matching every canvas board — only
     the *board* collapses with the map, not the nav. This same .open
     state (via :has(), above) also decides whether #panel/#map are in
     map mode (no board at all — full-width map, a floating nav card) or
     panel mode (a board is open — the fixed-width panel and the map
     offset) — round 1's fix for the panel's own blank space in map mode. */
  #sidebar:not(.open) { display: none; }
  #sidebar.open {
    position: static; width: auto; max-width: none;
    height: auto; max-height: none;
    transform: none; transition: none;
    box-shadow: none; border-radius: 0;
    flex: 1; min-height: 0;
  }
  .sheet-handle { display: none; }   /* no handle in the panel layout */
}

/* ── Map controls: zoom and locate, both 44×44 ───────────────────────────
   Phone: zoom top-left 16px in, just under the nav pill; locate top-right,
   mirroring it. Panel: zoom top-left of the *map* 24px in; locate moves to
   the map's bottom-right 24px in. Real Leaflet controls, restyled and
   repositioned via their own corner containers (map.js/live.js create
   them; nothing else here uses the top-right corner, so moving that whole
   container at 600px is safe — docs/testing.md's worktree-serving note
   doesn't apply to pure CSS). */
.leaflet-top.leaflet-left,
.leaflet-top.leaflet-right {
  top: 68px;
}
.leaflet-top.leaflet-left { left: 16px; }
.leaflet-top.leaflet-right { right: 16px; }
.leaflet-top.leaflet-left .leaflet-control,
.leaflet-top.leaflet-right .leaflet-control {
  margin: 0;
}

.leaflet-control-zoom {
  border-radius: var(--radius-zoom-group) !important;
  overflow: hidden;
  box-shadow: var(--shadow-map-control);
  border: none !important;
}
.leaflet-control-zoom a {
  /* !important: leaflet.min.css's own .leaflet-touch .leaflet-bar a rule
     (0,2,1) is more specific than this selector (0,1,1) and would
     otherwise win regardless of source order, pinning these back to its
     30px touch-device size (measured: every touch-capable viewport in the
     browser tier still showed 30x30 without this). Same reason for
     background/color: unified.css's existing `body.dark .leaflet-bar a`
     override (0,2,2) also matches these <a> tags (descendants of this
     control's own .leaflet-bar class) and otherwise wins over this
     selector's (0,1,1) in dark mode, pinning them to its own literal
     #25272f/#e6e8eb instead of the tokens below. */
  width: 44px !important; height: 44px !important; line-height: 44px !important;
  background: var(--surface) !important;
  color: var(--ink-muted) !important;
  border: none !important;
}
.leaflet-control-zoom a:hover { background: var(--surface-raised) !important; color: var(--navy) !important; }
body.dark .leaflet-control-zoom a:hover { color: #93c5fd !important; }
/* Same specificity reason as the width/height rule above: leaflet.min.css
   sets its own touch-device font-size on these two classes at (0,2,1). */
.leaflet-control-zoom-in { font-size: 19px !important; border-bottom: 1px solid var(--line) !important; }
.leaflet-control-zoom-out { font-size: 21px !important; }

.locate-control {
  background: var(--surface);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-map-control);
  cursor: pointer;
}
.locate-control button {
  width: 44px; height: 44px; padding: 0; margin: 0;
  background: transparent; border: none; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-muted);
}
.locate-control button:hover { color: var(--navy); }
body.dark .locate-control button:hover { color: #93c5fd; }
.locate-control.loading button svg { animation: spin .8s linear infinite; color: var(--navy); }
.locate-control button svg { width: 20px; height: 20px; }

@media (min-width: 600px) {
  /* Map mode (no panel, just the floating nav card): zoom sits clear
     below the card, at the same 16px inset the card itself uses — the
     card's own rendered height (mark+title row, a 10px gap, the 44px tab
     row, 14px padding all round, 16px above it) comes to ~132px; 160px
     leaves comfortable clearance. Panel mode: zoom moves to the map's own
     top-left, 24px in, since the panel already reserves the space the
     card used to need room for. */
  .leaflet-top.leaflet-left { top: 160px; left: 16px; }
  body:has(#sidebar.open) .leaflet-top.leaflet-left { top: 24px; left: 24px; }
  /* The locate control is created with options:{position:'topright'}
     (map.js) so it stays a real Leaflet control; from 600px its corner
     container is simply anchored to the opposite edge — bottom-right of
     the map, 24px in, per docs/design.md's Layout table — rather than
     re-registered under a different Leaflet position on every resize.
     Unaffected by map-vs-panel mode: the floating card and the panel both
     only ever occupy the left side. */
  .leaflet-top.leaflet-right { top: auto; bottom: 24px; right: 24px; }
}

/* ── Your location (P2.22i, docs/design.md "Your location") ───────────────
   A 16px dot, a 3px white border, a soft halo; the accuracy ring is the same
   blue at 15%. Both sit in the map's own pane and take no taps. */
.loc-dot {
  box-sizing: border-box; width: 16px; height: 16px; border-radius: 50%;
  background: var(--loc-dot); border: 3px solid #fff;
  box-shadow: 0 0 0 6px color-mix(in srgb, var(--loc-dot) 25%, transparent);
  pointer-events: none;
}
.loc-ring { fill: var(--loc-dot); fill-opacity: .15; stroke: none; pointer-events: none; }

/* Moved here from board-states.css (P5.9n): that sheet loads after the first paint, but the
   "Loading live vehicles" chip and the failure/offline banners must be styled from the first one. */
/* ── A failed live load (P5.9e): one banner, any failing fetch ──────────────
   Warn pair like .board-notice.stale (same in both themes, design.md), under
   the phone's nav pill; top of the map beside the panel from 600px. */
.load-banner {
  position: fixed; z-index: 1500; top: 72px; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 8px;
  width: max-content; max-width: calc(100vw - 32px);
  padding: 4px 4px 4px 16px; border-radius: var(--radius-pill);
  background: var(--chip-warn-bg); color: var(--chip-warn-ink);
  font-size: 13px; font-weight: 600; line-height: 1.3;
  box-shadow: var(--shadow-map-control);
}
.load-banner[hidden] { display: none; }
/* P5.9f: offline (and "Back online") is the same banner in navy and white (it reads against a busy map in both themes), with no Retry: nothing to retry without a connection. */
.load-banner.offline { padding: 10px 16px; background: var(--navy); color: #fff; }
/* P5.9b: "Loading live vehicles…" until the first vehicle fetch answers; same place as the banner that replaces it on failure. */
.load-chip {
  position: fixed; z-index: 1500; top: 72px; left: 50%; transform: translateX(-50%);
  width: max-content; max-width: calc(100vw - 32px);
  padding: 8px 16px; border-radius: var(--radius-pill);
  background: var(--surface-raised); color: var(--ink);
  font-size: 13px; font-weight: 600; line-height: 1.3;
  box-shadow: var(--shadow-map-control); pointer-events: none;
}
.load-chip[hidden] { display: none; }
/* P5.13 F8: an operator missing from NTA's feed: the warn pair, a quiet line under the nav pill, no taps. */
.gap-chip {
  position: fixed; z-index: 1400; top: 72px; left: 50%; transform: translateX(-50%);
  width: max-content; max-width: calc(100vw - 32px);
  padding: 8px 16px; border-radius: var(--radius-pill);
  background: var(--chip-warn-bg); color: var(--chip-warn-ink);
  font-size: 13px; font-weight: 600; line-height: 1.3; text-align: center; white-space: nowrap;
  box-shadow: var(--shadow-map-control); pointer-events: none;
}
.gap-chip[hidden] { display: none; }
@media (min-width: 600px) { .gap-chip { top: 16px; } }
.load-banner-retry {
  min-height: 44px; padding: 0 18px; border: none; border-radius: var(--radius-pill);
  background: var(--navy); color: #fff; font: inherit; font-weight: 700; cursor: pointer;
}
.load-banner-retry:disabled { opacity: .6; cursor: default; }
@media (min-width: 600px) { .load-banner, .load-chip { top: 16px; } }

/* P5.9n: while the deferred sheets (stop board, planner, bus sheet, ride sheet, search/saved, board
   states) are on their way, early.js sets .css-wait on <html>: no unstyled panel flash. */
html.css-wait #sidebar { visibility: hidden; }

/* P5.13 L4: on a phone the offline/failed banner sits under the nav pill, where a panel screen's own title is
   (Saved, Plan, Stop): the screen's body moves down by the banner's height while the banner shows. */
@media (max-width: 599px) {
  body.has-load-banner #sidebar.open:not(.vehicle-open):not(.stop-board-open) #sb-body { margin-top: 44px; }
}

/* P5.13 H5: a shared link that landed on nothing (a finished or missing trip, an unknown stop). Stays until dismissed. */
.link-note {
  position: fixed; z-index: 1500; top: 72px; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 8px;
  width: max-content; max-width: calc(100vw - 32px);
  padding: 4px 4px 4px 16px; border-radius: var(--radius-pill);
  background: var(--surface-raised); color: var(--ink);
  font-size: 13px; font-weight: 600; line-height: 1.3;
  box-shadow: var(--shadow-map-control);
}
.link-note-act, .link-note-x {
  min-height: 44px; border: none; border-radius: var(--radius-pill);
  font: inherit; font-weight: 700; cursor: pointer;
}
.link-note-act { padding: 0 16px; background: var(--navy); color: #fff; }
.link-note-x { min-width: 44px; background: transparent; color: var(--ink-secondary); font-size: 20px; }
@media (min-width: 600px) { .link-note { top: 16px; } }
