/* ──────────────────────────────────────────────────────────────────────────
 * codriver.io: in-car app styles · "Tesla native"
 *
 * This file binds the design system to the live DOM in index.html. The
 * vocabulary is the CAR's, not the web's: the palette, the radii, the blur
 * and the type are firmware 2026.8.3's own (--t-* in /styles/tokens.css,
 * resolved from skin-common.conf), because this page runs on the same screen
 * as the car's native UI and anything else reads as a web page parked on top
 * of it.
 *
 * What that replaced: the previous sheet drew a square-edged, corner-bracket,
 * mono-uppercase system ("Concrete & Cobalt", still the MARKETING surface's
 * language and still what --cd-* means in tokens.css). The two surfaces share
 * tokens, not looks (see docs/BRAND.md).
 *
 * Conventions used throughout:
 *   • Floating cards, never full-bleed chrome. Everything is a rounded panel
 *     over the map with --t-shadow and a backdrop blur.
 *   • 10px is the panel radius, 8px the control radius, 50% for round buttons.
 *   • Colour comes from --t-* only. No hex literals except real-world signage
 *     (speed-limit plates, network swatches) and the police lightbar.
 *   • Anything a driver taps is >= 44px, and the primary controls are 52-54px
 *     (the documented Tesla 17" target).
 *   • .bk-bl / .bk-br are the old corner-bracket spans. The markup still
 *     carries them (they are load-bearing for nothing) and they are neutered
 *     once, below, rather than removed from a dozen templates.
 * ───────────────────────────────────────────────────────────────────────── */

/* Low-end GPU profile (Tesla MCU2 / Intel HD 505). app.js detects the GPU
 * from the unmasked WebGL renderer at boot and sets <html class="cd-no-motion">.
 * On that hardware the compositor is competing with the 3D map for the same
 * starved GPU, so we kill ALL CSS motion: panel slides, toast fades, the
 * recenter pulse, every hover transition. Map camera eases and speed-driven
 * zoom are disabled separately in app.js. */
html.cd-no-motion *,
html.cd-no-motion *::before,
html.cd-no-motion *::after {
  transition-duration: 0s !important;
  transition-delay: 0s !important;
  animation-duration: 0s !important;
  animation-delay: 0s !important;
  animation-iteration-count: 1 !important;
  scroll-behavior: auto !important;
}

:root {
  /* One gutter for the whole screen. The firmware insets its own floating
     controls by 24px and the eye reads a mismatch immediately. */
  --t-gutter: 24px;
  --t-gutter-b: 26px;
  /* The nav column's width. Referenced by the banner's clearance maths and by
     the widget column, so it lives here rather than in three places. */
  --t-col-w: 392px;
  --t-radius: 10px;
  --t-radius-sm: 8px;
  --t-radius-lg: 12px;
  --t-blur: blur(22px);
  --t-blur-strong: blur(26px);
  /* Bottom-centre stack. The trip pill owns the floor; everything else in that
     column is lifted clear of whatever is actually on screen. Redefined by the
     :has() rules further down rather than hard-coded per element. */
  --t-bottom-base: var(--t-gutter-b);
  --t-ease: cubic-bezier(.25, 1.1, .35, 1);
  --t-ease-pop: cubic-bezier(.3, 1.25, .4, 1);

  /* The viewport height AS THE ZOOMED SUBTREE SEES IT.
     Everything except #map lives under `zoom: var(--cd-ui-zoom)`, and `vh` is
     NOT rescaled by zoom: 100vh inside that subtree is still 784 zoomed
     units, which the zoom then renders as 784 x 0.846 = 663 real px. So a cap
     written as `100vh - chrome` silently reserves a sixth of the screen for
     nothing. That is conservative-but-harmless for a modal sheet, and it was
     costing the nav panel ~120px of list. Dividing by the zoom is what makes
     the number mean what it reads as. */
  --t-vh: calc(100vh / var(--cd-ui-zoom, 1));
  --t-vw: calc(100vw / var(--cd-ui-zoom, 1));
}

html, body {
  margin: 0; padding: 0;
  width: 100%; height: 100%;
  background: var(--t-bg);
  font-family: Roboto, 'Trebuchet MS', Helvetica, system-ui, sans-serif;
  color: var(--t-text);
  overflow: hidden;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
}

#map { position: absolute; inset: 0; }

/* The corner-bracket spans of the previous design language. Kept in the markup
   (removing them would touch every template for no gain) and switched off in
   one place here, so nothing in this sheet has to work around them. */
.bk-bl, .bk-br { display: none !important; }

/* Peregrine's attribution chip. Mapbox's terms require it VISIBLE over the
   map, so where it sits is a compliance question, not a taste one. It rides
   directly UNDER the ETA strip, in the sliver of the bottom edge the strip
   does not use, centred with it, so the two read as one bottom-centre block
   rather than as a notice hunting for a gap.

   The chip lives inside #map, so it is outside --cd-ui-zoom and this offset is
   raw viewport px; --t-gutter-b is in the zoomed space and reserves the room
   above it. That is why the narrow-pane override raises the gutter rather than
   lowering the chip: there is no divisor that makes 18px of unzoomed chip fit
   under a strip whose own inset shrinks with the UI-size lever. */
.mapboxgl-ctrl-bottom-left,
.peregrine-attrib {
  left: 50% !important;
  right: auto !important;
  bottom: 3px !important;
  transform: translateX(-50%);
}

/* ============================================================
   UI density (summer-2026 Tesla firmware).
   The update changed the in-car browser from devicePixelRatio 1.0
   to 1.53: same panel, every CSS px 1.53x bigger physically. The
   head script in index.html decides the scale before first paint
   (so the UI never flashes and re-snaps, ALL-26) and publishes it
   here as --cd-ui-zoom, driven by Settings -> View -> UI size.
   The map is excluded: Peregrine scales via dpr on its own and its
   canvas must never be CSS-zoomed. Popup CONTENT gets the zoom so
   in-map cards match the rest of the UI.
   Note on viewport units under zoom: vh/vw lengths get multiplied
   by the zoom factor, so our (max-height/max-width) caps become
   MORE conservative: sheets render slightly shorter, nothing
   overflows. Inset-anchored fills (top:0;bottom:0) are unaffected.
   ============================================================ */
body > :not(#map) { zoom: var(--cd-ui-zoom, 1); }
#map .mapboxgl-popup-content { zoom: var(--cd-ui-zoom, 1); }

/* The browser's default [hidden] is display:none, but plenty of descendants
   below set display:flex, which wins on specificity. Re-assert it globally. */
[hidden] { display: none !important; }

/* ── shared card + control primitives ───────────────────────────────────────
   Not classes: the markup is fixed, so these are the property groups every
   panel and button below repeats. Kept as custom properties so a change to
   the material happens once. */
/* These animate the INDEPENDENT transform properties (`translate`, `scale`),
   never `transform`. Half the surfaces they play on are centred with
   `transform: translateX(-50%)`, and a keyframe that sets `transform` replaces
   that outright, which parks the trip pill and the maneuver banner half a
   card to the right of centre for as long as the rule applies (and `both`
   means forever). The independent properties compose with `transform`
   instead of replacing it. */
@keyframes t-up   { from { opacity: 0; translate: 0 16px; }  to { opacity: 1; translate: 0 0; } }
@keyframes t-down { from { opacity: 0; translate: 0 -16px; } to { opacity: 1; translate: 0 0; } }
@keyframes t-right{ from { opacity: 0; translate: 26px 0; }  to { opacity: 1; translate: 0 0; } }
/* No scale here on purpose: a surface that zooms in on a map that is itself
   moving reads as the MAP zooming, which is the one thing a driver is
   entitled to trust. Everything enters by sliding from where it belongs. */
@keyframes t-pop  { from { opacity: 0; translate: 0 8px; }   to { opacity: 1; translate: 0 0; } }
@keyframes t-lamp { 0%, 38% { opacity: 1; } 55%, 100% { opacity: .25; } }

/* ============================================================
   NAV COLUMN: top-left. Collapsed it is a single "Navigate"
   pill; expanded it is a 392px card carrying search, the active
   route, favourites, recents and Settings.

   It is a CARD, not a drawer: it does not reach the bottom of the
   screen and it does not cover the map's centre, which is why the
   maneuver banner and the trip pill stay up while it is open.
   ============================================================ */
#nav-drawer {
  position: absolute;
  top: var(--t-gutter);
  left: var(--t-gutter);
  z-index: 6;
  width: var(--t-col-w);
  max-width: calc(100vw - var(--t-gutter) * 2);
  /* As tall as the pane allows, stopping just clear of the speed cluster in
     the bottom-left corner (56px pill + its gutter + 6px of air). --t-vh, not
     100vh: see its definition. The raw form was reserving a sixth of the
     screen for nothing and burying the favourites list.
     ...minus whatever the alert stack below needs while cards are up
     (--cd-alert-stack-reserve, measured by app.js). The alerts hang off this
     card's bottom edge and must never be pushed on top of it. The drawer
     scrolls, so it is the one that can afford to yield the pixels (ALL-185). */
  max-height: calc(var(--t-vh) - var(--t-gutter) - 88px - var(--cd-alert-stack-reserve, 0px));
  display: flex;
  flex-direction: column;
  background: var(--t-panel-opaque);
  backdrop-filter: var(--t-blur-strong);
  -webkit-backdrop-filter: var(--t-blur-strong);
  border: 0;
  border-radius: var(--t-radius);
  box-shadow: var(--t-shadow);
  overflow: hidden;
  animation: t-down .5s var(--t-ease) both;
  transition: width .22s var(--t-ease), background .22s var(--t-ease);
}

/* Collapsed, the column IS the Navigate button, at the full column width.
   It was briefly narrowed to its label because it shared the top strip with
   the maneuver banner and the two crowded each other; the banner is gone, the
   strip is the column's alone, and a full-width target is the easier one to
   hit at the wheel. */
#nav-drawer.collapsed {
  background: var(--t-panel);
  backdrop-filter: var(--t-blur);
  -webkit-backdrop-filter: var(--t-blur);
  max-height: none;
}
#nav-drawer.collapsed .nav-header { padding: 0; min-height: 0; border-bottom: 0; }
#nav-drawer.collapsed .nav-lockup,
#nav-drawer.collapsed .nav-rail,
#nav-drawer.collapsed #nav-body,
#nav-drawer.collapsed .nav-footer { display: none; }
#nav-drawer.collapsed .nav-toggle {
  width: 100%;
  height: 56px;
  justify-content: flex-start;
  gap: 14px;
  padding: 0 20px;
  color: var(--t-text-2);
}
#nav-drawer.collapsed .nav-toggle .icon-search,
#nav-drawer.collapsed .nav-toggle .nav-toggle__label { display: inline-flex; }
#nav-drawer.collapsed .nav-toggle .icon-menu,
#nav-drawer.collapsed .nav-toggle .icon-close { display: none; }

/* Header strip: helmet + lockup + close. Expanded only. */
.nav-header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 16px 12px 20px;
  min-height: 60px;
}
.nav-lockup {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: var(--t-text);
  font-weight: 500;
  font-size: 19px;
  line-height: 1;
  min-width: 0;
}
.nav-lockup .cd-logo__mark {
  width: 24px; height: 24px;
  color: var(--t-blue);
  flex: 0 0 auto;
}
.nav-lockup__text { display: inline-flex; align-items: baseline; min-width: 0; }
.nav-lockup__text > span:first-child { white-space: nowrap; }
.cd-logo__suffix { color: var(--t-text-3); font-weight: 400; }

.nav-toggle {
  appearance: none; -webkit-appearance: none;
  width: 40px; height: 40px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent;
  color: var(--t-text-2);
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  font-family: inherit;
  transition: background .15s var(--t-ease), color .15s var(--t-ease);
}
/* Collapsed, this button is the whole 392px pill, so its press state has to
   be the whole pill too. The 50% radius above is for the 40px close control in
   the expanded header; left on the pill it painted the highlight as a dark
   circle floating inside a much wider control. */
#nav-drawer.collapsed .nav-toggle { border-radius: var(--t-radius); }
/* No way to close the panel while a route is running: it is the only place
   the maneuver exists now. End, in the card, is how you stop navigating.
   setDrawerOpen() refuses the same thing in JS; this is the half the driver
   can see. */
body[data-navigating="1"] #nav-drawer .nav-toggle { display: none; }
.nav-toggle:hover, .nav-toggle:active { background: var(--t-hl); color: var(--t-text); }
.nav-toggle .icon-search,
.nav-toggle .nav-toggle__label { display: none; }
.nav-toggle__label { font-size: 19px; font-weight: 400; }
.nav-toggle .icon-menu { display: none; }
#nav-drawer:not(.collapsed) .nav-toggle .icon-close { display: inline-block; }

/* Rail icons. In the Tesla-native layout the collapsed column is one pill, so
   this vertical stack only exists inside the OPEN panel, as a compact
   Home/Work/Charging/Recent-style quick row across the top of the body, which
   is exactly the shape the design asks for. */
.nav-rail {
  display: flex;
  flex-direction: row;
  flex: 0 0 auto;
  gap: 4px;
  padding: 0 12px 6px;
}
.nav-rail__btn {
  appearance: none; -webkit-appearance: none;
  flex: 1 1 0;
  min-width: 0;
  height: 62px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  background: transparent;
  border: 0;
  border-radius: var(--t-radius-sm);
  color: var(--t-text-2);
  cursor: pointer;
  font-family: inherit;
  font-size: 13.5px;
  letter-spacing: 0;
  text-transform: none;
}
.nav-rail__btn span { text-transform: none; }
.nav-rail__btn:hover, .nav-rail__btn:active { background: var(--t-hl); color: var(--t-text); }
/* The two list tabs carry an underline; Home and Work are actions and never
   look selected, because nothing about them persists. */
.nav-rail__btn[data-nav-tab] { position: relative; }
.nav-rail__btn[data-nav-tab].is-active { color: var(--t-text); }
.nav-rail__btn[data-nav-tab].is-active::after {
  content: '';
  position: absolute;
  left: 18px; right: 18px; bottom: 4px;
  height: 2px;
  border-radius: 1px;
  background: var(--t-blue);
}
.nav-rail__btn--bottom { margin-top: 0; border-top: 0; padding-top: 0; }

/* Panel body */
#nav-body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

/* Footer: connection + version. */
.nav-footer {
  flex: 0 0 auto;
  padding: 11px 20px;
  border-top: 1px solid var(--t-divider);
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 13px;
  color: var(--t-text-3);
  text-transform: none;
  letter-spacing: 0;
}
.nav-footer__status { display: inline-flex; align-items: center; gap: 8px; }
.nav-footer__dot {
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--t-green);
}

/* ---------- search ---------- */
#nav-search-mount {
  padding: 0 12px 10px;
  flex: 0 0 auto;
  animation: t-up .28s ease both;
}
.nav-rail { animation: t-up .26s ease both; }
.poi-search { position: relative; width: 100%; }
.poi-search-icon {
  position: absolute;
  left: 18px;
  top: 50%;
  transform: translateY(-50%);
  pointer-events: none;
  color: var(--t-text-2);
  display: inline-flex;
  align-items: center;
}
.poi-search-input {
  width: 100%;
  box-sizing: border-box;
  height: 56px;
  padding: 0 44px;
  background: var(--t-search);
  color: var(--t-text);
  border: 0;
  border-radius: var(--t-radius-sm);
  font: inherit;
  font-size: 19px;
  caret-color: var(--t-blue);
  outline: none;
  -webkit-appearance: none;
  appearance: none;
  transition: box-shadow .15s var(--t-ease);
}
.poi-search-input::placeholder { color: var(--t-text-2); }
.poi-search-input::selection { background: var(--t-blue); color: #fff; }
.poi-search-input:focus { box-shadow: inset 0 0 0 2px var(--t-blue); }
.poi-search-input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.poi-search-clear {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  width: 38px; height: 38px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent;
  border: 0;
  border-radius: 50%;
  color: var(--t-text-2);
  cursor: pointer;
  font-size: 15px;
  line-height: 1;
}
.poi-search-clear:hover, .poi-search-clear:active { background: var(--t-btn-press); color: var(--t-text); }

.poi-search-list {
  position: absolute;
  top: calc(100% + 6px);
  left: 0; right: 0;
  margin: 0;
  padding: 4px 0;
  list-style: none;
  /* OPAQUE, not glass. This list is the only surface in the app that overlaps
     other TEXT rather than the map (it hangs over the favourites underneath
     it), and an 88%-opaque panel over a list of addresses left both unreadable.
     Glass is for floating over the map; over type it is just noise. */
  background: var(--t-bg-2);
  border: 0;
  border-radius: var(--t-radius);
  box-shadow: var(--t-shadow);
  /* Ends inside the card, not at the viewport edge: the card clips with
     overflow:hidden, so a list running past its bottom is not scrolled, it
     is cut off, and rows under the cut can never be reached (ALL-266).
     316 = the list's top offset (220) + the card's bottom inset (88) + air. */
  max-height: calc(var(--t-vh) - 316px - var(--cd-alert-stack-reserve, 0px));
  overflow-y: auto;
  scrollbar-width: none;
  z-index: 30;
}
.poi-search-list::-webkit-scrollbar { width: 0; height: 0; }
.poi-search-list:empty { display: none; }
/* While there are suggestions, the lists they cover step aside. Belt to the
   opaque background's braces: the overlap was the other half of why the
   suggestions were unreadable, and a list you cannot act on is not worth the
   room it is fighting for. */
#nav-drawer:has(#search-suggestions:not([hidden])) #nav-favorites,
#nav-drawer:has(#search-suggestions:not([hidden])) #nav-recents { visibility: hidden; }
/* ...and the card takes its full height while they are open. Since the card
   became content-sized (ALL-50) it was only as tall as the lists it hides, so
   with two favourites the suggestions were clipped to two rows and there was
   nothing to scroll: "can't scroll past 2 search results" (ALL-266). */
#nav-drawer:has(#search-suggestions:not([hidden])) {
  height: calc(var(--t-vh) - var(--t-gutter) - 88px - var(--cd-alert-stack-reserve, 0px));
}
.poi-suggestion {
  display: grid;
  grid-template-columns: 20px 1fr auto;
  align-items: center;
  gap: 16px;
  padding: 13px 20px;
  cursor: pointer;
  color: var(--t-text);
  font-size: 17.5px;
  user-select: none;
  border: 0;
  animation: t-up .28s ease both;
}
.poi-suggestion:hover,
.poi-suggestion[aria-selected="true"] { background: var(--t-hl); }
.poi-suggestion-icon {
  color: var(--t-text-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.poi-suggestion[aria-selected="true"] .poi-suggestion-icon { color: var(--t-blue); }
.poi-suggestion-body { min-width: 0; }
.poi-suggestion-name {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 400;
  color: var(--t-text);
}
.poi-suggestion-meta {
  display: block;
  font-size: 14px;
  color: var(--t-text-2);
  letter-spacing: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 1px;
}
.poi-suggestion-distance {
  font-size: 14px;
  color: var(--t-text-2);
  white-space: nowrap;
}
.poi-suggestion[aria-selected="true"] .poi-suggestion-distance { color: var(--t-blue); }
.poi-search-empty {
  padding: 14px 20px;
  color: var(--t-text-2);
  font-size: 14.5px;
}

/* ---------- active route: the trip-plan card ----------
   The design's Voyager card. Head = the live maneuver (distance big, street
   small: the driver knows they are turning, they need how far), a disclosure
   for the rest of the steps, then the trip line and the actions.

   This is the ONLY place the maneuver lives. The floating banner that used
   to duplicate it over the map is gone, and the panel cannot be closed while
   a route is running, so the card is always on screen when there is an
   instruction to give. */
#nav-route {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  animation: t-pop .32s var(--t-ease-pop) both;
}
.route-maneuver {
  appearance: none; -webkit-appearance: none;
  position: relative;
  display: flex;
  align-items: center;
  gap: 18px;
  width: 100%;
  padding: 16px 20px;
  background: var(--t-card);
  border: 0;
  color: var(--t-text);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  overflow: hidden;
  flex: 0 0 auto;
}
.route-maneuver__glyph {
  flex: 0 0 auto;
  width: 46px; height: 46px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--t-text);
}
.route-maneuver__glyph svg { width: 100%; height: 100%; display: block; }
.route-maneuver__body { flex: 1 1 auto; min-width: 0; }
.route-maneuver__dist {
  display: block;
  font-size: 30px;
  font-weight: 500;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.route-maneuver__text {
  display: block;
  margin-top: 3px;
  font-size: 17px;
  color: var(--t-text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Lane guidance and the chained preview, moved in from the retired banner. */
.route-lanes {
  display: flex;
  gap: 4px;
  margin-top: 8px;
}
.route-lanes[hidden] { display: none; }
.route-lane {
  min-width: 28px;
  padding: 3px 5px;
  text-align: center;
  font-size: 15px;
  line-height: 1.2;
  border-radius: 6px;
  background: var(--t-btn);
  color: var(--t-text-2);
  opacity: 0.5;
}
.route-lane.is-active { background: var(--t-toggle-on); color: #fff; opacity: 1; }
.route-then {
  display: block;
  margin-top: 6px;
  font-size: 13.5px;
  color: var(--t-text-3);
}
.route-then[hidden] { display: none; }
/* Roundabout exit ordinal, riding on the glyph. */
.route-maneuver__glyph { position: relative; }
.route-maneuver__exit {
  position: absolute;
  right: -2px;
  bottom: -2px;
  min-width: 17px;
  padding: 0 3px;
  border-radius: 9px;
  font-size: 11px;
  font-weight: 700;
  line-height: 17px;
  text-align: center;
  color: #fff;
  background: var(--t-blue);
}
/* Speed-adaptive staging, inherited from the banner: 'prepare' warms the head
   as the maneuver nears, 'now' pulses it. */
.route-maneuver[data-stage="prepare"] .route-maneuver__dist { color: var(--t-text); }
.route-maneuver[data-stage="now"] .route-maneuver__dist { color: var(--t-blue); }
.route-maneuver[data-stage="now"] { animation: t-maneuver-now .9s ease-in-out infinite; }
@keyframes t-maneuver-now {
  0%, 100% { background: var(--t-card); }
  50%      { background: color-mix(in srgb, var(--t-blue) 18%, var(--t-card)); }
}

.route-maneuver__chev {
  flex: 0 0 auto;
  color: var(--t-text-3);
  transition: transform .25s var(--t-ease);
}
.route-maneuver[aria-expanded="true"] .route-maneuver__chev { transform: rotate(180deg); }
/* How far through the current step you are: empty when the maneuver is a whole
   leg away, full as you reach it. Driven per GPS fix by paintRouteProgress().

   The transition is SHORTER than the fix cadence, deliberately. Fixes land
   about once a second; a 1 s transition meant the bar was still travelling
   toward one target when the next arrived, so it never settled and always
   rendered a value behind the distance printed directly above it, measured
   at 17% while the text said 75%. A third of a second is long enough to glide
   instead of stepping, and short enough to be showing the truth by the time
   anyone looks. `.no-anim` suppresses it entirely for the single paint after a
   step change, where the value jumps 100% -> 0% and animating that would run
   the bar visibly backwards at the exact moment the driver is turning. */
.route-maneuver__progress {
  position: absolute;
  left: 0; bottom: 0;
  height: 3px;
  width: 0;
  background: var(--t-blue);
  transition: width .35s linear;
}
.route-maneuver__progress.no-anim { transition: none; }
.route-maneuver__progress[hidden] { display: none; }

.route-summary {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 13px 20px;
  background: transparent;
  border-top: 1px solid var(--t-divider);
  border-bottom: 1px solid var(--t-divider);
}
.route-eta {
  font-size: 19px;
  font-weight: 500;
  color: var(--t-text);
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
  margin-left: auto;
  flex: 0 0 auto;
}
.route-dist {
  font-size: 15px;
  color: var(--t-text-2);
  flex: 0 0 auto;
}
.route-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 11px 14px;
  flex: 0 0 auto;
}
.route-icon-btn {
  width: 40px; height: 40px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--t-btn);
  color: var(--t-text-2);
  border: 0;
  border-radius: var(--t-radius-sm);
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  flex: 0 0 auto;
  transition: background .15s var(--t-ease), color .15s var(--t-ease);
}
.route-icon-btn:hover, .route-icon-btn:active { background: var(--t-btn-press); color: var(--t-text); }
.route-steps[hidden] { display: none; }
#route-save.saved { color: var(--t-blue); background: var(--t-btn-press); }
#route-save.saved svg path { fill: var(--t-blue); stroke: var(--t-blue); }
#route-clear:hover, #route-clear:active { background: var(--t-btn-press); color: var(--t-red); }

.route-dest {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 15px;
  color: var(--t-text);
  letter-spacing: 0;
  text-transform: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.route-icon-btn--end {
  width: auto;
  padding: 0 16px;
  margin-left: auto;
  color: var(--t-red);
  font-size: 15px;
  font-weight: 500;
}

.route-steps {
  list-style: none;
  padding: 0; margin: 0;
  overflow-y: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  flex: 1 1 auto;
  min-height: 0;
}
.route-steps::-webkit-scrollbar { width: 0; height: 0; }
.route-steps li {
  padding: 13px 20px 13px 56px;
  border-top: 1px solid var(--t-divider);
  position: relative;
  font-size: 16.5px;
  letter-spacing: 0;
  line-height: 1.3;
  color: var(--t-text-2);
}
.route-steps .step-icon {
  position: absolute;
  left: 20px; top: 12px;
  width: 24px; height: 24px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--t-text-2);
}
.route-steps .step-icon svg { width: 100%; height: 100%; display: block; }
.route-steps .step-distance {
  display: block;
  font-size: 14px;
  color: var(--t-text-3);
  margin-top: 3px;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
  text-transform: none;
}

/* ---------- favourites / recents ---------- */
#nav-route:not([hidden]) ~ #nav-favorites { display: none; }
#nav-favorites {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}
.favs-header {
  padding: 12px 20px 6px;
  font-size: 13.5px;
  font-weight: 500;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--t-text-2);
  display: flex;
  align-items: center;
  border: 0;
  background: transparent;
}
.favs-header svg { color: var(--t-text-2); }
#nav-favorites-list {
  overflow-y: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  flex: 1 1 auto;
  min-height: 0;
}
#nav-favorites-list::-webkit-scrollbar,
#nav-recents-list::-webkit-scrollbar { width: 0; height: 0; }
.fav-item {
  display: flex;
  align-items: center;
  gap: 14px;
  animation: t-up .26s ease both;
  padding: 12px 12px 12px 20px;
  border: 0;
  border-radius: var(--t-radius-sm);
  margin: 0 8px;
  cursor: pointer;
  transition: background .15s var(--t-ease);
}
.fav-item:hover, .fav-item:active { background: var(--t-hl); }
.fav-item .fav-body { flex: 1 1 auto; min-width: 0; }
.fav-item .fav-name {
  display: block;
  font-size: 16.5px;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--t-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.fav-item .fav-meta {
  display: block;
  font-size: 13.5px;
  letter-spacing: 0;
  color: var(--t-text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 1px;
}
/* Leading glyph: what KIND of place this is. It replaced a HOME/WORK/SYNC
   text tag that led the row. The tag repeated what an icon says in a
   quarter of the width, and it pushed the name off the left edge. */
.fav-item__glyph {
  flex: 0 0 auto;
  width: 21px; height: 21px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--t-text-2);
}
.fav-item__glyph svg { display: block; }
.fav-dist {
  flex: 0 0 auto;
  font-size: 13.5px;
  color: var(--t-text-2);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.fav-item .fav-edit,
.fav-item .fav-delete {
  width: 36px; height: 36px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent;
  color: var(--t-text-3);
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  font-size: 14px;
  flex: 0 0 auto;
}
.fav-item .fav-edit:hover { background: var(--t-btn-press); color: var(--t-text); }
.fav-item .fav-delete:hover { background: var(--t-btn-press); color: var(--t-red); }
.fav-name-input {
  display: block;
  width: 100%;
  background: var(--t-search);
  color: var(--t-text);
  border: 0;
  border-radius: 6px;
  box-shadow: inset 0 0 0 2px var(--t-blue);
  padding: 7px 10px;
  font: inherit;
  font-size: 16.5px;
  outline: none;
  box-sizing: border-box;
}
.fav-item[data-editing="true"] { cursor: text; background: var(--t-hl); }
.favs-empty {
  padding: 16px 20px;
  font-size: 14.5px;
  color: var(--t-text-3);
  text-align: center;
  letter-spacing: 0;
}

#nav-route:not([hidden]) ~ #nav-recents { display: none; }
#nav-recents {
  display: flex;
  flex-direction: column;
  flex: 0 1 auto;
  min-height: 0;
  border-top: 1px solid var(--t-divider);
}
#nav-recents[hidden] { display: none; }
.recents-header { justify-content: space-between; }
.recents-clear {
  margin-left: auto;
  background: transparent;
  border: 0;
  color: var(--t-text-2);
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  cursor: pointer;
  padding: 4px 6px;
  border-radius: 6px;
}
.recents-clear:hover { color: var(--t-red); background: var(--t-hl); }
#nav-recents-list {
  overflow-y: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  flex: 0 1 auto;
  max-height: 34vh;
  min-height: 0;
}

/* ---------- route picker (alternatives) ---------- */
#route-picker {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  background: transparent;
  color: var(--t-text);
  animation: t-pop .32s var(--t-ease-pop) both;
}
.picker-header {
  padding: 16px 20px 12px;
  border-bottom: 1px solid var(--t-divider);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.picker-title {
  font-size: 19px;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--t-text);
}
.picker-subtitle {
  font-size: 14px;
  color: var(--t-text-2);
  letter-spacing: 0;
  text-transform: none;
}
.picker-list {
  list-style: none;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  flex: 1 1 auto;
  min-height: 0;
}
.picker-list::-webkit-scrollbar { width: 0; height: 0; }
.picker-row {
  display: flex;
  align-items: stretch;
  gap: 14px;
  padding: 14px 20px;
  border: 0;
  cursor: pointer;
  transition: background .15s var(--t-ease);
  animation: t-up .3s ease both;
}
.picker-row:hover { background: var(--t-hl); }
.picker-row.active { background: var(--t-hl); }
.picker-row .picker-eta {
  font-size: 19px;
  font-weight: 500;
  color: var(--t-text);
  letter-spacing: 0;
  line-height: 1.15;
  flex: 0 0 auto;
  min-width: 88px;
  font-variant-numeric: tabular-nums;
}
.picker-row.active .picker-eta { color: var(--t-text); }
.picker-row .picker-eta-free {
  display: block;
  font-size: 13px;
  font-weight: 400;
  color: var(--t-text-2);
  margin-top: 3px;
  letter-spacing: 0;
  text-transform: none;
}
.picker-row.active .picker-eta-free { color: var(--t-green); }
.picker-row .picker-body { flex: 1 1 auto; min-width: 0; }
.picker-row .picker-name {
  font-size: 15px;
  letter-spacing: 0;
  color: var(--t-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.picker-row .picker-meta {
  margin-top: 3px;
  font-size: 14px;
  color: var(--t-text-2);
  letter-spacing: 0;
}
.picker-row .picker-chips {
  margin-top: 8px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.picker-chip {
  font-size: 11.5px;
  padding: 4px 9px;
  border: 0;
  border-radius: 999px;
  background: var(--t-btn);
  color: var(--t-text-2);
  white-space: nowrap;
  letter-spacing: .02em;
  text-transform: none;
}
.picker-chip.best   { background: color-mix(in srgb, var(--t-green) 22%, transparent); color: var(--t-green); font-weight: 500; }
.picker-chip.toll   { background: color-mix(in srgb, var(--t-amber) 20%, transparent); color: var(--t-amber); }
.picker-chip.ferry  { background: color-mix(in srgb, var(--t-blue) 22%, transparent);  color: var(--t-blue); }
.picker-chip.hov    { background: var(--t-btn); color: var(--t-text-2); }
.picker-chip.alerts { background: color-mix(in srgb, var(--t-red) 20%, transparent);   color: var(--t-red); }
.picker-chip.alerts.zero { background: color-mix(in srgb, var(--t-green) 22%, transparent); color: var(--t-green); }
.picker-chip.warn   { background: color-mix(in srgb, var(--t-amber) 20%, transparent); color: var(--t-amber); }
.picker-cta {
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: 10px;
  padding: 12px 16px 16px;
  border-top: 1px solid var(--t-divider);
  background: transparent;
}
.picker-btn {
  appearance: none;
  -webkit-appearance: none;
  height: 52px;
  font-family: inherit;
  font-size: 16px;
  font-weight: 500;
  letter-spacing: 0;
  cursor: pointer;
  border-radius: var(--t-radius-sm);
  border: 0;
  transition: background .15s var(--t-ease), filter .15s var(--t-ease);
}
.picker-btn-secondary {
  background: var(--t-btn);
  color: var(--t-text-2);
}
.picker-btn-secondary:hover, .picker-btn-secondary:active { background: var(--t-btn-press); color: var(--t-text); }
.picker-btn-primary {
  background: var(--t-blue);
  color: #fff;
  font-size: 17px;
}
.picker-btn-primary:hover, .picker-btn-primary:active { filter: brightness(1.15); }

/* Auto-confirm countdown (ALL-195). The bar fills the Go button itself rather
   than sitting beside it: at 52px and an arm's length away, a wash across the
   control that is about to press itself is legible in a glance where a hairline
   rule under it is not.

   app.js aims it one step ahead once per step and lets this transition carry it
   there, so the sweep is smooth without a per-frame timer, and reduced motion
   then only has to switch the transition off to get the same countdown in
   honest one-second steps. Duration is set inline from PICKER_AUTO_CONFIRM_STEP_MS
   so the two cannot drift; `transition: none` below still wins over it, because
   it zeroes transition-property, not the duration.

   scaleX rather than the `width` .route-maneuver__progress animates: that bar
   is 3px and moves for a third of a second per fix, this one is the size of the
   button and moves for ten seconds without pause, and laying out that every
   frame is a cost MCU2 does not have to pay for a transform. */
#picker-go { position: relative; overflow: hidden; }
.picker-go-progress {
  position: absolute;
  inset: 0;
  transform: scaleX(0);
  transform-origin: left center;
  background: rgba(255, 255, 255, .22);
  border-radius: inherit;
  pointer-events: none;
  transition: transform 1s linear;
}
@media (prefers-reduced-motion: reduce) {
  .picker-go-progress { transition: none; }
}
.picker-go-label { position: relative; }

/* ============================================================
   SETTINGS: its own right-edge panel.

   It used to be a second VIEW nested inside the nav column, which
   is why opening it hid the search field the driver had just used
   to get there and closing it dumped them back into a panel they
   had not asked to reopen. It is a sibling of the column now:
   full-height on the opposite edge, independent of it, and reached
   from exactly one place (the gear in the action stack).
   ============================================================ */
#nav-settings {
  position: fixed;
  top: var(--t-gutter);
  right: var(--t-gutter);
  bottom: var(--t-gutter-b);
  width: 400px;
  max-width: calc(100vw - var(--t-gutter) * 2);
  z-index: 8;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  background: var(--t-panel-opaque);
  backdrop-filter: blur(28px);
  -webkit-backdrop-filter: blur(28px);
  border: 0;
  border-radius: var(--t-radius-lg);
  box-shadow: var(--t-shadow);
  animation: t-right .4s var(--t-ease) both;
}

/* While the panel is up it owns the right edge. The status pill, the camera
   column and the action stack all sit underneath it, so the panel paints
   above them (they are z-index 5-6), and the two it fully covers are hidden
   rather than left to ghost through 88%-opaque glass, which reads as a smudge
   rather than as a control. They are unreachable either way; the panel's own
   back arrow is how you leave. */
body:has(#nav-settings:not([hidden])) #camera-controls,
body:has(#nav-settings:not([hidden])) #hud { display: none; }

.nav-settings-row {
  appearance: none; -webkit-appearance: none;
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  flex: 0 0 auto;
  padding: 15px 20px;
  border: 0;
  border-top: 1px solid var(--t-divider);
  background: transparent;
  color: var(--t-text);
  font-family: inherit;
  font-weight: 400;
  font-size: 17px;
  letter-spacing: 0;
  text-align: left;
  cursor: pointer;
  transition: background .15s var(--t-ease);
}
.nav-settings-row svg { color: var(--t-text-2); flex: 0 0 auto; }
.nav-settings-row span:nth-of-type(1) { flex: 1; }
.nav-settings-row__chev {
  color: var(--t-text-3);
  font-size: 20px;
  line-height: 1;
}
.nav-settings-row:hover, .nav-settings-row:active { background: var(--t-hl); }

.settings-header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 16px 14px 12px 22px;
  border: 0;
  background: transparent;
}
/* The helmet sits with the title, not in the card below: the card is about
   the person, the header is about the app. */
.settings-header__mark {
  width: 26px;
  height: 26px;
  flex: none;
  color: var(--t-blue);
}
.settings-header__mark svg { display: block; }
.settings-header__title {
  flex: 1 1 auto;
  font-size: 22px;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--t-text);
}
/* Close, on the right, where every other sheet in the app keeps it. 44px so
   it is a target at the wheel, not a glyph. */
.settings-close {
  appearance: none; -webkit-appearance: none;
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  background: transparent;
  color: var(--t-text-2);
  border: 0;
  border-radius: 50%;
  cursor: pointer;
}
.settings-close:hover, .settings-close:active { background: var(--t-hl); color: var(--t-text); }
.settings-body {
  flex: 1 1 auto;
  overflow-y: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  padding: 0 22px 24px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}
.settings-body::-webkit-scrollbar { width: 0; height: 0; }
.setting-group { display: flex; flex-direction: column; gap: 16px; }
.setting-group__title {
  font-size: 13.5px;
  font-weight: 500;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--t-text-2);
  padding-bottom: 0;
  border: 0;
}
.setting-group__subtitle {
  font-size: 13px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--t-text-3);
  margin-top: 2px;
}
.setting-row { display: flex; flex-direction: column; gap: 10px; }
.setting-row__label {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
}
.setting-row .setting-name {
  font-size: 17px;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--t-text);
}
.setting-row .setting-meta {
  font-size: 14px;
  color: var(--t-text-2);
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}
.setting-row__hint {
  font-size: 13.5px;
  color: var(--t-text-2);
  letter-spacing: 0;
  line-height: 1.45;
}
.setting-check {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 0;
  cursor: pointer;
  font-size: 16px;
  color: var(--t-text);
  border-bottom: 1px solid var(--t-divider);
}
.setting-check input[type="checkbox"] {
  width: 20px;
  height: 20px;
  margin: 1px 0 0;
  flex: 0 0 auto;
  accent-color: var(--t-blue);
  cursor: pointer;
}
.setting-check__meta {
  display: block;
  font-size: 13px;
  color: var(--t-text-2);
  letter-spacing: 0;
}
.setting-row__ticks {
  display: flex;
  justify-content: space-between;
  font-size: 12.5px;
  letter-spacing: 0;
  color: var(--t-text-3);
  padding: 0 2px;
}

.setting-slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 34px;
  background: transparent;
  cursor: pointer;
}
.setting-slider:focus { outline: none; }
/* --t-btn-press, not --t-btn: this track carries no fill, so it is the only
   thing showing the range, and --t-btn is within a few points of the panel
   behind it. */
.setting-slider::-webkit-slider-runnable-track {
  height: 5px;
  border-radius: 3px;
  background: var(--t-btn-press);
  border: 0;
}
.setting-slider::-moz-range-track {
  height: 5px;
  border-radius: 3px;
  background: var(--t-btn-press);
  border: 0;
}
.setting-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--t-blue);
  border: 0;
  margin-top: -10.5px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .35);
}
.setting-slider::-moz-range-thumb {
  width: 26px;
  height: 26px;
  background: var(--t-blue);
  border: 0;
  border-radius: 50%;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .35);
}

/* Segmented control, the firmware's own: a --t-btn trough with 3px of
   padding and a filled --t-toggle-on pill for the active segment. */
.setting-segmented {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 0;
  padding: 3px;
  background: var(--t-btn);
  border: 0;
  border-radius: var(--t-radius-sm);
}
.setting-segmented button {
  appearance: none; -webkit-appearance: none;
  min-height: 44px;
  background: transparent;
  color: var(--t-text-2);
  border: 0;
  border-radius: 6px;
  font-family: inherit;
  font-weight: 500;
  font-size: 15.5px;
  letter-spacing: 0;
  cursor: pointer;
  padding: 0 8px;
  transition: background .15s var(--t-ease), color .15s var(--t-ease);
}
.setting-segmented button:first-child { border-left: 0; }
.setting-segmented button:hover { background: var(--t-hl); color: var(--t-text); }
.setting-segmented button.is-active,
.setting-segmented button[aria-pressed="true"] {
  background: var(--t-toggle-on);
  color: #fff;
}

.setting-segmented button.is-locked {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
  position: relative;
}
.setting-segmented button.is-locked::after {
  content: '★';
  position: absolute;
  top: 4px;
  right: 6px;
  font-size: 9px;
  color: var(--t-blue);
}

/* View-mode picker: four two-line tiles. */
.setting-segmented--4 button {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 6px 4px;
  min-height: 58px;
  font-size: 13px;
}
.setting-view__name {
  font-weight: 500;
  font-size: 14px;
  line-height: 1.1;
}
.setting-view__sub {
  font-size: 11px;
  letter-spacing: 0;
  color: var(--t-text-3);
  text-transform: none;
}
.setting-segmented--4 button.is-active .setting-view__sub {
  color: #fff;
  opacity: 0.75;
}

/* Free-tier locks. */
.setting-segmented.is-locked button {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}
.setting-row__locked {
  font-size: 13px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--t-blue);
  margin-top: 4px;
}

/* Car-model picker. */
.setting-select {
  width: 100%;
  min-height: 48px;
  padding: 0 38px 0 14px;
  border-radius: var(--t-radius-sm);
  border: 0;
  background: var(--t-btn);
  color: var(--t-text);
  font: inherit;
  font-size: 15.5px;
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 20px) calc(50% + 2px), calc(100% - 15px) calc(50% + 2px);
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.setting-select:focus-visible { outline: 2px solid var(--t-blue); outline-offset: 2px; }
.setting-wide-btn { width: 100%; }

/* ============================================================
   STATUS CLUSTER: top right. The live-feed pill and the sources panel it opens.
   ============================================================ */

#hud-status {
  appearance: none; -webkit-appearance: none;
  position: fixed;
  top: var(--t-gutter);
  right: var(--t-gutter);
  z-index: 6;
  height: 46px;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 0 14px 0 16px;
  background: var(--t-panel);
  backdrop-filter: var(--t-blur);
  -webkit-backdrop-filter: var(--t-blur);
  border: 0;
  border-radius: 23px;
  box-shadow: var(--t-shadow);
  font-family: inherit;
  font-size: 14.5px;
  font-weight: 500;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--t-text-2);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  animation: t-down .5s .1s var(--t-ease) both;
  transition: background .15s var(--t-ease);
}
#hud-status:hover, #hud-status:active { background: var(--t-hl); }
#hud-status[aria-expanded="true"] { background: var(--t-hl); }
.hud-status__chev {
  display: inline-flex;
  margin-left: 2px;
  color: var(--t-text-3);
  transition: transform .22s var(--t-ease), color .15s var(--t-ease);
}
#hud-status[aria-expanded="true"] .hud-status__chev {
  color: var(--t-blue);
  transform: rotate(180deg);
}
.hud-status__mark {
  display: block;
  width: 17px; height: 17px;
  flex: none;
  color: var(--t-blue);
}
.hud-status__mark svg { display: block; }
/* In flow, between the label and the chevron. It used to be positioned
   absolutely against the pill's left inset. With the mark now occupying that
   slot the two stacked, and moving it to the right inset put it under the
   chevron instead. A flex child cannot collide with either. */
#hud-status .dot {
  flex: none;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--t-amber);
  color: var(--t-amber);
}
#hud-status .dot.ok   { background: var(--t-green); color: var(--t-green); }
#hud-status .dot.err  { background: var(--t-red);   color: var(--t-red); }
#hud-status .dot.warn { background: var(--t-amber); color: var(--t-amber); }

#sources-panel {
  position: fixed;
  top: calc(var(--t-gutter) + 58px);
  right: var(--t-gutter);
  z-index: 7;
  width: 400px;
  max-width: calc(100vw - var(--t-gutter) * 2);
  background: var(--t-info-bg);
  backdrop-filter: blur(28px);
  -webkit-backdrop-filter: blur(28px);
  border: 0;
  border-radius: var(--t-radius-lg);
  box-shadow: var(--t-shadow);
  overflow: hidden;
  animation: t-right .4s var(--t-ease) both;
}

.sources-panel__head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 16px 20px 12px;
  border: 0;
}
.sources-panel__eyebrow {
  font-size: 13px;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--t-text-3);
  order: 2;
  margin-left: auto;
}
.sources-panel__summary {
  margin: 0;
  font-size: 17px;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--t-text);
  order: 1;
}

.sources-panel__rows {
  max-height: 46vh;
  overflow-y: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.sources-panel__rows::-webkit-scrollbar { width: 0; height: 0; }
.sources-panel__row {
  display: grid;
  grid-template-columns: 8px minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  padding: 11px 20px;
  border: 0;
}
.sources-panel__row > .sources-panel__body { min-width: 0; }
.sources-panel__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--t-text-3);
  /* status colour set inline by JS */
  box-shadow: none;
}
.sources-panel__name {
  display: block;
  font-size: 15.5px;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--t-text);
}
.sources-panel__sub {
  display: block;
  margin-top: 1px;
  font-size: 13px;
  letter-spacing: 0;
  color: var(--t-text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sources-panel__count {
  font-size: 13.5px;
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
  color: var(--t-text-2);
}
.sources-panel__count[data-status="degraded"]        { color: var(--t-amber); }
.sources-panel__count[data-status="highly_degraded"] { color: var(--t-amber); }
.sources-panel__count[data-status="offline"]         { color: var(--t-red); }
.sources-panel__count[data-status="online"]          { color: var(--t-text-2); }

.sources-panel__legend {
  padding: 12px 20px;
  border-top: 1px solid var(--t-divider);
}
.sources-panel__legend-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.sources-panel__legend-title {
  font-size: 15.5px;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--t-text);
}
.sources-panel__legend-src {
  font-size: 13px;
  letter-spacing: 0;
  color: var(--t-text-2);
  white-space: nowrap;
}
.sources-panel__legend-keys {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin-top: 8px;
}
.sources-panel__key {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 13.5px;
  color: var(--t-text-2);
}
.sources-panel__key i {
  width: 16px;
  height: 3px;
  border-radius: 2px;
}
/* Traffic-flow swatches mirror the paint in public/app.js. These are data
   colours, not theme colours, so they are literals on purpose. */
.sources-panel__key[data-level="moderate"]    i { background: #facc15; }
.sources-panel__key[data-level="heavy"]       i { background: #f97316; }
.sources-panel__key[data-level="stand-still"] i { background: #ef4444; }
.sources-panel__legend-note {
  margin-top: 8px;
  font-size: 12.5px;
  letter-spacing: 0;
  color: var(--t-text-3);
}

.sources-panel__foot {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 11px 20px;
  border-top: 1px solid var(--t-divider);
  background: transparent;
  font-size: 13px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--t-text-3);
}
.sources-panel__foot a { color: var(--t-blue); text-decoration: none; }
.sources-panel__foot a:hover { text-decoration: underline; }

/* ============================================================
   SPEED CLUSTER, bottom left: the speed readout, the posted
   limit plate and the matched street name.

   Three separately-positioned fixed elements rather than one row,
   because each appears independently. #speed-pill therefore has a
   min-width so the plate to its right has a fixed anchor instead
   of a number that moves when the speed goes from 99 to 100.
   ============================================================ */
#speed-pill {
  position: fixed;
  bottom: var(--t-gutter-b);
  left: var(--t-gutter);
  z-index: 5;
  height: 56px;
  min-width: 148px;
  display: inline-flex;
  /* The figure and its unit share a BASELINE: "km/h" sits on the bottom of
     the 92, which is the only way the pair reads as one measurement rather
     than as two labels. But `align-items: baseline` aligns items WITHIN the
     flex line and says nothing about where that line sits, so in a
     fixed-height box it parks against the top edge: the readout was hugging
     the ceiling with 20px of dead space under it.
     `flex-wrap: wrap` turns this into a (single-line) multi-line container,
     which is what makes `align-content` apply, and that is what centres the
     line. Both properties are load-bearing; dropping the wrap silently
     re-breaks it. */
  flex-wrap: wrap;
  align-items: baseline;
  align-content: center;
  justify-content: center;
  gap: 9px;
  padding: 0 20px;
  background: var(--t-panel);
  backdrop-filter: var(--t-blur);
  -webkit-backdrop-filter: var(--t-blur);
  border: 0;
  border-radius: var(--t-radius);
  box-shadow: var(--t-shadow);
  font-variant-numeric: tabular-nums;
  animation: t-up .5s .08s var(--t-ease) both;
}
#speed-pill #speed-value {
  font-size: 32px;
  font-weight: 500;
  color: var(--t-text);
  line-height: 1;
  letter-spacing: 0;
}
#speed-pill .unit {
  font-size: 14px;
  color: var(--t-text-2);
  letter-spacing: 0;
}

/* Posted speed limit. Real-world signage: white plate, black figures, in both
   themes; a theme-tinted road sign is a wrong road sign. */
#speed-limit {
  position: fixed;
  bottom: var(--t-gutter-b);
  left: calc(var(--t-gutter) + 148px + 12px);
  z-index: 5;
  width: 52px;
  height: 56px;
  border-radius: var(--t-radius-sm);
  background: #ffffff;
  border: 2.5px solid #d0d1d2;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-family: inherit;
  font-weight: 700;
  font-size: 21px;
  color: #171717;
  box-shadow: var(--t-shadow);
}
/* Vienna-convention roundel is the rest-of-world default; the NA variant is
   the MUTCD "SPEED LIMIT" plate. Toggled per GPS fix in updateNavBanner(). */
#speed-limit:not(.speed-limit--na) {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  border: 5px solid #d0021b;
  font-size: 20px;
}
.speed-limit__cap { display: none; }
#speed-limit.speed-limit--na .speed-limit__cap {
  display: block;
  font-size: 8.5px;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: .04em;
  text-align: center;
  color: #5b5b5b;
}
#speed-limit.speed-limit--na #speed-limit-value {
  font-size: 21px;
  line-height: 1.05;
}

#road-name {
  position: fixed;
  bottom: var(--t-gutter-b);
  left: calc(var(--t-gutter) + 148px + 12px + 56px + 12px);
  z-index: 5;
  max-width: 280px;
  height: 56px;
  padding: 0 18px;
  display: flex;
  align-items: center;
  background: var(--t-panel);
  backdrop-filter: var(--t-blur);
  -webkit-backdrop-filter: var(--t-blur);
  border: 0;
  border-radius: var(--t-radius);
  box-shadow: var(--t-shadow);
  font-size: 16px;
  color: var(--t-text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
#road-name[hidden] { display: none; }

/* ============================================================
   MAP CONTROLS: right edge, vertically centred. Everything that
   moves the CAMERA: orientation, zoom, tilt. The bottom-right
   action stack carries only actions.
   ============================================================ */
#camera-controls {
  position: fixed;
  top: 50%;
  right: var(--t-gutter);
  transform: translateY(-50%);
  z-index: 6;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 12px;
  animation: t-right .5s .15s var(--t-ease) both;
}

/* Round glass button, the firmware's map-control shape. */
.hud-btn {
  appearance: none; -webkit-appearance: none;
  width: 54px; height: 54px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--t-panel);
  color: var(--t-text-2);
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  position: relative;
  backdrop-filter: var(--t-blur);
  -webkit-backdrop-filter: var(--t-blur);
  box-shadow: var(--t-shadow);
  font-family: inherit;
  transition: background .15s var(--t-ease), color .15s var(--t-ease), filter .15s var(--t-ease);
}
.hud-btn:hover, .hud-btn:active { background: var(--t-hl); color: var(--t-text); }
.hud-btn.active,
.hud-btn.needs-recenter { color: var(--t-blue); }

/* Orientation toggle. Grey with the needle tracking true north, or filled
   toggle-blue with a lock badge when north is locked (.north-up). */
#compass { --bearing: 0deg; color: var(--t-text-2); }
#compass .compass-rose { transform: rotate(calc(var(--bearing) - 45deg)); transition: transform .2s linear; }
#compass .compass-lock {
  position: absolute;
  top: 7px; right: 7px;
  display: none;
}
#compass.north-up .compass-lock { display: block; }
#compass.north-up {
  color: #fff;
  background: var(--t-toggle-on);
}
#compass.north-up:hover { background: var(--t-toggle-on); filter: brightness(1.15); }

/* Camera steppers (ALL-27). A single rounded capsule per lever with the
   level-raising button on top, matching how the firmware stacks zoom. */
.hud-stepper {
  display: inline-flex;
  flex-direction: column;
  align-items: stretch;
  width: 54px;
  background: var(--t-panel);
  color: var(--t-text-2);
  border: 0;
  border-radius: 27px;
  overflow: hidden;
  position: relative;
  backdrop-filter: var(--t-blur);
  -webkit-backdrop-filter: var(--t-blur);
  box-shadow: var(--t-shadow);
}
.hud-stepper__btn {
  appearance: none; -webkit-appearance: none;
  width: 54px; height: 52px;
  background: transparent;
  color: inherit;
  border: 0;
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background .15s var(--t-ease), color .15s var(--t-ease);
}
.hud-stepper__btn:hover:not(:disabled),
.hud-stepper__btn:active:not(:disabled) { background: var(--t-hl); color: var(--t-text); }
/* End of travel. Dimmed rather than hidden so the control keeps its shape and
   the driver can see WHICH end they are at without reading the number. */
.hud-stepper__btn:disabled {
  color: var(--t-text-3);
  cursor: default;
}
.hud-stepper__value {
  height: 30px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 14px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--t-blue);
  border-top: 1px solid var(--t-divider);
  border-bottom: 1px solid var(--t-divider);
  margin: 0 14px;
  pointer-events: none;
}

/* ============================================================
   ACTION STACK, bottom right: settings, then report.
   ============================================================ */
#hud {
  position: absolute;
  right: var(--t-gutter);
  bottom: var(--t-gutter-b);
  display: flex;
  flex-direction: column;
  gap: 12px;
  align-items: flex-end;
  z-index: 5;
  pointer-events: none;
  animation: t-up .5s .12s var(--t-ease) both;
}
#hud > * { pointer-events: auto; }

/* Settings ▸ View ▸ Report button = Raised (ALL-43).
   Some cars paint OS chrome into the bottom-right corner of the browser pane:
   a 2026 Model X puts a ~113x46 CSS px "PASSENGER AIRBAG OFF" badge there,
   directly over the action stack. We cannot see or move that badge, so the
   driver gets to lift the stack off it.

   The lift is DIVIDED by --cd-ui-zoom on purpose. #hud lives in the zoomed
   subtree and the badge does not: it is drawn by the car at physical pixels
   and does not care what the UI-size slider says. Without the divisor the
   clearance would ride the slider, under-clearing at level 1 and throwing
   the button into the middle of the map at level 16. */
body[data-hud-pos="raised"] #hud { bottom: calc(var(--t-gutter-b) + 64px / var(--cd-ui-zoom, 1)); }

/* Report is the one filled control on the map: it is the thing we want a
   driver to reach for. */
.hud-btn--report {
  background: var(--t-blue);
  color: #ffffff;
  overflow: hidden;
}
.hud-btn--report:hover, .hud-btn--report:active { background: var(--t-blue); filter: brightness(1.15); color: #fff; }
.hud-btn--report[disabled] { opacity: 0.5; cursor: not-allowed; }
.hud-btn--report[disabled]:hover { filter: none; }
/* Cooldown: a bottom-edge line sweeping over the 5 s window (ALL-184);
   keep in step with REPORT_COOLDOWN_MS in app.js. */
.hud-btn--report .hud-btn__ring {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  width: 100%; height: 3px;
  pointer-events: none;
  color: rgba(255, 255, 255, .9);
  opacity: 0;
  transition: opacity .15s var(--t-ease);
}
.hud-btn--report.is-cooldown { background: var(--t-btn); color: var(--t-text-3); cursor: not-allowed; }
.hud-btn--report.is-cooldown .hud-btn__ring { opacity: 1; color: var(--t-blue); }
.hud-btn--report.is-cooldown .hud-btn__ring line {
  transition: stroke-dashoffset 5s linear;
  stroke-dashoffset: 0;
}

/* Recenter leaves the action stack: the design puts it bottom-CENTRE, on the
   driver's thumb path and directly above the trip pill it re-attaches to.
   `fixed` lifts it out of #hud's flex flow without touching the markup. */
#recenter {
  position: fixed;
  left: 50%;
  bottom: var(--t-bottom-base);
  transform: translateX(-50%);
  width: auto;
  height: 50px;
  gap: 10px;
  padding: 0 24px;
  border-radius: 25px;
  background: var(--t-panel-opaque);
  color: var(--t-blue);
  z-index: 6;
  animation: t-up .3s var(--t-ease-pop) both;
}
#recenter:hover, #recenter:active { background: var(--t-hl); color: var(--t-blue); }
#recenter.needs-recenter { color: var(--t-blue); }
.hud-btn--labeled { width: auto; }
.hud-btn__label {
  font-size: 16px;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  white-space: nowrap;
}
/* The action stack's buttons are labelled pills. A gear and a map-pin-plus
   are not self-evident at arm's length in a moving car, and the two controls
   a driver reaches for while driving are the two worth spelling out. */
#hud .hud-btn--labeled {
  width: auto;
  height: 54px;
  gap: 10px;
  padding: 0 20px 0 18px;
  border-radius: 27px;
  justify-content: flex-start;
}
#hud .hud-btn--labeled .hud-btn__label { display: inline; }

/* Free-tier lock on the orientation toggle. */
#compass.is-locked {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
  position: relative;
}
#compass.is-locked::after {
  content: '★';
  position: absolute;
  top: 6px;
  right: 8px;
  font-size: 9px;
  color: var(--t-blue);
  letter-spacing: 0;
}

/* Legacy toggles kept for the debug surfaces that still render them. */
#lite-toggle.active { color: var(--t-amber); }
#theme-toggle[data-mode="dark"]  { color: var(--t-blue); }
#theme-toggle[data-mode="light"] { color: var(--t-amber); }
#theme-toggle[data-mode="auto"]  { color: var(--t-text-2); }
#rechoose-route { color: var(--t-text-2); }
#route-rechoose:hover { background: var(--t-btn-press); color: var(--t-text); }

/* ============================================================
   LEVER TOAST: the one shared transient slider (zoom / tilt).
   Top-centre, configured per lever in app.js. The thumb and fill
   transition so a stepper tap visibly sweeps the thumb along the
   scale; .dragging kills the transition so it tracks the finger.
   ============================================================ */
#lever-toast {
  position: fixed;
  left: 50%;
  top: calc(var(--t-gutter) + 56px);
  transform: translateX(-50%) translateY(-8px);
  z-index: 7;
  display: flex;
  align-items: center;
  gap: 12px;
  width: min(540px, calc(100vw - 240px));
  height: 64px;
  padding: 0 18px;
  background: var(--t-panel-opaque);
  border: 0;
  border-radius: 32px;
  box-shadow: var(--t-shadow);
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  color: var(--t-text);
  opacity: 0;
  pointer-events: none;
  transition: opacity .22s var(--t-ease), transform .22s var(--t-ease);
}
#lever-toast.visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
  pointer-events: auto;
}
#lever-toast.no-anim, #lever-toast.no-anim * { transition: none !important; }
.lever-toast__label {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--t-text-2);
  flex: none;
}
.lever-toast__icon {
  display: inline-flex;
  color: var(--t-text-2);
  flex: none;
}
.lever-toast__track {
  position: relative;
  flex: 1;
  align-self: stretch;   /* full 64px hit area; the visual rail is drawn inside */
  cursor: pointer;
  touch-action: none;    /* the drag IS the gesture; don't let the page pan */
  outline: none;
  /* Half a thumb of slack each side so the thumb centre can reach 0% and 100%
     without the thumb poking out of the track box. */
  margin: 0 19px;
}
.lever-toast__track::before {
  content: '';
  position: absolute;
  left: -19px; right: -19px;
  top: 50%; height: 5px;
  transform: translateY(-50%);
  border-radius: 3px;
  background: var(--t-btn);
}
.lever-toast__tick {
  position: absolute;
  top: 50%;
  width: 2px; height: 12px;
  transform: translate(-50%, -50%);
  background: var(--t-divider);
}
.lever-toast__fill {
  position: absolute;
  left: -19px;
  top: 50%; height: 5px;
  transform: translateY(-50%);
  border-radius: 3px;
  background: var(--t-blue);
  transition: width .3s cubic-bezier(.33, 1, .68, 1);
}
.lever-toast__thumb {
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 38px; height: 38px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--t-blue);
  color: #fff;
  font-size: 13.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 2px 10px rgba(0, 0, 0, .35);
  pointer-events: none;  /* the track handles the pointer; the thumb is a readout */
  transition: left .3s cubic-bezier(.33, 1, .68, 1);
}
#lever-toast.dragging .lever-toast__fill,
#lever-toast.dragging .lever-toast__thumb { transition: none; }

/* ============================================================
   TURN-BY-TURN: the trip pill (bottom centre).

   The maneuver banner that used to live top-centre is GONE. The
   trip-plan card in the nav column carries the instruction, the
   column cannot be closed while a route is running, and a second
   copy floating over the map was the same sentence twice
   competing for the same glance. Its lane strip and "then"
   preview moved into the card with it (see .route-lanes /
   .route-then under the turn card).
   ============================================================ */
#nav-overlay { display: contents; }

/* Trip pill: arrival / remaining / distance, bottom centre. */
#nav-eta {
  position: fixed;
  bottom: var(--t-gutter-b);
  left: 50%;
  transform: translateX(-50%);
  height: 64px;
  z-index: 6;
  display: flex;
  align-items: stretch;
  gap: 0;
  padding: 0 8px;
  background: var(--t-panel-opaque);
  border: 0;
  border-radius: var(--t-radius-lg);
  box-shadow: var(--t-shadow);
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  animation: t-up .4s var(--t-ease-pop) both;
}
/* Journey progress rail, the foot of the ETA strip, inside its radius.
   Deliberately 3px and unlabelled: it is peripheral vision, not a readout,
   and the three values above it are what the driver actually reads. Hidden
   until there is an honest fraction to show (tripProgressFraction() returns
   null before the first route resolves; zero would be a claim we cannot
   make yet). */
#nav-progress {
  position: absolute;
  left: 8px; right: 8px; bottom: 0;
  height: 3px;
  border-radius: 0 0 var(--t-radius-lg) var(--t-radius-lg);
  overflow: hidden;
  background: var(--t-hl);
  pointer-events: none;
}
#nav-progress[hidden] { display: none; }
#nav-progress-fill {
  height: 100%;
  width: 0;
  background: var(--t-blue);
  border-radius: inherit;
  /* Eases rather than jumps, but only over a fraction of the ~1 Hz fix
     cadence, so it is always settled before the next update rather than
     permanently chasing one. */
  transition: width .35s linear;
}
@media (prefers-reduced-motion: reduce) { #nav-progress-fill { transition: none; } }
/* Whose route (ADR-0029 D14): a small tab straddling the pill's top edge.
   Absolutely placed so the three readouts below keep their exact layout. */
.nav-route-source {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-50%, -55%);
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--t-panel-opaque);
  box-shadow: var(--t-shadow);
  font-size: 11.5px;
  line-height: 1.2;
  color: var(--t-text);
  white-space: nowrap;
  pointer-events: none;
}
.nav-route-source[hidden] { display: none; }
.nav-route-source-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
  flex: 0 0 auto;
}
.nav-eta-cell {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  padding: 0 20px;
  min-width: 92px;
}
.nav-eta-value {
  font-size: 20px;
  font-weight: 500;
  color: var(--t-text);
  letter-spacing: 0;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.nav-eta-label {
  margin-top: 0;
  font-size: 11.5px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--t-text-2);
}
/* The distance cell's label is the unit, not a caption: app.js writes KM/MI
   into it. Lowercase so it reads as "141 km" and not as a third heading. */
#eta-distance-label { text-transform: lowercase; }
.nav-eta-sep {
  flex: 0 0 1px;
  background: var(--t-divider);
  margin: 16px 0;
}
.nav-eta-btn {
  appearance: none; -webkit-appearance: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 0 20px;
  min-width: 78px;
  background: transparent;
  border: 0;
  border-left: 1px solid var(--t-divider);
  color: var(--t-text-2);
  font: inherit;
  cursor: pointer;
  transition: background .15s var(--t-ease);
}
.nav-eta-btn:hover, .nav-eta-btn:active { background: var(--t-hl); color: var(--t-text); }

/* Bottom-centre stack. The trip pill owns the floor; anything else in that
   column is lifted clear of whatever is actually on screen, and this is the
   one place that arithmetic lives. */
body:has(#nav-eta:not([hidden])) { --t-bottom-base: calc(var(--t-gutter-b) + 76px); }
#incident-alert-banner { bottom: var(--t-bottom-base); }
/* The recenter pill lifts by the outage banner's own height plus a 16px gap.
   (The notification stack lifts everything by a MEASURED variable instead,
   see --cd-notify-lift below, which is why this is the only hand-summed
   offset left in the column.) */
body:has(#incident-alert-banner.visible) #recenter { bottom: calc(var(--t-bottom-base) + 62px); }

/* ---------- transient toast ---------- */
#nav-toast {
  position: fixed;
  top: calc(var(--t-gutter) + 56px);
  left: 50%;
  transform: translateX(-50%) translateY(-8px);
  z-index: 7;
  max-width: min(520px, calc(100vw - 32px));
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 24px;
  min-height: 50px;
  background: var(--t-panel-opaque);
  border: 0;
  border-radius: 25px;
  box-shadow: var(--t-shadow);
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  color: var(--t-text);
  font-size: 16px;
  font-weight: 500;
  letter-spacing: 0;
  text-align: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity .22s var(--t-ease), transform .22s var(--t-ease);
}
#nav-toast.visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
.toast-body { flex: 1 1 auto; min-width: 0; }
.toast-text { display: block; }
.toast-sub { display: block; }
.toast-glyph, .toast-dismiss, .toast-sub { display: none; }

/* ── banner shape ──
   The design has TWO transient surfaces and they are not interchangeable: a
   rounded pill for a confirmation the driver does not have to act on, and a
   squared banner with a glyph, a reason line and controls for one they do.
   showActionToast() sets data-shape; showToast() clears it. Same element, so
   there is still exactly one place on screen where a transient notice
   appears. */
#nav-toast[data-shape="banner"] {
  max-width: min(560px, calc(100vw - 32px));
  padding: 13px 12px 13px 20px;
  gap: 14px;
  border-radius: var(--t-radius);
  border-left: 3px solid var(--t-blue);
  text-align: left;
  font-weight: 400;
  pointer-events: auto;
}
#nav-toast[data-shape="banner"] .toast-glyph,
#nav-toast[data-shape="banner"] .toast-dismiss { display: inline-flex; }
#nav-toast[data-shape="banner"] .toast-sub:not([hidden]) { display: block; }
#nav-toast[data-shape="banner"] .toast-text {
  font-size: 15.5px;
  font-weight: 500;
  color: var(--t-text);
}
#nav-toast[data-shape="banner"] .toast-sub {
  margin-top: 1px;
  font-size: 13px;
  font-weight: 400;
  color: var(--t-text-2);
  line-height: 1.35;
}
.toast-glyph {
  flex: none;
  align-items: center;
  width: 21px; height: 21px;
  color: var(--t-blue);
}
.toast-dismiss {
  flex: none;
  align-items: center;
  justify-content: center;
  width: 34px; height: 34px;
  background: transparent;
  border: 0;
  border-radius: 50%;
  color: var(--t-text-3);
  cursor: pointer;
}
.toast-dismiss:hover, .toast-dismiss:active { background: var(--t-hl); color: var(--t-text); }
#nav-toast[data-tone="warn"] { border-left-color: var(--t-amber); }
#nav-toast[data-tone="warn"] .toast-glyph { color: var(--t-amber); }
.toast-action {
  pointer-events: auto;
  flex: none;
  margin-left: 4px;
  padding: 0 16px;
  height: 38px;
  border-radius: var(--t-radius-sm);
  background: var(--t-btn);
  color: var(--t-blue);
  border: none;
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  cursor: pointer;
}
.toast-action:active { background: var(--t-btn-press); }

/* The top-centre column is the transient surfaces' alone now that the
   maneuver banner has left it, so there is nothing to drop under. */

/* ---------- route-planning banner ----------
   Same shell as the notification banner: it IS one. A destination tap starts
   a round trip that can take seconds on a car's connection, and the old flow
   showed nothing for it, so the tap read as ignored, and drivers tapped
   again. Persistent, cancellable, cleared by the resolve or the failure. */
#route-loading {
  position: fixed;
  top: calc(var(--t-gutter) + 56px);
  left: 50%;
  transform: translateX(-50%) translateY(-8px);
  z-index: 9;
  display: flex;
  align-items: center;
  gap: 14px;
  max-width: min(560px, calc(100vw - 32px));
  padding: 13px 12px 13px 20px;
  background: var(--t-panel-opaque);
  border: 0;
  border-left: 3px solid var(--t-blue);
  border-radius: var(--t-radius);
  box-shadow: var(--t-shadow);
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  color: var(--t-text);
  opacity: 0;
  pointer-events: none;
  transition: opacity .22s var(--t-ease), transform .22s var(--t-ease);
}
#route-loading.visible { opacity: 1; transform: translateX(-50%) translateY(0); pointer-events: auto; }
.route-loading__spinner {
  flex: none;
  width: 20px; height: 20px;
  border-radius: 50%;
  border: 2.5px solid var(--t-btn-press);
  border-top-color: var(--t-blue);
  animation: t-spin .9s linear infinite;
}
@keyframes t-spin { to { transform: rotate(360deg); } }
.route-loading__body { flex: 1 1 auto; min-width: 0; }
.route-loading__title {
  display: block;
  font-size: 15.5px;
  font-weight: 500;
  color: var(--t-text);
}
.route-loading__sub {
  display: block;
  margin-top: 1px;
  font-size: 13px;
  color: var(--t-text-2);
  line-height: 1.35;
}
.route-loading__cancel {
  flex: none;
  width: 38px; height: 38px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  border-radius: 50%;
  color: var(--t-text-3);
  cursor: pointer;
}
.route-loading__cancel:hover, .route-loading__cancel:active { background: var(--t-hl); color: var(--t-text); }
/* On the low-end profile every animation is frozen, so a spinner would sit
   there as a static ring reading "broken". Pulse the whole banner instead. */
html.cd-no-motion .route-loading__spinner { border-top-color: var(--t-blue); }

/* ============================================================
   AHEAD-OF-TRAVEL ANNOUNCEMENTS (ALL-52)

   Two stacks, one card. Which stack a card lands in is the whole
   difference between the "notify" and "alert" tiers, so the card
   itself is one set of rules and the stacks own position, order
   and the loud extras (lamp strip, sound is app.js's half).

   Anatomy is the design library's pin-notification card: accent
   edge, pin glyph, title + sub, distance, dismiss, and, for a pin
   the driver can vote on, the divided confirm/dispute row. One card
   is both the heads-up and the vote; the separate drive-by prompt
   that used to be the second is gone.
   ============================================================ */

/* The alert stack hangs under the nav block on the left. Its top is the
   drawer's own bottom edge, published by app.js as --cd-alert-stack-top
   (a ResizeObserver on #nav-drawer keeps it current; the drawer is 56px
   collapsed and most of the column expanded, and the alerts have to follow).
   The fallback is the collapsed height, which is the driving case.
   Strictly BELOW the drawer, never over it: the room comes out of the
   drawer's max-height via --cd-alert-stack-reserve above (ALL-185). */
body { --cd-alert-stack-reserve: 0px; }
#cd-alert-stack {
  position: fixed;
  top: var(--cd-alert-stack-top, calc(var(--t-gutter) + 68px));
  left: var(--t-gutter);
  z-index: 12;
  width: var(--t-col-w);
  max-width: calc(100vw - var(--t-gutter) * 2);
  display: flex;
  flex-direction: column;
  gap: 8px;
  pointer-events: none;
}
/* Bottom-centre, growing upward off the trip pill. It shares the column with
   the outage banner, and it is the FLOOR of that
   column: everything above lifts by --cd-notify-lift, which app.js measures
   from this element. Spelling the lift as one measured variable is what keeps
   this out of the combinatorial :has() arithmetic the rest of the column had
   to spell out by hand. */
#cd-notify-stack {
  position: fixed;
  bottom: var(--t-bottom-base);
  left: 50%;
  transform: translateX(-50%);
  z-index: 8;
  width: min(420px, calc(100vw - 32px));
  display: flex;
  flex-direction: column-reverse;
  gap: 8px;
  pointer-events: none;
}
body { --cd-notify-lift: 0px; }
/* margin-bottom, NOT another `bottom` rule: the three surfaces above already
   carry a stack of :has()-driven `bottom` values for each other, and a fourth
   competing `bottom` at the same specificity would silently win over them.
   A margin on a bottom-anchored fixed box adds to whatever `bottom` resolved
   to, which is exactly the composition wanted here. */
#incident-alert-banner,
#recenter { margin-bottom: var(--cd-notify-lift); }

.cd-ann {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  align-items: center;
  gap: 6px 14px;
  padding: 14px 14px 14px 18px;
  background: var(--t-panel-opaque);
  border: 0;
  border-radius: var(--t-radius);
  box-shadow: var(--t-shadow);
  backdrop-filter: var(--t-blur-strong);
  -webkit-backdrop-filter: var(--t-blur-strong);
  color: var(--t-text);
  overflow: hidden;
  pointer-events: auto;
  opacity: 0;
  transform: translateY(10px);
  transition: opacity .2s var(--t-ease), transform .2s var(--t-ease);
}
.cd-ann.visible { opacity: 1; transform: translateY(0); }
/* Alerts enter from the card above them, notifications from the pill below;
   each stack's newest card slides in from the direction that stack grows. */
#cd-alert-stack .cd-ann { transform: translateY(-10px); }
#cd-alert-stack .cd-ann.visible { transform: translateY(0); }

.cd-ann__glyph {
  width: 38px;
  height: 38px;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
}
.cd-ann__glyph svg { width: 100%; height: 100%; }
.cd-ann__body { min-width: 0; }
.cd-ann__title {
  display: block;
  font-size: 17px;
  font-weight: 500;
  letter-spacing: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cd-ann__sub {
  display: block;
  margin-top: 1px;
  font-size: 13.5px;
  color: var(--t-text-2);
  letter-spacing: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cd-ann__dist {
  font-size: 20px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.cd-ann__dist span { font-size: 12.5px; color: var(--t-text-2); }
.cd-ann__close {
  width: 34px;
  height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  border-radius: 50%;
  color: var(--t-text-3);
  cursor: pointer;
}
.cd-ann__close:hover, .cd-ann__close:active { background: var(--t-hl); color: var(--t-text); }
/* The divided action row (design: "Still there | Gone"). Spans the grid,
   two equal targets 44px tall (a vote at the wheel is a tap, not a pick),
   with a hairline between them so the halves read as two answers rather
   than one wide button. */
.cd-ann--votable { padding-bottom: 6px; }
.cd-ann__actions {
  grid-column: 1 / -1;
  display: flex;
  align-items: stretch;
  gap: 4px;
  margin: 0 -6px;
}
.cd-ann__vote {
  appearance: none; -webkit-appearance: none;
  flex: 1 1 0;
  height: 44px;
  border: 0;
  border-radius: var(--t-radius-sm);
  background: transparent;
  color: var(--t-text-2);
  font-family: inherit;
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0;
  cursor: pointer;
}
.cd-ann__vote--up { color: var(--t-blue); }
.cd-ann__vote:hover, .cd-ann__vote:active { background: var(--t-hl); }
.cd-ann__vote[disabled] { opacity: .4; cursor: default; }
.cd-ann__vsep { width: 1px; background: var(--t-divider); margin: 8px 0; flex: none; }

/* The fork advice (ALL-201 02 §6.1). Same card shell as a pin, but the body is
   a short stack (reason, saving, Autopilot) that WRAPS rather than truncates,
   and the action row is one full-width primary. */
/* 2026-09-21 rework: the glyph slot is gone (the advice has no pin), the
   title row is action · distance · ✕, and the lines get the card's full
   width in a row of their own, so they no longer wrap a word at a time.
   The saving is the headline number, in the card's green; the Autopilot line
   is a quieter note. */
.cd-ann--advice {
  grid-template-columns: 1fr auto auto;
  row-gap: 6px;
  padding: 14px 10px 8px 18px;
}
.cd-ann--advice .cd-ann__glyph { display: none; }
.cd-ann--advice .cd-ann__title { font-size: 19px; font-weight: 600; white-space: normal; }
.cd-ann__alines {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding-right: 8px;
}
.cd-ann--advice .cd-ann__aline {
  display: block;
  font-size: 15px;
  line-height: 1.3;
  color: var(--t-text);
  letter-spacing: 0;
  white-space: normal;
  text-wrap: pretty;
}
.cd-ann--advice .cd-ann__aline--save {
  font-size: 17px;
  font-weight: 600;
  color: var(--cd-ann-color, var(--t-blue));
}
.cd-ann--advice .cd-ann__aline--note {
  margin-top: 2px;
  font-size: 13.5px;
  color: var(--t-text-2);
}
.cd-ann__take {
  appearance: none; -webkit-appearance: none;
  grid-column: 1 / -1;
  width: 100%;
  height: 44px;
  border: 0;
  border-radius: var(--t-radius-sm);
  background: var(--cd-ann-color, var(--t-blue));
  color: #fff;
  font-family: inherit;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0;
  cursor: pointer;
}
.cd-ann__take:hover, .cd-ann__take:active { filter: brightness(1.08); }

/* The accent edge. `notify` gets a flat 3px left border in the category's own
   colour, the same edge every other banner in the app uses. */
#cd-notify-stack .cd-ann { border-left: 3px solid var(--cd-ann-color, var(--t-blue)); }

/* `alert` gets the strip along the top instead, and the strip MOVES. Which
   motion is per category (see alert-prefs.js): a lightbar for police, a fast
   blink for something happening right now, a slow breathe for something that
   is simply there, a runner for queueing traffic, chevrons for works.
   The strip is the only animated thing on the card. The card itself must not
   move, or the text stops being readable at a glance. */
#cd-alert-stack .cd-ann__lamp {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 4px;
  display: flex;
  overflow: hidden;
}
#cd-alert-stack .cd-ann__lamp > span { flex: 1; }
/* Lamp: two halves in ANTI-PHASE, which is what makes it read as a lightbar
   rather than as a bar that dims. Same signal as the map's police flash. */
.cd-ann[data-anim="lamp"] .cd-ann__lamp > span:first-child {
  background: var(--t-red); color: var(--t-red);
  animation: t-lamp 1.15s ease-in-out infinite;
}
.cd-ann[data-anim="lamp"] .cd-ann__lamp > span:last-child {
  background: var(--t-blue); color: var(--t-blue);
  animation: t-lamp 1.15s ease-in-out .575s infinite;
}
.cd-ann[data-anim="flash"] .cd-ann__lamp > span,
.cd-ann[data-anim="pulse"] .cd-ann__lamp > span {
  background: var(--cd-ann-color);
  color: var(--cd-ann-color);
  animation: cd-ann-pulse var(--cd-ann-dur, 1.6s) ease-in-out infinite;
}
.cd-ann[data-anim="flash"] .cd-ann__lamp > span { --cd-ann-dur: .5s; }
.cd-ann[data-anim="flow"] .cd-ann__lamp > span {
  background: linear-gradient(90deg, transparent 0%, var(--cd-ann-color) 35%, var(--cd-ann-color) 65%, transparent 100%);
  background-size: 200% 100%;
  animation: cd-ann-flow 1.6s linear infinite;
}
.cd-ann[data-anim="stripes"] .cd-ann__lamp > span {
  background: repeating-linear-gradient(-45deg, var(--cd-ann-color) 0 12px, transparent 12px 24px);
  background-size: 34px 4px;
  animation: cd-ann-stripes .7s linear infinite;
}
@keyframes cd-ann-pulse {
  0%, 100% { opacity: 1; box-shadow: 0 0 10px 2px currentColor; }
  50%      { opacity: .3; box-shadow: none; }
}
@keyframes cd-ann-flow   { from { background-position: 200% 0; } to { background-position: -100% 0; } }
@keyframes cd-ann-stripes { to  { background-position: 34px 0; } }
/* Low-end profile freezes animation everywhere else; a static strip in the
   category colour still carries the signal, so the card degrades rather than
   disappears. */
html.cd-no-motion .cd-ann__lamp > span {
  animation: none !important;
  background: var(--cd-ann-color) !important;
  opacity: 1;
}
html.cd-no-motion .cd-ann[data-anim="lamp"] .cd-ann__lamp > span:first-child { background: var(--t-red) !important; }
html.cd-no-motion .cd-ann[data-anim="lamp"] .cd-ann__lamp > span:last-child  { background: var(--t-blue) !important; }


/* ---------- persistent incident-feed outage banner ---------- */
#incident-alert-banner {
  position: fixed;
  left: 50%;
  transform: translateX(-50%) translateY(8px);
  z-index: 6;
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: min(560px, calc(100vw - 32px));
  padding: 13px 20px;
  background: var(--t-panel-opaque);
  border: 0;
  border-left: 3px solid var(--t-amber);
  border-radius: var(--t-radius);
  box-shadow: var(--t-shadow);
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  color: var(--t-text);
  font-size: 15px;
  line-height: 1.35;
  letter-spacing: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity .22s var(--t-ease), transform .22s var(--t-ease), bottom .22s var(--t-ease);
}
#incident-alert-banner.visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
.incident-alert-banner__dot {
  flex: none;
  width: 9px; height: 9px;
  border-radius: 50%;
  background: var(--t-amber);
  animation: t-incident-pulse 1.8s ease-in-out infinite;
}
@keyframes t-incident-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.35; }
}
@media (prefers-reduced-motion: reduce) {
  .incident-alert-banner__dot { animation: none; }
}

/* ---------- police-ahead flash ---------- */
/* Lightbar: left and right halves strobe red/blue in anti-phase. Literal
   colours: this is a lightbar, not a themed surface. */
#police-alert {
  position: fixed;
  inset: 0;
  z-index: 30;
  pointer-events: none;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* The flash lives on the road now. The police pin's lights layer runs
   boosted for the same three seconds (public/app.js firePoliceStrobe →
   layers/lightbar.js). What the screen keeps is an EDGE vignette pulsing
   red/blue: peripheral, unmistakable, and the map stays readable in the
   middle, which is where the driver is looking for the police. The old
   two-half wash covered the whole scene at exactly that moment. */
#police-alert::before {
  content: '';
  position: absolute;
  inset: 0;
  animation: t-police-strobe .5s steps(1) infinite;
}
@keyframes t-police-strobe {
  0%, 100% { box-shadow: inset 0 0 140px 30px rgba(214, 67, 69, 0.62); }
  50%      { box-shadow: inset 0 0 140px 30px rgba(62, 107, 226, 0.66); }
}
.police-alert-label {
  position: relative;
  z-index: 1;
  padding: 18px 34px;
  border-radius: var(--t-radius);
  background: rgba(0, 0, 0, 0.72);
  color: #fff;
  font-size: clamp(26px, 4.6vw, 46px);
  font-weight: 500;
  letter-spacing: .12em;
  border: 0;
}

/* ============================================================
   MAP CONTEXT MENU (long-press / right-click) and the in-map
   alert popup. Both are the design's "rich tooltip": an
   --t-info-bg card with a divided action row.
   ============================================================ */
#map-context-menu {
  position: absolute;
  z-index: 8;
  width: 320px;
  --ctx-tip-x: 50%;
  background: var(--t-info-bg);
  border: 0;
  border-radius: var(--t-radius-lg);
  box-shadow: var(--t-shadow);
  backdrop-filter: var(--t-blur);
  -webkit-backdrop-filter: var(--t-blur);
  padding: 0;
  animation: t-pop .25s var(--t-ease-pop) both;
}
/* The tip is what makes this belong to the spot the driver pressed rather
   than float over the map. It tracks the press point even when the card was
   nudged sideways to stay on screen, and flips to the top edge when the card
   had to drop below. */
.ctx-tip {
  position: absolute;
  left: var(--ctx-tip-x);
  bottom: -6px;
  width: 14px; height: 14px;
  margin-left: -7px;
  transform: rotate(45deg);
  background: var(--t-info-bg);
}
#map-context-menu.is-below .ctx-tip { bottom: auto; top: -6px; }
.ctx-head { padding: 14px 16px 8px; }
.ctx-title {
  display: block;
  font-size: 16px;
  font-weight: 500;
  color: var(--t-text);
}
#map-context-menu .ctx-coords {
  display: block;
  margin-top: 3px;
  padding: 0;
  font-size: 12.5px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--t-text-3);
  border: 0;
  font-variant-numeric: tabular-nums;
}
.ctx-actions {
  display: flex;
  gap: 4px;
  padding: 6px 8px 10px;
}
#map-context-menu .ctx-item {
  appearance: none; -webkit-appearance: none;
  flex: 1 1 0;
  min-width: 0;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  color: var(--t-text-2);
  border: 0;
  border-radius: var(--t-radius-sm);
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
}
#map-context-menu .ctx-item:hover,
#map-context-menu .ctx-item:active { background: var(--t-hl); color: var(--t-text); }
#map-context-menu .ctx-item--primary {
  flex: 1.2 1 0;
  background: var(--t-blue);
  color: #fff;
}
#map-context-menu .ctx-item--primary:hover,
#map-context-menu .ctx-item--primary:active { background: var(--t-blue); filter: brightness(1.15); color: #fff; }

.alert-popup .mapboxgl-popup-content {
  background: var(--t-info-bg);
  color: var(--t-text);
  border: 0;
  border-radius: var(--t-radius-lg);
  padding: 12px 14px;
  font-size: 13.5px;
  line-height: 1.5;
  box-shadow: var(--t-shadow);
  backdrop-filter: var(--t-blur);
  -webkit-backdrop-filter: var(--t-blur);
}
.alert-popup .mapboxgl-popup-tip { display: none; }
.alert-popup .mapboxgl-popup-close-button {
  background: transparent;
  border: 0;
  color: var(--t-text-3);
  font-size: 18px;
  padding: 2px 8px;
  cursor: pointer;
}
.alert-popup .alert-popup__head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 0 6px;
}
/* Colour-coded disc with the kind's initial. The map pin already carries the
   full icon; at popup scale the disc is doing colour identification, and an
   initial survives the 34px it gets where a detailed glyph does not. */
.alert-popup__glyph {
  flex: none;
  width: 34px; height: 34px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 13px;
  font-weight: 700;
}
.alert-popup__body { flex: 1 1 auto; min-width: 0; }
.alert-popup .title {
  display: block;
  font-weight: 500;
  font-size: 15px;
  letter-spacing: 0;
}
.alert-popup .meta {
  display: block;
  font-size: 12.5px;
  color: var(--t-text-2);
  letter-spacing: 0;
}
.alert-popup__meta {
  padding: 0 0 4px;
  font-size: 12.5px;
  color: var(--t-text-2);
}
/* Two text buttons split by a hairline, the firmware's own action row. */
.alert-popup-actions {
  display: flex;
  align-items: stretch;
  gap: 4px;
  margin: 6px -6px -4px;
}
.alert-popup-act {
  flex: 1 1 0;
  min-width: 0;
  height: 40px;
  background: transparent;
  border: 0;
  border-radius: var(--t-radius-sm);
  color: var(--t-text-2);
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
}
.alert-popup-act--yes { color: var(--t-blue); }
.alert-popup-act:hover, .alert-popup-act:active { background: var(--t-hl); }
.alert-popup-act__sep {
  flex: none;
  width: 1px;
  margin: 6px 0;
  background: var(--t-divider);
}

/* ============================================================
   MODAL SHEETS: save-favorite, report, charger filters, paywall.
   One backdrop treatment, one card treatment, one CTA row.
   ============================================================ */
#save-fav-overlay,
#report-overlay,
#charger-filter-overlay,
#alert-prefs-overlay,
#charger-paywall-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: var(--t-scrim);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}
/* Anchored to the TOP: the Tesla touchscreen keyboard takes the bottom half of
   the screen the moment the NAME field gains focus. */
#save-fav-overlay { align-items: flex-start; }

#save-fav-sheet,
#charger-filter-sheet,
#alert-prefs-sheet,
#charger-paywall-sheet {
  position: relative;
  width: 100%;
  background: var(--t-panel-opaque);
  backdrop-filter: blur(28px);
  -webkit-backdrop-filter: blur(28px);
  border: 0;
  border-radius: var(--t-radius-lg);
  box-shadow: var(--t-shadow);
  color: var(--t-text);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: t-pop .3s var(--t-ease-pop) both;
}
#save-fav-sheet {
  max-width: 760px;
  /* The whole viewport, not its upper half: the in-car keyboard RESIZES the
     pane rather than covering it, so the old 50vh cap was half of what was
     already the half above the keyboard, and the storage tiles were clipped
     to a sliver. The overlay is top-anchored with 16px padding either side. */
  max-height: calc(100vh - 32px);
}
/* Wide enough for it, STORAGE and SLOT share a row: five tiles across instead
   of two rows of them, which is what lets the sheet fit above the keyboard
   without scrolling. With SLOT collapsed (local storage) STORAGE takes the row. */
@media (min-width: 640px) {
  #save-fav-sheet .ssheet-body {
    display: grid;
    grid-template-columns: 2fr 3fr;
    column-gap: 16px;
    align-items: start;
  }
  #save-fav-sheet .ssheet-body > * { grid-column: 1 / -1; }
  #save-fav-sheet .ssheet-body > .ssheet-field:has(#ssheet-storage) { grid-column: 1; }
  #save-fav-sheet .ssheet-body > #ssheet-slot-field { grid-column: 2; }
  #save-fav-sheet .ssheet-body > .ssheet-field:has(#ssheet-storage):has(+ #ssheet-slot-field.is-hidden) { grid-column: 1 / -1; }
}
#charger-filter-sheet {
  max-width: 680px;
  /* No text input in this sheet. The Tesla keyboard never opens, so it can
     use the full viewport height. */
  max-height: calc(100vh - 32px);
}
#charger-filter-sheet .ssheet-body { overflow-y: auto; }
#charger-paywall-sheet { max-width: 520px; }

/* Alerts-by-type sheet (ALL-52). Wider than the charger sheet because every
   row carries a four-way control AND has to stay tappable at the wheel, and
   taller for the same reason the charger sheet is: no text input, so the
   Tesla keyboard never opens. */
#alert-prefs-sheet {
  max-width: 760px;
  max-height: calc(100vh - 32px);
}
#alert-prefs-sheet .ssheet-body { overflow-y: auto; }
/* The legend earns its place: "Pin" and "Notify" are not self-evident labels
   on a 44px button, and the sheet is opened rarely enough that re-reading the
   key each time is the normal case rather than the failure case. */
.apf-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  padding: 4px 22px 10px;
  font-size: 13px;
  color: var(--t-text-3);
  letter-spacing: 0;
}
.apf-legend b { color: var(--t-text-2); font-weight: 500; }
/* ALL-214: Alert sounds is off, so Alert is a card and nothing speaks. Sits
   between the header and the legend it corrects, on the same info-card ground
   the other Tesla-native notices use, with the amber reserved for the one
   word-level fact (the title) rather than a warning-coloured slab: it is a
   state the driver chose, not a fault. The button is the Settings switch. */
.apf-sounds-off {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 2px 22px 10px;
  padding: 8px 8px 8px 14px;
  border-radius: var(--t-radius-sm);
  background: var(--t-info-bg);
  font-size: 14px;
  letter-spacing: 0;
  color: var(--t-text-2);
}
.apf-sounds-off__text { flex: 1 1 auto; min-width: 0; }
.apf-sounds-off__text b { color: var(--t-amber); font-weight: 500; }
.apf-sounds-off__btn { flex: 0 0 auto; height: 44px; padding: 0 18px; font-size: 15px; }
/* Set-every-row control (ALL-194). Deliberately the SAME four-way control as
   the rows below, aligned to the same right edge, so it reads as the row that
   drives all the rows rather than a new kind of thing. The rule under it is
   what separates a control ON the list from the first item IN it. It lives
   outside the scroll container, so it never scrolls away. */
.apf-bulk {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 0 22px;
  padding: 2px 0 10px;
  border-bottom: 1px solid var(--t-divider);
}
.apf-bulk__label {
  min-width: 0;
  font-size: 15px;
  letter-spacing: 0;
  color: var(--t-text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.apf-bulk .setting-segmented { width: 296px; flex: 0 0 auto; }
.apf-bulk .setting-segmented button { height: 40px; font-size: 14px; padding: 0 6px; }
.apf-group { display: flex; flex-direction: column; gap: 4px; }
.apf-row {
  display: grid;
  grid-template-columns: 34px 1fr auto;
  align-items: center;
  gap: 12px;
  padding: 5px 0;
}
.apf-row__icon {
  width: 34px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.apf-row__icon svg { width: 100%; height: 100%; }
.apf-row__label {
  font-size: 16px;
  letter-spacing: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* A row set to Hidden dims its own name: the map is missing those pins, and
   that fact should be visible while scrolling rather than only in the control. */
.apf-row[data-tier="off"] .apf-row__label,
.apf-row[data-tier="off"] .apf-row__icon { opacity: .42; }
.apf-row .setting-segmented { width: 296px; }
.apf-row .setting-segmented button { height: 44px; font-size: 14px; padding: 0 6px; }
/* "Flash + sound" (ALL-32, D1): one checkbox on each Police-and-cameras row,
   the strobe + police cue on top of the spoken sentence. Present at every
   tier so the row keeps its shape; live only at Alert, dimmed otherwise;
   the option is visible, and so is the fact that it needs the tier. Same
   accent checkbox as the debug rows, no border, Tesla-native. */
.apf-row--watch { grid-template-columns: 34px 1fr auto auto; }
.apf-flash {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 0 4px 0 6px;
  font-size: 13.5px;
  letter-spacing: 0;
  color: var(--t-text-2);
  white-space: nowrap;
  cursor: pointer;
  transition: opacity .15s var(--t-ease);
}
.apf-flash input[type="checkbox"] {
  width: 20px;
  height: 20px;
  margin: 0;
  flex: 0 0 auto;
  accent-color: var(--t-blue);
  cursor: pointer;
}
.apf-flash:has(input:checked) { color: var(--t-text); }
.apf-flash:has(input:disabled) { opacity: .35; cursor: default; }
.apf-flash input:disabled { cursor: default; }

/* The report sheet is the design's bottom sheet: it slides up from the edge
   the Report button lives on, and the map stays visible above it. */
#report-overlay { align-items: flex-end; padding: 0; }
#report-sheet {
  position: relative;
  width: 100%;
  max-width: 640px;
  background: var(--t-panel-opaque);
  backdrop-filter: blur(28px);
  -webkit-backdrop-filter: blur(28px);
  border: 0;
  border-radius: var(--t-radius-lg) var(--t-radius-lg) 0 0;
  box-shadow: 0 -4px 24px rgba(0, 0, 0, .35);
  color: var(--t-text);
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - 32px);
  animation: t-up .35s var(--t-ease-pop) both;
}

.ssheet-header {
  padding: 18px 22px 8px;
  border: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ssheet-title {
  font-weight: 500;
  font-size: 20px;
  letter-spacing: 0;
}
.ssheet-subtitle {
  font-size: 14px;
  color: var(--t-text-2);
  letter-spacing: 0;
  text-transform: none;
}
.ssheet-body {
  padding: 12px 22px 6px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  overflow-y: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.ssheet-body::-webkit-scrollbar { width: 0; height: 0; }
.ssheet-field { display: flex; flex-direction: column; gap: 8px; }
.ssheet-field-label {
  font-size: 13px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--t-text-2);
}
#ssheet-name {
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  height: 52px;
  padding: 0 16px;
  background: var(--t-search);
  border: 0;
  border-radius: var(--t-radius-sm);
  color: var(--t-text);
  font-family: inherit;
  font-size: 16.5px;
}
#ssheet-name:focus { outline: none; box-shadow: inset 0 0 0 2px var(--t-blue); }

.ssheet-storage,
.ssheet-slots { display: grid; gap: 6px; }
.ssheet-storage { grid-template-columns: 1fr 1fr; }
.ssheet-slots { grid-template-columns: repeat(3, 1fr); }
.ssheet-tile {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 12px 8px;
  min-height: 88px;
  background: var(--t-btn);
  border: 0;
  border-radius: var(--t-radius-sm);
  color: var(--t-text-2);
  cursor: pointer;
  text-align: center;
  font-family: inherit;
  transition: background .15s var(--t-ease), color .15s var(--t-ease);
}
.ssheet-tile:hover { background: var(--t-btn-press); color: var(--t-text); }
.ssheet-tile.is-selected {
  background: var(--t-toggle-on);
  color: #fff;
}
.ssheet-tile.is-disabled { opacity: 0.45; cursor: not-allowed; }
.ssheet-tile.is-disabled:hover { background: var(--t-btn); color: var(--t-text-2); }
.ssheet-tile__icon { font-size: 22px; line-height: 1; }
.ssheet-tile__label {
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0;
}
.ssheet-tile__sub {
  font-size: 11.5px;
  letter-spacing: 0;
  color: var(--t-text-3);
  text-transform: none;
}
.ssheet-tile.is-selected .ssheet-tile__sub { color: rgba(255, 255, 255, .75); }
.ssheet-tile__check {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #fff;
  color: var(--t-toggle-on);
  display: none;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: bold;
}
.ssheet-tile.is-selected .ssheet-tile__check { display: flex; }
.ssheet-tile__badge {
  position: absolute;
  top: 8px;
  right: 8px;
  font-size: 9px;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: 3px 6px;
  border-radius: 999px;
  border: 0;
  color: #fff;
  background: var(--t-blue);
}
.ssheet-tile.is-disabled .ssheet-tile__badge { background: var(--t-btn-press); color: var(--t-text-2); }

#ssheet-slot-field.is-hidden { display: none; }

.ssheet-cta {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  gap: 8px;
  padding: 12px 16px 16px;
  border-top: 1px solid var(--t-divider);
  background: transparent;
}
.ssheet-btn-delete {
  background: transparent;
  color: var(--t-red);
}
.ssheet-btn-delete:hover { background: var(--t-hl); color: var(--t-red); }
.ssheet-cta > #ssheet-cancel { grid-column: 3; }
.ssheet-cta > #ssheet-save   { grid-column: 4; }
/* The auto columns size to their labels, which on "Save" is four characters,
   a 40px-wide button at the wheel. Floor them at a real target. */
.ssheet-cta .picker-btn { min-width: 116px; padding: 0 20px; }
.ssheet-error {
  font-size: 13.5px;
  color: var(--t-red);
  padding: 10px 14px;
  border: 0;
  border-radius: var(--t-radius-sm);
  background: color-mix(in srgb, var(--t-red) 16%, transparent);
}

.ssheet-upgrade {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border: 0;
  border-radius: var(--t-radius-sm);
  background: color-mix(in srgb, var(--t-blue) 18%, transparent);
  flex-wrap: wrap;
}
.ssheet-upgrade__text {
  flex: 1 1 220px;
  font-size: 13.5px;
  line-height: 1.4;
  color: var(--t-text);
}
.ssheet-upgrade .picker-btn {
  height: 42px;
  font-size: 14px;
  padding: 0 16px;
  flex: 0 0 auto;
}

/* ---------- report sheet body ---------- */
.report-header {
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  padding: 16px 22px 6px;
}
.report-close {
  appearance: none;
  -webkit-appearance: none;
  width: 40px; height: 40px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent;
  border: 0;
  border-radius: 50%;
  color: var(--t-text-2);
  cursor: pointer;
  font-size: 17px;
}
.report-close:hover, .report-close:active { background: var(--t-btn-press); color: var(--t-text); }
.report-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
  padding: 8px 16px 18px;
}
.report-tile {
  appearance: none;
  -webkit-appearance: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: 4px;
  padding: 10px 4px;
  min-height: 104px;
  background: transparent;
  border: 0;
  border-radius: var(--t-radius-sm);
  color: var(--t-text-2);
  cursor: pointer;
  text-align: center;
  font-family: inherit;
  transition: background .15s var(--t-ease);
  animation: t-up .28s ease both;
}
.report-tile:hover, .report-tile:active { background: var(--t-hl); }
.report-tile__icon {
  width: 56px;
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
  filter: drop-shadow(0 5px 7px rgba(0, 0, 0, .3));
}
.report-tile__icon svg { width: 100%; height: 100%; display: block; }
.report-tile__label {
  font-size: 13.5px;
  font-weight: 500;
  letter-spacing: 0;
  line-height: 1.15;
}
@media (max-width: 620px) {
  .report-grid { grid-template-columns: repeat(3, 1fr); }
}

/* ---------- favourite slot badges ---------- */
.fav-item__slot {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: 3px 8px;
  margin-right: 10px;
  border: 0;
  border-radius: 999px;
  background: var(--t-btn);
  color: var(--t-text-2);
}
.fav-item__slot.home   { color: var(--t-blue); }
.fav-item__slot.work   { color: var(--t-green); }
.fav-item__slot.synced { color: var(--t-text); }

/* ── ALL-5 charger filters ──────────────────────────────────────────────────
   The filter sheet and the paywall reuse the ssheet vocabulary
   (header/body/field/tile/cta). Only the charger-specific pieces live here:
   the axis grids, the network rows and the paywall swatch strip. */
.chf-grid { display: grid; gap: 6px; }
.chf-grid--4 { grid-template-columns: repeat(4, 1fr); }
.chf-grid--3 { grid-template-columns: repeat(3, 1fr); }
.chf-grid--2 { grid-template-columns: 1fr 1fr; }
.chf-bolts { display: flex; gap: 2px; color: var(--t-text-3); }
.ssheet-tile.is-selected .chf-bolts { color: #fff; }

.chf-nets { display: flex; flex-direction: column; gap: 2px; }
.chf-net {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 52px;
  padding: 0 14px;
  background: transparent;
  border: 0;
  border-radius: var(--t-radius-sm);
  color: var(--t-text);
  cursor: pointer;
  text-align: left;
  font: inherit;
  font-size: 15.5px;
  transition: background .15s var(--t-ease);
}
.chf-net:hover { background: var(--t-hl); }
.chf-net.is-on { background: color-mix(in srgb, var(--t-blue) 16%, transparent); }
.chf-net__swatch {
  width: 10px;
  height: 10px;
  border-radius: 3px;
  border: 0;
  flex: none;
}
.chf-net__name {
  flex: 1;
  font-size: 15.5px;
  font-weight: 400;
  letter-spacing: 0;
  min-width: 0;
}
.chf-net__sub,
.chf-net__state {
  font-size: 13px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--t-text-2);
  white-space: nowrap;
}
.chf-net.is-on .chf-net__state { color: var(--t-blue); }
.chf-net.is-locked .chf-net__state {
  color: var(--t-amber);
  border: 0;
  padding: 0;
}

.chp-swatches { display: flex; gap: 4px; }
.chp-swatches > span {
  width: 22px;
  height: 22px;
  border-radius: 4px;
  border: 0;
}
.chp-text {
  font-size: 15.5px;
  line-height: 1.55;
  color: var(--t-text);
}

/* Charger detail popup bits (rendered inside .alert-popup). */
.chpopup-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  margin-right: 7px;
  border-radius: 3px;
  border: 0;
  vertical-align: baseline;
}
.chpopup-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 8px;
}
.chpopup-chip {
  font-size: 10.5px;
  letter-spacing: .04em;
  text-transform: none;
  padding: 4px 9px;
  border: 0;
  border-radius: 999px;
  background: var(--t-btn);
  color: var(--t-text-2);
  white-space: nowrap;
}
.chpopup-chip.is-ok {
  background: color-mix(in srgb, var(--t-blue) 22%, transparent);
  color: var(--t-blue);
}
.chpopup-nav { width: 100%; margin-top: 10px; }

/* --- Closure picker ---------------------------------------------------------
   Step 2 of a "Road closed" report: the driver taps one of the arrows drawn
   over the junction ahead. Only the chrome (header / hint / actions) is
   opaque. The middle stays clear so the junction underneath is readable,
   which is the whole point of the screen. */
#closure-overlay {
  position: fixed;
  inset: 0;
  z-index: 9;
  display: grid;
  grid-template-rows: auto auto 1fr auto;
  /* The map must stay visible AND interactive underneath; each child
     re-enables pointer events for itself. */
  pointer-events: none;
}
#closure-header {
  pointer-events: auto;
  padding: 16px 20px;
  text-align: center;
  font-size: 20px;
  font-weight: 500;
  color: var(--t-text);
  background: var(--t-panel-opaque);
  border: 0;
  backdrop-filter: var(--t-blur);
  -webkit-backdrop-filter: var(--t-blur);
}
#closure-hint {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 12px auto 0;
  padding: 13px 18px;
  max-width: min(560px, calc(100vw - 32px));
  background: var(--t-panel-opaque);
  border: 0;
  border-radius: var(--t-radius);
  backdrop-filter: var(--t-blur);
  -webkit-backdrop-filter: var(--t-blur);
  color: var(--t-text);
  font-size: 15.5px;
  line-height: 1.35;
  box-shadow: var(--t-shadow);
}
#closure-hint[hidden] { display: none; }
#closure-hint-close {
  flex: 0 0 auto;
  background: transparent;
  border: 0;
  color: var(--t-text-3);
  cursor: pointer;
  padding: 4px;
  line-height: 0;
}
#closure-arrows { position: relative; pointer-events: none; }
.closure-arrow {
  position: absolute;
  pointer-events: auto;
  width: 62px;
  height: 62px;
  margin: -31px 0 0 -31px;   /* centre on the projected point */
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  transition: transform .15s var(--t-ease);
}
.closure-arrow svg { display: block; width: 100%; height: 100%; }
.closure-arrow:active { transform: scale(0.92); }
.closure-arrow.is-selected .closure-arrow__body { fill: var(--t-red); }
.closure-arrow.is-selected .closure-arrow__glyph { stroke: #fff; }
#closure-actions {
  pointer-events: auto;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  padding: 16px 20px calc(16px + env(safe-area-inset-bottom, 0px));
  background: var(--t-panel-opaque);
  border: 0;
  backdrop-filter: var(--t-blur);
  -webkit-backdrop-filter: var(--t-blur);
}
#closure-send:disabled { opacity: 0.5; cursor: default; }

/* ============================================================
   Third-party widget slots (ADR-0015).

   RIGHT column, under the status pill. They used to live on the
   left, under the nav column, and hid themselves whenever that
   column opened, which was fine when the column was a collapsed
   rail for most of a drive. It is not any more: v2.1 locks the nav
   panel OPEN while a route is running (the turn card is the only
   maneuver surface, so it cannot be closed), which left the widgets
   hidden for the entire drive. A surface a driver paid for should
   not be the one thing navigation costs them.

   The right column is the only band nothing permanently owns: the
   status pill ends above it and the action stack is anchored to the
   bottom. The two panels that DO open here (the sources panel and
   Settings) hide the slots while they are up, because they would
   otherwise be covering them.

   THE RAIL IS NOT ONE OF THEM. The camera controls are vertically
   centred on this same edge, and "centred" has no relationship to a
   top-anchored column: a slot height was once picked (110, not 130)
   in the belief that two slots "clear the camera controls", and it
   never did: measured, the rail sat 126px INSIDE the second slot at
   1254x784 and 179px at a Model X's 2200x1300. The rail wins that
   overlap (z-index 6 against 5), so the control stayed tappable and
   the WIDGET lost its right 54px: a tap meant for the widget rotated
   the map instead.

   Vertically the two cannot both fit. In 3D the rail is 412px, the
   free band between pill and action stack is 544px, and two slots
   need 654px, so they are separated HORIZONTALLY instead: the
   column sits one rail-width plus a gap left of the edge, and the
   two never share a band at any height. Nothing a driver paid for is
   hidden to buy it; the cost is ~66px of map width at UI size 10.

   The rail's width is MEASURED, not written here (app.js publishes
   --cd-right-rail-w from #camera-controls). A constant is what this
   comment used to contain, and it is what was wrong with it. The
   fallback is the 54px round-button width, which is the value the
   measurement almost always lands on.

   Clearance on the left is not guarded, and does not need to be: the
   nav column is 392px, and the tightest real case (773x601 at UI
   size 2) leaves 63px between the two.

   SCALING: nothing here opts in to the UI-size lever explicitly:
   #widget-slots is a direct child of <body>, so the single
   `body > :not(#map) { zoom: var(--cd-ui-zoom) }` rule scales it
   exactly like the speed pill and the camera controls. Keep it a
   body-level element for that reason; nesting it inside #map would
   silently drop it out of the lever (and out of the theme, which
   #map does not inherit either).
   ============================================================ */
#widget-slots {
  position: fixed;
  top: calc(var(--t-gutter) + 58px);
  right: calc(var(--t-gutter) + var(--cd-right-rail-w, 54px) + 12px);
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 300px;
}
/* Both of these open over this column and would bury the slots. */
body:has(#sources-panel:not([hidden])) #widget-slots,
body:has(#nav-settings:not([hidden])) #widget-slots { display: none; }

.widget-slot {
  position: relative;
  /* The column clears the camera controls horizontally now, so this height
     no longer has a vertical budget to fit inside; it is just the size at
     which a widget reads at a glance. It was 110 rather than 130 to "clear
     the camera controls", which it never did; see the block comment above. */
  height: 110px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--t-panel);
  backdrop-filter: var(--t-blur);
  -webkit-backdrop-filter: var(--t-blur);
  border: 0;
  border-radius: var(--t-radius);
  box-shadow: var(--t-shadow);
}
/* Host-drawn chrome. The name lives HERE, outside the iframe, so framed
   content cannot paint something that reads as codriver UI. */
.widget-slot__head {
  flex: 0 0 auto;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 0 12px;
  border-bottom: 1px solid var(--t-divider);
  font-size: 11px;
  letter-spacing: .04em;
  text-transform: uppercase;
}
.widget-slot__name {
  color: var(--t-text-2);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
/* Permanent "this is not us" marker next to the label. */
.widget-slot__badge {
  flex: 0 0 auto;
  color: var(--t-text-3);
  letter-spacing: .1em;
}
.widget-slot__body {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  background: var(--t-card);
}
.widget-slot__frame {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  background: var(--t-card);
}
.widget-slot__skeleton {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 10px;
  padding: 0 14px;
  background: var(--t-card);
  pointer-events: none;
}
.widget-slot__skeleton span {
  height: 10px;
  border-radius: 5px;
  background: var(--t-btn);
  animation: widget-skeleton 1.4s ease-in-out infinite;
}
.widget-slot__skeleton span:nth-child(1) { width: 70%; }
.widget-slot__skeleton span:nth-child(2) { width: 45%; animation-delay: 0.18s; }
.widget-slot__skeleton span:nth-child(3) { width: 60%; animation-delay: 0.36s; }
@keyframes widget-skeleton {
  0%, 100% { opacity: 0.45; }
  50%      { opacity: 1; }
}
.widget-slot__note {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  padding: 0 14px;
  font-size: 13px;
  line-height: 1.35;
  color: var(--t-text-2);
}

/* ============================================================
   Notification channels: the second extension surface (ADR-0045)
   ============================================================
   #widget-notifiers holds the channel frames. It is ONE PIXEL and
   it is not `display:none`, deliberately: Chromium throttles timers
   hard in a hidden subtree, and a notifier's reconnect backoff is a
   timer. It must keep running while the nav panel is open, which is
   also why it lives outside #widget-slots (that container is hidden
   on panel-open by the rule above).
   ============================================================ */
#widget-notifiers {
  position: fixed;
  left: -9999px;
  top: 0;
  width: 1px;
  height: 1px;
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
}
.widget-notifier__frame {
  width: 1px;
  height: 1px;
  border: 0;
}

/* The toast stack. Top-centre over the map, clear of the status pill on the
   right and the nav column on the left, and ABOVE #widget-slots (z-index 5)
   because a toast is transient and a widget is not. Not hidden on panel-open:
   a notification a driver misses because a menu was open is a notification
   that failed. */
#widget-toasts {
  position: fixed;
  top: var(--t-gutter);
  left: 50%;
  transform: translateX(-50%);
  z-index: 7;
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: min(420px, calc(100vw - 240px));
  pointer-events: none;
}
#widget-toasts[hidden] { display: none; }

.widget-toast {
  pointer-events: auto;
  cursor: pointer;
  padding: 12px 16px;
  background: var(--t-panel-opaque);
  backdrop-filter: var(--t-blur);
  -webkit-backdrop-filter: var(--t-blur);
  border: 0;
  border-left: 3px solid var(--t-text-3);
  border-radius: var(--t-radius);
  box-shadow: var(--t-shadow);
  animation: widget-toast-in 180ms ease-out;
}
/* Priority 4-5. A wider accent bar and a longer dwell, never a colour that
   reads as a codriver hazard alert, which is a road-safety signal and not
   something a third party gets to raise. */
.widget-toast--urgent { border-left-color: var(--t-blue); }

.widget-toast__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: 11px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--t-text-3);
}
.widget-toast__app {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.widget-toast__badge { flex: 0 0 auto; letter-spacing: .1em; }
.widget-toast__title {
  margin-top: 4px;
  font-size: 16px;
  font-weight: 500;
  color: var(--t-text);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.widget-toast__body {
  margin-top: 2px;
  font-size: 13.5px;
  line-height: 1.35;
  color: var(--t-text-2);
  /* Two lines, then it stops. A driver glances; anything longer is for the
     phone the message came from. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

@keyframes widget-toast-in {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .widget-toast { animation: none; }
}

/* ---------- FPS badge (ADR-0011) ----------------------------------------
   Renderer readout. Bottom-left is the speed cluster and top-centre the
   maneuver banner, so this sits directly above the speed pill. Monospace +
   tabular numerals so the digits don't jitter as they update four times a
   second, which is what makes a changing number unreadable at a glance. */
#fps-badge {
  position: fixed;
  /* Above the speed cluster AND above the attribution chip that now sits
     between them; the badge is a diagnostic and yields to a legal notice. */
  bottom: calc(var(--t-gutter-b) + 56px + 52px);
  left: var(--t-gutter);
  /* Below the nav column (6) and the sheets above it. A diagnostic readout
     must never cover a control the driver is reaching for. */
  z-index: 5;
  padding: 7px 10px;
  border-radius: var(--t-radius-sm);
  border: 0;
  background: var(--t-panel-opaque);
  backdrop-filter: var(--t-blur);
  -webkit-backdrop-filter: var(--t-blur);
  color: var(--t-text);
  font: 500 12px/1.35 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-variant-numeric: tabular-nums;
  white-space: pre;
  text-align: left;
  pointer-events: none;
  box-shadow: var(--t-shadow);
}
#fps-badge[hidden] { display: none; }

/* ============================================================
   PEREGRINE BOOT LOADER

   Shown for every view now: Peregrine draws all four (ADR-0011),
   so there is no longer a "2D path" that skips it. Markup is
   document.write'n in index.html so it is on screen while
   /peregrine.js is still downloading, since no script inside that
   bundle can cover its own download.

   Ported from boot-screen/template.html in
   codriver-peregrine-3d-maps-engine, minus the preview chrome that
   file marks as not part of the boot screen. Names carry a pgb-
   prefix: .frame/.status/.rail/.fill/.step all collide otherwise.

   Palette: the firmware set, like everything else on this screen.
   It used to carry its own blue-slate token block, which read as a
   different product's splash for the two seconds it is up.
   ============================================================ */
:root {
  --pgb-accent: var(--t-blue);
  --pgb-accent-wash: color-mix(in srgb, var(--t-blue) 25%, transparent);
  --pgb-hair-soft: var(--t-text-3);
}

#peregrine-boot *, #peregrine-boot *::before, #peregrine-boot *::after { box-sizing: border-box; }
#peregrine-boot {
  /* The original was the whole document; here it is an overlay that has to
     cover the app's own chrome while the map builds. The modal sheets top out
     at z-index 60. */
  position: fixed;
  inset: 0;
  z-index: 1000;
  opacity: 1;
  transition: opacity 400ms ease;
  margin: 0;
  background: var(--t-bg);
  color: var(--t-text);
  font-family: Roboto, 'Trebuchet MS', Helvetica, system-ui, sans-serif;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
}
#peregrine-boot.is-done { opacity: 0; pointer-events: none; }

/* The falcon reveals once the scene reports a first frame; a failed WebGL
   context leaves the lockup and rail doing the job on their own. */
#peregrine-boot .pgb-canvas { opacity: 0; transition: opacity 500ms ease; }
#peregrine-boot.is-live .pgb-canvas { opacity: 1; }

/* ---- stage --------------------------------------------------------- */
#peregrine-boot .pgb-canvas { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
.pgb-halo, .pgb-scrim, .pgb-grain { position: fixed; inset: 0; pointer-events: none; }
.pgb-halo { background: radial-gradient(60% 55% at 68% 46%, color-mix(in srgb, var(--t-blue) 14%, transparent), transparent 70%); }
/* Left-to-right scrim so the lockup reads over the falcon without dimming it. */
.pgb-scrim {
  background: linear-gradient(105deg,
    var(--t-bg) 0%,
    color-mix(in srgb, var(--t-bg) 72%, transparent) 34%,
    transparent 58%);
}
.pgb-grain {
  opacity: 0.35;
  background-image: radial-gradient(circle at 50% 50%, var(--pgb-hair-soft) 0.5px, transparent 0.6px);
  background-size: 3px 3px;
  mix-blend-mode: overlay;
}

/* ---- lockup -------------------------------------------------------- */
.pgb-frame {
  position: fixed; inset: 0;
  display: grid;
  grid-template-rows: 1fr auto;
  padding: clamp(24px, 4.2vw, 64px);
  pointer-events: none;
}
.pgb-lockup {
  align-self: center;
  display: flex;
  flex-direction: column;
  gap: 14px;
  max-width: min(740px, 64vw);
}
.pgb-wordmark {
  width: max-content;
  max-width: 100%;
  font-family: var(--cd-font-note);
  font-style: italic;
  font-weight: 700;
  font-size: clamp(42px, 6.4vw, 96px);
  line-height: 0.92;
  letter-spacing: 0.012em;
  margin: 0;
  text-wrap: balance;
  color: var(--t-text);
  position: relative;
}
.pgb-wordmark::after {
  content: attr(data-text);
  position: absolute;
  inset: 0;
  background-image: linear-gradient(100deg, transparent 38%, color-mix(in srgb, var(--t-blue) 85%, transparent) 50%, transparent 62%);
  background-size: 260% 100%;
  background-position: 180% 0;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: sheen 6.5s cubic-bezier(0.4, 0, 0.2, 1) infinite;
  pointer-events: none;
}
@keyframes sheen {
  0%, 62% { background-position: 180% 0; }
  88%, 100% { background-position: -80% 0; }
}

.pgb-sublabel {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--t-blue);
  font-family: var(--cd-font-mono);
  font-size: clamp(9.5px, 1.05vw, 12px);
  font-weight: 500;
  letter-spacing: 0.44em;
  text-transform: uppercase;
  white-space: nowrap;
}
.pgb-sublabel::before, .pgb-sublabel::after {
  content: "";
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--t-blue));
  flex: 0 0 clamp(16px, 3vw, 44px);
}
.pgb-sublabel::after { background: linear-gradient(90deg, var(--t-blue), transparent); flex: 1 1 auto; }

/* ---- parent-brand byline ------------------------------------------- */
.pgb-byline {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 4px;
  font-size: clamp(11.5px, 1.05vw, 13px);
  letter-spacing: 0.015em;
  color: var(--t-text-2);
}
.pgb-byline .pgb-cd-mark {
  width: 1.35em;
  height: 1.35em;
  flex: none;
  /* The favicon carries its own prefers-color-scheme rule; inlined here it
     would ignore an explicit theme choice, so the fill comes from a token. */
  fill: var(--t-blue);
}
.pgb-byline b { font-weight: 600; color: var(--t-text); }

/* ---- boot status --------------------------------------------------- */
.pgb-status {
  align-self: end;
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: min(560px, 62vw);
}
.pgb-rail {
  position: relative;
  height: 2px;
  background: var(--t-divider);
  overflow: visible;
}
.pgb-fill {
  position: absolute; inset: 0 auto 0 0;
  width: 0%;
  background: linear-gradient(90deg, var(--pgb-accent-wash), var(--t-blue));
  transition: width 0.35s cubic-bezier(0.33, 1, 0.68, 1);
}
.pgb-comet {
  position: absolute;
  top: 50%;
  left: 0%;
  width: 44px; height: 2px;
  margin-left: -44px;
  transform: translateY(-50%);
  background: linear-gradient(90deg, transparent, var(--t-blue));
  box-shadow: 0 0 12px 1px var(--t-blue), 0 0 30px 3px var(--pgb-accent-wash);
  transition: left 0.35s cubic-bezier(0.33, 1, 0.68, 1);
}
.pgb-readout {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 20px;
  font-family: var(--cd-font-mono);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  color: var(--t-text-2);
  text-transform: uppercase;
}
.pgb-step { display: flex; align-items: baseline; gap: 9px; min-width: 0; }
.pgb-step-dot {
  width: 5px; height: 5px; flex: none;
  background: var(--t-blue);
  box-shadow: 0 0 8px var(--t-blue);
  transform: translateY(-1px);
}
.pgb-step-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pgb-pct { font-variant-numeric: tabular-nums; color: var(--t-text); font-weight: 500; }
.pgb-status.pgb-done .pgb-step-dot { background: var(--t-green); box-shadow: 0 0 8px var(--t-green); }
.pgb-status.pgb-done .pgb-pct { color: var(--t-blue); }

@media (max-width: 780px) {
  .pgb-lockup { max-width: none; align-self: end; }
  .pgb-frame { grid-template-rows: 1fr auto auto; gap: 26px; padding-bottom: clamp(28px, 6vw, 48px); }
  .pgb-status { max-width: none; }
  .pgb-sublabel { letter-spacing: 0.3em; }
}
@media (prefers-reduced-motion: reduce) {
  .pgb-wordmark::after { animation: none; }
  .pgb-fill, .pgb-comet { transition: none; }
}

/* ============================================================
   Narrow panes (Tesla split-screen, ~640 CSS px and down). The
   nav column stops being a floating card and takes the width it
   needs; the right-hand panels follow.
   ============================================================ */
@media (max-width: 720px) {
  /* --t-gutter-b stays at the wide value: it is in the zoomed space and the
     attribution chip under the ETA strip is not, so it is the gutter that has
     to reserve the chip's room. Shrinking it here would tuck the strip onto
     the chip on exactly the pane with the least height to spare. */
  :root { --t-gutter: 12px; --t-col-w: calc(100vw - 24px); }
  #nav-settings { width: calc(100vw - 24px); }
  #sources-panel { width: calc(100vw - 24px); }
  #road-name { display: none; }
  .nav-eta-cell { min-width: 72px; padding: 0 12px; }
}

/* ============================================================
   Feedback dialog (ALL-127). Speaks the Tesla-native language
   the rest of the app moved to: a blurred opaque panel, no
   border, rounded corners, --t-* tokens throughout. It was
   written against the old Concrete & Cobalt set (square edges,
   corner brackets, --cd-*) and read as a different app.

   Top-anchored and height-capped rather than a bottom sheet
   like #report-sheet: this dialog has a text field, and the
   Tesla keyboard takes the bottom half of the screen the moment
   it gains focus. Same reasoning as #save-fav-overlay.
   ============================================================ */
#fb-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: var(--t-scrim);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 16px;
}
#fb-overlay[hidden] { display: none; }
#fb-sheet {
  position: relative;
  width: 100%;
  max-width: 560px;
  background: var(--t-panel-opaque);
  backdrop-filter: blur(28px);
  -webkit-backdrop-filter: blur(28px);
  border: 0;
  border-radius: var(--t-radius-lg);
  box-shadow: var(--t-shadow);
  color: var(--t-text);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  max-height: min(calc(100vh - 32px), 620px);
  animation: t-pop .3s var(--t-ease-pop) both;
}
#fb-sheet .ssheet-header { flex-direction: row; align-items: center; gap: 12px; }
#fb-sheet .ssheet-title { flex: 1 1 auto; }
#fb-sheet .report-close { flex: 0 0 auto; }

#fb-body { padding: 8px 22px 18px; overflow-y: auto; display: grid; gap: 18px; }

/* Voice. The microphone is the first thing in the sheet because speaking is
   the point; typing is the fallback, not the other way round. */
.fb-voice { display: flex; align-items: center; gap: 14px; }
.fb-mic {
  appearance: none; -webkit-appearance: none;
  flex: 0 0 auto;
  width: 64px; height: 64px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--t-btn);
  color: var(--t-text);
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  transition: background .15s var(--t-ease), color .15s var(--t-ease), transform .15s var(--t-ease);
}
.fb-mic:hover { background: var(--t-btn-press); }
.fb-mic:active { transform: scale(.96); }
.fb-mic:disabled { opacity: .4; cursor: not-allowed; }
.fb-mic.is-recording { background: var(--t-red); color: #fff; }
/* The pulse says "live" at a glance from arm's length, where a small state
   line does not. Motion is dropped with everything else on a starved GPU. */
.fb-mic.is-recording .fb-mic__icon { animation: fb-pulse 1.2s ease-in-out infinite; }
@keyframes fb-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }

.fb-mic__side { flex: 1 1 auto; display: grid; gap: 8px; min-width: 0; }
.fb-mic__state { font-size: 14px; color: var(--t-text-2); }
.fb-mic__bar { height: 4px; background: var(--t-divider); border-radius: 999px; overflow: hidden; }
.fb-mic__fill { height: 100%; width: 100%; background: var(--t-red); transition: width .2s linear; }
.fb-mic__play { width: 100%; height: 34px; }

.fb-text {
  appearance: none; -webkit-appearance: none;
  width: 100%;
  min-height: 96px;
  padding: 14px 16px;
  background: var(--t-search);
  border: 0;
  border-radius: var(--t-radius-sm);
  color: var(--t-text);
  font-family: inherit;
  font-size: 16.5px;
  line-height: 1.45;
  resize: vertical;
}
.fb-text::placeholder { color: var(--t-text-3); }
.fb-text:focus { outline: none; box-shadow: inset 0 0 0 2px var(--t-blue); }

.fb-label { font-size: 13px; font-weight: 500; color: var(--t-text-2); letter-spacing: 0; text-transform: none; }

/* Mirrors .setting-segmented (the app's existing way of showing a chosen
   option) but keeps aria-pressed as the state, which is what a screen reader
   and the click handler both read. */
.fb-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.fb-chips button {
  appearance: none; -webkit-appearance: none;
  min-height: 44px;
  padding: 0 16px;
  background: var(--t-btn);
  color: var(--t-text-2);
  border: 0;
  border-radius: 6px;
  font-family: inherit;
  font-size: 15px;
  font-weight: 500;
  cursor: pointer;
  transition: background .15s var(--t-ease), color .15s var(--t-ease);
}
.fb-chips button:hover { background: var(--t-btn-press); color: var(--t-text); }
.fb-chips button[aria-pressed="true"] { background: var(--t-toggle-on); color: #fff; }
.fb-chips--sm button { min-height: 36px; padding: 0 12px; font-size: 13.5px; }

.fb-attach { background: var(--t-card); border: 0; border-radius: var(--t-radius-sm); padding: 14px; display: grid; gap: 12px; }
.fb-attach__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.fb-shot {
  width: 100%; max-height: 180px; object-fit: cover; object-position: center;
  display: block; border-radius: 6px; background: #000;
}
.fb-hint { font-size: 13px; color: var(--t-text-3); }
/* The privacy disclosure is NOT a hint. It inherited .fb-hint's colour, which
   resolves to 20% white: legible enough for "no screenshot: the map did not
   paint in time" and nowhere near legible enough for the one line that tells a
   driver what this report is about to send about them. It gets --t-text-2 and
   a real link colour, and it keeps a top rule so it reads as a statement
   rather than a caption on the thing above it. */
.fb-privacy {
  margin: 10px 0 0;
  padding-top: 10px;
  border-top: 1px solid var(--t-sep, rgba(255,255,255,0.12));
  font-size: 13px;
  line-height: 1.45;
  color: var(--t-text-2);
}
.fb-privacy a { color: var(--t-blue); text-decoration: underline; }
.fb-switch { display: inline-flex; align-items: center; gap: 8px; font-size: 14px; color: var(--t-text-2); cursor: pointer; }
.fb-switch input { width: 18px; height: 18px; accent-color: var(--t-blue); }

.fb-review { background: var(--t-card); border-radius: var(--t-radius-sm); overflow: hidden; }
.fb-review > summary { cursor: pointer; padding: 12px 14px; font-size: 14px; color: var(--t-text-2); list-style: none; }
.fb-review > summary::-webkit-details-marker { display: none; }
.fb-review > pre {
  margin: 0; padding: 12px 14px; max-height: 220px; overflow: auto;
  border-top: 1px solid var(--t-divider);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11.5px; line-height: 1.5; color: var(--t-text-2);
  white-space: pre-wrap; word-break: break-word;
}

#fb-foot { display: flex; align-items: center; gap: 12px; padding: 14px 22px 18px; border-top: 1px solid var(--t-divider); }
#fb-foot .fb-size { flex: 0 0 auto; white-space: nowrap; font-size: 13px; color: var(--t-text-3); }
#fb-foot .fb-error { flex: 1 1 auto; font-size: 13px; line-height: 1.3; color: var(--t-red); }
/* The reason Send is disabled (ALL-230) takes the error's slot. It is there to
   be read, not glanced at, so it gets --t-text-2 rather than .fb-hint's
   caption grey. */
#fb-foot .fb-gate { flex: 1 1 auto; font-size: 13px; line-height: 1.3; color: var(--t-text-2); }
#fb-foot #fb-send { margin-left: auto; padding: 0 24px; }
/* A held Send must LOOK held: the driver in ALL-230 tapped a full-strength
   blue button that did nothing. Same treatment as .fb-mic:disabled. */
#fb-foot #fb-send:disabled { opacity: .45; cursor: default; }
#fb-foot #fb-discard:not([hidden]) ~ #fb-send { margin-left: 0; }
/* Discard is destructive and sits beside the primary action, so it must not
   read as one: transparent, muted, no fill. The tap target keeps its full
   height; this is a car, and a small target is its own bug. `auto` margin
   lives here so Send stays pinned right whether or not Discard is shown. */
#fb-foot #fb-discard {
  margin-left: auto;
  padding: 0 14px;
  background: transparent;
  color: var(--t-text-2);
  font-weight: 400;
}
#fb-foot #fb-discard:hover { background: var(--t-btn); color: var(--t-text); }
#fb-foot #fb-discard[hidden] { display: none; }

.fb-sent { padding: 32px 22px; display: grid; gap: 12px; justify-items: center; text-align: center; }
.fb-sent__title { font-size: 20px; font-weight: 500; }
.fb-sent__note { color: var(--t-text-2); font-size: 14px; max-width: 34ch; }

/* ============================================================
   SETTINGS: account header + the Advanced disclosure
   (ALL-52 settings pass)
   ============================================================ */

/* Who this car is, at the top of the panel: the design's account card
   (templates/settings-account in the Design System project). Identity on
   top, then facts in rows: a dot or a star, the fact, its consequence on the
   right. Chips were the first cut; rows say WHY a thing matters, which is
   what a driver deciding whether to tap Manage needs. */
.settings-account {
  /* flex: none is load-bearing. .settings-body is a column flex container and
     this card clips its corners with overflow:hidden, which makes its automatic
     min-height 0, so without this the flex algorithm shrinks the card to nothing
     while every group below keeps its height (the card vanished, the panel
     looked fine). */
  flex: none;
  margin: 2px 0 18px;
  background: var(--t-card);
  border: 0;
  border-radius: var(--t-radius);
  overflow: hidden;
}
.settings-account__head {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px;
}
/* Monogram, not a photo: the app never fetches profile images, and one
   letter at 24px reads at arm's length. Dashed and empty when signed out. */
.settings-account__avatar {
  width: 52px;
  height: 52px;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--t-radius-sm);
  background: var(--t-blue);
  color: #fff;
  font-size: 24px;
  font-weight: 600;
  letter-spacing: 0;
}
.settings-account__avatar--empty {
  background: transparent;
  border: 1px dashed var(--t-divider);
  color: var(--t-text-2);
  font-size: 22px;
  font-weight: 400;
}
.settings-account__text { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1 1 auto; }
/* An email can be long; it truncates rather than wrapping, because a second
   line here pushes every control below it and the address is recognisable
   from its first half. */
.settings-account__name {
  font-size: 17px;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--t-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.settings-account__plan { font-size: 13px; letter-spacing: 0; color: var(--t-text-2); }
/* Premium says so in the app's own accent; Free and Guest stay quiet. The
   plan line is information, not an upsell; the upsell has its own surfaces. */
.settings-account[data-plan="premium"] .settings-account__plan { color: var(--t-blue); }
.settings-account__manage {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 44px;
  padding: 0 16px;
  background: transparent;
  border: 1px solid var(--t-divider);
  border-radius: var(--t-radius-sm);
  color: var(--t-text-2);
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0;
  text-decoration: none;
  white-space: nowrap;
  transition: background .15s var(--t-ease), color .15s var(--t-ease), border-color .15s var(--t-ease);
}
.settings-account__manage:hover, .settings-account__manage:active {
  background: var(--t-btn-press);
  border-color: transparent;
  color: var(--t-text);
}
.settings-account__rows {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 0 16px;
  padding: 14px 0 16px;
  border-top: 1px solid var(--t-divider);
}
.settings-account__row {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 14px;
  letter-spacing: 0;
  color: var(--t-text);
}
.settings-account__dot {
  width: 8px;
  height: 8px;
  flex: none;
  border-radius: 999px;
  background: var(--t-text-2);
  opacity: .5;
}
.settings-account__dot--blank { background: transparent; opacity: 1; }
/* Green = linked and syncing. Amber = linked but the grant lapsed: Tesla
   revoked or expired it, and the car has silently stopped following the
   account; that is exactly the state worth a colour. Grey = nothing linked. */
.settings-account__row[data-state="linked"] .settings-account__dot { background: var(--t-green); opacity: 1; }
.settings-account__row[data-state="stale"]  .settings-account__dot { background: var(--t-amber); opacity: 1; }
.settings-account__row[data-state="none"]   .settings-account__fact { color: var(--t-text-2); }
.settings-account__star {
  width: 8px;
  flex: none;
  text-align: center;
  color: var(--t-blue);
  font-size: 12px;
  line-height: 1;
}
.settings-account__fact { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.settings-account__fact--dim { color: var(--t-text-2); }
.settings-account__note {
  margin-left: auto;
  flex: none;
  font-size: 13px;
  color: var(--t-text-2);
}
/* Signed out: the way in, full width, and the phone alternative under it. */
.settings-account__cta {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 0 16px 16px;
}
.settings-account__signin {
  appearance: none; -webkit-appearance: none;
  height: 48px;
  width: 100%;
  border: 0;
  border-radius: var(--t-radius-sm);
  background: var(--t-blue);
  color: #fff;
  font-family: inherit;
  font-size: 16px;
  font-weight: 500;
  letter-spacing: 0;
  cursor: pointer;
}
.settings-account__signin:hover, .settings-account__signin:active { filter: brightness(1.15); }
.settings-account__caption { font-size: 13px; color: var(--t-text-2); text-align: center; letter-spacing: 0; }

/* The Advanced disclosure. A native <details>, so it is collapsed before the
   first frame with no script, since the panel is rendered cold in the car.
   `display: block` on the details is what makes the marker/summary layout
   work, so the 16px rhythm the other groups get from their flex gap has to be
   restored on the rows themselves. Otherwise a hint runs straight into the
   next row's name and the section reads as one wall of text. */
.setting-group--advanced { display: block; }
.setting-group--advanced > .setting-row { margin-top: 16px; }
.setting-group__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 4px 0 6px;
  cursor: pointer;
  list-style: none;
  color: var(--t-text-3);
}
.setting-group__summary::-webkit-details-marker { display: none; }
.setting-group__summary svg { transition: transform .18s var(--t-ease); }
.setting-group--advanced[open] .setting-group__summary svg { transform: rotate(180deg); }
.setting-group__summary:hover .setting-group__title { color: var(--t-text-2); }

/* Charger sheet: the filters grey out while the layer itself is off. Disabled
   rather than hidden: turning the layer back on should reveal the selection
   still waiting, not a sheet that changed shape. */
.ssheet-body.is-layer-off .ssheet-field:not(:first-child) {
  opacity: .38;
  pointer-events: none;
}

/* ALL-131: destination hand-off, visible before Navigate is opened. */
#nav-drawer.collapsed.has-tesla-sync .nav-header { display: flex; gap: 4px; padding: 4px; }
#nav-drawer.collapsed.has-tesla-sync .nav-toggle { flex: 1 1 auto; width: auto; min-width: 0; padding: 0 12px; gap: 10px; height: 56px; font-size: 18px; }
#tesla-sync-collapsed, #tesla-sync-expanded { flex: 0 0 auto; min-width: 0; }
#tesla-sync-collapsed:empty, #tesla-sync-expanded:empty { display: none; }
.nav-search-row { display: flex; align-items: stretch; gap: 6px; }
.nav-search-row > .poi-search { flex: 1 1 auto; min-width: 0; }
.nav-search-row:has(.tesla-sync:not([hidden])) .poi-search-input { font-size: 17px; padding-left: 34px; padding-right: 28px; }
.nav-search-row:has(.tesla-sync:not([hidden])) .poi-search-icon { left: 12px; }
.nav-search-row:has(.tesla-sync:not([hidden])) .poi-search-clear { right: 0; width: 28px; }
.nav-search-row:has(.tesla-sync:not([hidden])) .poi-search-list { width: calc(var(--t-col-w) - 24px); max-width: calc(100vw - 72px); }
.tesla-sync { display: flex; gap: 2px; align-items: stretch; }
.tesla-sync button { appearance: none; font-family: inherit; cursor: pointer; border: 0; }
.tesla-sync__button { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-width: 122px; max-width: 154px; height: 56px; padding: 0 12px; border-radius: var(--t-radius-sm); background: var(--t-blue); color: #fff; }
.tesla-sync__button:hover { filter: brightness(1.12); }
.tesla-sync__logo { width: 18px; height: 18px; flex: 0 0 18px; background: currentColor; mask: url('/tesla-logo.svg') center / contain no-repeat; -webkit-mask: url('/tesla-logo.svg') center / contain no-repeat; }
.tesla-sync__label { display: flex; flex-direction: column; min-width: 0; text-align: left; line-height: 1.3; }
.tesla-sync__label small { font-size: 11px; font-weight: 400; opacity: .85; }
.tesla-sync__label strong { font-size: 15px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100px; }
.tesla-sync__label > span { font-size: 13px; white-space: nowrap; }
.tesla-sync__switch { width: 44px; height: 56px; display: inline-flex; align-items: center; justify-content: center; border-radius: var(--t-radius-sm); background: transparent; color: var(--t-text-2); }
.tesla-sync__switch:hover { background: var(--t-btn-press); color: var(--t-text); }
.tesla-sync button:focus-visible, .tesla-sync-panel button:focus-visible, .tesla-sync-panel a:focus-visible { outline: 2px solid var(--t-blue); outline-offset: 2px; }
.tesla-sync button:disabled, .tesla-sync-panel button:disabled { opacity: .65; cursor: default; }
.tesla-sync-panel { position: relative; flex: 0 0 auto; max-height: min(460px, calc(var(--t-vh) - 150px)); overflow-y: auto; padding: 20px; background: var(--t-card); border-top: 1px solid var(--t-divider); color: var(--t-text); }
#nav-search-mount + .tesla-sync-panel { margin: 0 12px 12px; border: 0; border-radius: var(--t-radius-sm); }
#nav-drawer:has(.tesla-sync-panel:not([hidden])) .tesla-sync__button { background: var(--t-btn-press); color: var(--t-text); }
.tesla-sync-panel h3 { margin: 0 30px 10px 0; font-size: 19px; font-weight: 500; line-height: 1.35; overflow-wrap: anywhere; outline: none; }
.tesla-sync-panel p { margin: 0 0 16px; color: var(--t-text-2); font-size: 14px; line-height: 1.5; }
.tesla-sync-panel button, .tesla-sync-panel a { font-family: inherit; cursor: pointer; }
.tesla-sync__close { position: absolute; top: 5px; right: 5px; width: 44px; height: 44px; border: 0; border-radius: var(--t-radius-sm); background: transparent; color: var(--t-text-2); display: grid; place-items: center; }
.tesla-sync__close:hover { background: var(--t-hl); }
.tesla-sync__action { width: 100%; min-height: 48px; padding: 12px; border: 0; border-radius: var(--t-radius-sm); background: var(--t-blue); color: #fff; font-size: 15px; font-weight: 500; }
.tesla-sync__action:hover:not(:disabled) { filter: brightness(1.12); }
.tesla-sync__cars { display: flex; flex-direction: column; gap: 6px; }
.tesla-sync__car { display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 100%; min-height: 64px; padding: 10px 14px; text-align: left; border: 1px solid transparent; border-radius: var(--t-radius-sm); background: var(--t-search); color: var(--t-text); }
.tesla-sync__car[aria-pressed="true"] { border-color: var(--t-blue); }
.tesla-sync__car:hover { background: var(--t-hl); }
.tesla-sync__car strong { display: block; font-size: 16px; font-weight: 500; overflow-wrap: anywhere; }
.tesla-sync__car small { display: block; margin-top: 3px; color: var(--t-text-2); font-size: 12px; }
.tesla-sync__qr { display: block; width: 160px; height: 160px; padding: 8px; margin: 0 auto 10px; background: #fff; border-radius: var(--t-radius-sm); }
.tesla-sync__account { display: flex; align-items: center; justify-content: center; min-height: 44px; margin-bottom: 10px; color: var(--t-text); font-size: 14px; }
.tesla-sync__account:hover { color: var(--t-blue); }

/* ALL-201: Car route pairing entry point. Reuses the tesla-sync panel content
   (h3 / p / __qr / __account / __action / __close) but floats it as a centred
   overlay card the way the Settings sheets do, since it opens over Settings. */
#car-route-pair-overlay {
  position: fixed; inset: 0; z-index: 60; background: var(--t-scrim);
  display: flex; align-items: center; justify-content: center; padding: 16px;
}
#car-route-pair-panel {
  width: 100%; max-width: 460px; border-top: 0;
  border-radius: var(--t-radius-lg); box-shadow: var(--t-shadow);
  animation: t-pop .3s var(--t-ease-pop) both;
}
@media (max-width: 420px) {
  #nav-drawer.collapsed.has-tesla-sync .nav-toggle { padding: 0 8px; gap: 7px; font-size: 16px; }
  .tesla-sync__button { min-width: 112px; max-width: 132px; padding: 0 8px; gap: 6px; }
  .tesla-sync__label strong { max-width: 88px; font-size: 14px; }
  .tesla-sync__label > span { font-size: 12px; }
}

/* ADR-0029 D6: the saving on a drawn faster branch. A Popup (DOM, projected
   by the facade) because no symbol layer here draws text. Non-interactive:
   the branch itself is the tap target. */
.mapboxgl-popup.advice-chip { pointer-events: none; }
.mapboxgl-popup.advice-chip .mapboxgl-popup-tip { display: none; }
.mapboxgl-popup.advice-chip .mapboxgl-popup-content {
  padding: 2px 8px;
  border-radius: 999px;
  background: #17b26a;
  color: #fff;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.3;
  box-shadow: var(--t-shadow);
  white-space: nowrap;
}

.settings-account__badges { display: flex; flex-wrap: wrap; gap: 8px; }
.settings-account__badges[hidden] { display: none; }
.settings-account__badge { display: inline-flex; align-items: center; gap: 5px; padding: 4px 8px; border: 1px solid var(--t-divider); border-radius: var(--t-radius-sm); font-size: 13px; color: var(--t-text); }
.settings-account__badge-icon { flex: none; }
