/* ===== CSS Variables ===== */

/* ===== RECOVERED FROM THE OLDER COPIES =====================================
   This file used to be FOUR different versions of itself concatenated (306 KB;
   `:root {` four times), and the last copy was the one the browser obeyed.

   The earlier copies were NOT a subset of the last one, so truncating to it -
   the way EU and USA were fixed - would have deleted styles still in use
   (#login-form, .password-wrap, .password-toggle, #grant-ending-note,
   the account tow tick, now .acct-vehicle-tow, and the offline-session login
   screen among them).

   These are the rules the earlier copies uniquely defined. They are kept
   verbatim and placed HERE, BEFORE the surviving copy, because that is where
   they were: a rule that used to lose to a later copy must go on losing. Moving
   them to the end of the file would silently flip the cascade for any element
   two rules of equal specificity both match.

   Some are Leaflet-era and almost certainly dead. They are kept because this
   change is a de-duplication, not a cleanup, and proving a rule dead is its own
   job. (2026-09-18)
   ========================================================================= */


html.offline-session #login-screen { display: none; }
/* Stored session: the card stays hidden while it is checked (see index.html). */
html.stored-session #login-screen { visibility: hidden; }

#login-form { width: 100%; display: flex; flex-direction: column; align-items: center; }

.password-wrap { position: relative; width: 100%; }

.password-wrap .login-input { padding-right: 52px; }

.password-toggle {
  position: absolute; right: 10px; top: 10px;
  background: none; border: 0; color: var(--text-muted, #999);
  font: inherit; font-size: max(0.72rem, var(--text-floor, 0px)); font-weight: 600;
  cursor: pointer; padding: 0;
}

/* The account panel's "Towing a caravan or trailer" tick (js/auth.js
   #acct-tow-row). It shared .route-vehicle-tow with the route picker's old
   checkbox row; that row is now the in-grid towing button below, so the
   account tick has its own name and the button rule cannot restyle it. */
.acct-vehicle-tow {
  display: flex; align-items: center; gap: 7px;
  margin-top: 8px;
  font-size: max(0.76rem, var(--text-floor, 0px));
  cursor: pointer;
}

.acct-vehicle-tow[hidden] { display: none; }

#nav-close:hover { color: var(--danger); }

#grant-ending-note {
  position: fixed; bottom: 132px; left: 50%; transform: translateX(-50%);
  z-index: 2001; display: none; align-items: center; gap: 8px;
  background: #243342; color: #e8edf2; border: 1px solid rgba(255,255,255,0.12);
  border-radius: 8px; padding: 10px 14px; font-size: 0.8rem;
  max-width: min(440px, 92vw); box-shadow: 0 8px 30px rgba(0,0,0,0.5);
}

#grant-ending-note i { color: var(--accent, #00c853); flex: none; }

#grant-ending-note span { flex: 1 1 auto; }

#grant-ending-note a {
  color: var(--accent, #00c853); font-weight: 700; text-decoration: none; flex: none;
}

#grant-ending-note button {
  background: none; border: none; cursor: pointer; flex: none;
  color: var(--text-muted, #999); font-size: 1rem; padding: 0 2px; line-height: 1;
}

#grant-ending-note.urgent {
  border-color: rgba(255, 167, 38, 0.55);
  background: #33291d;
}

#grant-ending-note.urgent i { color: #ffa726; }



:root {
  --bg-primary: #0f1923;
  --bg-secondary: #1a2632;
  --bg-tertiary: #243342;
  --bg-card: #1e2d3d;
  --text-primary: #e8edf2;
  --text-secondary: #a3b5c4;
  --text-muted: #8ba0b2;
  --accent: #00c853;
  --accent-hover: #00e676;
  --accent-dim: rgba(0, 200, 83, 0.15);
  --danger: #ff5252;
  --warning: #ffab40;
  --info: #40c4ff;
  --spark-color: #ffe600;
  --vodafone-color: #e60000;
  --twodeg-color: #00aaff;
  --sidebar-width: 380px;
  --mobile-header-height: calc(56px + env(safe-area-inset-top, 0px)); /* fullscreen display: bar background reaches the true screen top; content pads below the camera cutout */
  --radius: 8px;
  --radius-sm: 4px;
  --shadow: 0 4px 24px rgba(0,0,0,0.4);
  --transition: 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}

/* ===== Reset ===== */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; overflow: hidden; }
body {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  background: var(--bg-primary);
  color: var(--text-primary);
  -webkit-font-smoothing: antialiased;
}

/* ===== Login Screen ===== */
#login-screen {
  position: fixed; inset: 0;
  background: var(--bg-primary);
  display: flex; flex-direction: column;
  align-items: center; justify-content: flex-start;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 20px 12px;
  z-index: 10001;
  transition: opacity 0.4s ease;
}
#login-screen.fade-out { opacity: 0; pointer-events: none; }
.login-card {
  display: flex; flex-direction: column; align-items: center;
  padding: 32px 28px;
  background: var(--bg-secondary);
  border-radius: 16px;
  border: 1px solid var(--border, rgba(255,255,255,0.06));
  width: min(360px, 90vw);
  margin-block: auto;
  flex-shrink: 0;
}
.login-input {
  width: 100%; padding: 10px 12px;
  border-radius: var(--radius-sm, 6px);
  border: 1px solid var(--border, rgba(255,255,255,0.1));
  background: var(--bg-tertiary); color: var(--text-primary);
  font-size: 0.88rem; font-family: inherit;
  margin-bottom: 8px; outline: none;
}
.login-input:focus { border-color: var(--accent); }
.login-error {
  font-size: max(0.75rem, var(--text-floor, 0px)); color: #ff5252;
  margin-bottom: 8px; display: none;
  text-align: center;
}
.login-btn {
  width: 100%; padding: 10px; font-size: 0.88rem;
  border-radius: var(--radius-sm, 6px);
  cursor: pointer; margin-bottom: 6px;
}
/* "Message us if you can't sign in" (js/signin-help.js): an outlined button,
   not grey text, so a person stuck on the card sees it. While its form is
   open every other part of the card is hidden; Back shows them again. */
#signin-help-btn {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  margin: 14px 0 0; text-decoration: none; font-size: 0.8rem;
  text-align: center; padding: 10px 8px;
  background: none; color: var(--text-primary, #fff);
  border: 1px solid var(--accent, #00c853);
}
#signin-help-btn i { color: var(--accent, #00c853); }
#signin-help-panel { display: none; width: 100%; text-align: center; }
#signin-help-back { justify-content: center; }
.login-card.signin-help-open > :not(#signin-help-panel) { display: none !important; }
.login-card.signin-help-open > #signin-help-panel { display: block; }
.login-google-btn {
  background: var(--bg-tertiary); color: var(--text-secondary);
  border: 1px solid var(--border, rgba(255,255,255,0.1));
}
.login-google-btn:hover { background: var(--bg-card); }

/* ===== Loading Screen ===== */
#loading-screen {
  position: fixed; inset: 0;
  background: var(--bg-primary);
  display: flex; align-items: center; justify-content: center;
  z-index: 10000;
  transition: opacity 0.5s ease;
}
#loading-screen.fade-out { opacity: 0; pointer-events: none; }
.loader-content { text-align: center; }
.loader-icon { font-size: 3rem; color: var(--accent); margin-bottom: 1rem; animation: pulse 1.5s infinite; }
.loader-content h2 { font-size: 1.5rem; font-weight: 700; margin-bottom: 0.5rem; }
.loader-content p { color: var(--text-secondary); margin-bottom: 1.5rem; }
.loader-bar { width: 200px; height: 3px; background: var(--bg-tertiary); border-radius: 3px; overflow: hidden; margin: 0 auto; }
.loader-progress { height: 100%; width: 30%; background: var(--accent); border-radius: 3px; animation: loading 1.5s ease-in-out infinite; }
@keyframes loading { 0% { transform: translateX(-100%); } 100% { transform: translateX(400%); } }
@keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.1); } }

/* ===== Mobile Header ===== */
#mobile-header {
  display: none;
  position: fixed; top: 0; left: 0; right: 0;
  height: var(--mobile-header-height);
  background: var(--bg-secondary);
  border-bottom: 1px solid var(--bg-tertiary);
  align-items: center;
  justify-content: space-between;
  padding: env(safe-area-inset-top, 0px) 12px 0;
  z-index: 1001;
}
#mobile-header h1 { font-size: 1.1rem; font-weight: 700; }
#mobile-header button {
  background: none; border: none; color: var(--text-primary);
  font-size: 1.2rem; padding: 8px; cursor: pointer;
}

/* ===== Sidebar ===== */
.sidebar {
  position: fixed; top: 0; left: 0; bottom: 0;
  width: var(--sidebar-width);
  background: var(--bg-secondary);
  border-right: 1px solid var(--bg-tertiary);
  display: flex; flex-direction: column;
  z-index: 1000;
  transition: transform var(--transition);
  overflow: hidden;
}
.sidebar.collapsed { transform: translateX(-100%); }

/* Desktop sidebar collapse (onX-style). One body-level var override moves every
   --sidebar-width dependent (#map, #info-panel, #weather-timeline, 3D/basemap
   switches) at once. Desktop-only: mobile keeps the .open overlay pattern. */
@media (min-width: 769px) {
  body.sidebar-collapsed { --sidebar-width: 0px; }
  body.sidebar-collapsed .sidebar { transform: translateX(-100%); }
  /* Collapsed sidebar puts #info-panel at left:12px — the same corner the
     fixed #sidebar-expand (») button occupies — covering the site name.
     Shift the pane right to clear the 38px button + gaps. */
  body.sidebar-collapsed #info-panel { left: 62px; }
}
#sidebar-collapse {
  background: none; border: none; color: var(--text-secondary);
  font-size: 1rem; cursor: pointer; padding: 4px 8px;
}
#sidebar-collapse:hover { color: var(--text-primary); }
#sidebar-expand {
  position: fixed; top: 12px; left: 12px; z-index: 1001;
  width: 38px; height: 38px; border-radius: var(--radius-sm);
  background: var(--bg-secondary); border: 1px solid var(--bg-tertiary);
  color: var(--text-primary); font-size: 1rem; cursor: pointer;
  display: none; align-items: center; justify-content: center;
  box-shadow: 0 2px 10px rgba(0,0,0,0.4);
}
body.sidebar-collapsed #sidebar-expand { display: flex; }

.sidebar-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 16px 20px;
  border-bottom: 1px solid var(--bg-tertiary);
  flex-shrink: 0;
}
.brand { display: flex; align-items: center; gap: 10px; }
.brand i { font-size: 1.5rem; color: var(--accent); }
.brand h1 { font-size: 1.25rem; font-weight: 700; }
#sidebar-close {
  background: none; border: none; color: var(--text-secondary);
  font-size: 1.2rem; cursor: pointer; padding: 4px;
  display: none;
}

.sidebar-body {
  flex: 1; overflow-y: auto; overflow-x: hidden;
  padding: 0;
  scrollbar-width: thin;
  scrollbar-color: var(--bg-tertiary) transparent;
}
.sidebar-body::-webkit-scrollbar { width: 6px; }
.sidebar-body::-webkit-scrollbar-track { background: transparent; }
.sidebar-body::-webkit-scrollbar-thumb { background: var(--bg-tertiary); border-radius: 3px; }

.sidebar-footer {
  padding: 12px 20px;
  border-top: 1px solid var(--bg-tertiary);
  flex-shrink: 0;
}
.sidebar-footer p { font-size: max(0.7rem, var(--text-floor, 0px)); color: var(--text-muted); line-height: 1.5; }

/* ===== Tab Navigation ===== */
.tab-nav {
  display: flex;
  border-bottom: 1px solid var(--bg-tertiary);
  flex-shrink: 0;
}
.tab-btn {
  flex: 1;
  padding: 12px 8px;
  background: none; border: none;
  color: var(--text-secondary);
  font-size: 0.8rem; font-weight: 500;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  transition: all var(--transition);
  display: flex; align-items: center; justify-content: center; gap: 6px;
}
.tab-btn:hover { color: var(--text-primary); background: var(--bg-tertiary); }
.tab-btn.active { color: var(--accent); border-bottom-color: var(--accent); }
.tab-btn i { font-size: 0.9rem; }

.tab-content { display: none; }
.tab-content.active { display: block; }

/* ===== Sidebar Sections ===== */
.sidebar-section {
  padding: 16px 20px;
  border-bottom: 1px solid rgba(255,255,255,0.04);
}
.sidebar-section h3 {
  font-size: 0.8rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--text-secondary);
  margin-bottom: 12px;
  display: flex; align-items: center; gap: 8px;
}
.sidebar-section h3 i { color: var(--accent); font-size: 0.85rem; }
.sidebar-section h4 {
  font-size: 0.78rem; font-weight: 600;
  color: var(--text-secondary);
  margin: 12px 0 8px;
}
.hint { font-size: 0.78rem; color: var(--text-muted); margin-bottom: 12px; line-height: 1.4; }

/* ===== Search ===== */
.search-box {
  display: flex; align-items: center;
  background: var(--bg-tertiary);
  border-radius: var(--radius);
  padding: 0 12px;
  border: 1px solid transparent;
  transition: border-color var(--transition);
}
.search-box:focus-within { border-color: var(--accent); }
.search-box i { color: var(--text-muted); font-size: 0.85rem; }
.search-box input {
  flex: 1; background: none; border: none; outline: none;
  color: var(--text-primary); padding: 10px 10px;
  font-size: 0.85rem; font-family: inherit;
}
.search-box input::placeholder { color: var(--text-muted); }
.search-box button { background: none; border: none; color: var(--text-muted); cursor: pointer; padding: 4px; }
.search-results {
  margin-top: 8px;
  background: var(--bg-tertiary);
  border-radius: var(--radius);
  max-height: 240px;
  overflow-y: auto;
}
.search-results.hidden { display: none; }
.search-result-item {
  padding: 10px 14px;
  cursor: pointer;
  font-size: 0.82rem;
  border-bottom: 1px solid rgba(255,255,255,0.04);
  transition: background var(--transition);
  display: flex; align-items: center; gap: 8px;
}
.search-result-item:hover { background: rgba(255,255,255,0.05); }
.search-result-item i { color: var(--accent); width: 16px; text-align: center; }
.search-result-item:last-child { border-bottom: none; }
/* Recent searches, listed when the empty box is focused (js/app.js showRecent). */
.search-recent-head { padding: 8px 14px 4px; font-size: max(0.7rem, var(--text-floor, 0px)); font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-muted); }
.search-recent-clear { display: block; width: 100%; padding: 10px 14px; background: none; border: none; text-align: left; color: var(--text-muted); font: inherit; font-size: max(0.76rem, var(--text-floor, 0px)); cursor: pointer; }
.search-recent-clear:hover { color: var(--text-primary); }
.search-results:focus { outline: none; }
/* How far a near-me row is (RoutePlanner._distAttr): after the name, so the
   row's text, which the pick handlers read, stays the name. */
.search-result-item[data-dist]::after, .nav-dest-item[data-dist]::after {
  content: attr(data-dist); margin-left: auto; padding-left: 8px;
  color: var(--text-muted, #9aa4b2); font-size: max(0.85em, var(--text-floor, 0px)); white-space: nowrap; flex: 0 0 auto;
}

/* ===== Layer Toggles ===== */
.layer-toggle {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 0;
  cursor: pointer;
  font-size: 0.85rem;
}
.layer-toggle input { display: none; }
.toggle-slider {
  width: 36px; height: 20px;
  background: var(--bg-tertiary);
  border-radius: 10px;
  position: relative;
  transition: background var(--transition);
  flex-shrink: 0;
}
.toggle-slider::after {
  content: '';
  position: absolute;
  width: 16px; height: 16px;
  background: var(--text-secondary);
  border-radius: 50%;
  top: 2px; left: 2px;
  transition: all var(--transition);
}
.layer-toggle input:checked + .toggle-slider { background: var(--accent-dim); }
.layer-toggle input:checked + .toggle-slider::after { transform: translateX(16px); background: var(--accent); }
.toggle-label { flex: 1; }

/* ===== Route vehicle picker (js/vehicle.js) ===== */
/* Ported from Motocamp USA, minus the towing row — NZ ships no towing modifier
   (it would change nothing about the route here; see js/vehicle.js). */
.route-vehicle {
  padding-bottom: 10px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--bg-tertiary);
}
.route-vehicle-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 6px; margin-bottom: 7px;
}
.route-vehicle-title {
  font-size: max(0.72rem, var(--text-floor, 0px)); font-weight: 600;
  color: var(--text-secondary);
  text-transform: uppercase; letter-spacing: 0.04em;
}
.route-vehicle-reset {
  background: none; border: none; padding: 0;
  color: var(--text-muted, #888);
  font-size: max(0.66rem, var(--text-floor, 0px)); cursor: pointer; text-decoration: underline;
}
.route-vehicle-reset:hover { color: var(--accent); }
.route-vehicle-reset[hidden] { display: none; }
.route-vehicle-picker {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 5px;
}
.route-vehicle-btn {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 7px 3px;
  border: 1px solid var(--bg-tertiary);
  border-radius: 6px;
  background: transparent;
  color: var(--text-secondary);
  font-size: max(0.62rem, var(--text-floor, 0px));
  cursor: pointer;
  transition: all var(--transition);
  min-width: 0;
}
.route-vehicle-btn i { font-size: 0.92rem; }
.route-vehicle-btn span { overflow-wrap: anywhere; text-align: center; line-height: 1.2; }
.route-vehicle-btn:hover { border-color: var(--text-muted, #888); color: var(--text-primary); }
.route-vehicle-btn[aria-pressed="true"] {
  border-color: var(--accent);
  background: var(--accent-dim);
  color: var(--text-primary);
}
.route-vehicle-btn[aria-pressed="true"] i { color: var(--accent); }
/* The tow tick is a TOGGLE, not another class. It answers a different
   question, "and what's behind you", so it gets its own full-width row at the
   end of the grid, laid out horizontally to read as a switch rather than as
   another rig to choose between. Same rule as NZ, USA and EU. */
.route-vehicle-tow {
  grid-column: 1 / -1;
  flex-direction: row;
  justify-content: center;
  gap: 6px;
  padding: 6px 8px;
}
.route-vehicle-note {
  font-size: max(0.66rem, var(--text-floor, 0px));
  color: var(--text-muted, #888);
  margin-top: 7px;
  line-height: 1.45;
}
.route-vehicle-note:empty { display: none; }
.route-vehicle-note .warn { color: #ffab40; }

.badge {
  font-size: max(0.65rem, var(--text-floor, 0px)); font-weight: 600;
  padding: 2px 6px; border-radius: 3px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.badge-green { background: rgba(0,200,83,0.15); color: #00c853; }
.badge-blue { background: rgba(64,196,255,0.15); color: #40c4ff; }
.badge-orange { background: rgba(255,171,64,0.15); color: #ffab40; }
.badge-purple { background: rgba(176,128,255,0.15); color: #b080ff; }
.badge-teal { background: rgba(38,198,178,0.15); color: #26c6b2; }
.badge-yellow { background: rgba(255,230,0,0.15); color: #ffe600; }
.badge-red { background: rgba(230,0,0,0.15); color: #ff5252; }

/* ===== Base Map Selector ===== */
/* Warm up CARTO Voyager's pale palette so the base map reads richer, not washed out */
.basemap-selector { display: flex; gap: 6px; }
.basemap-btn {
  flex: 1; padding: 8px;
  background: var(--bg-tertiary);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--text-secondary);
  font-size: max(0.75rem, var(--text-floor, 0px)); font-weight: 500;
  cursor: pointer;
  transition: all var(--transition);
  display: flex; flex-direction: column; align-items: center; gap: 4px;
}
.basemap-btn i { font-size: 1rem; }
.basemap-btn:hover { color: var(--text-primary); background: rgba(255,255,255,0.08); }
.basemap-btn.active { border-color: var(--accent); color: var(--accent); background: var(--accent-dim); }

/* ===== Filter Chips ===== */
.filter-group { display: flex; flex-wrap: wrap; gap: 6px; }
.filter-chip {
  display: flex; align-items: center; gap: 4px;
  padding: 6px 10px;
  background: var(--bg-tertiary);
  border-radius: 20px;
  font-size: max(0.75rem, var(--text-floor, 0px));
  cursor: pointer;
  transition: all var(--transition);
  border: 1px solid transparent;
  user-select: none;
}
.filter-chip:hover { background: rgba(255,255,255,0.08); }
.filter-chip input { display: none; }
.filter-chip:has(input:checked) { border-color: var(--accent); background: var(--accent-dim); color: var(--accent); }
.filter-chip i { font-size: max(0.7rem, var(--text-floor, 0px)); }

/* ===== Route Planning ===== */
.route-inputs { display: flex; flex-direction: column; gap: 4px; }
.route-point {
  display: flex; align-items: center; gap: 10px;
  background: var(--bg-tertiary);
  border-radius: var(--radius-sm);
  padding: 0 12px;
}
/* The box gives way, the buttons never: with the moon added, a stop row on a
   384 px phone pushed its remove button off the screen (S23, 2026-10-08). An
   input will not shrink below its natural width without min-width: 0. */
.route-point > button { flex: none; }
.route-point input {
  flex: 1; min-width: 0; background: none; border: none; outline: none;
  color: var(--text-primary); padding: 10px 0;
  font-size: 0.85rem; font-family: inherit;
}
.route-point input::placeholder { color: var(--text-muted); }
/* Route search results dropdown */
.route-search-results {
  position: absolute;
  left: 0; right: 0;
  z-index: 9999;
  margin-top: 2px;
  max-height: 200px;
  overflow-y: auto;
  box-shadow: 0 6px 20px rgba(0,0,0,0.4);
  border: 1px solid rgba(255,255,255,0.08);
}
/* Suggestions are positioned in JS (see _positionResults in js/route.js):
   fixed to the viewport but anchored to the input's real position, flipping
   above it when the phone keyboard leaves no room below. The old mobile rule
   pinned them to top:52px of the SCREEN — detached from the box being typed
   in, floating over the header ("the destination box broke out"). */
.route-search-results {
  position: fixed;
  z-index: 10000;
}
.route-start-icon { color: var(--accent); font-size: max(0.6rem, var(--text-floor, 0px)); }
.route-locate-btn {
  flex-shrink: 0;
  padding: 6px 8px;
  border: none;
  color: var(--accent);
  background: none;
  font-size: 1rem;
  cursor: pointer;
  opacity: 0.7;
  transition: opacity 0.2s;
}
.route-locate-btn:hover { opacity: 1; }
.route-locate-btn.locating { animation: pulse-locate 1s infinite; }
@keyframes pulse-locate { 0%, 100% { opacity: 0.4; } 50% { opacity: 1; } }
.route-end-icon { color: var(--danger); font-size: 0.85rem; }
.route-waypoint-icon { color: var(--info); font-size: max(0.6rem, var(--text-floor, 0px)); }
/* "Stay here overnight" on a stop's row: dim until set. The input comes
   first in the row, so the mark on it lights its own moon. */
.route-night-btn { padding: 4px; border: none; background: none; color: var(--text-muted); opacity: 0.55; }
.route-point input[data-night] ~ .route-night-btn { color: #ffb300; opacity: 1; }
/* The same amber as the bed the stop becomes on the map (route.js WP_STYLE.night). */
.route-point:has(input[data-night]) .route-waypoint-icon { color: #ffb300; }

.route-actions { display: flex; gap: 8px; margin-top: 8px; }
.route-dates { margin-top: 4px; }
.date-inputs { display: flex; gap: 8px; }
.date-field { flex: 1; }
.date-field label { display: block; font-size: max(0.72rem, var(--text-floor, 0px)); color: var(--text-muted); margin-bottom: 4px; }
.date-field input {
  width: 100%; padding: 8px 10px;
  background: var(--bg-tertiary);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  font-size: 0.82rem; font-family: inherit;
  outline: none;
}
.date-field input:focus { border-color: var(--accent); }

/* ===== Buttons ===== */
.btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 14px;
  background: var(--bg-tertiary);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: var(--radius-sm);
  color: var(--text-secondary);
  font-size: 0.8rem; font-weight: 500;
  cursor: pointer;
  transition: all var(--transition);
  font-family: inherit;
}
.btn:hover { background: rgba(255,255,255,0.08); color: var(--text-primary); }
.btn-sm { padding: 6px 10px; font-size: max(0.75rem, var(--text-floor, 0px)); }
.btn-primary { background: var(--accent); color: #000; border-color: var(--accent); font-weight: 600; }
.btn-primary:hover { background: var(--accent-hover); color: #000; }
.btn-danger { border-color: rgba(255,82,82,0.3); color: var(--danger); }
.btn-danger:hover { background: rgba(255,82,82,0.1); }
.btn-block { width: 100%; justify-content: center; margin-top: 12px; }

/* ===== Weather Controls ===== */
.time-slider-container { padding: 4px 0; }
.time-slider-container input[type="range"] {
  width: 100%; -webkit-appearance: none; appearance: none;
  height: 4px; background: var(--bg-tertiary);
  border-radius: 2px; outline: none;
}
.time-slider-container input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 16px; height: 16px;
  background: var(--accent); border-radius: 50%;
  cursor: pointer;
}
.time-labels { display: flex; justify-content: space-between; margin-top: 4px; font-size: max(0.7rem, var(--text-floor, 0px)); color: var(--text-muted); }

/* ===== Map ===== */
#map {
  position: fixed;
  top: 0; bottom: 0;
  left: var(--sidebar-width);
  right: 0;
  z-index: 1;
}

/* ===== Map Controls ===== */
#map-controls {
  position: fixed;
  top: 16px; right: 16px;
  display: flex; flex-direction: column; gap: 8px;
  z-index: 500;
}
.map-btn {
  width: auto; height: auto;
  padding: 8px 10px;
  background: var(--bg-secondary);
  border: 1px solid var(--bg-tertiary);
  border-radius: var(--radius);
  color: var(--text-primary);
  font-size: 1rem;
  cursor: pointer;
  display: flex; align-items: center; gap: 6px;
  transition: all var(--transition);
  box-shadow: var(--shadow);
}
.map-btn-label {
  font-size: max(0.65rem, var(--text-floor, 0px));
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--text-secondary);
}
.map-btn:hover { background: var(--bg-tertiary); color: var(--accent); }
.map-btn:hover .map-btn-label { color: var(--accent); }
.map-btn.active { background: var(--accent); color: #fff; border-color: var(--accent); }
.map-btn.active .map-btn-label { color: #fff; }

/* ===== Weather Tile Overlays ===== */
.weather-tile-temperature { filter: saturate(2.5) contrast(1.8) brightness(1.1); }
.weather-tile-precipitation { filter: saturate(2.0) contrast(1.5); }
.weather-tile-clouds { filter: contrast(1.4) brightness(0.95); }
.weather-tile-wind { filter: saturate(1.8) contrast(1.4); }

/* ===== Weather Timeline ===== */
/* ===== Info Panel =====
   Vertical side-loading pane (onX-style), 2026-07-27. Was a bottom-anchored
   horizontal strip eating 50vh; now a left card over the map. Mobile keeps the
   bottom-sheet geometry (overridden in the 768px block below). */
#info-panel {
  position: fixed;
  /* Height follows the content, capped at the viewport — NOT top+bottom, which
     stretched a one-line POI (a toilet) into a full-height empty column
     (Josh, 2026-08-10). Long cards still scroll inside the cap. */
  top: 12px;
  max-height: calc(100vh - 24px);
  left: calc(var(--sidebar-width) + 12px);
  width: 380px;
  max-width: calc(100vw - var(--sidebar-width) - 24px);
  background: var(--bg-secondary);
  border: 1px solid var(--bg-tertiary);
  border-radius: var(--radius);
  z-index: 600;
  overflow-y: auto;
  box-shadow: 0 8px 30px rgba(0,0,0,0.45);
  transition: transform var(--transition), opacity var(--transition);
}
#info-panel.hidden {
  transform: translateX(calc(-100% - 420px));
  opacity: 0;
  pointer-events: none;
}
#info-close {
  position: sticky; top: 0; right: 0;
  float: right; margin: 12px 12px 0 0;
  background: var(--bg-tertiary); border: none;
  z-index: 10;
  color: var(--text-secondary); width: 32px; height: 32px;
  border-radius: 50%; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  font-size: 0.9rem;
}
#info-content { padding: 20px 24px 24px; }

/* Info Panel Content */
.info-header { margin-bottom: 16px; }
.info-header h2 { font-size: 1.15rem; font-weight: 700; margin-bottom: 4px; }
.info-header .info-type { font-size: 0.78rem; color: var(--accent); font-weight: 500; text-transform: uppercase; letter-spacing: 0.03em; }
.info-header .info-status { font-size: max(0.75rem, var(--text-floor, 0px)); padding: 2px 8px; border-radius: 3px; margin-left: 8px; }
.info-header .info-status.open { background: rgba(0,200,83,0.15); color: #00c853; }
.info-header .info-status.closed { background: rgba(255,82,82,0.15); color: #ff5252; }

.info-facilities {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin-bottom: 16px;
}
/* The dog rule, its own row above the facility badges (it was the first item
   of their strip, which on a phone scrolls sideways and cut it). */
.info-dogs { margin-bottom: 8px; }
/* The phone's popup card and the cards' drag handle (js/sheet.js) exist only
   in the phone layout; a desktop keeps MapLibre popups. */
#map-sheet, #route-sheet, #layers-sheet, .sheet-handle { display: none; }
/* The route card's Save / Share / Clear row (js/route-start.js) shows only in
   the route card on a phone; the menu has its own Save and Clear. */
#route-card-actions { display: none; }
/* The route card's red hazard line (js/sheet.js _buildHazardLine): only in the card on a phone. */
#route-hazard-line { display: none; }
/* The route card's mini bar (js/sheet.js _buildMini): only in its mini state, on a phone. */
#route-mini { display: none; }
.facility-badge {
  display: flex; align-items: center; gap: 5px;
  padding: 6px 10px;
  background: var(--bg-tertiary);
  border-radius: var(--radius-sm);
  font-size: max(0.75rem, var(--text-floor, 0px));
  color: var(--text-secondary);
}
.facility-badge.available { color: var(--accent); background: var(--accent-dim); }
.facility-badge i { font-size: 0.8rem; }

.info-details { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 16px; }
.info-detail { }
.info-detail-label { font-size: max(0.7rem, var(--text-floor, 0px)); color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.03em; margin-bottom: 2px; }
.info-detail-value { font-size: 0.85rem; color: var(--text-primary); }

.info-weather {
  background: var(--bg-tertiary);
  border-radius: var(--radius);
  padding: 14px;
  margin-bottom: 16px;
}
.info-weather h4 { font-size: 0.78rem; color: var(--text-secondary); margin-bottom: 8px; }
.weather-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(80px, 1fr)); gap: 8px;
}
.weather-item { text-align: center; }
.weather-item .wx-icon { font-size: 1.3rem; margin-bottom: 2px; }
.weather-item .wx-temp { font-size: 1rem; font-weight: 600; }
.weather-item .wx-desc { font-size: max(0.68rem, var(--text-floor, 0px)); color: var(--text-muted); }
.weather-item .wx-day { font-size: max(0.68rem, var(--text-floor, 0px)); color: var(--text-secondary); font-weight: 500; }

.info-cell-coverage { margin-bottom: 16px; }
.cell-bar-container { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.cell-carrier { font-size: 0.78rem; width: 70px; color: var(--text-secondary); }
.cell-bar { flex: 1; height: 6px; background: var(--bg-tertiary); border-radius: 3px; overflow: hidden; }
.cell-bar-fill { height: 100%; border-radius: 3px; transition: width 0.5s ease; }
.cell-bar-fill.spark { background: var(--spark-color); }
.cell-bar-fill.vodafone { background: var(--vodafone-color); }
.cell-bar-fill.twodeg { background: var(--twodeg-color); }
.cell-tech { font-size: max(0.68rem, var(--text-floor, 0px)); color: var(--text-muted); width: 30px; }

.info-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.info-actions .btn i { font-size: 0.8rem; }
/* A card action's phone label (RoutePlanner.routeActionsHTML): the desktop shows the full words. */
.act-short { display: none; }

/* ===== Pane card components (onX card anatomy, shared by trail/land/hazard/
   waypoint cards and reusable by any info-panel content) ===== */
.pane-tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 14px; }
.pane-tile { background: var(--bg-tertiary); border-radius: var(--radius-sm); padding: 8px 10px; }
.pane-tile.wide { grid-column: 1 / -1; }
.pane-tile .pt-label { font-size: max(0.68rem, var(--text-floor, 0px)); color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.03em; }
.pane-tile .pt-value { font-size: 0.95rem; font-weight: 700; margin-top: 2px; }
.pane-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.pane-chip { border: 1px solid var(--bg-tertiary); background: var(--bg-tertiary); border-radius: 999px; padding: 3px 10px; font-size: max(0.72rem, var(--text-floor, 0px)); color: var(--text-secondary); }
.pane-chip.ok { border-color: var(--accent); color: var(--accent); background: none; }
.pane-chip.warn { border-color: #f59e0b; color: #f59e0b; background: none; }
.pane-chip.bad { border-color: #ff5252; color: #ff5252; background: none; }
.pane-kv { border-top: 1px solid var(--bg-tertiary); padding: 8px 2px; display: flex; gap: 8px; font-size: 0.82rem; }
.pane-kv .k { color: var(--text-muted); min-width: 110px; flex-shrink: 0; }
.pane-kv .v { font-weight: 600; }
.pane-actions { display: flex; gap: 12px; justify-content: space-around; border-top: 1px solid var(--bg-tertiary); padding: 12px 4px 2px; margin-top: 12px; }
.pane-action { background: none; border: none; color: var(--text-secondary); cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 5px; font-size: max(0.7rem, var(--text-floor, 0px)); }
.pane-action .pa-ico { width: 42px; height: 42px; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: var(--bg-tertiary); border: 1px solid var(--bg-tertiary); font-size: 1rem; color: var(--text-primary); }
.pane-action.primary .pa-ico { background: var(--accent); color: #00120a; }
.pane-action:hover .pa-ico { border-color: var(--accent); }
.diff-pill { display: inline-block; border-radius: 5px; padding: 2px 10px; font-weight: 700; font-size: 0.78rem; color: #08131c; }
.pane-note { color: var(--text-muted); font-size: max(0.72rem, var(--text-floor, 0px)); margin-top: 10px; line-height: 1.45; }

/* Waypoints */
.wp-icon-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 6px; margin: 8px 0; }
.wp-icon-grid button { font-size: 1.05rem; padding: 8px 0; border-radius: var(--radius-sm); border: 2px solid var(--bg-tertiary); background: var(--bg-tertiary); cursor: pointer; }
.wp-icon-grid button.sel { border-color: var(--accent); }
.wp-color-row { display: flex; gap: 8px; margin: 8px 0; }
.wp-color-row button { width: 26px; height: 26px; border-radius: 50%; cursor: pointer; border: 2px solid transparent; }
.wp-color-row button.sel { border-color: #fff; }
.wp-marker { width: 30px; height: 30px; border-radius: 50% 50% 50% 0; transform: rotate(-45deg); display: flex; align-items: center; justify-content: center; border: 2px solid #fff; box-shadow: 0 2px 6px rgba(0,0,0,0.5); cursor: pointer; }
.wp-marker span { transform: rotate(45deg); font-size: max(13px, var(--text-floor, 0px)); }
.wp-list-item { display: flex; align-items: center; gap: 10px; padding: 8px 4px; border-bottom: 1px solid var(--bg-tertiary); cursor: pointer; border-radius: var(--radius-sm); }
.wp-list-item:hover { background: var(--bg-tertiary); }
.wp-list-dot { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 0.85rem; flex-shrink: 0; }

/* Trail overlay legend lines (rendered inside the sidebar section) */
.trail-legend-line { display: flex; align-items: center; gap: 10px; padding: 3px 0; font-size: max(0.75rem, var(--text-floor, 0px)); color: var(--text-secondary); }
.trail-legend-swatch { width: 30px; height: 5px; border-radius: 3px; flex-shrink: 0; }

/* ===== Route Summary Styles ===== */
.route-overview {
  display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px;
  margin-bottom: 16px;
}
.route-stat {
  text-align: center;
  padding: 10px;
  background: var(--bg-tertiary);
  border-radius: var(--radius-sm);
}
.route-stat-value { font-size: 1.2rem; font-weight: 700; color: var(--accent); }
.route-stat-label { font-size: max(0.68rem, var(--text-floor, 0px)); color: var(--text-muted); margin-top: 2px; }

.route-segment {
  padding: 12px 0;
  border-bottom: 1px solid rgba(255,255,255,0.04);
}
.route-segment:last-child { border-bottom: none; }
.route-segment-header {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 6px;
}
.route-segment-header h4 { font-size: 0.85rem; font-weight: 600; }
.route-segment-header span { font-size: max(0.75rem, var(--text-floor, 0px)); color: var(--text-muted); }
.route-segment-weather {
  display: flex; gap: 12px;
  margin-bottom: 6px;
}
.route-segment-weather .wx-mini {
  display: flex; align-items: center; gap: 4px;
  font-size: 0.78rem; color: var(--text-secondary);
}
.route-segment-services { display: flex; flex-wrap: wrap; gap: 4px; }
.route-segment-services .service-tag {
  font-size: max(0.68rem, var(--text-floor, 0px));
  padding: 2px 6px;
  background: var(--bg-tertiary);
  border-radius: 3px;
  color: var(--text-muted);
}
.route-segment-services .service-tag.available { color: var(--accent); background: var(--accent-dim); }

/* ===== Elevation Profile ===== */
.elevation-profile {
  margin-bottom: 14px;
  padding-bottom: 12px;
  border-bottom: 1px solid rgba(255,255,255,0.04);
}
.elevation-stats {
  display: flex; gap: 12px;
  margin-bottom: 8px;
  font-size: max(0.75rem, var(--text-floor, 0px));
  color: var(--text-secondary);
}
.elevation-stats span {
  display: flex; align-items: center; gap: 4px;
}
.elevation-chart-wrap {
  position: relative;
  background: var(--bg-tertiary);
  border-radius: var(--radius-sm);
  padding: 4px;
  cursor: crosshair;
}
.elevation-chart-wrap canvas {
  display: block;
  width: 100%;
}
.elevation-tooltip {
  position: absolute;
  background: var(--bg-secondary);
  border: 1px solid var(--bg-tertiary);
  border-radius: var(--radius-sm);
  padding: 4px 8px;
  font-size: max(0.72rem, var(--text-floor, 0px));
  color: var(--text-primary);
  pointer-events: none;
  white-space: nowrap;
  z-index: 10;
  box-shadow: var(--shadow);
  line-height: 1.3;
}
.elevation-map-tooltip {
  background: var(--bg-secondary) !important;
  border: 1px solid var(--accent) !important;
  color: var(--text-primary) !important;
  font-size: max(0.75rem, var(--text-floor, 0px)) !important;
  font-weight: 600 !important;
  padding: 2px 6px !important;
  border-radius: 4px !important;
  box-shadow: var(--shadow) !important;
}
.elevation-map-tooltip::before {
  border-top-color: var(--accent) !important;
}

/* ===== Weather Overlay Legend ===== */
.weather-legend-bar {
  display: flex; height: 12px;
  border-radius: 6px; overflow: hidden;
  margin-bottom: 4px;
}
.weather-legend-labels { display: flex; justify-content: space-between; font-size: max(0.68rem, var(--text-floor, 0px)); color: var(--text-muted); }

/* ===== MapLibre GL Overrides ===== */
.maplibregl-ctrl-attrib { font-size: max(0.6rem, var(--text-floor, 0px)) !important; background: rgba(15,25,35,0.8) !important; color: var(--text-muted) !important; }
.maplibregl-ctrl-attrib a { color: var(--text-secondary) !important; }
.maplibregl-popup-content {
  background: var(--bg-secondary) !important;
  color: var(--text-primary) !important;
  border-radius: var(--radius) !important;
  box-shadow: var(--shadow) !important;
  border: 1px solid var(--bg-tertiary) !important;
  padding: 12px 14px !important;
  font-family: 'Inter', sans-serif !important;
}
/* The popup tip is a CSS border triangle — theme every anchor direction */
.maplibregl-popup-anchor-bottom .maplibregl-popup-tip { border-top-color: var(--bg-secondary) !important; }
.maplibregl-popup-anchor-top .maplibregl-popup-tip { border-bottom-color: var(--bg-secondary) !important; }
.maplibregl-popup-anchor-left .maplibregl-popup-tip { border-right-color: var(--bg-secondary) !important; }
.maplibregl-popup-anchor-right .maplibregl-popup-tip { border-left-color: var(--bg-secondary) !important; }
.maplibregl-popup-close-button { color: var(--text-secondary) !important; font-size: 20px !important; right: 4px !important; top: 2px !important; }
/* Cluster count styling is data-driven GL symbol paint now (Utils.clusterColors) — no DOM cluster CSS. */


/* Home base pin — a green house badge on the main map at the saved home. */
.home-map-marker {
  display: flex; align-items: center; justify-content: center;
  width: 30px; height: 30px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid #fff;
  box-shadow: 0 1px 4px rgba(0,0,0,0.5);
  color: #fff;
  font-size: 14px;
  transition: transform 0.15s ease;
}
.home-map-marker:hover { transform: scale(1.15); }
/* (retired .marker-* divIcon classes removed — markers now draw on canvas via Utils.dotMarker; palette lives in Utils.markerStyles) */

/* ===== Responsive ===== */
@media (max-width: 768px) {
  /* Older riders are the bulk of the audience (AUS: 79% aged 55+) and cannot
     read the 11-13px chrome on a phone. The in-between size (Josh, 2026-11-01:
     "yeah go inbetween"): 112.5% takes the root from 16px to 18px, the body
     size presbyopes use on their own phones (Boccardo 2021/2023), and scales
     every rem size in the UI. It was 125% (20px) from 2026-08-22. Map labels
     are WebGL and stay put. Percentage, not 18px, so a user who already raised
     the browser default still gets the extra step.
     --text-floor: nothing on a phone renders under 14px. Every size below it
     is written max(size, var(--text-floor, 0px)), here and in the builders'
     inline styles; the desktop never sets the token, so its sizes do not move. */
  html { font-size: 112.5%; --text-floor: 14px; }

  /* The old title bar is gone from a phone: the floating bar (#mobile-search,
     phone redesign step 3) replaced it. Its buttons stay in the page, hidden. */
  #mobile-header { display: none; }
  #sidebar-close { display: block; }

  .sidebar {
    width: 100%;
    transform: translateX(-100%);
    top: 0;
  }
  .sidebar.open { transform: translateX(0); }
  .sidebar-header { padding-top: calc(var(--mobile-header-height) + 8px); }

  /* Full bleed: the map runs under the floating top bar (phone redesign step
     3). Whatever frames the map measures the bar instead (RoutePlanner
     FRAME_FURNITURE, Sheet._box, Onboard.MAP_TOP). */
  #map {
    left: 0;
    top: 0;
  }

  /* Under the bar, with the bar's own 10 px of air (the token holds it). */
  #map-controls {
    top: var(--mobile-header-height);
    right: 12px;
  }

  /* ===== One card at the bottom: #info-panel and #map-sheet (js/sheet.js) =====
     Phone redesign step 1 (tasks/phone-ux.md). The card opened at up to 60vh
     and covered what it described; it is now a sheet: a peek that ends under
     the actions (and under every warning, which is never cut), a pull up to
     88% for the rest. Its height is --sheet-h, set by Sheet; nothing here
     sizes it. top/right/width are RESET explicitly: the desktop rule sets them. */
  #info-panel, #map-sheet, #route-sheet {
    position: fixed;
    top: auto; bottom: 0; left: 0; right: 0;
    width: 100%; max-width: 100%;
    height: var(--sheet-h, 0px);
    /* Never under the notch, whatever --sheet-h says (Sheet sizes it from
       innerHeight and the shown bar; this binds only if that read fails). */
    max-height: calc(100% - env(safe-area-inset-top, 0px) - 8px);
    background: var(--bg-secondary); color: var(--text-primary);
    border: none; border-top: 1px solid var(--bg-tertiary);
    border-radius: 16px 16px 0 0;
    box-shadow: 0 -12px 32px rgba(0, 0, 0, 0.42);
    z-index: 600;
    /* The last row would sit under the system bar: padding, not an offset. */
    padding-bottom: env(safe-area-inset-bottom, 0px);
    /* In the peek nothing scrolls: a vertical drag moves the card (Sheet),
       a sideways one still scrolls the facilities row. */
    overflow: hidden; overscroll-behavior: contain; touch-action: pan-x;
    transition: height 0.22s ease, transform 0.22s ease;
  }
  #map-sheet, #route-sheet { display: block; }
  #info-panel.sheet-full, #map-sheet.sheet-full, #route-sheet.sheet-full { overflow-y: auto; touch-action: auto; }
  /* The peek ends under the actions; a short fade says "more below" instead
     of the next line showing cut in half. */
  #info-panel.sheet-peek, #map-sheet.sheet-peek, #route-sheet.sheet-peek {
    -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 14px), transparent);
    mask-image: linear-gradient(to bottom, #000 calc(100% - 14px), transparent);
  }
  #info-panel.sheet-dragging, #map-sheet.sheet-dragging, #route-sheet.sheet-dragging { transition: none; }
  #info-panel.hidden, #map-sheet.hidden, #route-sheet.hidden { transform: translateY(100%); opacity: 1; pointer-events: none; }
  body.nav-active #map-sheet, body.dashboard-active #map-sheet,
  body.nav-active #route-sheet, body.dashboard-active #route-sheet { display: none !important; }
  .sheet-handle {
    display: block; position: sticky; top: 0; z-index: 2;
    height: 22px; background: inherit; cursor: grab; touch-action: none;
  }
  /* The bar is 38 x 5; the tap target around it is 48 px tall. */
  .sheet-handle::before {
    content: ''; position: absolute; left: 50%; top: 9px; transform: translateX(-50%);
    width: 38px; height: 5px; border-radius: 3px; background: rgba(255, 255, 255, 0.28);
  }
  /* The tap area starts at the card's top edge (the panel clips anything
     above it) and reaches 48 px down over the name's top margin. */
  .sheet-handle::after { content: ''; position: absolute; left: 30%; right: 30%; top: 0; height: 48px; }
  /* The X: 48 px, under the handle, and taking no height of its own (the
     negative bottom margin), so the card's content keeps the full width;
     the name makes room for it instead. */
  #info-close, #map-sheet .sheet-close, #route-sheet .sheet-close {
    position: sticky; top: 22px; float: right; z-index: 3;
    width: 48px; height: 48px; margin: 0 8px -48px 0; border-radius: 50%;
    border: none; cursor: pointer; background: var(--bg-tertiary); color: var(--text-secondary);
  }
  #info-content { padding: 2px 14px 12px; }
  /* The cards follow the root (1rem, 18 px); density comes from fewer sizes and less padding. */
  #map-sheet .sheet-content, #route-sheet .sheet-content { padding: 2px 14px 12px; font-size: 1rem; }
  #info-content > .info-header, #info-content .pc-head, #info-content .pop-title,
  #map-sheet .pop-title, #map-sheet .popup-title { padding-right: 52px; min-height: 48px; }
  /* Desktop popups size themselves inline; in the card they take its width. */
  #info-content > .pop-card, #map-sheet .sheet-content > * { min-width: 0 !important; max-width: none !important; }

  /* ===== The map menu: a card at the bottom (js/app.js placeCtxCard) =====
     Josh, 2026-11-01, S10: "the 'head here next' popup takes almost the whole
     screen with almost no way to exit the dialog box". The place card's look:
     rounded top, the card's colour and shadow, no drag handle. 40% of the
     screen at most (--ctx-max-h), over the route card or its mini bar when the
     tap menu keeps it (--ctx-bottom); the rest scrolls under a top bar that
     stays, with its 48 px X. Rows 48 px, 18 px words. */
  #context-menu.ctx-card {
    left: 0; right: 0; top: auto; bottom: var(--ctx-bottom, 0px);
    width: auto; min-width: 0; max-width: 480px; margin: 0 auto;
    max-height: var(--ctx-max-h, 40vh);
    padding: 0 0 env(safe-area-inset-bottom, 0px);
    border: none; border-top: 1px solid var(--bg-tertiary);
    border-radius: 16px 16px 0 0;
    box-shadow: 0 -12px 32px rgba(0, 0, 0, 0.42);
    overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain;
  }
  #context-menu.ctx-card .ctx-top {
    position: sticky; top: 0; z-index: 1;
    display: flex; align-items: center; gap: 8px;
    min-height: 56px; padding: 4px 6px 4px 18px;
    background: var(--bg-secondary); border-bottom: 1px solid var(--bg-tertiary);
  }
  #context-menu.ctx-card .ctx-head {
    flex: 1 1 auto; min-width: 0; max-width: none;
    padding: 0; margin: 0; border: none; font-size: 0.8rem;
  }
  #context-menu.ctx-card .ctx-close {
    flex: 0 0 auto; width: 48px; height: 48px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    border: none; cursor: pointer; font-size: 20px;
    background: var(--bg-tertiary); color: var(--text-primary);
  }
  #context-menu.ctx-card .ctx-item { min-height: 48px; padding: 0 18px; gap: 14px; font-size: 1rem; }
  #context-menu.ctx-card .ctx-item:active { background: rgba(255, 255, 255, 0.08); }
  #context-menu.ctx-card .ctx-item i { width: 22px; font-size: 1rem; }
  #context-menu.ctx-card .ctx-sub { font-size: 0.9rem; padding-top: 0; padding-bottom: 0; }

  /* ===== The place card's first view on a phone: Ridgeflow's =====
     Josh, 2026-11-01, on the S10: "the holiday park info is still far too
     large on screen. make it like ridgeflow". Ridgeflow's Lakes Edge card
     peeks at 238 px (36% of 668): the name on one line, the type line, ONE
     green link to the place's own site, a row of small facility chips, ONE
     row of three buttons. Ours peeked at 370 px (55%): a two-line name at the
     125% root, Save on its own row, the contact block, the dog row, the
     facilities and six buttons.
     The first view, in this order: the name; the type line with Open /
     Closed; the place's own link row (Book direct / Website / Call, one
     44 px row); the facilities (one sideways row of chips); ONE row of three
     actions: Directions (Head here next with a route up), Stay overnight and
     Save. The actions are the ones RoutePlanner.routeActionsHTML marks
     act-main, and the card's own Save (on the desktop it stays under the
     name, Josh 2026-08-22). A real warning (DOC's alerts, every .card-warn,
     "nobody has confirmed this yet", a stated dog rule) comes straight after
     that row, and the peek grows to show it whole (js/sheet.js). Then the
     card's other actions (Directions from here, a NEW route, a saved spot's
     Rename / Remove), "No dog info" (an empty state, not a warning), the
     details, provenance, reviews, nearby, coverage and weather, and last the
     links that leave the app (Search web, Google Maps, Reviews).
     A wrapping flex row, not a column: every part takes a whole line except
     the actions. An action row is display: contents, so its buttons are laid
     out here, each by its own kind: a first-view action (act-main) a third of
     the line, another button or href="#" link (in-app) half of it, a link
     with an address of its own (it leaves the app) at the foot. js/sheet.js
     _peekEnd() and leaves() read them the same way. CSS only for the order:
     the builders and the desktop card are untouched. */
  #info-content { display: flex; flex-flow: row wrap; column-gap: 8px; align-content: flex-start; }
  #info-content > * { order: 10; flex: 1 1 100%; min-width: 0; }
  #info-content > .info-header { order: 1; margin-bottom: 8px; }
  #info-content > .place-direct { order: 2; }
  #info-content > .info-facilities { order: 3; flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; gap: 6px; margin-bottom: 8px; }
  #info-content > .info-facilities:empty { display: none; }
  #info-content > .info-facilities > * { flex: 0 0 auto; }
  /* A vertical drag that starts on the sideways chip row moves the card (iPhone
     WebKit run, 2026-11-01): a scroller resets touch-action, so iOS took the
     pan and sent pointercancel. pan-x keeps the sideways scroll and hands the
     vertical drag to js/sheet.js while the card is a peek. */
  #info-panel.sheet-peek #info-content > .info-facilities,
  #map-sheet.sheet-peek .pop-facilities { touch-action: pan-x; }
  /* Save joins the action row, last of the three, as Ridgeflow's does. */
  #info-content > .info-save-row { display: contents; }
  #info-content > .info-save-row > * { order: 5; }
  /* "Added by a camper, nobody has confirmed this yet" stays directly under
     the name (community-sites.js puts it after the header in the page). */
  #info-content > #community-site-block { order: 1; }
  #info-content > .doc-alerts-slot,
  #info-content > .card-warn, #info-content > .drive-hut-access { order: 6; }
  /* The details grid opens out so the access block, when it is restrictive
     (layers.js _accessRestrictive marks data-access-warn), joins the warnings
     in the first view (Sheet.WARN); otherwise it keeps its place among the
     details below. */
  #info-content > .info-details { display: contents; }
  #info-content > .info-details > * { order: 10; flex: 1 1 100%; min-width: 0; margin-bottom: 12px; }
  /* "Sealed access" and other good news stay with the details, below the first view. */
  #info-content > .info-details > #info-access[data-access-warn] { order: 6; margin-bottom: 8px; }
  /* The dog row: placed by its own data (Utils.dogBannerHTML marks a
     restrictive rule, official or established by riders, with data-dog-rule:
     a warning, up with the others; "Dogs allowed" sits below the first view). */
  #info-content > .info-dogs { display: contents; }
  #info-content > .info-dogs > #dog-banner { order: 8; margin-bottom: 8px; }
  #info-content > .info-dogs > #dog-banner[data-dog-rule] { order: 6; }
  /* The action rows: the campsite / hut / saved spot strip, and the map
     POI and route-audit rows. A form's or a list's own buttons (the waypoint
     editor's Save / Cancel, the list's Export) stay a row after its fields. */
  #info-content > .info-actions:not(.wp-form-actions), #info-content > .pop-actions { display: contents !important; }
  #info-content > .info-actions:not(.wp-form-actions) > *,
  #info-content > .info-actions:not(.wp-form-actions) > .card-route-actions > *,
  #info-content > .pop-actions > *, #info-content > .pop-actions > .card-route-actions > * { order: 7; }
  #info-content > .info-actions:not(.wp-form-actions) > .act-main,
  #info-content > .info-actions:not(.wp-form-actions) > .card-route-actions > .act-main,
  #info-content > .pop-actions > .act-main, #info-content > .pop-actions > .card-route-actions > .act-main { order: 4; }
  #info-content > .info-actions:not(.wp-form-actions) > a:not([href="#"]),
  #info-content > .pop-actions > a:not([href="#"]) { order: 11; }
  /* AUS: a footer row (.info-foot: What's here?'s Navigate, the waypoint
     editor's Save / Cancel, the list's Export) stays one row after what it
     acts on, below the forecast or the fields, and ends no peek (js/sheet.js
     _peekEnd), as in AUS step 1. */
  #info-content > .info-actions.info-foot { display: flex !important; order: 10; }
  /* The search card (js/place-card.js): the same first view. Its Go button
     stays first among the actions; its address and hours wait below. */
  #info-content .place-card { display: flex; flex-flow: row wrap; column-gap: 8px; align-content: flex-start; }
  #info-content .place-card > * { order: 10; flex: 1 1 100%; min-width: 0; }
  #info-content .place-card > .pc-head { order: 1; }
  #info-content .place-card > .pc-contact { order: 2; border-top: none; padding-top: 0; margin-top: 0; }
  #info-content .place-card > .pc-actions { order: 4; margin: 0 0 8px; }
  #info-content .place-card > .pc-more { display: contents; }
  #info-content .place-card > .pc-more > *, #info-content .place-card > .pc-more > .card-route-actions > * { order: 7; }
  #info-content .place-card > .pc-more > .act-main,
  #info-content .place-card > .pc-more > .card-route-actions > .act-main { order: 4; }
  /* AUS live fuel prices: right under the first-view actions (Go above the
     prices, 2026-10-28); the peek ends under them (data-sheet-peek-end,
     js/fuel-prices.js, Sheet._lowerMarkedEnd). */
  #info-content .place-card > .pc-fuel { order: 5; }
  /* The popup-builder cards (rest areas, POIs, eateries, fuel, water, pubs,
     webcams): the name with its type line, the place's own links, the
     badges (a servo's fuels), the first-view actions, the warnings, the
     other in-app actions, the rest; the outside links last. */
  .pop-card { display: flex; flex-flow: row wrap; column-gap: 8px; align-content: flex-start; }
  .pop-card > * { order: 10; flex: 1 1 100%; min-width: 0; }
  .pop-card > .pop-hero { order: 0; }
  .pop-card > .pop-title, .pop-card > .pop-sub { order: 1; }
  .pop-card > .place-direct { order: 2; }
  .pop-card > .pop-facilities { order: 3; }
  .pop-card > .pop-actions { display: contents !important; }
  .pop-card > .pop-actions > *, .pop-card > .pop-actions > .card-route-actions > * { order: 7; }
  .pop-card > .pop-actions > .act-main, .pop-card > .pop-actions > .card-route-actions > .act-main { order: 4; }
  .pop-card > .card-warn { order: 6; }
  .pop-card > .pop-actions > a:not([href="#"]) { order: 11; }
  /* AUS live fuel prices: right under the servo's first-view actions, as on
     the search card; the peek ends under them (data-sheet-peek-end). */
  .pop-card > #fuel-prices-block { order: 5; }
  /* An in-app action: 48 px, its label wrapping inside the button rather
     than off the card; half the line, or a third in the first view's row
     (two when a card has two). 0.85rem (15.3 px), under the 18 px body, so
     Directions (or Next stop), Stay overnight and Save fit one row at 360 px. */
  #info-content > .info-actions:not(.wp-form-actions) > *,
  #info-content > .info-actions:not(.wp-form-actions) > .card-route-actions > *,
  #info-content > .pop-actions > *, #info-content > .pop-actions > .card-route-actions > *,
  .pop-card > .pop-actions > *, .pop-card > .pop-actions > .card-route-actions > *,
  #info-content .place-card > .pc-more > *, #info-content .place-card > .pc-more > .card-route-actions > *,
  #info-content > .info-save-row > * {
    flex: 1 1 calc(50% - 4px); min-width: 0; margin: 0 0 8px; padding: 2px 6px; gap: 5px;
    justify-content: center; text-align: center; font-size: 0.85rem; line-height: 1.2; white-space: normal; text-wrap: balance;
  }
  #info-content > .info-actions:not(.wp-form-actions) > .act-main,
  #info-content > .info-actions:not(.wp-form-actions) > .card-route-actions > .act-main,
  #info-content > .pop-actions > .act-main, #info-content > .pop-actions > .card-route-actions > .act-main,
  .pop-card > .pop-actions > .act-main, .pop-card > .pop-actions > .card-route-actions > .act-main,
  #info-content .place-card > .pc-more > .act-main, #info-content .place-card > .pc-more > .card-route-actions > .act-main,
  #info-content > .info-save-row > * {
    /* Each takes its own label's width, then an equal share of what is left:
       "Stay overnight" gets the room it needs and never wraps (it did in a
       third of 384 px on the S10), Directions and Save are short. A row too
       narrow for all three puts the last on the next line, unbroken. */
    flex: 1 0 auto; white-space: nowrap;
  }
  #info-content .btn i, #map-sheet .btn i, .pop-actions > a i, .pop-actions .card-route-actions > a i,
  #info-content .pc-more > a i { font-size: 0.8rem; }
  /* A popup's links carry their row's inline size (0.72em): the card's
     buttons are one size. The search card's Save (Layers._saveLink, inline
     0.72em) sits in .pc-more, not .pop-actions: it read 11.5 px beside 15 px
     Directions and Stay overnight (card checker, 2026-11-01). */
  .pop-actions > a, .pop-actions .card-route-actions > a,
  #info-content .pc-more > a { font-size: 0.85rem !important; }
  /* The phone's short label ("Stay overnight"); the full words stay in its
     title and aria-label (RoutePlanner.routeActionsHTML). */
  #info-panel .act-long, #map-sheet .act-long { display: none; }
  #info-panel .act-short, #map-sheet .act-short { display: inline; }
  /* An outside link at the foot: its own width, side by side. */
  #info-content > .info-actions:not(.wp-form-actions) > a:not([href="#"]),
  #info-content > .pop-actions > a:not([href="#"]),
  .pop-card > .pop-actions > a:not([href="#"]) { flex: 0 1 auto; }
  /* The card's buttons are its main targets: 48 px tall (.btn-sm was 30). */
  #info-content .btn, #map-sheet .btn, .pop-actions > a, .pop-actions .card-route-actions > a,
  #info-content .place-direct a, #info-content .pc-more > a {
    min-height: 48px; display: inline-flex; align-items: center; gap: 6px; box-sizing: border-box;
  }
  /* The card follows the root: an 18 px body (1rem), a name a step above it
     (1.1rem), the type line, status and chips at 0.8rem (14.4 px), never
     under the 14 px floor. Fewer sizes, closer together. */
  #info-panel, #map-sheet { font-size: 1rem; }
  #info-content > .info-header h2, #info-content .pc-name { font-size: 1.1rem; line-height: 1.25; margin-bottom: 2px; }
  #info-content > .info-header .info-type { font-size: 0.8rem; }
  #info-content > .info-header .info-status { font-size: 0.8rem; padding: 1px 6px; }
  #info-content .pc-sub { font-size: 0.8rem; }
  #info-panel .facility-badge, #map-sheet .facility-badge { font-size: 0.8rem; padding: 3px 8px; gap: 4px; }
  #info-panel .facility-badge i, #map-sheet .facility-badge i { font-size: 0.8rem; }
  /* The place's own links: ONE row, side by side; the site (the one to
     press, Josh 2026-10-02) takes the room, its address cut with an
     ellipsis; Call keeps its own width. */
  #info-panel .place-direct, #map-sheet .place-direct { flex-wrap: nowrap; margin: 0 0 8px; }
  #info-panel .place-direct a, #map-sheet .place-direct a { flex: 1 1 auto; min-width: 0; font-size: 0.9rem; padding: 0 12px; gap: 8px; }
  #info-panel .place-direct a.place-direct-call, #map-sheet .place-direct a.place-direct-call { flex: 0 1 auto; }
  #info-panel .place-direct-detail, #map-sheet .place-direct-detail { font-size: 0.8rem; }
  .pop-actions > a, .pop-actions .card-route-actions > a, #info-content .pc-more > a {
    padding: 0 12px; border: 1px solid rgba(255, 255, 255, 0.14); border-radius: var(--radius-sm);
  }
  /* A link that stands alone on its line (not a word in a sentence) is a
     tap target too: 48 px ("Report a problem with this place" was 29). */
  #info-content [data-error-report-link], #map-sheet [data-error-report-link],
  #info-content .pane-kv .v > a:only-child, #map-sheet .pane-kv .v > a:only-child,
  #info-content .card-link, #map-sheet .card-link {
    min-height: 48px; display: inline-flex; align-items: center; gap: 6px; box-sizing: border-box;
  }
  /* One surface at a time: while a card is up the pan pad, the bottom pills
     and the bottom Start / Clear step aside. Never during the tour
     (body.onboard-on): its "views" step rings the pills. */
  body.sheet-open:not(.onboard-on) #map-ptz,
  body.sheet-open:not(.onboard-on) #basemap-switch,
  body.sheet-open:not(.onboard-on) #mode-3d-switch,
  body.sheet-open:not(.onboard-on) #route-start-btn,
  body.sheet-open:not(.onboard-on) #route-clear-btn,
  body.sheet-open:not(.onboard-on) #search-go-btn { display: none !important; }
  /* The guided first trip (js/first-trip.js, its own bottom sheet, on in
     AUS) is a surface too. A card opened while it is up takes the bottom of
     the screen and the first trip waits under it, hidden but still measured
     (visibility, not display: its _frame reads its height), and comes back
     when the card closes. It never starts while a place card is up
     (FirstTrip._screenBusy, via js/launch-order.js). The route card never
     shares the screen with it: the guide closes that card at its start and
     gives it back at its end (js/sheet.js _firstTrip). The home-screen,
     share and Facebook cards are cleared by a card instead (Sheet._clearPrompts,
     NZ's way; AUS step 1 hid them with visibility here). */
  body.sheet-open:not(.onboard-on) #first-trip { visibility: hidden; pointer-events: none; }
  @media (prefers-reduced-motion: reduce) {
    #info-panel, #map-sheet, #route-sheet { transition: none; }
  }

  /* ===== The route card (js/sheet.js bootRoute, phone redesign step 2) =====
     #route-summary moves in here while the map shows. Its first view, by CSS
     order only (route.js renders the menu's order, untouched): the stats
     row, the red hazard line when the route has a hazard, the climb on one
     line, Start, Save / Share / Clear; about 40% of the screen at most
     (Sheet.ROUTE_PEEK_MAX). One pull away: every warning and closure note
     whole, the climb chart, the informational notes, the rest. The summary's
     wrappers are display: contents here so their children can be ordered. */
  #route-sheet #route-summary {
    display: flex; flex-direction: column;
    padding: 0; margin: 0; border: none; background: none; box-shadow: none;
  }
  #route-sheet #route-summary > h3 { display: none; }
  /* The chart keeps its own heading, right above it (Josh, 2026-11-01: "the elevation profile chart
     has no descriptive header"). Same order as the chart, and first in the DOM, so it sits on top. */
  #route-sheet .elevation-profile > h4 { order: 6; margin: 4px 0 6px; }
  #route-sheet #route-summary-content, #route-sheet #route-elevation-slot,
  #route-sheet .elevation-profile { display: contents; }
  #route-sheet #route-summary > *, #route-sheet #route-summary-content > * { order: 10; }
  #route-sheet #route-summary-content > .route-overview { order: 1; margin: 0 52px 6px 0; gap: 6px; }
  #route-sheet .route-stat { padding: 6px 4px; }
  #route-sheet .route-stat-value { font-size: 1rem; }
  #route-sheet .route-stat-label { font-size: 0.78rem; }
  /* The climb fits the card: nothing runs past its edge, nothing scrolls
     sideways (Josh, 2026-11-01, S10, at 360-384 px: "555m gain · 269m loss ·
     753m max · 4% max g..."). The first view has room for three figures on
     one line, so it shows gain, loss and the top; the steepest grade
     (.elev-maxgrade, js/route.js) joins them in the full card, where a line
     that does not fit wraps whole figures, never cut ones. */
  #route-sheet .elevation-stats {
    order: 2; flex-wrap: wrap; gap: 4px 12px; margin: 0 0 8px; font-size: 0.8rem;
    min-width: 0; max-width: 100%;
  }
  #route-sheet .elevation-stats span { flex: 0 0 auto; white-space: nowrap; }
  #route-sheet:not(.sheet-full) .elevation-stats .elev-maxgrade { display: none; }
  /* The red hazard line (js/sheet.js _hazardLineFill; Josh, 2026-11-01, S10:
     "the no dogs warning made the menu come and cover most of the screen"):
     right under the stats, the first hazard's words and "(+1 more)", one line
     cut with an ellipsis, 48 px tall to tap; the tap opens the card full at
     the warnings. Order 2 with the climb: it comes first in the page. */
  #route-sheet #route-hazard-line:not([hidden]) {
    order: 2; display: flex; align-items: center; gap: 8px; width: 100%;
    min-height: 48px; margin: 0 0 6px; padding: 0 12px;
    border: 1.5px solid #ff5252; border-radius: var(--radius-sm);
    background: rgba(255, 82, 82, 0.12); color: #ff8a80;
    font: inherit; font-size: 0.8rem; font-weight: 700; text-align: left; cursor: pointer;
  }
  #route-sheet #route-hazard-line i { flex: 0 0 auto; }
  #route-sheet .route-hazard-line-text { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  #route-sheet #route-start-inline { order: 3; min-height: 48px; margin: 0 0 8px; }
  #route-sheet #route-card-actions { order: 4; display: flex; gap: 8px; margin: 0 0 10px; }
  #route-sheet #route-card-actions .btn { flex: 1 1 0; min-height: 48px; justify-content: center; display: inline-flex; align-items: center; gap: 6px; }
  #route-sheet #route-card-actions .btn[hidden] { display: none; }
  /* Clear in red, last in the row so it sits on the right under Start (Josh,
     2026-11-01: "the 'clear' button needs to be red, under the start button
     on the right"), styled like the old floating Clear (#route-clear-btn):
     a 1.5 px red border, the text and its icon in #ff8a80 on the card's own
     dark background, 48 px tall. Save and Share stay neutral. */
  #route-sheet #route-card-actions #route-card-clear {
    border: 1.5px solid #ff5252; background: transparent; color: #ff8a80; font-weight: 700;
  }
  #route-sheet #route-card-actions #route-card-clear i { color: #ff8a80; }
  #route-sheet #route-card-actions #route-card-clear:active { background: #ff5252; color: #fff; }
  #route-sheet #route-card-actions #route-card-clear:active i { color: #fff; }
  /* Hazards, whole right under the first view, where the red line's tap
     lands (Sheet._openWarnings): the stops cut off, the red "Do not take this
     route" box, and a notes or warnings slot holding a hazard route.js marked
     (.route-hazard: a closure on the route, a rig limit, a red warning). A
     slot of informational notes only ("Worth a Stop", "Offline ready", speed
     cameras) goes after the climb chart. */
  #route-sheet #route-sealed-slot, #route-sheet #route-block-slot { order: 5; }
  #route-sheet #route-notes-slot, #route-sheet #route-warnings-slot { order: 7; }
  #route-sheet #route-notes-slot:has(.route-hazard),
  #route-sheet #route-warnings-slot:has(.route-hazard) { order: 5; }
  /* The offline router's "planned on your device" box is the route's distance
     and time (an offline route has no stat tiles): with the stats, whole. */
  #route-sheet #route-summary-content > .route-device-summary { order: 1; margin: 0 52px 8px 0; }
  /* AUS writes a placeholder into the climb slot ("Building elevation
     profile...", or "Elevation unavailable" offline): it takes the chart's
     place, never the card's first line above the figures. */
  #route-sheet .elevation-chart-wrap, #route-sheet .elev-pending { order: 6; margin: 0 0 12px; }
  /* GPX is a file to take elsewhere, not a step to make the route work (Josh, 2026-11-01): last. */
  #route-sheet #route-summary-content > .route-gpx { order: 20; }
  /* Below the fold: the surface key and the route options. Not the GPX block,
     which is also a div with no id after the key slot: :not([id]) made this the
     more specific rule and put GPX at order 8, above the fuel line, the trip
     plan and "Problem with this route" (S10, 2026-11-02). It stays last (20). */
  #route-sheet #route-key-slot, #route-sheet #route-key-slot ~ div:not([id]):not(.route-gpx),
  #route-sheet #route-segments-slot { order: 8; }
  /* One surface for a route on a phone: the route card, in its peek, full or
     mini state (Josh, 2026-11-01, S10: "i closed the route and the start
     button was below it": closing the card brought back the floating Start
     and Clear, the distance tiles and the Save / Email me / Share row). They
     never show on a phone; js/route-start.js and js/route-hud.js do not put
     them up there either, so nothing is framed around them. The desktop
     keeps all four. The tiles show during the tour alone: tour v3's plan
     step rings them, and the route card is shut on every step but its own. */
  #route-actions, #route-start-btn, #route-clear-btn, #btn-clear-route,
  body:not(.onboard-on) #route-hud { display: none !important; }
  /* The mini bar: the handle (22 px) and one 46 px row over the card's top
     (Sheet.MINI = 22 + 46): the distance and time (a tap is the peek) and
     Start. No X: the bar is as small as the card gets while a route is up. */
  #route-sheet.sheet-mini #route-mini {
    display: flex; align-items: center; gap: 10px;
    position: absolute; left: 0; right: 0; top: 22px; height: 48px; z-index: 2;
    padding: 0 12px 0 18px; background: var(--bg-secondary);
  }
  #route-sheet.sheet-mini .sheet-close { display: none; }
  #route-mini .route-mini-figs {
    flex: 1 1 auto; min-width: 0; min-height: 48px; padding: 0; text-align: left;
    background: none; border: none; color: var(--text-primary); cursor: pointer;
    font: inherit; font-size: 1rem; font-weight: 700;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  #route-mini .route-mini-start {
    flex: 0 0 auto; width: auto; min-height: 48px; padding: 0 18px;
    display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  }
  #route-mini .route-mini-start[hidden] { display: none; }
  /* Under the bar, the phone's own bottom inset (the panel's padding, 34 px
     on an iPhone) would show the card's content: half its Start, a second
     one to tap. Hidden, not removed: Sheet still measures it. */
  #route-sheet.sheet-mini .sheet-content { visibility: hidden; }
  /* One Start in the slot, and the route's own: while a route is up its mini
     bar keeps its Start, and a searched pin's floating Start (js/search-go.js)
     never shows over it. Floated just above the bar it read as the route's
     Start, and a tap cleared the route and went to the searched place
     (2026-11-01 review). The searched place's card has its own Go. */
  body.route-mini #search-go-btn { display: none !important; }
  /* The mini bar is not a card over the map: Topo/Sat, 2D/3D, the pan pad
     and the scale ruler stay, lifted above it (body.route-mini, Sheet._body). */
  body.route-mini:not(.onboard-on) #map-ptz,
  body.route-mini:not(.onboard-on) #basemap-switch,
  body.route-mini:not(.onboard-on) #mode-3d-switch,
  body.route-mini:not(.onboard-on) .maplibregl-ctrl-bottom-right,
  /* ...and the launch cards along the bottom edge (bottom 22 px), which no
     longer wait for the bar (Sheet.cardUp): above it, never over its Start. */
  body.route-mini:not(.onboard-on) #install-card,
  body.route-mini:not(.onboard-on) #share-card,
  body.route-mini:not(.onboard-on) #fb-follow-card,
  /* The trip pass offer too (inline bottom 16 px, z-index 9000): it covered the bar's Start. */
  body.route-mini:not(.onboard-on) #pass-upgrade-card { translate: 0 calc(-70px - env(safe-area-inset-bottom, 0px)); }
  #sidebar-collapse, #sidebar-expand { display: none !important; }

  .info-details { grid-template-columns: 1fr; }
  .route-overview { grid-template-columns: 1fr 1fr; }

  /* Inputs must be ≥16px to prevent iOS Safari from zooming on focus */
  .route-point input,
  .search-box input,
  .date-field input {
    font-size: 1rem;
  }
}

/* In landscape on mobile: compact the sidebar so content fits above the keyboard */
@media (max-width: 768px) and (orientation: landscape) {
  .sidebar-header { padding-top: calc(var(--mobile-header-height) + 4px); padding-bottom: 6px; }
  .sidebar-section { padding: 10px 16px; }
  .sidebar-section h3 { margin-bottom: 8px; }
  .tab-btn { padding: 8px 4px; }
}

@media (max-width: 480px) {
  .tab-btn { padding: 10px 4px; }
  .basemap-selector { flex-direction: column; }
  .weather-grid { grid-template-columns: repeat(3, 1fr); }
}

/* ===== Context Menu ===== */
#context-menu {
  position: fixed;
  background: var(--bg-secondary);
  border: 1px solid var(--bg-tertiary);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  z-index: 2000;
  min-width: 200px;
  padding: 4px 0;
  overflow: hidden;
  /* Bounded, or the tail is unreachable: on a landscape phone with about
     304px of viewport the both-ends menu is roughly 390px tall, the clamp
     pins it at top 8px, and "Something missing here? Add it yourself" / "Something's wrong
     here" sat off the bottom of the screen with no way to reach them.
     (Routing sweep, 2026-08-31.) 16px is the clamp's 8px margin at each end;
     the dvh line wins where supported so a phone browser's toolbar does not
     eat the bottom rows. overflow-y after overflow, so x stays clipped to
     the rounded corners while y scrolls. */
  max-height: calc(100vh - 16px);
  max-height: calc(100dvh - 16px);
  overflow-y: auto;
}
.ctx-item {
  padding: 10px 16px;
  font-size: 0.82rem;
  cursor: pointer;
  display: flex; align-items: center; gap: 10px;
  transition: background 0.15s;
  color: var(--text-primary);
}
.ctx-item:hover { background: rgba(255,255,255,0.06); }
.ctx-item i { color: var(--accent); width: 16px; text-align: center; font-size: 0.85rem; }
.ctx-divider { height: 1px; background: var(--bg-tertiary); margin: 4px 0; }
/* The state header. Names what the app ALREADY holds, so the rider can see
   which half of the route they are on before they choose. Not a .ctx-item: it
   takes no click. Place names sit in .place-name, which EU's i18n skips. */
.ctx-head {
  padding: 8px 16px 7px;
  font-size: max(0.72rem, var(--text-floor, 0px));
  color: var(--text-muted);
  border-bottom: 1px solid var(--bg-tertiary);
  margin-bottom: 4px;
  max-width: 270px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The "instead" lines. Real choices, deliberately quieter than the one obvious
   next step above them: a rider who wants to fix a wrong start can, but nobody
   picks one by mistake while reaching for the loud item. */
.ctx-sub {
  font-size: max(0.76rem, var(--text-floor, 0px));
  padding-top: 7px; padding-bottom: 7px;
  color: var(--text-muted);
}
.ctx-sub i { color: var(--text-muted); }

/* ===== Ride Dashboard ===== */

/* When dashboard is open, hide EVERYTHING else */
body.dashboard-active #map,
body.dashboard-active #sidebar,
body.dashboard-active #mobile-header,
body.dashboard-active #map-controls,
body.dashboard-active #info-panel,
body.dashboard-active #context-menu,
body.dashboard-active .maplibregl-control-container {
  display: none !important;
  visibility: hidden !important;
}

body.dashboard-active {
  overflow: hidden;
  background: #0a0e14;
}

/* index.html asks for viewport-fit=cover and a black-translucent status bar, so
   an installed iPhone app draws UNDER the status bar, the Dynamic Island and
   the home indicator. Pad the dash in by those insets so no readout hides
   behind them (an iPhone 16 Pro Max keeps 59px upright). */
#dashboard-overlay {
  position: fixed;
  inset: 0;
  z-index: 99999;
  background: #0a0e14;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: env(safe-area-inset-top, 0px) env(safe-area-inset-right, 0px)
           env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px);
  overflow: hidden;
}

/* An absolutely positioned child measures from the overlay's PADDING box, so
   the padding above does not move it — it needs the insets itself. Without
   them the X sat at y=6..46 on an iPhone, inside the strip iOS keeps for the
   status bar and the Control Centre pull-down: the rider could see it and his
   taps went to the system, so the only way out of the dash was to kill the app
   (rider report, 2026-09-18). */
.dash-close-btn {
  position: absolute;
  top: calc(6px + env(safe-area-inset-top, 0px));
  right: calc(6px + env(safe-area-inset-right, 0px));
  z-index: 100002;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1px solid var(--bg-tertiary);
  background: rgba(26,38,50,0.8);
  color: var(--text-primary);
  font-size: 1.1rem;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  touch-action: manipulation;
}
.dash-close-btn:hover { color: var(--danger); }

/* ===== Full-screen Navigation Overlay (map + rain radar + speed) ===== */
/* Like the speedo dash, hide everything else behind it. */
body.nav-active #map,
body.nav-active #sidebar,
body.nav-active #mobile-header,
body.nav-active #map-controls,
body.nav-active #info-panel,
body.nav-active #context-menu,
body.nav-active .maplibregl-control-container {
  display: none !important;
  visibility: hidden !important;
}
body.nav-active { overflow: hidden; background: #0a0e14; }

#nav-overlay {
  position: fixed;
  inset: 0;
  z-index: 99999;
  background: #0a0e14;
  overflow: hidden;
}

/* Clip the oversized, rotating map. */
#nav-map-wrap {
  position: absolute;
  inset: 0;
  overflow: hidden;
}
/* A square whose side (145vmax) exceeds any viewport's diagonal (≤ ~1.415·vmax),
   centred with negative margins. It was sized so a CSS rotation never showed a
   corner gap; heading-up is the GL bearing now (js/nav.js applyRotation) and
   the element is never rotated. Nav now shrinks it to the screen in 2D and 3D
   (nav-fit, js/nav.js fitNavCanvasFlat / fitNavCanvasTo3D); the square is
   what the battery switch's "old" puts back. */
#nav-map {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 145vmax;
  height: 145vmax;
  margin-left: -72.5vmax;
  margin-top: -72.5vmax;
  transform-origin: center center;
  /* No CSS transition: nav.js eases the rotation itself, frame by frame (see
     startHeadingLoop). A transition on top would restart every frame and lag. */
  background: #0a0e14;
  will-change: transform;
}
/* The square's size from the LARGE viewport, which does not change as Safari's
   bars slide in and out (vmax does, and every change resized a WebGL canvas,
   which leaks on iOS). max(lvw, lvh) is the long side in either orientation. */
@supports (width: 1lvh) {
  #nav-map {
    width: calc(1.45 * max(100lvw, 100lvh));
    height: calc(1.45 * max(100lvw, 100lvh));
    margin-left: calc(-0.725 * max(100lvw, 100lvh));
    margin-top: calc(-0.725 * max(100lvw, 100lvh));
  }
}
/* The screen-sized canvas, 2D and 3D. The GL camera bearing does heading-up,
   so the square above is never rotated, and every pixel of it outside the
   screen was drawn and then clipped away (2D on an S23: 6.0 Mpx a frame for a
   2.5 Mpx screen). Centred where the square was centred, so the map's centre
   is the same point on screen; in 3D js/nav.js also narrows the field of
   view to match. */
#nav-map.nav-fit {
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  will-change: auto;
}
/* Sized from the LARGE viewport, as the square is: a size that follows
   Safari's sliding bars resizes the WebGL canvas each time, which leaks on
   iOS (WebKit bug 219780). The few rows under the bars are clipped. */
@supports (width: 1lvh) {
  #nav-map.nav-fit {
    top: 50%;
    left: 50%;
    width: 100lvw;
    height: 100lvh;
    margin-left: -50lvw;
    margin-top: -50lvh;
  }
}
#nav-map .maplibregl-control-container { display: none !important; }

/* Rider chevron — points up (direction of travel): the marker is viewport-aligned under the GL bearing. */
.nav-rider { background: none !important; border: none !important; }
.nav-rider-chevron {
  width: 44px; height: 44px;
  display: flex; align-items: center; justify-content: center;
  /* eased in JS per frame (see startHeadingLoop) — no CSS transition */
  /* Arrow size setting (js/display-sizes.js). `scale`, not transform: JS owns transform. */
  scale: var(--arrow-scale, 1);
}
.nav-rider-chevron i {
  font-size: 36px;
  color: #1a73e8;   /* Google-nav blue — a bold directional arrow, not a faint dot */
  /* fa-location-arrow's art points NE (45°); correct it to point straight up. */
  transform: rotate(-45deg);
  /* Heavy white outline OUTSIDE the blue so the arrow reads on the green line
     and any basemap (paint-order draws the stroke first, then the fill). */
  -webkit-text-stroke: 2.5px #fff;
  paint-order: stroke fill;
  filter: drop-shadow(0 1px 3px rgba(0,0,0,0.55));
}

/* Current speed + speed-limit sign — bottom-left corner. */
#nav-speed {
  position: absolute;
  left: calc(14px + env(safe-area-inset-left, 0px));
  bottom: calc(18px + env(safe-area-inset-bottom, 0px));
  z-index: 10;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 14px;
  background: rgba(10,14,20,0.72);
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 14px;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.nav-speed-read { display: flex; align-items: baseline; gap: 4px; }
#nav-speed-value {
  font-size: 4.2rem;
  font-weight: 800;
  line-height: 1;
  color: #fff;
  font-variant-numeric: tabular-nums;
  letter-spacing: -1.5px;
}
#nav-speed-unit { font-size: 0.9rem; font-weight: 600; color: var(--text-secondary); text-transform: uppercase; }

/* Speed-limit sign — a circular white disc with a red ring (NZ/EU style). */
#nav-speed-limit {
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  width: 54px; height: 54px;
  border-radius: 50%;
  background: #fff;
  border: 5px solid #d32f2f;
  box-shadow: 0 1px 5px rgba(0,0,0,0.55);
}
#nav-speed-limit-value {
  color: #111;
  font-weight: 800;
  font-size: 1.3rem;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.5px;
}

/* Next-turn banner — top. */
#nav-turn {
  position: absolute;
  top: calc(12px + env(safe-area-inset-top, 0px));
  left: 50%;
  transform: translateX(-50%);
  z-index: 10;
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: min(88vw, 460px);
  padding: 10px 16px 10px 10px;
  background: rgba(10,14,20,0.82);
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 16px;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  box-shadow: 0 6px 24px rgba(0,0,0,0.4);
}
.nav-turn-icon {
  flex: 0 0 auto;
  width: 46px; height: 46px;
  border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  font-size: 1.5rem;
}
.nav-turn-icon.prox-far  { background: rgba(0,200,83,0.15);  color: #00c853; }
.nav-turn-icon.prox-mid  { background: rgba(255,193,7,0.2);  color: #ffc107; }
.nav-turn-icon.prox-near { background: rgba(255,82,82,0.22); color: #ff5252; }
.nav-turn-info { min-width: 0; }
/* A turn more than 3 km off (js/nav.js turnCardFar): not the full card, a
   one-line pill, "Next turn 46 km, Anzac Highway", about a third of the
   card's height. Rod, AUS, 2026-11-03: "it allows you to relax a little
   knowing the next turn of the road you are on is still 46 km away".
   Voice still waits for the usual distances (js/voice.js). Under 3 km the
   same element turns back into the full card, so the two never show at once. */
.nav-turn-next { display: none; }
#nav-turn.turn-far { gap: 8px; padding: 5px 12px 5px 6px; border-radius: 999px; box-shadow: 0 3px 12px rgba(0,0,0,0.35); }
#nav-turn.turn-far .nav-turn-icon { width: 24px; height: 24px; border-radius: 999px; font-size: 0.8rem; background: rgba(255,255,255,0.1); color: #e5e7eb; }
#nav-turn.turn-far .nav-turn-info { display: flex; align-items: baseline; gap: 6px; min-width: 0; white-space: nowrap; }
#nav-turn.turn-far .nav-turn-next { display: inline; font-size: 0.85rem; color: var(--text-secondary); }
#nav-turn.turn-far #nav-turn-dist { font-size: 0.95rem; font-weight: 700; }
.nav-turn-far-road { display: none; }
#nav-turn.turn-far #nav-turn-road { display: none; }
#nav-turn.turn-far .nav-turn-far-road { display: block; font-size: 0.85rem; color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; max-width: 44vw; }
#nav-turn.turn-far .nav-turn-far-road:empty { display: none; }
#nav-turn.turn-far #nav-turn-lanes { display: none; }
#nav-turn-dist { font-size: 1.5rem; font-weight: 800; color: #fff; line-height: 1.05; }
#nav-turn-road {
  font-size: 0.9rem; color: var(--text-secondary);
  /* The card now names the road turned onto (js/route-attrs.js), and
     "At the roundabout, take exit 2 onto Howe Street" is longer than a phone
     is wide: one clipped line showed "onto Ho...", which is the part the
     rider is looking up for. Upright, the line may fold once; on its side
     (no height to spare) it stays one line, clipped. */
  white-space: normal; overflow: hidden; word-break: break-word;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
@media (min-width: 600px) and (max-height: 500px), (orientation: landscape) and (max-height: 500px) {
  #nav-turn-road { display: block; white-space: nowrap; text-overflow: ellipsis; }
}
/* Lane arrows under the turn text (nav.js laneStripSVG). Static SVG, a
   lane is 24 x 28 units drawn at 1.4x: a glance at speed. */
#nav-turn-lanes { margin-top: 6px; line-height: 0; }
#nav-turn-lanes svg { display: block; height: 40px; width: auto; max-width: 100%; }

/* "Worth a Stop" heads-up — always SUBORDINATE to the turn banner: lower
   z-index and positioned below it, so an imminent turn instruction is never
   obscured or delayed by a POI alert. Tap target: the whole pill dismisses. */
#nav-poi {
  position: absolute;
  top: calc(92px + env(safe-area-inset-top, 0px)); /* clears #nav-turn (~78px at 46px icon) */
  left: 50%;
  transform: translateX(-50%);
  z-index: 9;
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: min(84vw, 420px);
  padding: 10px 14px;
  background: rgba(22, 14, 34, 0.86);
  border: 1px solid rgba(126, 87, 194, 0.55);
  border-radius: 14px;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
  cursor: pointer;
}
#nav-poi.hidden { display: none; }

/* Fixed camera warning — same geometry as #nav-poi so they stack cleanly, but
   red-edged and it owns the slot right under the turn banner. A "possible"
   camera is amber and visibly softer: it must never read as a confirmed one. */
#nav-camera {
  position: absolute;
  top: calc(92px + env(safe-area-inset-top, 0px)); /* the slot #nav-poi used */
  left: 50%;
  transform: translateX(-50%);
  z-index: 9;
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: min(84vw, 420px);
  padding: 10px 14px;
  background: rgba(40, 12, 12, 0.9);
  border: 1px solid rgba(255, 82, 82, 0.75);
  border-radius: 14px;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45);
}
#nav-camera.hidden { display: none; }
#nav-camera-icon { font-size: 1.5rem; color: #ff5252; flex-shrink: 0; width: 34px; text-align: center; }
#nav-camera-name {
  font-weight: 700; font-size: 0.95rem; color: #fff;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#nav-camera-detail { font-size: 0.78rem; color: var(--text-secondary); }
#nav-camera.cam-possible {
  background: rgba(40, 30, 10, 0.88);
  border-color: rgba(255, 171, 64, 0.7);
}
#nav-camera.cam-possible #nav-camera-icon { color: #ffab40; }
/* Scenery yields to the camera warning rather than overlapping it. */
#nav-overlay.cam-visible #nav-poi { top: calc(166px + env(safe-area-inset-top, 0px)); }

/* Camera popup honesty lines (Layers.showCameraPopup) */
.cam-possible { color: #ffab40; font-size: 0.8rem; margin: 4px 0; }
.cam-note { color: var(--text-muted, #999); font-size: max(0.72rem, var(--text-floor, 0px)); margin-top: 6px; line-height: 1.35; }
#nav-poi-icon { font-size: 1.5rem; color: #b39ddb; flex-shrink: 0; width: 34px; text-align: center; }
.nav-poi-info { min-width: 0; }
#nav-poi-name {
  font-weight: 700; font-size: 0.95rem; color: #fff;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#nav-poi-detail { font-size: 0.78rem; color: var(--text-secondary); }

/* Distance / ETA remaining — top-right. */
#nav-eta {
  position: absolute;
  top: calc(12px + env(safe-area-inset-top, 0px));
  right: calc(14px + env(safe-area-inset-right, 0px));
  z-index: 10;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 1px;
  padding: 7px 12px;
  background: rgba(10,14,20,0.72);
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 12px;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
#nav-eta-dist { font-size: 1.15rem; font-weight: 800; color: #fff; line-height: 1.1; }
#nav-eta-time { font-size: 0.8rem; font-weight: 600; color: var(--accent); }

/* Current time — the fullscreen display hides the OS status bar clock (2026-07-28). */
/* The top band (js/nav.js updateTopBand): two small pills in the strip above the clock row,
   left and right of the middle, so a centred camera hole sits between them. Full-screen nav
   hides the phone's status bar; this is ours. Hidden on iPhones (their status bar stays and the
   pills would sit under it) and on the car screen (Motocamp Drive draws its own battery). */
/* Its size comes from the phone's own cutout strip (safe-area-inset-top), so it can never
   reach the clock row in CSS alone: a 34 px strip (S23) gives the full 26 px band 16 px clear
   of the clock; a phone with no strip (or landscape) gives a band 0 px high, nothing drawn. */
.nav-topband { position: absolute; top: max(4px, calc(env(safe-area-inset-top, 0px) - 36px)); z-index: 10; display: flex; align-items: center; gap: 7px;
  height: clamp(0px, calc(env(safe-area-inset-top, 0px) - 8px), 26px); overflow: hidden; padding: 0 10px; box-sizing: border-box; border-radius: 999px;
  background: rgba(10,14,20,0.72); color: #fff; font-weight: 700; font-size: max(0.875rem, var(--text-floor, 0px));
  line-height: 1; white-space: nowrap; pointer-events: none; }
.nav-topband.hidden, html.mc-car .nav-topband, html.ios-top-bar .nav-topband { display: none; }
#nav-status { left: calc(16px + env(safe-area-inset-left, 0px)); }
#nav-battery { right: calc(16px + env(safe-area-inset-right, 0px)); }
#nav-status .nts-sep { width: 1.5px; height: 14px; background: rgba(255,255,255,0.35); border-radius: 1px; }
#nav-battery.low { color: #ff5252; }
#nav-clock {
  position: absolute;
  top: calc(12px + env(safe-area-inset-top, 0px));
  right: calc(14px + env(safe-area-inset-right, 0px));
  z-index: 10;
  padding: 7px 12px;
  background: rgba(10,14,20,0.72);
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 12px;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: #fff;
  font-size: 0.95rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
/* Direction of travel: a small pill in the same chrome as the clock. */
#nav-compass {
  position: absolute;
  top: calc(12px + env(safe-area-inset-top, 0px));
  left: 50%;
  transform: translateX(-50%);
  z-index: 10;
  padding: 7px 10px;
  background: rgba(10,14,20,0.72);
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 12px;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: #fff;
  font-size: 0.9rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  min-width: 2.2em;
  text-align: center;
  display: flex; align-items: center; gap: 6px;
}
/* A small north-up rose; the needle inside turns to the direction of travel. */
#nav-compass .nav-compass-rose { width: 16px; height: 16px; flex: none; display: block; }
/* Temperature and wind: the compass's twins (same chrome, same height). 32px
   so two rows of them plus the 10px gap (74px) stay inside the ETA chip's
   height: the top row grows not a pixel, which a 360x640 screen with the
   radar note lit has none of (NZ/AUS, 2026-10-18). */
/* The top row is one height: clock, compass, temperature and wind. */
#nav-clock, #nav-compass, .nav-wx-pill { height: 32px; box-sizing: border-box; }
.nav-wx-pill {
  padding: 6px 10px;
  background: rgba(10,14,20,0.72);
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 12px;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: #fff;
  font-size: 0.9rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  white-space: nowrap;
  display: flex; align-items: center; gap: 6px;
}
.nav-wx-pill i { font-size: 0.85rem; opacity: 0.8; }
#nav-wind-arrow { width: 16px; height: 16px; flex: none; display: block; color: rgba(255,255,255,0.45); }
.nav-wx-unit { font-size: max(0.7rem, var(--text-floor, 0px)); font-weight: 600; opacity: 0.75; margin-left: -3px; }
#nav-wx { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; }
/* Route active -> the ETA chip owns the corner; the clock tucks below it. */
#nav-eta:not(.hidden) ~ #nav-clock {
  top: calc(80px + env(safe-area-inset-top, 0px));
}

/* GPS elevation — bottom-right corner. */
#nav-elev {
  position: absolute;
  right: calc(14px + env(safe-area-inset-right, 0px));
  bottom: calc(18px + env(safe-area-inset-bottom, 0px));
  z-index: 10;
  display: flex;
  align-items: baseline;
  gap: 5px;
  padding: 7px 12px;
  background: rgba(10,14,20,0.72);
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 12px;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: #fff;
}
#nav-elev i { font-size: 0.85rem; color: var(--text-secondary); align-self: center; }
#nav-elev-value { font-size: 1.3rem; font-weight: 800; font-variant-numeric: tabular-nums; }
.nav-elev-unit { font-size: 0.8rem; font-weight: 600; color: var(--text-secondary); }

/* Compact in-nav 2D|3D pill — bottom-center. */



/* Nav basemap pill (Topo | Sat) — stacked just above the 2D|3D pill. */



/* Rain-radar offline notice — small transient pill, top-centre */
.radar-note {
  position: fixed;
  top: 14px; left: 50%; transform: translateX(-50%);
  z-index: 1200;
  display: flex; align-items: center; gap: 7px;
  max-width: 90vw;
  padding: 8px 14px;
  background: var(--bg-secondary); color: var(--text-primary);
  border: 1px solid var(--bg-tertiary); border-left: 3px solid #ffab40;
  border-radius: 999px; box-shadow: var(--shadow);
  font-size: 0.8rem; font-weight: 600; white-space: nowrap;
  pointer-events: none;
}
.radar-note i { color: #ffab40; }
.radar-note.hidden { display: none; }
#nav-radar-note { top: 70px; z-index: 40; }  /* clear the turn banner in nav */
@media (max-width: 768px) {
  #radar-offline-note { top: 58px; }          /* clear the mobile header */
  .radar-note { font-size: max(0.74rem, var(--text-floor, 0px)); white-space: normal; text-align: center; }
}

/* ===== Social sign-in buttons (js/auth.js _renderSocialButtons) ===== */
/* Ported from NZ (which ported from Trailhead). Rendered only for enabled
   AUTH_PROVIDERS, so these styles are inert until a provider is flipped on at
   go-live. */
.login-divider {
  display: flex; align-items: center; gap: 10px;
  width: 100%; margin: 12px 0;
  font-size: max(0.68rem, var(--text-floor, 0px)); color: var(--text-muted, #999);
  text-transform: uppercase; letter-spacing: 0.06em; white-space: nowrap;
}
.login-divider::before, .login-divider::after {
  content: ''; flex: 1; height: 1px; background: rgba(255, 255, 255, 0.12);
}
.social-btn {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%; padding: 10px; margin-bottom: 8px;
  border-radius: var(--radius-sm, 6px); border: 1px solid transparent;
  font-size: 0.85rem; font-weight: 600; font-family: inherit;
  cursor: pointer; transition: filter 0.15s ease;
}
.social-btn:hover { filter: brightness(1.08); }
.social-btn i { font-size: 1rem; }
.social-btn-google   { background: #fff;    color: #1f1f1f; border-color: #dadce0; }
.social-btn-google i { color: #4285f4; }
.social-btn-facebook { background: #1877f2; color: #fff; }
.social-btn-apple    { background: #000;    color: #fff; border-color: rgba(255,255,255,0.25); }

/* ===== AI route planner (js/ai-route.js) ===== */
.ai-route-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 12px; }
.ai-route-head h3 { margin-bottom: 0; }
/* NO max-height and NO overflow here, deliberately. This used to be a 260px
   scroller inside a sidebar that itself scrolls 2011px in a 708px window: a
   scroller inside a scroller, which is the single most confusing thing you can
   put in front of someone who is not a confident computer user, and this is a
   key feature for exactly those riders (Josh, 2026-09-11). The thread grows;
   the sidebar scrolls once. */
.ai-route-log { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.ai-route-log:empty { display: none; }
.ai-route-msg { padding: 9px 12px; border-radius: 10px; font-size: 0.95rem; line-height: 1.4; max-width: 90%; white-space: pre-wrap; word-break: break-word; }
.ai-route-user { align-self: flex-end; background: var(--accent-dim); color: var(--text-primary); }
.ai-route-assistant { align-self: flex-start; background: var(--bg-tertiary); color: var(--text-secondary); }
.ai-route-cards { display: flex; flex-direction: column; gap: 6px; margin-top: 4px; }
/* A turn the APP asked for on the rider's behalf: same bubble, but it says so,
   because a message the rider never typed must not sit in their own voice. */
.ai-route-msg.ai-route-auto { opacity: 0.85; border-left: 3px solid var(--accent, #00c853); }
.ai-route-msg.ai-route-auto::before {
  content: 'Asked for you';
  display: block;
  font-size: max(0.62rem, var(--text-floor, 0px));
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-muted, #9aa8a1);
  margin-bottom: 3px;
}

.ai-route-card { display: grid; grid-template-columns: 1fr auto; gap: 4px 10px; text-align: left; padding: 12px 14px; border: 1px solid var(--bg-tertiary); border-radius: var(--radius); background: var(--bg-card); color: var(--text-primary); cursor: pointer; }
.ai-route-card:hover { border-color: var(--accent); }
.ai-route-card-on { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.ai-route-card:disabled { opacity: 0.5; cursor: default; }
.ai-card-title { font-weight: 600; font-size: 1rem; }
.ai-card-dist { font-size: 0.9rem; color: var(--accent); font-weight: 600; }
.ai-card-sum { grid-column: 1 / -1; font-size: 0.88rem; line-height: 1.45; color: var(--text-muted); }
.ai-route-form { display: flex; gap: 6px; }
/* The ask box grows with the question. `field-sizing: content` does this with
   no JS where it is supported; js/ai-route.js sizes it everywhere else. The max
   keeps a very long question from pushing the Send button off the panel. */
.ai-route-form { align-items: flex-end; }
.ai-route-form textarea { flex: 1; min-width: 0; background: var(--bg-tertiary);
  border: 1px solid transparent; border-radius: var(--radius); padding: 12px;
  color: var(--text-primary); font-size: 1rem; font-family: inherit; outline: none;
  resize: none; overflow-y: auto; line-height: 1.45;
  min-height: 46px; max-height: 8.2em; field-sizing: content; }
.ai-route-form textarea:focus { border-color: var(--accent); }
.ai-route-form button { flex: none; min-height: 46px; }
/* AI route — accommodation suggestions */
.ai-stay-town { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; font-weight: 600; font-size: 0.95rem;
  margin: 14px 0 6px; padding-top: 10px; border-top: 1px solid var(--bg-tertiary); color: var(--text-primary); }
.ai-stay-town:first-child { border-top: 0; padding-top: 0; margin-top: 6px; }

/* The day's ride to this night's town, measured along the drawn route. The km
   on a stay ROW is the campground's own offset from the town, which is a
   different number and reads as the leg if the leg is not also shown
   (Josh, 2026-09-11: "are they correct? from night one to night 2 etc?"). */
.ai-stay-leg {
  margin-left: auto;
  flex: none;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--text-muted, #97a3b2);
  font-variant-numeric: tabular-nums;
}
/* The night number leads, so the eye reads down the trip in order. */
/* The date of a night, beside its town. A date the rider gave is in the accent. */
.ai-stay-date { flex: none; font-size: 0.78rem; color: var(--text-muted, #97a3b2); }
.ai-stay-date.ai-stay-fixed { color: var(--accent, #5fd08a); font-weight: 700; }
.ai-stay-night { flex: none; font-size: 0.78rem; font-weight: 700; letter-spacing: 0.03em;
  text-transform: uppercase; padding: 3px 8px; border-radius: 999px;
  background: var(--accent-dim); color: var(--text-primary); }
/* A STAY ROW WRAPS, IT DOES NOT CRUSH. Josh, 2026-09-11: "there are line breaks
   in the accom options, like 3-4 letters per line." The row was one flex line
   with a nowrap price and a nowrap booking link, so in a narrow sidebar the
   place name was squeezed to a few pixels and broke mid-word, every word. The
   name now keeps a floor of 9em and the price and link drop to the next line
   when there is no room for them beside it. */
.ai-stay { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px;
  font-size: 0.95rem; padding: 5px 0; }
.ai-stay-tier { font-size: max(0.72rem, var(--text-floor, 0px)); font-weight: 700; padding: 1px 5px; border-radius: 4px; text-transform: uppercase; letter-spacing: 0.03em; }
.ai-stay-lo { background: rgba(0,200,83,0.18); color: var(--accent); }
.ai-stay-hi { background: rgba(64,196,255,0.18); color: var(--info, #40c4ff); }
.ai-stay-camp { background: rgba(210,105,30,0.2); color: #e0a060; }

/* What sort of place each night — a row of taps over the stay list, so a rider
   can swap free camps for holiday parks without planning the trip again
   (Josh, 2026-09-11). Sized for a thumb on a phone, not a mouse. */
.ai-stay-chip {
  background: var(--bg-elevated, #1f2630);
  border: 1px solid var(--border, #2a3340);
  color: var(--text, #e8edf3);
  border-radius: 999px;
  padding: 7px 13px;
  font-size: 0.84rem;
  line-height: 1.1;
  min-height: 34px;
  cursor: pointer;
  white-space: nowrap;
}
.ai-stay-chip:hover { border-color: var(--accent, #4caf50); }
.ai-stay-chip.on {
  background: var(--accent, #4caf50);
  border-color: var(--accent, #4caf50);
  color: #0d1117;
  font-weight: 600;
}
.ai-stay-name { flex: 1 1 9em; min-width: 9em; color: var(--text-secondary);
  overflow-wrap: break-word; }
.ai-stay-book { flex: none; color: var(--accent); text-decoration: none; font-weight: 600; white-space: nowrap;
  display: inline-flex; align-items: center; min-height: 44px; padding: 0 6px; }
.ai-stay-book:hover { text-decoration: underline; }
.ai-stay-est { flex: none; font-size: 0.85rem; color: var(--text-muted); white-space: nowrap; }

/* ===== AI mode: the planner takes the sidebar over =====
 *
 * WHY A TAKEOVER AND NOT A FIFTH TAB. What comes back from the planner is not
 * a route, it is a TRIP: three options, four nights, night stops, places to
 * stay, booking links. That was being rendered inside a form built for "A to
 * B", in a 379px column the rider had to scroll 2.8 screens to read. A new tab
 * would be a permanent cost in the nav to solve a temporary state, and it
 * would split "plan a trip" across two places, which is worse for the riders
 * this is for. So: while you are planning with AI, the planner owns the panel,
 * and one obvious control gives it back.
 *
 * Everything stays in the DOM and is only hidden, so anything that writes into
 * the route inputs while AI mode is on (the tour, the map menu's "Directions
 * from here") still finds its fields. AiRoute.exit() is called on those paths
 * so the rider actually sees the result. */
/* The planner owns the panel, EXCEPT the thing it just produced. Hiding
   #route-summary here meant planning an AI trip hid the trip: distance, climb,
   the warnings and the save button all vanished behind the chat (Josh,
   2026-09-11: "once an AI route is planned that's the priority for the
   sidebar"). The summary stays, and once it is up it goes FIRST, because the
   answer matters more than the question that produced it. */
body.ai-mode #tab-route > .sidebar-section:not(#ai-route):not(#route-summary) { display: none; }
body.ai-mode #tab-route { display: flex; flex-direction: column; }
body.ai-mode #route-summary { order: -1; }
body.ai-mode #ai-route { order: 0; }
body.ai-mode #ai-route { border-bottom: 0; }
/* The exit is a full-width control, not an icon: it has to be findable by
   someone who does not hunt for affordances. 44px tall for the same reason. */
/* Saving is the whole point: a saved trip is what brings a rider back, and
   only 4 of 21 cancelled riders ever saved one. So this is not a small button
   in a list, it is the loudest thing on the panel once a route exists. */
.ai-save-offer { display: none; align-items: center; justify-content: center; gap: 10px;
  width: 100%; min-height: 52px; margin: 14px 0 0; padding: 0 16px;
  background: var(--accent); color: #04150c; border: 0; border-radius: var(--radius);
  font-family: inherit; font-size: 1.02rem; font-weight: 700; cursor: pointer; }
.ai-save-offer:hover { filter: brightness(1.06); }
.ai-save-offer.on { display: flex; }
.ai-save-note { margin: 6px 0 0; font-size: 0.85rem; color: var(--text-muted); text-align: center; display: none; }
.ai-save-note.on { display: block; }
.ai-edit-row { margin-top: 10px; }
.ai-edit-row[hidden] { display: none; }
.ai-route-edit { display: flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%; min-height: 48px; padding: 0 14px;
  background: var(--bg-tertiary); color: var(--text-primary);
  border: 1px solid var(--accent); border-radius: var(--radius);
  font-family: inherit; font-size: 0.98rem; font-weight: 600; cursor: pointer; }
.ai-route-edit:hover { background: var(--accent-dim); }
.ai-edit-hint { margin: 6px 0 0; font-size: 0.85rem; color: var(--text-muted); text-align: center; }
.ai-route-actions { display: flex; flex-direction: column; gap: 8px; margin-top: 14px; }
.ai-route-exit, .ai-route-map { display: flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%; min-height: 44px; padding: 0 14px;
  background: var(--bg-tertiary); color: var(--text-secondary);
  border: 1px solid var(--bg-tertiary); border-radius: var(--radius);
  font-family: inherit; font-size: 0.95rem; cursor: pointer; }
.ai-route-exit:hover, .ai-route-map:hover { border-color: var(--accent); color: var(--text-primary); }
/* Looking at the map is the likelier of the two, so it leads and it is the one
   that looks like a button worth pressing. */
.ai-route-map { background: var(--accent-dim); color: var(--text-primary); border-color: transparent; font-weight: 600; }
/* On a desktop the map is already beside the panel: peeking at it is pointless. */
.ai-route-map { display: none; }
@media (max-width: 768px) { .ai-route-map { display: flex; } }

/* On a phone the planner IS the screen (.sidebar is width:100% under 768px), so
   the way out has to be reachable without scrolling to the bottom of a long
   answer. Pinned to the foot of the panel, above the home indicator. */
@media (max-width: 768px) {
  body.ai-mode .ai-route-actions {
    position: sticky; bottom: 0; z-index: 2;
    padding: 10px 0 calc(10px + env(safe-area-inset-bottom, 0px));
    margin-top: 14px;
    background: linear-gradient(to top, var(--bg-secondary) 72%, transparent);
  }
}

/* ===== Trip recording (js/ride-recorder.js) ===== */
.map-btn.recording {
  background: #d32f2f;
  border-color: #d32f2f;
  color: #fff;
}
.map-btn.recording .map-btn-label { color: #fff; }
.map-btn.recording i { animation: rec-pulse 1.6s ease-in-out infinite; }
@keyframes rec-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

/* Current-location puck (shown while recording a trip).
   A green dot + heading arrow marking where you ARE now — the breadcrumb line
   shows where you've BEEN. The arrow points up in the element and the marker is
   rotation-aligned to the map, then rotated to the GPS heading, so it points
   your direction of travel; .no-heading hides it when stationary rather than
   leaving it frozen pointing somewhere you are not going. */
.loc-puck { width: 30px; height: 30px; position: relative; pointer-events: none; }
.loc-puck .puck-pulse {
  position: absolute; inset: 0; margin: auto; width: 20px; height: 20px;
  border-radius: 50%; background: #00e676; opacity: 0.4;
  animation: puckPulse 2s ease-out infinite;
}
.loc-puck .puck-dot {
  position: absolute; inset: 0; margin: auto; width: 18px; height: 18px;
  border-radius: 50%; background: #00e676; border: 3px solid #fff;
  box-shadow: 0 0 3px rgba(0,0,0,0.5);
}
.loc-puck .puck-arrow {
  position: absolute; top: -3px; left: 50%; transform: translateX(-50%);
  width: 0; height: 0; border-left: 6px solid transparent;
  border-right: 6px solid transparent; border-bottom: 9px solid #00e676;
  filter: drop-shadow(0 -1px 0 #fff);
}
.loc-puck.no-heading .puck-arrow { display: none; }
@keyframes puckPulse {
  0% { transform: scale(1); opacity: 0.45; }
  100% { transform: scale(2.6); opacity: 0; }
}

/* ===== Cancel intercept + renewal toast (retention, js/auth.js) ===== */
#cancel-intercept {
  position: fixed; inset: 0; z-index: 10002;
  background: rgba(0,0,0,0.6);
  align-items: center; justify-content: center; padding: 16px;
  /* display toggled 'none'/'flex' by Auth._openCancelIntercept */
}
#cancel-intercept .cancel-card { position: relative; width: min(400px, 92vw); text-align: center; }
#cancel-intercept h2 { margin: 0 0 14px; font-size: 1.05rem; color: var(--text-primary, #fff); }
#cancel-intercept-close {
  position: absolute; top: 8px; right: 12px;
  background: none; border: none; cursor: pointer;
  color: var(--text-muted, #999); font-size: 1.2rem; line-height: 1; padding: 4px;
}
#cancel-intercept-options { width: 100%; }
/* Seven reasons as wrapping pills (the js/leaving.js layout), not seven
   stacked full-width buttons: on a phone the stack pushed Continue below the
   fold. aria-pressed is the selected state (toggled in Auth._openCancelIntercept). */
.cancel-reasons { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; margin-bottom: 10px; }
.cancel-option { padding: 8px 13px; font-size: 0.82rem; border-radius: 999px; }
.cancel-option[aria-pressed="true"] {
  background: var(--accent, #00c853); color: #00120a;
  border-color: var(--accent, #00c853); font-weight: 700;
}
#cancel-intercept-note {
  width: 100%; box-sizing: border-box; margin-bottom: 10px;
  background: var(--bg-tertiary, #1c2530); color: var(--text-primary, #fff);
  border: 1px solid rgba(255,255,255,0.12); border-radius: 8px;
  padding: 9px; font-size: 0.85rem; font-family: inherit; resize: vertical;
}
#cancel-intercept-slot { width: 100%; }
#cancel-intercept-slot p {
  margin: 0 0 14px; font-size: 0.82rem; line-height: 1.55;
  color: var(--text-muted, #bbb); text-align: left;
}
#renewal-toast {
  position: fixed; bottom: 80px; left: 50%; transform: translateX(-50%);
  z-index: 2000; display: none; align-items: center; gap: 8px;
  background: #243342; color: #e8edf2; border: 1px solid rgba(255,255,255,0.1);
  border-radius: 8px; padding: 10px 14px; font-size: 0.8rem;
  max-width: min(420px, 90vw); box-shadow: 0 8px 30px rgba(0,0,0,0.5);
}
#renewal-toast a { color: var(--accent, #00c853); }
#renewal-toast button {
  background: none; border: none; cursor: pointer;
  color: var(--text-muted, #999); font-size: 1rem; padding: 0 2px;
}

/* Narrow (phone-portrait) nav: the bottom-CENTERED pills collide with the
   bottom-left speed cluster — on an S23 the 2D|3D pill sat ON the speed-limit
   roundel (2026-07-28, found on NZ; AUS stylesheet is 5 concatenated copies, so this lives at EOF to win the cascade). Nothing may cover
   anything: right-align both pills in a column above the elevation chip. */



/* ===== Arrival card (js/trip.js) — shown in BOTH ride views when a trip
   completes. Appended at the end of the sheet deliberately: AUS's style.css is
   five concatenated copies, so a rule inserted mid-file can be overridden by a
   later copy. ===== */

/* Full-screen nav: same pill language as #nav-turn, which it replaces, but
   with full-width buttons — this is the one thing in nav a rider taps wearing
   gloves, so both targets are 44 px+ tall. */
#nav-arrived {
  position: absolute;
  /* A THIRD of the way down, not at the top: up there it covered the close
     button, the ETA chip and the clock — the trip is over, but the rider still
     wants to read the screen (Josh, 2026-07-31). */
  top: 30%;
  left: 50%;
  transform: translateX(-50%);
  z-index: 12;                  /* above the banner it replaces and the POI/camera pills */
  width: min(88vw, 460px);
  padding: 12px 14px;
  background: rgba(10,14,20,0.9);
  border: 1px solid rgba(0,200,83,0.35);
  border-radius: 16px;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  box-shadow: 0 6px 24px rgba(0,0,0,0.45);
}
.nav-arrived-head { display: flex; align-items: center; gap: 12px; }
#nav-arrived-icon {
  flex: 0 0 auto;
  width: 56px; height: 56px;
  border-radius: 14px;
  display: flex; align-items: center; justify-content: center;
  font-size: 1.9rem;
  background: rgba(0,200,83,0.16); color: var(--accent, #00c853);
}
.nav-arrived-info { min-width: 0; }
.nav-arrived-title { font-size: 1.5rem; font-weight: 800; color: #fff; line-height: 1.1; }
#nav-arrived-name {
  font-size: 0.95rem; color: var(--text-secondary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Elevation profile of the route just ridden. */
.nav-arrived-elev { margin-top: 12px; }
.nav-arrived-elev canvas {
  display: block;
  width: 100%; height: 56px;
}
.nav-arrived-elev-cap {
  margin-top: 2px;
  font-size: max(0.7rem, var(--text-floor, 0px)); color: var(--text-secondary);
  letter-spacing: 0.02em;
}

/* Distance travelled · time moving · average over that moving time. */
.nav-arrived-stats {
  display: flex;
  gap: 8px;
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid rgba(255,255,255,0.08);
}
.nav-arrived-stat { flex: 1; min-width: 0; text-align: center; }
.nav-arrived-stat b {
  display: block;
  font-size: 1.15rem; font-weight: 800; color: #fff; line-height: 1.15;
  white-space: nowrap;
}
.nav-arrived-stat span {
  display: block;
  font-size: max(0.7rem, var(--text-floor, 0px)); color: var(--text-secondary);
  text-transform: uppercase; letter-spacing: 0.04em;
}
#nav-arrived-note {
  margin-top: 10px;
  font-size: 0.82rem; color: var(--text-secondary);
}
#nav-arrived-note i { color: #ff5252; margin-right: 5px; }
.nav-arrived-actions { display: flex; gap: 8px; margin-top: 12px; }
.nav-arrived-btn {
  flex: 1;
  padding: 12px 10px;
  font-size: 1rem; font-weight: 700;
  color: #fff;
  background: rgba(255,255,255,0.1);
  border: 1px solid rgba(255,255,255,0.14);
  border-radius: 12px;
  cursor: pointer;
}
.nav-arrived-btn.primary { background: var(--accent, #00c853); border-color: var(--accent, #00c853); color: #06210f; }

/* Speedo dash: the same card in the next-turn slot. */
.dash-arrived {
  width: 100%;
  max-width: 100%;
  min-width: 0;
  box-sizing: border-box;
  flex-shrink: 0;
  background: rgba(0, 200, 83, 0.1);
  border: 1px solid rgba(0, 200, 83, 0.3);
  border-radius: 10px;
  padding: 8px 12px;
}
.dash-arrived-head { display: flex; align-items: center; gap: 10px; }
.dash-arrived-icon {
  flex-shrink: 0;
  width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 8px;
  background: rgba(0, 200, 83, 0.15);
  color: #00c853;
  font-size: 1.3rem;
}
.dash-arrived-info { flex: 1; min-width: 0; }
.dash-arrived-title { font-size: 1.2rem; font-weight: 700; color: var(--text-primary, #fff); }
.dash-arrived-name {
  font-size: 0.8rem; color: var(--text-secondary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dash-arrived-elev { margin-top: 8px; }
.dash-arrived-elev canvas { display: block; width: 100%; height: 38px; }
.dash-arrived-elev-cap { margin-top: 1px; font-size: max(0.62rem, var(--text-floor, 0px)); color: var(--text-secondary); }

.dash-arrived-stats {
  display: flex;
  gap: 6px;
  margin-top: 8px;
  padding-top: 6px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.dash-arrived-stat { flex: 1; min-width: 0; text-align: center; }
.dash-arrived-stat b {
  display: block;
  font-size: 0.95rem; font-weight: 800; color: var(--text-primary, #fff);
  line-height: 1.15; white-space: nowrap;
}
.dash-arrived-stat span {
  display: block;
  font-size: max(0.62rem, var(--text-floor, 0px)); color: var(--text-secondary);
  text-transform: uppercase; letter-spacing: 0.04em;
}
.dash-arrived-note { margin-top: 8px; font-size: max(0.75rem, var(--text-floor, 0px)); color: var(--text-secondary); }
.dash-arrived-note i { color: #ff5252; margin-right: 5px; }
.dash-arrived-actions { display: flex; gap: 8px; margin-top: 8px; }
.dash-arrived-btn {
  flex: 1;
  padding: 10px 8px;
  font-size: 0.9rem; font-weight: 700;
  color: #fff;
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 8px;
  cursor: pointer;
}
.dash-arrived-btn.primary { background: #00c853; border-color: #00c853; color: #06210f; }

/* Off-route-with-no-network notice in nav. Appended at the END on purpose: this
   stylesheet carries duplicated .radar-note blocks, so a rule placed inline
   beside one copy could be overridden by a later one. */
#nav-offline-note { top: 112px; z-index: 40; }
#nav-gps-note { z-index: 40; }
#nav-gps-note i { color: #ffab40; }

/* Picker rows in the sidebar (Voice, Arrow and route line). Ported from NZ
   2026-09-26: AUS never had it, so each label and its select ran inline. */
.voice-pick-row { display: flex; align-items: center; gap: 8px; margin: 8px 0 4px; }
.voice-pick-row .toggle-label { flex-shrink: 0; }
.voice-pick-row select {
  flex: 1; min-width: 0; padding: 5px 7px; font-size: 0.78rem;
  background: var(--bg-elevated, #1c1c22); color: var(--text-primary, #fff);
  border: 1px solid rgba(255,255,255,0.14); border-radius: var(--radius, 8px);
}
.voice-pick-row .btn-sm { padding: 5px 10px; font-size: max(0.75rem, var(--text-floor, 0px)); flex-shrink: 0; }
/* A list that does nothing right now (the speed alert's, with its switch off). */
.voice-pick-row select:disabled { opacity: 0.45; cursor: default; }
.voice-pick-row:has(select:disabled) .toggle-label { opacity: 0.45; }
/* The speed alert's list has short options: it takes their width, so the
   label stays on one line, and is 44 px tall to tap. */
#speed-alert-over { flex: 0 0 auto; min-height: 44px; }

/* ===== Start button (js/route-start.js) ===== */
/* Centred on the MAP, not the viewport — on desktop the sidebar owns the left
   380px, so a plain 50% would sit visibly off to the left of the map area.
   --sidebar-width is 0 on mobile and when the sidebar is collapsed, so the one
   expression covers every case. */
#route-start-btn {
  position: fixed;
  /* ABOVE the whole bottom furniture, not beside it. The bottom strip is fully
     spoken for: pills bottom-left (2D/3D at 26px, basemap at 90px, 56px tall
     each, so the stack tops out at 146px) and the imagery credits bottom-right.
     A centred button at 26px was measured colliding with the credits at EVERY
     width from 769px to 1440px — it only cleared at 1600+, which is the one
     size it happened to be developed at. Esri's terms require their credit to
     stay visible, so the button moves, not the credits. 154px clears the pill
     stack with 8px of air and is far above the attribution. */
  bottom: 154px;
  left: calc(50% + var(--sidebar-width) / 2);
  transform: translateX(-50%);
  z-index: 600;          /* above the 2D/3D and basemap pills (500) */
  min-height: 56px;
  padding: 0 38px;
  border: none;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font-size: 1.15rem;
  font-weight: 800;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  cursor: pointer;
  display: flex; align-items: center; gap: 12px;
  /* Deliberately louder than any other map control: a green glow on top of the
     standard shadow, so it reads as THE next action rather than one more pill. */
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.5), 0 0 0 4px rgba(0, 200, 83, 0.18);
  transition: background var(--transition), box-shadow var(--transition), transform var(--transition);
}
#route-start-btn:hover {
  background: var(--accent-hover);
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.55), 0 0 0 7px rgba(0, 200, 83, 0.22);
}
#route-start-btn:active { transform: translateX(-50%) scale(0.97); }

/* Clear is CENTRED by default, because Start is phone-only: the rule further
   down hides #route-start-btn at >=769px, so on a desktop Clear is the only
   button on that baseline and an offset one would sit lopsided beside nothing.
   Right at the bottom edge and 30% smaller (Josh, 2026-08-11, twice: 154px
   hovered over the map, then 96px was still too high). At this size it is
   ~39px tall and ~99px wide, so a 12px baseline sits in an empty strip:
   measured at 769/1000/1280/1440/1600 with the sidebar open and collapsed,
   the pill stack is bottom-LEFT (2D at 20px, Sat at 66px, both 38px tall,
   x 12..92 collapsed / 402..472 open) and the PTZ pad is bottom-RIGHT
   (x W-150..W-16, y 44..264). Unlike USA's note there is NO credits line to
   clear: this map is built with attributionControl:false and never adds one
   (Josh, 2026-08-08 — the credits moved to the User Guide), so the bottom
   strip is genuinely empty. One narrow window is NOT new and NOT fixed here:
   at 769-779px with the sidebar OPEN the centred button clips the PTZ pad by
   up to 5px, which was equally true at 96px and at 154px. */
#route-clear-btn {
  position: fixed;
  bottom: 12px;
  left: calc(50% + var(--sidebar-width) / 2);
  transform: translateX(-50%);
  z-index: 600;
  min-height: 39px;
  padding: 0 18px;
  border: 1.5px solid #ff5252;
  border-radius: 999px;
  background: rgba(20, 16, 16, 0.92);
  color: #ff8a80;
  font-size: max(0.7rem, var(--text-floor, 0px));
  font-weight: 800;
  letter-spacing: 1px;
  text-transform: uppercase;
  cursor: pointer;
  display: flex; align-items: center; gap: 7px;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.5);
  transition: background var(--transition), box-shadow var(--transition), transform var(--transition);
}
#route-clear-btn:hover {
  background: #ff5252;
  color: #fff;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.55), 0 0 0 6px rgba(255, 82, 82, 0.22);
}
#route-clear-btn:active { transform: translateX(-50%) scale(0.97); }
#route-clear-btn i { font-size: max(0.66rem, var(--text-floor, 0px)); }
#route-clear-btn.hidden { display: none; }
body.dashboard-active #route-clear-btn,
body.nav-active #route-clear-btn { display: none !important; }
/* Phone: centred 8px above Start (Start: centred, bottom 56px, 54px tall), so
   the pair reads as one stacked control group clear of the PTZ pad on the
   right. `left: 50%`, NOT the desktop calc: nothing zeroes --sidebar-width
   below 769px (only desktop body.sidebar-collapsed does), so the inherited
   calc sat the button 302px right of centre — measured fully OFF-SCREEN at
   384px. The on-map Clear had never been visible on a phone until 2026-08-11,
   found while lowering it.
   The button SHRINKS with its desktop twin (padding/font) but KEEPS bottom
   118px — a deliberate divergence from USA, which tightened this to 102px
   "to match the smaller pill". The gap does not depend on this button's
   height: Start is bottom 56px and 54px tall, so its TOP edge is at 110px
   whatever Clear does, and 118 is exactly the 8px of air this comment
   describes. 102 would put Clear's lower 8px INSIDE Start, and Clear paints
   second (later in index.html, same z-index 600), so it would sit visibly on
   top of it. Measured, not assumed. */
@media (max-width: 768px) {
  /* 106px keeps the documented 8px of air above Start (bottom 44, 54 tall, so
     its top edge is 98). That puts Clear's top at 145, and the height slider
     moves from 148 to 168 in the same pass so the gap is 23px rather than 3:
     the strip between the scale ruler and the slider is only 110px and the two
     buttons need 101 of it, so both had to give. */
  #route-clear-btn { left: 50%; bottom: 106px; transform: translateX(-50%); padding: 0 13px; font-size: max(0.63rem, var(--text-floor, 0px)); }
  #route-clear-btn:active { transform: translateX(-50%) scale(0.97); }
}
#route-start-btn i { font-size: 1.05rem; }
#route-start-btn.hidden { display: none; }
/* Full-screen overlays own the whole screen — the button goes with them.
   (Nav in particular: it IS nav, so offering to start it again is nonsense.) */
body.dashboard-active #route-start-btn,
body.nav-active #route-start-btn { display: none !important; }

/* The panel twin in Route Summary. Same job, so the same loud green.
   Taller than the .btn default: measured 33px on an S23, under the 44px tap
   target the rest of the map UI holds itself to, and this is the primary action
   on the panel. (The Google / GPX buttons below it are the same 33px — that is
   pre-existing and deliberately left alone.) */
.route-start-inline {
  min-height: 48px;
  font-weight: 800;
  letter-spacing: 1px;
  text-transform: uppercase;
  display: flex; align-items: center; justify-content: center; gap: 9px;
}
.route-start-inline.hidden { display: none; }

@media (max-width: 768px) {
  /* CENTRED on a phone (Josh, 2026-08-11): bottom-right sat straight on the
     PTZ pad — measured at 384px, the pad spans x 277-373 / bottom 55-195 and
     Start covered its lower half. The old reason for going right (a centred
     button hit the basemap pill under ~460px) died when the pills went
     icon-narrow (~90px wide now). `left: 50%`, not the desktop calc — nothing
     zeroes --sidebar-width below 769px. 56px still clears the full-width
     imagery credits (Esri's terms require their credit stays visible). */
  #route-start-btn {
    left: 50%;
    right: auto;
    /* 20px, level with the 2D/3D pill's baseline, NOT 56px.
       Josh, 2026-08-25: "The Start & clear route button cover the height slider
       on mobile. Move the start & clear button down a bit."
       At 56 the pair sat Start y 56..110 and Clear y 118..157, and the height
       slider is at 148..186 with a 210px-wide box from the left edge, so Clear
       lay ON the slider by up to 69x9px at 390-500px wide. MEASURED, by
       scripts/qa/map-furniture-overlap-test.js, which now fails if any two
       floating map controls touch at 360/390/412/500/768px.
       There is room to go down because there is nothing below: the old comment
       here said 56 "clears the full-width imagery credits", but this map is
       built with attributionControl:false (js/gl-map.js) and never draws one.
       The comment on #route-clear-btn's desktop rule already said so; the two
       contradicted each other and the wrong one was load-bearing.
       It stops at 44 and not lower because there IS one thing down there: the
       MapLibre scale ruler, bottom-right at bottom 12px and 26px tall. At
       bottom 20 the measured overlap was 1x18px at 381px wide, which is the
       narrowest screen this rule applies to and therefore the one to tune on. */
    bottom: 44px;
    transform: translateX(-50%);
    min-height: 54px;
    padding: 0 26px;
    font-size: 1.05rem;
  }
  #route-start-btn:active { transform: translateX(-50%) scale(0.97); }
}

/* Narrow phones: centred still clips the PTZ pad's left edge (on a 320px SE
   the pad spans x 213-309 and Start reaches 231). The pad tops out 195px off
   the bottom, so lift the WHOLE pair above it — 204 clears it with 9px of
   air, and Clear keeps its 62px step above Start. */
@media (max-width: 380px) {
  /* 214/276, up from 204/266: the height slider moved from 148 to 168 in the
     same pass, and at 204 Start's lower edge sat 2px inside it (measured at
     360px). 214 restores the 8px of air this block was tuned with. */
  #route-start-btn { bottom: 214px; }
  #route-clear-btn { bottom: 276px; }
  /* 125% root makes a row of four tab labels wider than a 320px SE, and
     the brand title wraps inside the 56px header. Stack the tabs (icon over
     word) and keep the title on one line. 0.7rem is still 14px — above the
     old 11.5px — so this is packing, not undoing the bump. */
  .tab-btn {
    flex-direction: column;
    gap: 2px;
    padding: 8px 2px;
    font-size: max(0.7rem, var(--text-floor, 0px));
  }
  .tab-btn i { font-size: 0.9rem; }
  #mobile-header h1 { font-size: 1rem; white-space: nowrap; }
}

/* Phones only (Josh, 2026-08-04). Start hands a planned route to turn-by-turn
   nav — something you do on the phone clipped to the bars, never at a desk. The
   gate is VIEWPORT WIDTH, not native-vs-web: the web app on a phone is exactly
   where this belongs, and 768/769 is already the phone breakpoint everywhere
   else in this file. Both go: the floating map button and its Route Summary
   twin are one feature with one label, so leaving one behind would just be the
   same button in a quieter place. Planning a route on a desktop is untouched —
   only the hand-off to nav disappears. */
@media (min-width: 769px) {
  #route-start-btn,
  .route-start-inline { display: none !important; }
}
/* No turn by turn on a desktop, at any window size (Josh, 2026-10-13: "users
   will never, or should never be offered or shown nav on desktop - only
   tablet & mobile"). Desktop is the app's own test, Utils.isDesktop: the
   primary pointer is not a finger. "not all and (pointer: coarse)" is that
   test exactly, so a narrow desktop window loses Nav and Start too, and a
   phone or tablet keeps them. js/nav.js and js/route-start.js hide the same
   buttons in script and refuse to open nav, for anything that gets past this. */
@media not all and (pointer: coarse) {
  #btn-nav,
  #mobile-nav,
  #route-start-btn,
  .route-start-inline { display: none !important; }
}

/* ===== Arrival clock (Utils.arrivalTime) ===== */
/* Quieter than the distance and dimmer than the remaining-time green: it is
   reference information, not what a rider glances down for at speed. Hidden
   when empty so the chip does not grow a blank line before the first ETA. */
#nav-eta-arrive { font-size: max(0.72rem, var(--text-floor, 0px)); font-weight: 600; color: rgba(255,255,255,0.6); }
#nav-eta-arrive:empty { display: none; }
@media (max-width: 768px) {
  /* Three short stats fit a phone fine; two columns would orphan Arrive onto
     a row of its own. Appended at EOF so it wins in AUS, whose style.css is
     five concatenated copies. */
  .route-overview { grid-template-columns: repeat(3, 1fr); gap: 6px; }
  .route-stat { padding: 8px 4px; }
}


/* ===== Guided search chips + elevation profile (onX port, 2026-08-03) ======
   APPENDED AT THE END ON PURPOSE. The AUS stylesheet is five concatenated
   copies of itself (tasks/lessons.md), so a rule written anywhere else can be
   silently overridden by a later duplicate. Last-in wins at equal specificity,
   and the end of the file is the only position that is last in all regions. */
/* Layers tab only (Josh, 2026-08-25: "remove waterfalls lookouts lakes huts
   buttons from routes"). The chips live ABOVE #tab-nav in the markup, so they
   were drawn on every tab — Route, Weather and Offline included, where they are
   noise on a panel the rider opened to do something else.

   Driven off the tab's own .active class, not a JS hook on the tab buttons:
   switchToRoute() and the onboarding tour both change tabs by setting those
   classes directly, so a handler-based version would leave the chips on screen
   down exactly the paths that matter. :has() is already shipped in this
   stylesheet (.filter-chip:has(input:checked)). */
#elev-canvas { background: #111a22; margin: 6px 0 10px; }

/* Road Closures is zoom-gated (Layers.ROAD_EVENT_MINZOOM) and ships ON, so the
   toggle can be lit while the map is empty. This says which. */
.layer-toggle .zoom-hint {
  margin-left: auto; font-size: max(.64rem, var(--text-floor, 0px)); font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-muted, #9aa8a1); opacity: .8; white-space: nowrap;
}

/* Mute for the spoken directions. Third row of the nav control column (2D/3D
   sits at 18px, Topo|Sat at 70px), in the same pill language: bottom-centre is
   the only band of this screen with nothing else in it at any width, and the
   turn banner, speed readout and elevation chip all keep their space. */

#nav-voice i { font-size: 0.95rem; }
/* Muted is a state a rider has to read at a glance from a moving bike, so it
   changes colour as well as the icon and the word. */
#nav-voice.muted { color: #ffab40; border-color: rgba(255,171,64,0.5); }

/* ===== Pan / tilt / zoom pad (js/app.js) =====
   Bottom-right, because the button column owns the top-right and the basemap
   and 2D/3D pills own the bottom-left. Lifted clear of MapLibre's attribution:
   Esri's terms require their credit to stay readable, so nothing may sit on it. */
#map-ptz {
  position: fixed;
  right: 16px;
  bottom: 44px;
  z-index: 500;
  display: flex; flex-direction: column; gap: 6px;
  padding: 6px;
  background: var(--bg-secondary);
  border: 1px solid var(--bg-tertiary);
  border-radius: var(--radius);
  box-shadow: 0 4px 16px rgba(0,0,0,0.35);
}
#map-ptz .ptz-pad {
  display: grid;
  grid-template-columns: repeat(3, 38px);
  gap: 4px;
}
/* Column-first, so each pair (in/out, tilt/flatten, left/right) is one column.
   Laid out across instead, the pairs split apart as soon as a phone drops the
   grid to two columns. */
#map-ptz .ptz-row {
  display: grid;
  grid-template-rows: repeat(2, 38px);
  grid-auto-flow: column;
  gap: 4px;
}
#map-ptz button {
  width: 38px; height: 38px;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-tertiary);
  border: 1px solid transparent;
  border-radius: 8px;
  color: var(--text-primary);
  font-size: 0.95rem;
  cursor: pointer;
  touch-action: manipulation;
}
#map-ptz button:hover { border-color: var(--accent); color: var(--accent); }
#map-ptz button:active { transform: scale(0.94); }
#map-ptz .ptz-home { color: var(--accent); }

/* Hidden wherever a full-screen view has taken the map away, exactly like the
   other on-map furniture. */
body.dashboard-active #map-ptz,
body.nav-active #map-ptz { display: none; }

@media (max-width: 768px) {
  /* A phone gets the camera controls that gestures make hardest: tilt, turn and
     zoom. Panning by dragging a map is the one thing everybody already does, and
     a 3x3 pad costs a third of the screen width to duplicate it. */
  #map-ptz { bottom: 56px; right: 12px; padding: 5px; }
  #map-ptz .ptz-pad { display: none; }
  /* Row-first here: two columns, one pair per row. */
  #map-ptz .ptz-row {
    grid-template-rows: none;
    grid-template-columns: repeat(2, 40px);
    grid-auto-flow: row;
  }
  #map-ptz button { width: 40px; height: 40px; }
}

/* One-time dismissible notices (software-GL warning, 3D quality floor), and
   the whole-country offline download card (js/offline-autopack.js). This block
   was missing here until 2026-09-02: the card was built with no CSS and sat in
   normal flow behind the fixed map, where no rider could see or answer it. */
.mode-notice {
  position: fixed;
  top: 18px; left: 50%;
  transform: translateX(-50%);
  z-index: 2500;
  max-width: min(92vw, 560px);
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px;
  background: var(--bg-secondary);
  border: 1px solid var(--warning);
  border-radius: var(--radius);
  color: var(--text-primary);
  font-size: 0.85rem;
  box-shadow: var(--shadow);
}
.mode-notice i { color: var(--warning); }
.mode-notice .mode-notice-ok {
  margin-left: 6px;
  padding: 8px 16px;
  min-height: 40px;
  background: var(--accent); color: #fff;
  border: none; border-radius: var(--radius-sm);
  font-weight: 700; cursor: pointer;
}
/* On a phone the buttons go UNDER the text, not beside it. The row above put
   Download and Not now alongside a five-line offer and squeezed the words into
   a column four words wide (Josh, 2026-09-02, screenshot: "put the buttons at
   the bottom. not the side."). Icon and text share the top row; the buttons
   take the row below, aligned with the text, two across when there are two.
   The card also drops below the mobile header rather than covering the menu. */
@media (max-width: 768px) {
  .mode-notice {
    top: calc(var(--mobile-header-height) + 8px);
    width: 92vw;             /* a grid shrinks to its buttons; take the row */
    display: grid;
    grid-template-columns: auto 1fr 1fr;
    gap: 10px;
  }
  .mode-notice > i { grid-row: 1; grid-column: 1; }
  .mode-notice > span { grid-row: 1; grid-column: 2 / -1; }
  .mode-notice .mode-notice-ok { grid-row: 2; margin-left: 0; }
  .mode-notice .mode-notice-ok:first-of-type { grid-column: 2; }
  .mode-notice .mode-notice-ok:nth-of-type(2) { grid-column: 3; }
  .mode-notice .mode-notice-ok:only-of-type { grid-column: 2 / -1; }
}


/* ===== Small on/off map buttons (Sat, 3D) =====
   Replaces the two-option pills. Lit = on; off is topo and flat, which is how
   the app opens, so an unlit pair is the default state rather than a missing
   choice. Sized to match the PTZ pad's cells so the map's furniture is one
   family. */
.map-toggle {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 38px;
  padding: 0 12px;
  background: var(--bg-secondary);
  border: 1px solid var(--bg-tertiary);
  border-radius: 8px;
  color: var(--text-secondary);
  font-size: 0.82rem; font-weight: 700; letter-spacing: 0.5px;
  cursor: pointer;
  touch-action: manipulation;
  box-shadow: 0 4px 16px rgba(0,0,0,0.35);
}
.map-toggle:hover { color: var(--text-primary); border-color: var(--accent); }
.map-toggle[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: #00120a;
}
/* The pills used to carry their own backgrounds and padding; the containers are
   now just anchors for one button each. */
#basemap-switch, #mode-3d-switch {
  position: fixed;
  left: calc(var(--sidebar-width) + 22px);
  z-index: 500;
  background: none;
  border: none;
  box-shadow: none;
  padding: 0;
  display: flex;
}
/* Re-derived from the new size: one 38px button and no container padding, where
   the old pills were 56px tall. 20 + 38 + 8 of air = 66. */
#mode-3d-switch { bottom: 20px; }
#basemap-switch { bottom: 66px; }
body.dashboard-active #basemap-switch,
body.nav-active #basemap-switch,
body.dashboard-active #mode-3d-switch,
body.nav-active #mode-3d-switch { display: none !important; }
/* Phone placement: the phone's map chrome, end of file. */
/* The phone's own part (the round picture of the other map): not on a
   desktop. Shown in the phone's map chrome, end of file. */
.map-toggle-pic { display: none; }

/* "Our welcome email to X came back" (js/auth.js _showEmailBounceCard,
   2026-10-13). It waits for an answer. */
#email-bounce-note {
  position: fixed; bottom: 132px; left: 50%; transform: translateX(-50%);
  z-index: 2001; display: none; align-items: center; gap: 8px; flex-wrap: wrap;
  background: #243342; color: #e8edf2; border: 1px solid rgba(255,255,255,0.12);
  border-radius: 8px; padding: 10px 14px; font-size: 0.85rem;
  width: min(460px, 92vw); box-shadow: 0 8px 30px rgba(0,0,0,0.5);
}
#email-bounce-note i { color: var(--accent, #00c853); flex: none; }
#email-bounce-note span { flex: 1 1 80%; min-width: 0; }
#email-bounce-note input {
  flex: 1 1 200px; min-width: 0; background: rgba(0,0,0,0.28);
  border: 1px solid rgba(255,255,255,0.16); border-radius: 6px;
  padding: 8px 9px; color: #e8edf2; font-size: 16px;
}
#email-bounce-note input:focus { outline: none; border-color: var(--accent, #00c853); }
#email-bounce-note .btn { flex: none; }
#email-bounce-note button[aria-label="Not now"] {
  background: none; border: none; cursor: pointer; flex: none;
  color: var(--text-muted, #999); font-size: 1.1rem; padding: 0 2px; line-height: 1;
}
#email-bounce-note .email-bounce-msg {
  flex: 1 1 100%; color: #ffa726; font-style: normal; font-size: 0.8rem;
}

/* Dropdown for the map stack's vehicle selector (app.js setupVehicleButton).
   top/right are set inline from the button's rect; above the mobile header
   overlay (1001) so it survives every stacking context. */
#vehicle-menu {
  position: fixed;
  z-index: 1002;
  display: flex; flex-direction: column; gap: 2px;
  background: var(--bg-secondary);
  border: 1px solid var(--bg-tertiary);
  border-radius: var(--radius);
  padding: 6px;
  box-shadow: 0 6px 24px rgba(0,0,0,0.45);
}
#vehicle-menu button {
  display: flex; align-items: center; gap: 10px;
  background: none; border: none; color: var(--text-primary);
  font-size: 0.9rem; padding: 9px 12px; border-radius: calc(var(--radius) - 2px);
  cursor: pointer; text-align: left;
}
#vehicle-menu button i { width: 20px; text-align: center; }
#vehicle-menu button[aria-pressed="true"] {
  background: var(--accent-dim); color: var(--accent);
}

/* ---------------------------------------------------------------------------
   The generic hide. AUS shipped WITHOUT this rule while USA, NZ and EU all had
   it, so `class="hidden"` did nothing here except on the few elements with
   their own targeted rule (#nav-poi.hidden, #info-panel.hidden, …).

   33 elements in index.html ship hidden, and two of them are #nav-overlay and
   #dashboard-overlay: position:fixed, z-index:99999, full screen. They were
   painted over the app from load. Found 2026-08-12 while capturing the welcome
   reel — every frame came back as the navigation "Arrived" card over a black
   map, and the stage dump showed class="hidden" with a computed display of
   block at 1600x800.

   !important and last in the file on purpose: this stylesheet is five diverged
   concatenated copies (see tasks/lessons.md), and #id rules beat .class on
   specificity no matter the order. This has to win against all of them.
--------------------------------------------------------------------------- */
.hidden { display: none !important; }

/* ===== Bike dash layout, ported from USA =================================
 *
 * AUS shipped the dash MARKUP with almost none of its CSS: 34 `.dash-` rules
 * against USA's 121. Only the "arrived" card and the close button were present,
 * so the dash had no columns, no speed numeral, no minimap frame and, crucially,
 * no size on the radar canvas.
 *
 * That last one was not cosmetic. js/dashboard.js drawRadar() does:
 *
 *     const size = canvas.clientWidth;
 *     canvas.width = size * dpr;
 *
 * With no CSS width a canvas's LAYOUT width is its own width attribute, so each
 * redraw multiplied it by devicePixelRatio. At dpr 1 that is stable, which is
 * why desktops never showed it. On a Galaxy S23 at dpr 2.8125 it compounded to
 * 33554432 x 33554432 within seconds, read live off the device; the GPU cannot
 * allocate that, the compositor gives up, and the dash paints white while JS
 * keeps running. That is the "tour locks up at the bike dash, cannot continue
 * or exit" report (Josh, 2026-08-22).
 *
 * The crash was masking the rest: with the dash never rendering, nobody saw
 * that the layout CSS was missing too. Porting the whole block from USA fixes
 * both, and keeps AUS in step with the other three regions.
 *
 * Appended at the end so it wins the cascade whichever of this file's several
 * concatenated copies applies.
 */
/* An absolutely positioned child measures from the overlay's PADDING box, so
   the padding above does not move it — it needs the insets itself. Without
   them the X sat at y=6..46 on an iPhone, inside the strip iOS keeps for the
   status bar and the Control Centre pull-down: the rider could see it and his
   taps went to the system, so the only way out of the dash was to kill the app
   (rider report, 2026-09-18). */
.dash-close-btn {
  position: absolute;
  top: calc(6px + env(safe-area-inset-top, 0px));
  right: calc(6px + env(safe-area-inset-right, 0px));
  z-index: 100002;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1px solid var(--bg-tertiary);
  background: rgba(26,38,50,0.8);
  color: var(--text-primary);
  font-size: 1.1rem;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  touch-action: manipulation;
}
.dash-close-btn:hover { color: var(--danger); }

/* Dashboard layout — landscape-first: top bar, 3-col main, bottom bar */
.dash-layout {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
  overflow: hidden;
  position: relative;
}

/* ── Top instrument bar ── */
.dash-top-bar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(16px, 4vw, 40px);
  padding: 10px 60px;
  background: rgba(10,14,20,0.9);
  border-bottom: 1px solid var(--bg-tertiary);
  min-width: 0;
  z-index: 2;
  overflow: visible;
}

.dash-alt-reading {
  display: flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
  min-width: 0;
  font-variant-numeric: tabular-nums;
}

.dash-alt-reading i {
  color: var(--accent);
  font-size: 1rem;
}

.dash-alt-value {
  font-size: clamp(1.3rem, 3vw, 1.8rem);
  font-weight: 700;
  color: var(--text-primary);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.dash-alt-unit {
  font-size: 0.9rem;
  color: var(--text-secondary);
}

.dash-alt-reading #dash-heading-value {
  font-size: clamp(1.2rem, 3vw, 1.7rem);
  font-weight: 600;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.dash-info-unit {
  font-size: 0.8rem;
  color: var(--text-secondary);
  margin-left: 2px;
}

/* Pressure trend arrow */
.dash-pressure-arrow {
  transition: transform 0.3s ease;
  align-self: center;
  flex-shrink: 0;
  margin-left: 2px;
}

/* Wind direction arrow */
.dash-wind-arrow {
  transition: transform 0.5s ease-out;
  align-self: center;
  flex-shrink: 0;
  margin-left: 1px;
}

/* ── Main 3-column area ── */
.dash-main {
  flex: 1;
  display: flex;
  min-height: 0;
  overflow: hidden;
}

/* Left column: radar */
.dash-left-col {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 8px;
  gap: 4px;
}

.dash-radar-title {
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 3px;
  color: var(--text-primary);
}

.dash-radar-wrap {
  position: relative;
  width: min(55vh, 220px);
  aspect-ratio: 1;
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid rgba(255,255,255,0.08);
  background: rgba(20,28,38,0.6);
}

.dash-radar-canvas {
  width: 100%;
  height: 100%;
  border-radius: 12px;
}

.dash-radar-label {
  position: absolute;
  bottom: 4px;
  left: 50%;
  transform: translateX(-50%);
  font-size: max(0.55rem, var(--text-floor, 0px));
  color: var(--text-muted);
  background: rgba(10,14,20,0.85);
  padding: 1px 5px;
  border-radius: 3px;
}

/* Centre column: speed + lean */
.dash-centre-col {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  position: relative;
}

.dash-speed-zone {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
}

.dash-speed-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  z-index: 1;
}

.dash-speed-num {
  font-size: min(28vw, 38vh);
  font-weight: 700;
  color: var(--text-primary);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.dash-speed-unit {
  font-size: 1rem;
  color: var(--text-secondary);
  text-transform: uppercase;
  letter-spacing: 3px;
  margin-top: -2px;
}

.dash-speed-sub {
  display: flex;
  align-items: baseline;
  gap: 16px;
  margin-top: 4px;
}

.dash-speed-max {
  font-size: 1.2rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* Lean angle arcs */
.dash-lean-arcs {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 0;
}

.dash-lean-readout {
  font-size: 1.3rem;
  font-weight: 600;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
  min-width: 4ch;
}

/* Right column: compass OR nav mode */
.dash-right-col {
  flex: 0 0 auto;
  min-width: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0;
  padding: 8px;
}

/* Nav mode container (minimap + next turn) */
.dash-nav-mode {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  width: 100%;
  min-width: 0;
  height: 100%;
  /* Landscape (the usual dash orientation) has little vertical room: the square
     minimap + next-turn + Home button can exceed the column height. Scroll
     instead of clipping so the buttons are never stranded off-window;
     overscroll-contain keeps the page behind from moving. */
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

/* Next turn indicator */
.dash-next-turn {
  display: flex;
  align-items: center;
  gap: 10px;
  background: rgba(255, 255, 255, 0.06);
  border-radius: 10px;
  padding: 8px 12px;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  box-sizing: border-box;
  flex-shrink: 0;
  overflow: hidden;
}

/* Arrival card — the dash's copy of #nav-arrived, in the next-turn slot. */
.dash-arrived {
  width: 100%;
  max-width: 100%;
  min-width: 0;
  box-sizing: border-box;
  flex-shrink: 0;
  background: rgba(0, 200, 83, 0.1);
  border: 1px solid rgba(0, 200, 83, 0.3);
  border-radius: 10px;
  padding: 8px 12px;
}
.dash-arrived-head { display: flex; align-items: center; gap: 10px; }
.dash-arrived-icon {
  flex-shrink: 0;
  width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 8px;
  background: rgba(0, 200, 83, 0.15);
  color: #00c853;
  font-size: 1.3rem;
}
.dash-arrived-info { flex: 1; min-width: 0; }
.dash-arrived-title { font-size: 1.2rem; font-weight: 700; color: var(--text-primary, #fff); }
.dash-arrived-name {
  font-size: 0.8rem; color: var(--text-secondary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dash-arrived-elev { margin-top: 8px; }
.dash-arrived-elev canvas { display: block; width: 100%; height: 38px; }
.dash-arrived-elev-cap { margin-top: 1px; font-size: max(0.62rem, var(--text-floor, 0px)); color: var(--text-secondary); }

.dash-arrived-stats {
  display: flex;
  gap: 6px;
  margin-top: 8px;
  padding-top: 6px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.dash-arrived-stat { flex: 1; min-width: 0; text-align: center; }
.dash-arrived-stat b {
  display: block;
  font-size: 0.95rem; font-weight: 800; color: var(--text-primary, #fff);
  line-height: 1.15; white-space: nowrap;
}
.dash-arrived-stat span {
  display: block;
  font-size: max(0.62rem, var(--text-floor, 0px)); color: var(--text-secondary);
  text-transform: uppercase; letter-spacing: 0.04em;
}
.dash-arrived-note { margin-top: 8px; font-size: max(0.75rem, var(--text-floor, 0px)); color: var(--text-secondary); }
.dash-arrived-note i { color: #ff5252; margin-right: 5px; }
.dash-arrived-actions { display: flex; gap: 8px; margin-top: 8px; }
.dash-arrived-btn {
  flex: 1;
  padding: 10px 8px;
  font-size: 0.9rem; font-weight: 700;
  color: #fff;
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 8px;
  cursor: pointer;
}
.dash-arrived-btn.primary { background: #00c853; border-color: #00c853; color: #06210f; }

.dash-turn-icon {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  background: rgba(0, 200, 83, 0.15);
  color: #00c853;
  font-size: 1.3rem;
}

.dash-turn-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.dash-turn-dist {
  font-size: 1.2rem;
  font-weight: 700;
  color: var(--text-primary, #fff);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}

.dash-turn-road {
  font-size: max(0.68rem, var(--text-floor, 0px));
  color: var(--text-muted, #999);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: 1.2;
}

/* Minimap */
.dash-minimap-wrap {
  position: relative;
  flex: 0 0 auto;
  width: min(55vh, 220px);
  aspect-ratio: 1 / 1;
  border-radius: 10px;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.08);
}

.dash-minimap {
  position: absolute;
  top: -21%;
  left: -21%;
  width: 142%;
  height: 142%;
  background: #0a0e14;
  transition: transform 0.4s ease-out;
  transform-origin: center center;
}

/* Hide Leaflet chrome on minimap */
.dash-minimap .maplibregl-control-container { display: none !important; }

/* Home button */
.dash-home-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  max-width: min(55vh, 220px);
  padding: 7px 12px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.06);
  color: var(--text-primary, #fff);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.5px;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.dash-home-btn:active { background: rgba(255, 255, 255, 0.14); }
.dash-home-btn.home-set { border-color: rgba(0, 200, 83, 0.4); color: #00c853; }

/* Mode toggle button (compass ↔ map) */
.dash-mode-toggle {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 5px 10px;
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 6px;
  background: rgba(255,255,255,0.06);
  color: var(--text-muted, #999);
  font-size: max(0.68rem, var(--text-floor, 0px));
  cursor: pointer;
  transition: background 0.2s, color 0.2s;
}
.dash-mode-toggle:hover, .dash-mode-toggle:active {
  background: rgba(255,255,255,0.12);
  color: var(--text-primary, #fff);
}

/* Rider dot on minimap */
.dash-minimap-rider { background: none !important; border: none !important; }
.dash-rider-dot {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #00c853;
  border: 2px solid #fff;
  box-shadow: 0 0 8px rgba(0, 200, 83, 0.7);
}

.dash-compass-zone {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  user-select: none;
}

.dash-compass-arrow-wrap {
  flex-shrink: 0;
  width: min(90px, 24vh);
  height: min(90px, 24vh);
  display: flex;
  align-items: center;
  justify-content: center;
  filter: drop-shadow(0 0 14px rgba(0,200,83,0.5));
}

.dash-compass-arrow {
  width: 100%;
  height: 100%;
  transition: transform 0.5s ease-out;
  transform-origin: 50% 50%;
}

.dash-compass-arrow.no-target {
  opacity: 0.25;
  filter: grayscale(1);
}

.dash-compass-info {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-width: 0;
}

.dash-compass-target {
  font-size: max(0.75rem, var(--text-floor, 0px));
  color: var(--text-primary);
  font-weight: 600;
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: center;
}

.dash-recalc-banner {
  font-size: max(0.72rem, var(--text-floor, 0px));
  font-weight: 600;
  color: #40c4ff;
  background: rgba(64, 196, 255, 0.1);
  border: 1px solid rgba(64, 196, 255, 0.3);
  border-radius: 10px;
  padding: 3px 9px;
  white-space: nowrap;
  margin-top: 2px;
}

.dash-compass-readout {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: center;
}

.dash-compass-num {
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}

.dash-compass-brg {
  font-size: max(0.65rem, var(--text-floor, 0px));
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* ── Bottom bar: elevation + stats ── */
.dash-bottom-bar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  background: rgba(10,14,20,0.9);
  border-top: 1px solid var(--bg-tertiary);
  min-height: 36px;
  z-index: 2;
}

/* Elevation sparkline strip */
.dash-elevation-strip {
  flex: 0 0 auto;
  width: 40%;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-right: 1px solid var(--bg-tertiary);
  height: 100%;
}

.dash-elevation-canvas {
  flex: 1;
  height: 32px;
  min-width: 0;
}

.dash-elevation-stats {
  display: flex;
  flex-direction: column;
  gap: 1px;
  font-size: max(0.6rem, var(--text-floor, 0px));
  color: var(--text-muted);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.dash-elevation-stats .elev-gain { color: var(--accent); }
.dash-elevation-stats .elev-max { color: var(--text-secondary); }

/* Trip stats strip */
.dash-stats-strip {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: space-evenly;
  padding: 4px 12px;
  gap: 12px;
}

.dash-stat {
  display: flex;
  align-items: baseline;
  gap: 3px;
  font-variant-numeric: tabular-nums;
}

.dash-stat i {
  color: var(--text-muted);
  font-size: max(0.6rem, var(--text-floor, 0px));
  align-self: center;
}

.dash-stat span:not(.dash-stat-unit) {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--text-secondary);
  min-width: 2.5ch;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.dash-stat-unit {
  font-size: max(0.6rem, var(--text-floor, 0px));
  color: var(--text-muted);
}

/* Address input popover */
.dash-compass-input {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: min(420px, 90vw);
  background: rgba(10,14,20,0.96);
  border: 1px solid var(--bg-tertiary);
  border-radius: 10px;
  padding: 12px;
  z-index: 100003;
  box-shadow: 0 8px 28px rgba(0,0,0,0.6);
}

/* In landscape the virtual keyboard takes the bottom half — anchor popup to top
   so it stays in the visible area above the keyboard. */
@media (orientation: landscape) {
  .dash-compass-input {
    top: max(env(safe-area-inset-top, 0px), 8px);
    transform: translateX(-50%);
    max-height: calc(100dvh - 24px);
    overflow-y: auto;
  }
}

.dash-compass-input input {
  width: 100%;
  padding: 10px 12px;
  background: var(--bg-secondary);
  color: var(--text-primary);
  border: 1px solid var(--bg-tertiary);
  border-radius: 6px;
  font-size: 1rem; /* must be ≥16px to suppress iOS auto-zoom */
  box-sizing: border-box;
}

.dash-compass-results {
  max-height: 180px;
  overflow-y: auto;
  margin-top: 8px;
}

@media (orientation: landscape) {
  .dash-compass-results {
    max-height: min(120px, 30vh);
  }
}

.dash-compass-results-item {
  padding: 8px 10px;
  color: var(--text-primary);
  font-size: 0.85rem;
  border-radius: 4px;
  cursor: pointer;
}

.dash-compass-results-item:hover,
.dash-compass-results-item:active {
  background: var(--bg-tertiary);
}

.dash-compass-input-row {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 8px;
}

.dash-compass-input-row button {
  padding: 8px 14px;
  background: var(--bg-secondary);
  color: var(--text-primary);
  border: 1px solid var(--bg-tertiary);
  border-radius: 6px;
  font-size: 0.85rem;
  cursor: pointer;
}

.dash-compass-input-row button:hover {
  background: var(--bg-tertiary);
}

/* ── Portrait fallback ── */
@media (orientation: portrait) {
  .dash-top-bar { padding: 6px 50px 6px 16px; gap: 10px; flex-wrap: wrap; }
  .dash-main { flex-direction: column; }
  .dash-left-col { flex: 0 0 auto; order: 3; padding: 4px; }
  .dash-left-col .dash-radar-wrap { width: min(30vw, 140px); }
  .dash-centre-col { flex: 1; }
  .dash-speed-num { font-size: min(42vw, 30vh); }
  .dash-right-col { flex: 0 0 auto; order: 2; padding: 4px; }
  .dash-right-col .dash-compass-zone { flex-direction: row; gap: 16px; }
  .dash-right-col .dash-compass-arrow-wrap { width: min(80px, 14vh); height: min(80px, 14vh); }
  .dash-right-col .dash-compass-info { align-items: flex-start; }
  .dash-nav-mode { flex-direction: row; height: auto; gap: 8px; }
  .dash-minimap-wrap { flex: 0 0 120px; height: 120px; }
  .dash-next-turn { padding: 6px 10px; }
  .dash-turn-icon { width: 32px; height: 32px; font-size: 1rem; }
  .dash-turn-dist { font-size: 1rem; }
  .dash-bottom-bar { flex-direction: column; }
  .dash-elevation-strip { width: 100%; border-right: none; border-bottom: 1px solid var(--bg-tertiary); }
  .dash-stats-strip { width: 100%; }
}

/* Desktop: cap the speed font size */
@media (min-width: 769px) and (orientation: landscape) {
  .dash-speed-num { font-size: min(28vw, 38vh); }
  .dash-right-col { flex: 0 0 auto; }
  .dash-radar-wrap { width: min(60vh, 240px); }
  .dash-compass-arrow-wrap { width: min(110px, 28vh); height: min(110px, 28vh); }
  .dash-minimap-wrap { width: min(60vh, 240px); }
}

/* Save sits under the campsite name, above everything else in the panel
   (2026-08-22). Its own row rather than a member of .info-actions, which is the
   button strip at the foot. Appended rather than placed beside .info-actions
   because some regions' style.css is several concatenated copies. */
.info-save-row { margin: -4px 0 12px; }

/* ===== Scale bar + route HUD (2026-08-22) ===== */

/* Bottom-RIGHT corner of the map (Josh, 2026-08-23), tucked under the PTZ pad.
   The left corner is already three deep in furniture — Topo/Sat, 2D/3D and the
   terrain slider — and the bar was landing on top of the 2D/3D button.

   Two things this block has to get right, both learned the hard way:
   (1) !important, because vendor/maplibre-gl/maplibre-gl.css is linked AFTER
       this file in index.html, so vendor rules win on load order at equal
       specificity. The old rules here carried none, so NONE of them applied and
       the bar sat wherever MapLibre put it.
   (2) No --sidebar-width offset. MapLibre's corner containers live inside #map,
       which already starts at the sidebar edge, so adding it offsets twice. */
/* bottom: 10px measured, not guessed — the bar renders 25px tall and the pad
   stops 44px up, so this leaves 9px of air between them. */
.maplibregl-ctrl-bottom-right { right: 0 !important; bottom: 10px !important; z-index: 400; }
/* Drawn as a RULER, not a label (Josh, 2026-08-23: "it's unclear how it's
   measuring that distance"). MapLibre sets the element's WIDTH to the distance
   the text names, so the two end ticks and the baseline between them are the
   whole point — a floating "50 mi" measures nothing you can see. The half-way
   tick is there because judging a span against one long bar is guesswork. */
.maplibregl-ctrl-bottom-right .maplibregl-ctrl-scale {
  position: relative;
  box-sizing: border-box;          /* the ticks are borders: keep them INSIDE
                                      the width, or the ruler over-reads */
  height: 26px;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  /* The label sits at the TOP, clear of the baseline: text resting on the
     ruler line hid the half-way tick behind its own letters. */
  padding: 2px 4px 0;
  background: rgba(16, 18, 21, 0.55);
  border: 2px solid rgba(255, 255, 255, 0.92);
  border-top: none;                /* end ticks + baseline, open at the top */
  border-radius: 0 0 3px 3px;
  color: #fff;
  font-size: max(0.63rem, var(--text-floor, 0px));
  font-weight: 700;
  line-height: 1;
  white-space: nowrap;
  /* CENTRED under #map-ptz, not right-aligned to it (Josh, 2026-09-20:
     "center the ruler to the zoom/tilt etc button box"). #map-ptz is 136px
     wide at right:16px (measured in the live app, not read off the CSS), so
     its centre sits 84px in from the right edge. The margin puts the ruler's
     RIGHT edge there and the translate slides it back by half its own width,
     which is the point: MapLibre resets that width on every zoom, so a fixed
     margin would only line up at one zoom level. */
  margin: 0 84px 0 0 !important;
  transform: translateX(50%);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.85);
}
.maplibregl-ctrl-bottom-right .maplibregl-ctrl-scale::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: 0;
  width: 2px;
  height: 7px;
  margin-left: -1px;
  background: rgba(255, 255, 255, 0.92);
}

/* Distance, time and arrival, on the map, while a route is up. The route panel
   has the same numbers and is a sidebar the rider closes to look at the map. */
#route-hud {
  position: fixed;
  top: 16px;
  left: calc(var(--sidebar-width, 0px) + 16px);
  /* Bounded on the RIGHT too (2026-09-02). A 2,300 km route printed
     "1429.2mi", "28h 30m" and "Wed 2:20 PM"; the row was only ever bounded on
     the left, grew past #map-controls (fixed top-right, the same z-index 500,
     later in the DOM, so it paints on top) and the Arrive tile sat under the
     Nav button. The controls column is one button wide, and that is its widest
     label: "VEHICLE", or Italian "CANCELLA" in EU, measure 96-99px in Roboto
     and SF (icon 20 + gap 6 + label 48-59 + padding 20 + border 2), which a
     360px S23 rendered at 116px, not the 100px first guessed: the wrapped
     Arrive tile still ran 8px under the column. So the width is MEASURED:
     route-hud.js reads #map-controls when the tiles go up and sets
     --map-controls-w on the HUD; 116px is only the fallback. Plus the column's
     own 16px margin, plus 8px of air. fit-content keeps the box as narrow as
     its tiles until they need the room: with left and right both set, that
     room is the space between them. */
  right: calc(16px + var(--map-controls-w, 116px) + 8px);
  width: fit-content;
  z-index: 500;
  display: flex;
  flex-wrap: wrap;                 /* whole tiles move to a second row; a value never breaks mid-string */
  gap: 6px;
  pointer-events: none;            /* never steal a tap meant for the map */
}
#route-hud.hidden { display: none !important; }
/* A wrapped tile goes to the RIGHT end of its row, not the left. The elevation
   chip (#map-readouts) and the 3D compass sit under the tiles at the left,
   stepped clear of ONE row, and a second row at the left would land on them:
   the 2026-09-01 overlap again. On one row this costs nothing, because a
   fit-content box has no spare width for an auto margin to take. */
.route-hud-tile:last-child { margin-left: auto; }
.route-hud-tile {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 64px;
  padding: 5px 9px;
  background: var(--bg-secondary, #1c1f24);
  border: 1px solid var(--bg-tertiary, #2a2f36);
  border-radius: var(--radius-sm, 6px);
  box-shadow: var(--shadow, 0 2px 10px rgba(0, 0, 0, 0.4));
}
.route-hud-label {
  /* A caption never wraps and never widens a tile past its cap: the third tile's
     caption is a PLACE NAME, and "Coffs Harbour" must cost the same room as
     "Arrive". It ellipsises instead. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: max(0.58rem, var(--text-floor, 0px));
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted, #9aa8a1);
}
.route-hud-value { font-size: 0.92rem; font-weight: 700; color: var(--text-primary, #fff); white-space: nowrap; }

/* The 3D compass is added to the map's own top-left corner, which is where the
   tiles sit. Only while they are up. */
body.route-hud-on .maplibregl-ctrl-top-left { margin-top: 52px; }

/* Sidebar collapsed on desktop: #sidebar-expand is fixed at top:12px left:12px,
   38px square, z-index 1001 — parked straight on top of the first tile (Josh,
   2026-08-25). 62px is the same step #info-panel already takes to clear that
   same button. Desktop only: on a phone the tiles are placed by the rule below
   and body.sidebar-collapsed must not drag them sideways. */
@media (min-width: 769px) {
  body.sidebar-collapsed #route-hud { left: 62px; }
}

@media (max-width: 768px) {
  /* On a phone the sidebar is an overlay, so no offset — and the tiles shrink
     rather than crowd the top of a small map. */
  /* BELOW the mobile header, not under it. #mobile-header is position:fixed at
     z-index 1001 and the HUD is 500, so top:10px parked the tiles behind the
     title bar: they had never once been visible on a phone since they shipped
     (measured on an S23, 2026-08-25 — header 0-56px, tiles 10-55px). A rider
     who closes the sidebar to look at the line is exactly who they are for.
     right: #map-controls sits at right:12px on a phone; the same 100px column
     and 8px of air as the desktop rule above. */
  #route-hud { left: 10px; top: calc(var(--mobile-header-height, 56px) + 8px); right: calc(12px + var(--map-controls-w, 116px) + 8px); gap: 4px; }
  body.route-hud-on .maplibregl-ctrl-top-left { margin-top: calc(var(--mobile-header-height, 56px) + 58px); }
  /* 5px sides, not 7: on a 360px phone the three tiles of a SHORT route need 221px
     and the space left of the button column is 213px, so at 7px even "74.6mi, 2h 12m,
     18:26" wrapped (measured 2026-09-02). Long routes still wrap, which is what the
     right-aligned second row is for. */
  .route-hud-tile { min-width: 0; padding: 4px 5px; }
  .route-hud-value { font-size: 0.82rem; }
  /* Three tiles, one row. The third tile's caption is the next stop's NAME, so
     its width is whatever the rider typed: capped here and ellipsised, because
     one long town name must not push a tile onto a second row. 94px is what is
     left after the other two on a 390 (measured 2026-09-11: distance 81px,
     driving 64px, 4px gaps, 244px beside the button column). */
  .route-hud-tile-next { max-width: 86px; }
  /* A place name is longer than a word like "Arrive", so this caption gets its
     own tighter type: at the shared 0.58rem/0.06em even "Cervantes" ellipsised
     inside the cap. Measured on the real app, not a harness — the app's own
     font is wider than a bare stylesheet renders. */
  .route-hud-tile-next .route-hud-label { font-size: max(0.52rem, var(--text-floor, 0px)); letter-spacing: 0.01em; }
  /* Matches the phone PTZ pad, which moves to right: 12px, bottom: 56px. */
  .maplibregl-ctrl-bottom-right { bottom: 12px !important; }
  /* Same centring on a phone. Here #map-ptz drops the 3x3 pad and is 96px wide
     (2 x 40px + 4px gap + 2 x 5px padding + 2 x 1px border) at right:12px, so
     its centre is 60px in. */
  .maplibregl-ctrl-bottom-right .maplibregl-ctrl-scale { margin: 0 60px 0 0 !important; transform: translateX(50%); }
}

/* ===== Trial first-charge banner (js/auth.js _maybeTrialChargeNotice) =====
   Appended at EOF DELIBERATELY: this stylesheet is five concatenated copies of
   itself, so a rule placed anywhere earlier is overridden by a later copy.
   Same trap as #grant-ending-note above — see tasks/lessons.md.

   Shares #grant-ending-note's slot (132px) rather than claiming a new one: the
   two can never fire for the same rider. This one requires a Stripe customer id
   and that one returns null the moment there is one. */
#charge-note {
  position: fixed; bottom: 132px; left: 50%; transform: translateX(-50%);
  z-index: 2001; display: none; align-items: center; gap: 8px;
  background: #243342; color: #e8edf2; border: 1px solid rgba(255,255,255,0.12);
  border-radius: 8px; padding: 10px 14px; font-size: 0.8rem;
  max-width: min(460px, 92vw); box-shadow: 0 8px 30px rgba(0,0,0,0.5);
}
#charge-note i { color: var(--accent, #00c853); flex: none; }
#charge-note span { flex: 1 1 auto; }
#charge-note button {
  background: none; border: none; cursor: pointer; flex: none;
  color: var(--text-muted, #999); font-size: 1rem; padding: 0 2px; line-height: 1;
}
/* Last 48 hours. Amber, not red: nothing is broken — but money is about to move,
   which is why this one names the amount and the grant banner does not. */
#charge-note.urgent {
  border-color: rgba(255, 167, 38, 0.55);
  background: #33291d;
}
#charge-note.urgent i { color: #ffa726; }

/* Offline map source: Auto / Offline only / Online only. Replaced the "Use
   offline maps" checkbox on 2026-08-25 — the app now switches itself when tiles
   stop arriving, and this is the override. */
/* "Advanced" on the Offline tab: the map source picker, folded away. */
.offline-advanced { margin: 0 0 8px; }
.offline-advanced > summary { font-size: max(0.72rem, var(--text-floor, 0px)); color: var(--text-muted); cursor: pointer; padding: 4px 0; }
.offline-advanced[open] > summary { margin-bottom: 4px; }
.offline-mode-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; padding: 8px 0; min-height: 44px;
}
.offline-mode-label { font-size: 0.85rem; color: var(--text-primary); }
.offline-mode-select {
  background: var(--bg-tertiary); color: var(--text-primary);
  border: 1px solid var(--bg-tertiary); border-radius: var(--radius);
  padding: 8px 10px; min-height: 40px; font-size: 0.82rem; font-family: inherit;
  cursor: pointer;
}
.offline-mode-select:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
#offline-maps-note .offline-dot {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  margin-right: 6px; vertical-align: 1px;
}

/* `hidden` must beat the flex display above, or a toggle js/app.js hides for a
   bicycle or a rig stays on screen. (Found 2026-08-28: the bicycle hiding had
   never worked for this reason.) */
.layer-toggle[hidden] { display: none; }

/* ===== Live map readouts (js/elevation-hud.js) =====
   Top-left of the map, on phones only (the module never starts on a desktop). NOT an empty corner, which is what the first cut of this
   comment claimed: #route-hud (the distance / time / arrival tiles) is fixed at
   exactly this spot and is up whenever a route is, which is most of the time
   the chips have anything to say. The port review caught the two chips drawing
   straight over the ETA tile (2026-09-01, found in AUS and EU, inherited from
   here). The rules after the mobile block step the column BELOW the tiles the
   same way the 3D compass already does (body.route-hud-on
   .maplibregl-ctrl-top-left), and clear #sidebar-expand the same way
   #route-hud and #info-panel already do. #map-controls owns top-right, the
   basemap and 2D/3D pills own bottom-left, MapLibre's attribution and scale own
   bottom-right. Deliberately quiet — it is a fact you glance at, not a control
   you press, so it takes no border and no shadow that would make it look
   tappable.

   The POSITION lives on the wrapper, not on the chip, so a second chip (the
   daylight countdown lived here until Josh cut it, 2026-09-02) stacks under it
   with a gap instead of hardcoding the first one's height. */
#map-readouts {
  position: fixed;
  top: 16px;
  left: calc(var(--sidebar-width) + 16px);
  z-index: 500;
  display: flex;
  flex-direction: column;
  align-items: flex-start;   /* each chip is only as wide as its own text */
  gap: 6px;
  pointer-events: none;      /* never eat a map drag that starts here */
}
.map-readout {
  display: flex; align-items: center; gap: 7px;
  padding: 6px 11px;
  border-radius: var(--radius);
  background: var(--bg-secondary);
  border: 1px solid var(--bg-tertiary);
  box-shadow: var(--shadow);
  color: var(--text-primary);
  font-size: 0.95rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;   /* the number must not jiggle as it changes */
  user-select: none;
}
.map-readout i { color: var(--accent); font-size: 0.85rem; }
.map-readout[hidden] { display: none !important; }


/* The dashboard and nav both carry their own altitude readout, and both take
   the whole screen. Same rule the basemap and 2D/3D pills follow. */
body.dashboard-active #map-readouts,
body.nav-active #map-readouts { display: none !important; }

/* While a route is up, #route-hud owns top:16px in this corner: a 36px row of
   tiles whose bottom edge is 52px (the same 52px body.route-hud-on gives the 3D
   compass). Step the column under it with 8px of air. If the tiles ever change
   height, this and the compass rule move together. */
body.route-hud-on #map-readouts { top: 60px; }

/* Sidebar collapsed on desktop: #sidebar-expand is fixed at top:12px left:12px,
   38px square, z-index 1001, and --sidebar-width goes to 0 — which parks the
   chips under that button. 62px is the step #info-panel and #route-hud already
   take to clear it (Josh, 2026-08-25). Desktop only, like theirs. */
@media (min-width: 769px) {
  body.sidebar-collapsed #map-readouts { left: 62px; }
}

@media (max-width: 768px) {
  /* The map starts below the mobile header, and the sidebar is an overlay
     rather than a column, so the left offset collapses to the plain margin. */
  #map-readouts {
    top: calc(var(--mobile-header-height) + 12px);
    left: 12px;
  }
  /* #route-hud sits at header + 8px on a phone and its tiles are shorter; its
     bottom edge is header + 58px, which is the figure the compass rule uses. */
  body.route-hud-on #map-readouts { top: calc(var(--mobile-header-height) + 66px); }
}

/* The signup error sits between the fields and the button now, and the bad box
   gets a red edge. Live telemetry, 21 days to 2026-09-04: 136 no_email, 26
   short_password, 12 bad_email, 10 no_password. Those messages exist to be
   read, and they were rendering below the button and below the microcopy. */
.signup-box .field-invalid {
  border-color: #ff5252 !important;
  box-shadow: 0 0 0 2px rgba(255, 82, 82, 0.18);
}

/* ===== Nav destination card (js/nav-destination.js) =====
   Nav opened with no route and offered nothing but a moving dot. This is the
   "where to?" box, over the nav map, top-centre so it clears the close button
   and the clock. Sized for gloved thumbs on a bar mount, not for a desk. */
#nav-dest {
  position: absolute;
  top: 74px;
  left: 12px;
  right: 12px;
  z-index: 1200;
  background: rgba(16, 24, 33, 0.94);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 14px;
  padding: 10px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.45);
  max-width: 560px;
  margin: 0 auto;
}
#nav-dest.hidden { display: none; }
.nav-dest-row { display: flex; align-items: center; gap: 8px; }
.nav-dest-icon { color: #8fa3b8; font-size: 16px; flex: 0 0 auto; }
#nav-dest-input {
  flex: 1 1 auto;
  min-width: 0;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 10px;
  color: #fff;
  font-size: 17px;              /* 17px: iOS zooms the page on anything smaller */
  padding: 11px 12px;
}
#nav-dest-input::placeholder { color: #8fa3b8; }
#nav-dest-close {
  flex: 0 0 auto;
  background: none;
  border: none;
  color: #8fa3b8;
  font-size: 18px;
  padding: 8px 10px;
  cursor: pointer;
}
#nav-dest-list { margin-top: 6px; max-height: 40vh; overflow-y: auto; }
.nav-dest-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  background: none;
  border: none;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  color: #e8eef5;
  font-size: 16px;
  font-family: inherit;
  text-align: left;
  padding: 13px 4px;            /* 44px total: a gloved thumb target */
  cursor: pointer;
}
.nav-dest-item:first-child { border-top: none; }
.nav-dest-item i { color: #00c853; font-size: 14px; flex: 0 0 auto; }
.nav-dest-item span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#nav-dest-note { margin: 6px 2px 0; color: #8fa3b8; font-size: max(13px, var(--text-floor, 0px)); line-height: 1.35; }
#nav-dest-list.hidden, #nav-dest-place.hidden, .pc-line.hidden { display: none; }
#nav-dest-place { margin-top: 8px; max-height: 60vh; overflow-y: auto; color: #e8eef5; }
/* A searched place's card (js/place-card.js): nav's "Where to?" box and the
   map search's info panel. Which one, before Go / Start. */
.pc-head { display: flex; align-items: flex-start; gap: 12px; padding: 2px 2px 10px; }
.pc-head > i { color: #00c853; font-size: 20px; margin-top: 3px; flex: 0 0 auto; }
.pc-name { font-size: 20px; font-weight: 700; line-height: 1.2; }
.pc-sub { color: var(--text-muted, #8fa3b8); font-size: 14px; margin-top: 3px; }
.pc-line { display: flex; gap: 10px; align-items: flex-start; font-size: 15px; line-height: 1.35; padding: 8px 2px; border-top: 1px solid rgba(255, 255, 255, 0.08); }
.pc-line i { color: var(--text-muted, #8fa3b8); width: 16px; text-align: center; margin-top: 2px; flex: 0 0 auto; }
.pc-fuel:empty, .pc-contact:empty { display: none; }
.pc-fuel, .pc-contact { border-top: 1px solid rgba(255, 255, 255, 0.08); padding-top: 8px; margin-top: 2px; }
.pc-actions { display: flex; gap: 8px; margin: 6px 0 10px; }
.pc-actions button { font-family: inherit; font-size: 17px; font-weight: 600; border-radius: 12px; padding: 14px 12px; cursor: pointer; border: none; }
.pc-back { flex: 0 0 auto; background: rgba(255, 255, 255, 0.1); color: #e8eef5; }
.pc-go { flex: 1 1 auto; background: #00c853; color: #04150b; }
/* The map's card keeps the app's own route actions under Start. */
.pc-more { display: flex; flex-wrap: wrap; gap: 6px; margin: -2px 0 10px; }
body.place-card-open #search-go-btn { display: none; }
#nav-dest-note.hidden { display: none; }

/* ===== Nav pill buttons (Topo|Sat, 2D|3D) =====
   These had NO base rule: the only styling was geometry inside
   #nav-mode-switch / #nav-base-switch, so every one of them rendered as a raw
   Chrome button — grey #efefef, black Arial, native border — sitting on top of
   the dark pill it belongs in, and spilling past it (Josh, phone, 2026-09-04).
   The pill also had no way to show which mode you were in: the JS has toggled
   .active and aria-pressed since it shipped and no rule ever read them. */
.mode-switch-btn {
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: none;
  border-radius: 999px;
  color: #cfd8e3;
  font-family: inherit;
  font-weight: 700;
  line-height: 1;
  text-transform: uppercase;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s ease, color 0.15s ease;
}
.mode-switch-btn:hover { color: #fff; }
/* The mode you are IN, not the one the press would take you to — the button's
   own text already says the current mode (see updateNavBaseToggle). */
.mode-switch-btn.active {
  background: rgba(0, 200, 83, 0.9);
  color: #06210f;
}

/* ===== The hamburger says where it goes (2026-09-07) =====
   A bare bars icon told a rider nothing about what the tap does. The button
   now stacks the icon over one small word: Menu while the map is up, Map while
   the menu is (app.js syncSidebarToggleLabel writes it). The column keeps the
   pair inside the 56px header, and the .58rem word still fits a 360px phone
   beside the nowrap title. Two ids so it beats #mobile-header button above. */
#mobile-search #sidebar-toggle {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  flex: none;
  width: 48px; height: 48px;
  border-radius: 24px;
  background: none; border: none;
  color: var(--text-primary);
  font-size: 1.05rem;
  padding: 0;
  line-height: 1;
  cursor: pointer;
}
#sidebar-toggle .map-btn-label {
  font-size: max(.58rem, var(--text-floor, 0px));
  letter-spacing: .5px;
  text-transform: uppercase;
}


/* Every POI row in the layer list wears the badge its dots wear on the map
   (Layers.setupToggles hangs a MarkerIcons.swatch canvas on the row,
   2026-09-07). The row is a flex line with its own 10px gap, which already
   keeps the icon clear of a LIVE / Trial / New tag beside it. */
.layer-toggle .layer-icon {
  margin-left: auto; flex-shrink: 0;
  width: 18px; height: 18px; border-radius: 50%;
}

/* Drag-to-reorder stops (2026-09-08, backtrack-audit fix). Pointer Events,
   not HTML5 drag-and-drop — DnD is mouse-only and this app is mobile-first. */
.route-point.stop-draggable .route-waypoint-icon { cursor: grab; touch-action: none; padding: 4px; margin: -4px; }
.route-point.stop-draggable.dragging { opacity: 0.55; background: var(--bg-secondary); }

/* The emailed trip plan (ported from NZ 2026-09-12). */
.ai-plan-row { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--bg-tertiary); }
.ai-plan-date-label { display: block; font-size: 0.92rem; font-weight: 600; margin-bottom: 6px; color: var(--text-primary); }
.ai-plan-date {
  width: 100%; min-height: 46px; padding: 0 12px; margin-bottom: 10px;
  background: var(--bg-tertiary); border: 1px solid transparent; border-radius: var(--radius);
  color: var(--text-primary); font-family: inherit; font-size: 1rem;
}
.ai-plan-date:focus { border-color: var(--accent); outline: none; }
.ai-plan-email {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%; min-height: 56px; padding: 0 16px;
  background: var(--accent); color: #0d1117; border: 0; border-radius: var(--radius);
  font-family: inherit; font-size: 1.05rem; font-weight: 700; cursor: pointer;
  box-shadow: 0 2px 10px rgba(0, 200, 83, 0.3);
}
.ai-plan-email:hover { filter: brightness(1.06); }
.ai-plan-email:disabled { opacity: 0.6; cursor: default; box-shadow: none; }
.ai-plan-hint { margin: 8px 0 0; font-size: 0.85rem; line-height: 1.45; color: var(--text-muted); }
/* The trip's days and nights, said before the PDF is made (js/trip-plan.js refreshDays). */
.trip-plan-days { margin: 0 0 8px; font-size: 0.9rem; line-height: 1.45; color: var(--text); }
.trip-plan-days:empty { display: none; }
.route-fuel { margin: 4px 0 10px; font-size: 0.9rem; line-height: 1.45; color: var(--text); }
.route-fuel:empty { display: none; }
.route-fuel .fa-gas-pump { color: var(--text-muted); margin-right: 2px; }
.route-fuel-set, .route-fuel-change { background: none; border: 0; padding: 0; font: inherit; color: var(--accent, #4caf50); cursor: pointer; text-decoration: underline; }
.route-fuel-set { text-align: left; }
.route-fuel-new { display: inline-block; font-size: max(.68rem, var(--text-floor, 0px)); font-weight: 800; text-transform: uppercase; letter-spacing: .04em; color: #0b1a10; background: var(--accent, #00c853); border-radius: 999px; padding: 1px 7px; margin-right: 4px; vertical-align: 1px; }
.ai-plan-note { margin: 8px 0 0; font-size: 0.9rem; color: var(--text-muted); display: none; text-align: center; }
.ai-plan-note.on { display: block; }
.ai-plan-note.bad { color: var(--warning, #ffab40); }
/* Download PDF and Share, under the big email button (js/trip-plan.js). */
.trip-plan-more { display: flex; gap: 8px; justify-content: center; margin-top: 8px; }
.trip-plan-more .btn[hidden] { display: none; }

/* ===== Nav screen: one grid, every readout in its own cell (2026-09-14) =====
   Until now each pill on the nav screen was placed by hand with absolute
   offsets spread over three media queries, and nothing tied a pill's slot to
   its neighbour's real height. When the ETA chip grew an arrival line the
   turn banner ended up 18px under the clock on an S23, the 2D/3D pill touched
   the elevation chip, and the voice pill never got the phone rule the other
   two pills had. Josh, 2026-09-14: nothing on the nav screen may cover
   anything else; there is space between every piece of information.

   The overlay is now a CSS grid and each readout is a grid item, so two of
   them cannot share a pixel by construction. The map stays absolute behind
   them. Every earlier top/left/right/bottom/transform for these ids is reset
   here and is dead. Narrow screens stack the three pills above the elevation
   chip on the right; from 600px wide they sit in a row along the bottom edge
   and the clock moves beside the ETA chip, which is what keeps a phone in
   landscape (354px tall) from running out of height. */
#nav-overlay:not(.hidden) {
  display: grid !important;   /* nav.js sets inline display:block on open */
  grid-template-columns: auto 1fr auto;
  /* 1 close/clock/eta · 2 turn · 3 battery · 4 camera · 5 worth-a-stop
     6 off-route · 7 radar · 8 spare · 9 voice · 10 basemap · 11 2D/3D · 12 speed/elev */
  grid-template-rows: auto auto auto auto auto auto auto 1fr auto auto auto auto;
  column-gap: 10px;   /* rows keep their air with margin-bottom: an empty row must add none */
  row-gap: 0;
  padding: calc(12px + env(safe-area-inset-top, 0px))
           calc(14px + env(safe-area-inset-right, 0px))
           calc(18px + env(safe-area-inset-bottom, 0px))
           calc(14px + env(safe-area-inset-left, 0px));
  box-sizing: border-box;
  transition: padding-bottom 0.25s ease;
}
/* The browser's exit-fullscreen notice sits over the bottom of the screen for
   ~10.5 s after nav asks for fullscreen and takes every tap there (js/nav.js
   liftForFsNotice). The bottom row rides above it until it goes. */
#nav-overlay.fs-notice:not(.hidden) { padding-bottom: calc(112px + env(safe-area-inset-bottom, 0px)); }
/* Manual zoom in nav: + / - and the Auto hand-back. Each button carries its own
   pill chrome: the other nav pills put the dark background, border and blur on
   the CONTAINER, because each is a single button. These are three separate
   targets with gaps between them, so without this they render as bare pale-grey
   glyphs on a moving map — present, and invisible (Josh, 2026-09-18: "eu nnot
   showing it, I've opened it 3 times"). Same treatment as #nav-close. */
#nav-zoom {
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: flex-end;
}
#nav-zoom .mode-switch-btn {
  min-width: 44px;
  min-height: 44px;
  padding: 0 10px;
  font-size: 1.15rem;
  font-weight: 700;
  line-height: 1;
  color: #fff;
  background: rgba(10,14,20,0.72);
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 999px;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

#nav-zoom .mode-switch-btn:active { background: rgba(0,200,83,0.9); }

#nav-overlay:not(.hidden) > #nav-close,
#nav-overlay:not(.hidden) > #nav-turn,
#nav-overlay:not(.hidden) > #nav-eta,
#nav-overlay:not(.hidden) > #nav-clock,
#nav-overlay:not(.hidden) > #nav-battery-suggest,
#nav-overlay:not(.hidden) > #nav-camera,
#nav-overlay:not(.hidden) > #nav-poi,
#nav-overlay:not(.hidden) > #nav-offline-note,
#nav-overlay:not(.hidden) > #nav-gps-note,
#nav-overlay:not(.hidden) > #nav-radar-note,
#nav-overlay:not(.hidden) > #nav-voice,
#nav-overlay:not(.hidden) > #nav-base-switch,
#nav-overlay:not(.hidden) > #nav-mode-switch,
#nav-overlay:not(.hidden) > #nav-zoom,
#nav-overlay:not(.hidden) > #nav-speed,
#nav-overlay:not(.hidden) > #nav-elev {
  position: relative;
  top: auto; right: auto; bottom: auto; left: auto;
  transform: none;
  margin: 0;
  min-width: 0;
  max-width: 100%;
  box-sizing: border-box;
  z-index: 10;
}
/* Narrow (a phone upright). Row 1: close, clock, ETA. Row 2: the turn banner
   on its own full-width row, so a road name has the whole screen width.
   Speed spans the first two columns and the right-hand stack the last, so
   neither the 172px speed cluster nor the pills can size the top row's columns
   (a grid item that spans the 1fr track does not grow the auto tracks). */
#nav-overlay:not(.hidden) > #nav-close { grid-column: 1; grid-row: 1; align-self: start; margin: 0 0 10px 0; }
#nav-overlay:not(.hidden) > #nav-clock { grid-column: 2; grid-row: 1; justify-self: end; align-self: start; margin: 0 0 10px 0; white-space: nowrap; }
#nav-overlay:not(.hidden) > #nav-eta { grid-column: 3; grid-row: 1; justify-self: end; margin: 0 0 10px 0; }
/* No route: no ETA chip, so the clock takes the corner. */
#nav-overlay:not(.hidden) > #nav-eta.hidden ~ #nav-clock { grid-column: 3; }
#nav-overlay:not(.hidden) > #nav-turn { grid-column: 1 / -1; grid-row: 2; justify-self: center; align-self: start; margin: 0 0 10px 0; }
/* Alerts stack under the turn banner, centred, one per row. */
#nav-overlay:not(.hidden) > #nav-battery-suggest { grid-column: 1 / -1; grid-row: 3; justify-self: center; margin: 0 0 10px 0; }
#nav-overlay:not(.hidden) > #nav-camera { grid-column: 1 / -1; grid-row: 4; justify-self: center; margin: 0 0 10px 0; }
#nav-overlay:not(.hidden) > #nav-poi { grid-column: 1 / -1; grid-row: 5; justify-self: center; margin: 0 0 10px 0; }
#nav-overlay:not(.hidden) > #nav-offline-note { grid-column: 1 / -1; grid-row: 6; justify-self: center; margin: 0 0 10px 0; white-space: normal; text-align: center; }
/* Weak GPS sits just above the speed it spoils, beside the elevation chip:
   row 10's first two cells hold nothing else, so it adds no row. */
#nav-overlay:not(.hidden) > #nav-gps-note { grid-column: 1 / 3; grid-row: 10; justify-self: start; align-self: end; margin: 0 0 10px 0; white-space: nowrap; }
/* Wide (a phone on its side) has no height to spare: the top row's first cell,
   left of the turn banner, is free there. */
@media (min-width: 600px), (orientation: landscape) and (max-height: 500px) {
  #nav-overlay:not(.hidden) > #nav-gps-note { grid-column: 1; grid-row: 1; align-self: start; }
}
#nav-overlay:not(.hidden) > #nav-radar-note { grid-column: 1 / -1; grid-row: 7; justify-self: center; margin: 0 0 10px 0; white-space: normal; text-align: center; }
/* Bottom: speed bottom-left; voice, basemap, 2D/3D and elevation stacked bottom-right. */
#nav-overlay:not(.hidden) > #nav-voice { grid-column: 3; grid-row: 9; justify-self: end; align-self: end; margin: 0 0 10px 0; }
#nav-overlay:not(.hidden) > #nav-base-switch { grid-column: 3; grid-row: 10; justify-self: end; align-self: end; margin: 0 0 10px 0; }
#nav-overlay:not(.hidden) > #nav-mode-switch { grid-column: 3; grid-row: 11; justify-self: end; align-self: end; margin: 0 0 10px 0; }
#nav-overlay:not(.hidden) > #nav-zoom { grid-column: 1; grid-row: 10; justify-self: start; align-self: end; margin: 0 0 10px 0; flex-direction: row; }
#nav-overlay:not(.hidden) > #nav-speed { grid-column: 1 / 3; grid-row: 11 / span 2; justify-self: start; align-self: end; margin: 0; }
#nav-overlay:not(.hidden) > #nav-elev { grid-column: 3; grid-row: 12; justify-self: end; align-self: end; margin: 0; }
#nav-turn-dist { white-space: nowrap; }

/* Wide enough (a phone on its side, a tablet, a desktop): the turn banner sits
   between the close button and the clock on the top row, the three pills in a
   row along the bottom edge between the speed and the elevation. The clock
   shares its columns with the 2D/3D pill, which is on another row, so a
   narrow 1fr track cannot wrap it. Height is what a phone on its side lacks
   (354px on an S23), and this shape spends 66px on the top row, not 137. */
@media (min-width: 600px), (orientation: landscape) and (max-height: 500px) {
  #nav-overlay:not(.hidden) { grid-template-columns: auto 1fr auto auto auto 1fr auto; }
  #nav-overlay:not(.hidden) > #nav-turn { grid-column: 2 / 5; grid-row: 1; }
  /* On its side the top row is full (at 640 px wide a column for the heading
     pushed the ETA chip off the screen), so the heading takes the one free
     cell: column 1, left of the turn banner. The weak-GPS note owns that cell
     while it shows, and a fix too weak to trust has no heading worth printing,
     so the heading steps aside for it (the note comes first in the markup). */
  #nav-overlay:not(.hidden) > #nav-compass { grid-column: 1; grid-row: 1; justify-self: start; align-self: start; margin: 0 0 10px 0; }
  #nav-overlay:not(.hidden) > #nav-gps-note:not(.hidden) ~ #nav-compass { display: none; }
  /* On its side there is no height to spare: under the compass they pushed
     the dock off a 640x360 screen with every alert lit, and the bottom row's
     gap beside the dock is too narrow at 640. They sit above the speed, in
     the elevation's row, which is empty on the left: no height of their
     own. Spanning into the 1fr columns keeps them from widening column 1
     and squeezing the turn banner. */
  #nav-overlay:not(.hidden) > #nav-wx { display: flex; grid-column: 1 / 7; grid-row: 11; justify-self: start; align-self: end; margin: 0 0 10px 0; flex-wrap: nowrap; }
  #nav-overlay:not(.hidden) > #nav-wx > .nav-wx-pill { margin: 0; }
  /* The speed alert owns that row while it shows; safety before weather. */
  #nav-overlay:not(.hidden) > #nav-speed-alert:not(.hidden) ~ #nav-wx { display: none; }
  #nav-overlay:not(.hidden) > #nav-clock { grid-column: 5 / 7; grid-row: 1; margin: 0 0 10px 0; }
  #nav-overlay:not(.hidden) > #nav-eta { grid-column: 7; }
  #nav-overlay:not(.hidden) > #nav-eta.hidden ~ #nav-clock { grid-column: 5 / 8; }
  #nav-overlay:not(.hidden) > #nav-voice { grid-column: 3; grid-row: 12; }
  #nav-overlay:not(.hidden) > #nav-base-switch { grid-column: 4; grid-row: 12; }
  #nav-overlay:not(.hidden) > #nav-mode-switch { grid-column: 5; grid-row: 12; }
  #nav-overlay:not(.hidden) > #nav-speed { grid-column: 1; grid-row: 12; }
  /* On its side there is no room above anything: the whole bottom edge is one
     row, so the zoom group sits just right of the speed cluster instead. */
  #nav-overlay:not(.hidden) > #nav-zoom { grid-column: 2; grid-row: 12; justify-self: start; align-self: end; flex-direction: row; align-items: center; }
  #nav-overlay:not(.hidden) > #nav-elev { grid-column: 7; }
}

/* Short (a phone on its side): the two heads-up pills share a row, worth-a-stop
   on the left, camera on the right, so four stacked alerts cannot push the
   speed off the bottom of a 354px screen. Pills lose a little padding. */
@media (min-width: 600px) and (max-height: 500px), (orientation: landscape) and (max-height: 500px) {
  #nav-overlay:not(.hidden) > #nav-poi { grid-column: 1 / 5; grid-row: 4; justify-self: end; }
  #nav-overlay:not(.hidden) > #nav-camera { grid-column: 5 / 8; grid-row: 4; justify-self: start; }
  #nav-overlay:not(.hidden) > #nav-voice { padding: 0 12px; }
  #nav-overlay:not(.hidden) .mode-switch-btn { min-width: 44px; padding: 0 10px; }
}

/* Phone-sized readouts (narrow, or short as a phone on its side): a step
   smaller so the top row and the bottom row both fit with air between them. */
@media (max-width: 480px), (max-height: 500px) {
  #nav-speed-value { font-size: 3rem; }
  #nav-speed { gap: 10px; padding: 6px 12px; }
  #nav-speed-limit { width: 46px; height: 46px; border-width: 4px; }
  #nav-speed-limit-value { font-size: 1.1rem; }
  #nav-turn { gap: 10px; padding: 8px 12px 8px 8px; }
  .nav-turn-icon { width: 40px; height: 40px; font-size: 1.3rem; }
  #nav-turn-dist { font-size: 1.35rem; }
  #nav-turn-road { font-size: 0.85rem; }
  #nav-eta-dist { font-size: 1.05rem; }
  #nav-clock { font-size: 0.85rem; padding: 6px 10px; }
  #nav-elev-value { font-size: 1.15rem; }
  #nav-elev { padding: 6px 10px; }
}

/* ===== NAV DOCK ============================================================
   Ported from EU, 2026-09-18. Appended at the END of the stylesheet on
   purpose: this file is (in AUS) four different versions of itself
   concatenated, and the last rule is the one the browser obeys. Splicing
   into the middle would have been overridden by a later copy.
   ========================================================================= */

/* Mute for the spoken directions. Lives in #nav-dock now; muted is a state a
   rider has to read at a glance from a moving bike, so it changes colour as
   well as the icon and the word. */
#nav-voice.muted { color: #ffab40; }
#nav-voice.muted i { color: #ffab40; }


/* Two cells side by side, not stacked: upright at 360x640 with every alert lit
   the screen is 17px short of fitting a 94px-tall stepper, and a wide one costs
   46px of height instead (measured, scripts/qa/nav-overlap-test.js). It is also
   the shape landscape needs, so there is now one shape at every width. */
#nav-zoom {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  border-radius: 22px;
  overflow: hidden;
  background: rgba(10,14,20,0.74);
  border: 1px solid rgba(255,255,255,0.12);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
/* Each button carries its own pill chrome. The other nav pills put the dark
   background, border and blur on the CONTAINER, because each is a single
   button; these are three separate targets with gaps between them, so without
   this they render as bare pale-grey glyphs on a moving map — present, and
   invisible (Josh, 2026-09-18: "eu nnot showing it, I've opened it 3 times").
   Same treatment as #nav-close. */
#nav-zoom .mode-switch-btn {
  min-width: 46px;
  min-height: 46px;
  padding: 0;
  font-size: 1.25rem;
  font-weight: 700;
  line-height: 1;
  color: #fff;
  background: transparent;
  border: 0;
  border-radius: 0;
}
#nav-zoom .mode-switch-btn + .mode-switch-btn { border-left: 1px solid rgba(255,255,255,0.12); }
#nav-zoom .mode-switch-btn:active { background: rgba(0,200,83,0.9); }
/* The rider holds the zoom. Shown by lighting the stepper rather than by
   growing a third button mid-ride: nothing appears or disappears while moving.
   A long press on either cell hands it back (js/nav.js). */
#nav-zoom.held .mode-switch-btn {
  border-color: rgba(0,200,83,0.75);
  color: #6dffa4;
}


/* ===== THE DOCK =====================================================
   One bar along the bottom carrying every riding-screen control (Josh,
   2026-09-18, option 1 of the ten-layout report). It replaced five separate
   floating clusters carrying three different target sizes.
   64px cells: above Apple's 44pt floor, and the tallest row that still fits
   four cells plus the speed and elevation readouts on a 360x640 phone.
   Google's Design for Driving asks 76dp for a screen used in a vehicle and
   says to trade width for height when it will not fit — which is why these
   are tall and narrow rather than short and wide. */
#nav-dock {
  display: flex;
  align-items: stretch;
  gap: 4px;
  padding: 5px;
  background: rgba(10,14,20,0.78);
  border: 1px solid rgba(255,255,255,0.14);
  border-radius: 20px;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
/* flex-basis auto, not 0 (2026-10-27): each cell starts at its own word's
   width and the spare room is shared out. Five equal cells on a 338px phone
   are 56px, and OVERVIEW in Inter is 71px; sized from the words, all five
   fit in ~276px of the 310px dock. */
.nav-dock-btn {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 64px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  padding: 0 4px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 15px;
  color: #fff;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
.nav-dock-btn i { font-size: 1.15rem; line-height: 1; }
/* The word, not an abbreviation the rider has to decode. Letter-spaced small
   caps are slow to read, so this is the one place the app keeps them short:
   END, VOICE, 2D, TOPO — four characters at most, never a sentence. */
.nav-dock-label {
  font-size: max(0.62rem, var(--text-floor, 0px));
  font-weight: 700;
  letter-spacing: 0.08em;
  line-height: 1;
  color: var(--text-secondary, #9aa8a1);
  text-transform: uppercase;
}
.nav-dock-btn:active { background: rgba(255,255,255,0.10); }
/* OVERVIEW is hidden until there is a route and the switch is on; the flex
   rule above would otherwise beat the hidden attribute. Its words (OVERVIEW,
   BACK TO ME; Josh chose them, 2026-10-27) are the only ones over four
   characters, and stay on one line: two lines made the dock taller and ran it
   off a 360x640 screen. */
.nav-dock-btn[hidden] { display: none; }
#nav-overview .nav-dock-label { white-space: nowrap; }
/* Lit = you are IN the non-default state. The word says which state; the light
   says the word is a readout, not a command. Both change together, which is
   what the bare "2D" pill could not do. */
.nav-dock-btn.active {
  background: rgba(0,200,83,0.22);
  border-color: rgba(0,200,83,0.6);
}
.nav-dock-btn.active i,
.nav-dock-btn.active .nav-dock-label { color: #6dffa4; }
#nav-voice.muted { background: rgba(255,171,64,0.16); border-color: rgba(255,171,64,0.55); }
#nav-voice.muted .nav-dock-label { color: #ffab40; }
/* END is the one press a rider cannot take back, so it never lights green and
   it reads in the warning colour the moment a finger is on it. */
#nav-close:active { background: rgba(255,82,82,0.25); }
#nav-close:hover i { color: var(--danger); }
/* A phone on its side has ~354px of height: the dock drops to a single row of
   icon-beside-word so it costs 52px instead of 74. */
@media (max-height: 500px) {
  .nav-dock-btn { min-height: 44px; flex-direction: row; gap: 6px; }
  .nav-dock-label { font-size: max(0.6rem, var(--text-floor, 0px)); }
  /* Five cells (OVERVIEW joined, 2026-10-27) do not fit the ~310px the bottom
     row leaves a 640x360 screen at one width each: 425px measured, pushing
     the zoom and elevation off the screen. On its side the row is as tall as
     the speed readout anyway, so the cells go icon-over-word and each takes
     its own word's width (44px at least): 5 cells in ~300px with BACK TO ME
     in Inter, the widest the row has to take. */
  #nav-dock { gap: 3px; }
  .nav-dock-btn { flex: 0 0 auto; min-width: 44px; flex-direction: column; gap: 3px; padding: 0 3px; }
}

#nav-overlay:not(.hidden) > #nav-turn,
#nav-overlay:not(.hidden) > #nav-eta,
#nav-overlay:not(.hidden) > #nav-clock,
#nav-overlay:not(.hidden) > #nav-compass,
#nav-overlay:not(.hidden) > #nav-wx,
#nav-overlay:not(.hidden) > #nav-battery-suggest,
#nav-overlay:not(.hidden) > #nav-camera,
#nav-overlay:not(.hidden) > #nav-poi,
#nav-overlay:not(.hidden) > #nav-offline-note,
#nav-overlay:not(.hidden) > #nav-gps-note,
#nav-overlay:not(.hidden) > #nav-radar-note,
#nav-overlay:not(.hidden) > #nav-dock,
#nav-overlay:not(.hidden) > #nav-zoom,
#nav-overlay:not(.hidden) > #nav-speed,
#nav-overlay:not(.hidden) > #nav-elev {
  position: relative;
  top: auto; right: auto; bottom: auto; left: auto;
  transform: none;
  margin: 0;
  min-width: 0;
  max-width: 100%;
  box-sizing: border-box;
  z-index: 10;
}
/* Narrow (a phone upright). Row 1: close, clock, ETA. Row 2: the turn banner
   on its own full-width row, so a road name has the whole screen width.
   Speed spans the first two columns and the right-hand stack the last, so
   neither the 172px speed cluster nor the pills can size the top row's columns
   (a grid item that spans the 1fr track does not grow the auto tracks). */
#nav-overlay:not(.hidden) > #nav-close { grid-column: 1; grid-row: 1; align-self: start; margin: 0 0 10px 0; }
/* The clock takes column 1, which the X left empty when it moved into the dock.
   It used to sit in the flexible 1fr track, and the grid reset above gives every
   child min-width: 0 — so a longer time string was squeezed narrower than its own
   text and the time drew OUTSIDE its pill, over the map (Josh, 2026-09-18: "the
   time is outside the pill"; measured on the S23, a 75px pill holding an 85px
   "04:26 PM"). A 24-hour "16:26" fitted, which is why it was never seen here.
   min-width: max-content is the guard, and it is the WHOLE fix: the track is
   1fr, which is minmax(auto, 1fr), so an item that refuses to go below its own
   text makes the track at least that wide. Moving the clock to another column
   instead was tried and reverted — it stole width from the auto tracks and
   pushed the dock off the bottom of a 360x640 screen. */
#nav-overlay:not(.hidden) > #nav-clock { grid-column: 1; grid-row: 1; justify-self: start; align-self: start; margin: 0 0 10px 0; white-space: nowrap; min-width: max-content; }
/* The ETA chip's three lines each stay on one line. Left to wrap, a long one
   ("arrive 11:42 PM") folded onto a fourth line, and the top row grew enough to
   push the dock off the bottom of a 360x640 screen. */
#nav-overlay:not(.hidden) > #nav-eta > span { white-space: nowrap; }
/* Same for the two corner readouts. Wrapped, the elevation chip went from 41px
   to 68px tall and took the dock off the bottom with it. A readout that folds
   is unreadable at a glance anyway. */
#nav-overlay:not(.hidden) > #nav-elev,
#nav-overlay:not(.hidden) > #nav-speed { white-space: nowrap; }
/* A CONTROL IS NEVER SMALLER THAN ITSELF.
   The grid reset above gives every child min-width: 0 so a long road name in the
   turn banner cannot widen the whole row. That is right for the banner and wrong
   for anything a rider presses: in landscape the 1fr track squeezed the zoom
   stepper to ELEVEN PIXELS on the S23 (measured, 2026-09-18) — present, and
   impossible to hit. The same squeeze had already put the clock's time outside
   its pill. Both controls now refuse to go below their own content. */
#nav-overlay:not(.hidden) > #nav-zoom,
#nav-overlay:not(.hidden) > #nav-dock { min-width: max-content; }
#nav-overlay:not(.hidden) > #nav-eta { grid-column: 3; grid-row: 1; justify-self: end; margin: 0 0 10px 0; }
/* The heading takes the top row's middle cell, which held nothing: its own
   cell, so it can never sit over the clock or the ETA (one grid, one readout
   per cell). min-width: max-content for the same reason as the clock. */
#nav-overlay:not(.hidden) > #nav-compass { grid-column: 2; grid-row: 1; justify-self: center; align-self: start; margin: 0 0 10px 0; white-space: nowrap; min-width: max-content; }
/* Upright, a 2x2 with the clock and compass (Josh, 2026-10-18): temperature
   under the clock, wind under the compass. The wrapper steps out of the way
   (display: contents) so each pill is a grid item in its own column, one
   pill height (32px) plus the 10px gap below the top row, so neither can sit on the
   clock or the compass. */
#nav-overlay:not(.hidden) > #nav-wx { display: contents; }
#nav-overlay:not(.hidden) > #nav-wx > #nav-temp { grid-column: 1; grid-row: 1; justify-self: start; align-self: start; margin: 42px 0 10px 0; }
#nav-overlay:not(.hidden) > #nav-wx > #nav-wind { grid-column: 2; grid-row: 1; justify-self: center; align-self: start; margin: 42px 0 10px 0; min-width: max-content; }
/* The clock keeps its corner with or without a route: column 1 is free at every
   width now that the X lives in the dock. */
#nav-overlay:not(.hidden) > #nav-turn { grid-column: 1 / -1; grid-row: 2; justify-self: center; align-self: start; margin: 0 0 10px 0; }
/* Alerts stack under the turn banner, centred, one per row. */
#nav-overlay:not(.hidden) > #nav-battery-suggest { grid-column: 1 / -1; grid-row: 3; justify-self: center; margin: 0 0 10px 0; }
#nav-overlay:not(.hidden) > #nav-camera { grid-column: 1 / -1; grid-row: 4; justify-self: center; margin: 0 0 10px 0; }
#nav-overlay:not(.hidden) > #nav-poi { grid-column: 1 / -1; grid-row: 5; justify-self: center; margin: 0 0 10px 0; }
#nav-overlay:not(.hidden) > #nav-offline-note { grid-column: 1 / -1; grid-row: 6; justify-self: center; margin: 0 0 10px 0; white-space: normal; text-align: center; }
/* Weak GPS sits just above the speed it spoils, beside the elevation chip:
   row 10's first two cells hold nothing else, so it adds no row. */
#nav-overlay:not(.hidden) > #nav-gps-note { grid-column: 1 / 3; grid-row: 10; justify-self: start; align-self: end; margin: 0 0 10px 0; white-space: nowrap; }
/* Wide (a phone on its side) has no height to spare: the top row's first cell,
   left of the turn banner, is free there. */
@media (min-width: 600px), (orientation: landscape) and (max-height: 500px) {
  #nav-overlay:not(.hidden) > #nav-gps-note { grid-column: 1; grid-row: 1; align-self: start; }
}
#nav-overlay:not(.hidden) > #nav-radar-note { grid-column: 1 / -1; grid-row: 7; justify-self: center; margin: 0 0 10px 0; white-space: normal; text-align: center; }
/* Bottom, in three bands rather than two ragged columns:
   row 10  the zoom stepper, floating right, clear of everything
   row 11  speed left, elevation right — the two readouts, on one line
   row 12  the dock, full width
   This is why the dock is the shape it is: it owns a whole row, so nothing can
   ever be stacked beside it and no future control needs a row of its own. */
#nav-overlay:not(.hidden) > #nav-elev { grid-column: 3; grid-row: 10; justify-self: end; align-self: end; margin: 0 0 10px 0; }
/* The zoom stepper shares the speed's row rather than taking one of its own:
   at 360x640 with every alert lit there is no row to spare, and the control
   belongs nearer the thumb than the elevation readout does anyway. */
#nav-overlay:not(.hidden) > #nav-speed { grid-column: 1; grid-row: 11; justify-self: start; align-self: end; margin: 0 0 10px 0; }
#nav-overlay:not(.hidden) > #nav-zoom { grid-column: 3; grid-row: 11; justify-self: end; align-self: end; margin: 0 0 10px 0; }
#nav-overlay:not(.hidden) > #nav-dock { grid-column: 1 / -1; grid-row: 12; justify-self: stretch; align-self: end; margin: 0; }
#nav-turn-dist { white-space: nowrap; }

/* Wide enough (a phone on its side, a tablet, a desktop): the turn banner sits
   between the close button and the clock on the top row, the three pills in a
   row along the bottom edge between the speed and the elevation. The clock
   shares its columns with the 2D/3D pill, which is on another row, so a
   narrow 1fr track cannot wrap it. Height is what a phone on its side lacks
   (354px on an S23), and this shape spends 66px on the top row, not 137. */
@media (min-width: 600px), (orientation: landscape) and (max-height: 500px) {
  #nav-overlay:not(.hidden) { grid-template-columns: auto 1fr auto auto auto 1fr auto; }
  #nav-overlay:not(.hidden) > #nav-turn { grid-column: 2 / 5; grid-row: 1; }
  /* On its side the top row is full (at 640 px wide a column for the heading
     pushed the ETA chip off the screen), so the heading takes the one free
     cell: column 1, left of the turn banner. The weak-GPS note owns that cell
     while it shows, and a fix too weak to trust has no heading worth printing,
     so the heading steps aside for it (the note comes first in the markup). */
  #nav-overlay:not(.hidden) > #nav-compass { grid-column: 1; grid-row: 1; justify-self: start; align-self: start; margin: 0 0 10px 0; }
  #nav-overlay:not(.hidden) > #nav-gps-note:not(.hidden) ~ #nav-compass { display: none; }
  /* On its side there is no height to spare: under the compass they pushed
     the dock off a 640x360 screen with every alert lit, and the bottom row's
     gap beside the dock is too narrow at 640. They sit above the speed, in
     the elevation's row, which is empty on the left: no height of their
     own. Spanning into the 1fr columns keeps them from widening column 1
     and squeezing the turn banner. */
  #nav-overlay:not(.hidden) > #nav-wx { display: flex; grid-column: 1 / 7; grid-row: 11; justify-self: start; align-self: end; margin: 0 0 10px 0; flex-wrap: nowrap; }
  #nav-overlay:not(.hidden) > #nav-wx > .nav-wx-pill { margin: 0; }
  /* The speed alert owns that row while it shows; safety before weather. */
  #nav-overlay:not(.hidden) > #nav-speed-alert:not(.hidden) ~ #nav-wx { display: none; }
  #nav-overlay:not(.hidden) > #nav-clock { grid-column: 5 / 7; grid-row: 1; margin: 0 0 10px 0; }
  #nav-overlay:not(.hidden) > #nav-eta { grid-column: 7; }
  #nav-overlay:not(.hidden) > #nav-eta.hidden ~ #nav-clock { grid-column: 5 / 8; }
  /* On its side there is no room for a band of its own: a 640x360 screen with
     every alert lit pushed a full-width dock clean off the bottom (measured by
     scripts/qa/nav-overlap-test.js, 2026-09-18). So the whole bottom edge is
     ONE row — speed, zoom, dock, elevation — and the dock takes the middle,
     where the thumb is, in its short icon-beside-word form. It is still one
     bar with four cells; it just is not the full width here. */
  #nav-overlay:not(.hidden) > #nav-speed { grid-column: 1; grid-row: 12; }
  #nav-overlay:not(.hidden) > #nav-zoom { grid-column: 2; grid-row: 12; justify-self: start; align-self: end; margin: 0 0 0 10px; }
  #nav-overlay:not(.hidden) > #nav-dock { grid-column: 4 / 6; grid-row: 12; justify-self: center; align-self: end; margin: 0; }
  #nav-overlay:not(.hidden) > #nav-elev { grid-column: 7; grid-row: 12; }
}

/* Short (a phone on its side): the two heads-up pills share a row, worth-a-stop
   on the left, camera on the right, so four stacked alerts cannot push the
   speed off the bottom of a 354px screen. Pills lose a little padding. */
@media (min-width: 600px) and (max-height: 500px), (orientation: landscape) and (max-height: 500px) {
  #nav-overlay:not(.hidden) > #nav-poi { grid-column: 1 / 5; grid-row: 4; justify-self: end; }
  #nav-overlay:not(.hidden) > #nav-camera { grid-column: 5 / 8; grid-row: 4; justify-self: start; }
  #nav-overlay:not(.hidden) #nav-zoom .mode-switch-btn { min-width: 44px; min-height: 44px; }
}

/* ===== Closure choice card (js/closure-choice-card.js) =====
 * A road on the route is shut and we know when it opens. Josh, 2026-09-19:
 * "a very loud warning on the main map ... the user has to say ok, or be
 * given the option to route around it." Over the map, map dimmed, no way out
 * but a decision. */
.closure-choice-backdrop {
  position: fixed;
  inset: 0;
  z-index: 100004;                 /* above every map control and panel */
  background: rgba(0, 0, 0, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;                   /* the phone gutter; never a horizontal scroll */
  overscroll-behavior: contain;
}
.closure-choice-card {
  background: #1b1b1d;
  color: #fff;
  border: 3px solid #ff1744;
  border-radius: 12px;
  max-width: 420px;
  width: 100%;
  max-height: calc(100vh - 32px);
  overflow-y: auto;
  padding: 18px 18px 16px;
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.6);
}
.closure-choice-flag {
  background: #ff1744;
  color: #fff;
  font-weight: 800;
  font-size: max(13px, var(--text-floor, 0px));
  letter-spacing: 0.06em;
  text-align: center;
  padding: 7px 10px;
  border-radius: 5px;
  margin: -4px -4px 12px;
}
.closure-choice-name { font-size: 21px; line-height: 1.25; margin: 0 0 8px; font-weight: 700; }
.closure-choice-why { font-size: 14px; line-height: 1.45; margin: 0 0 8px; color: #dcdcdf; }
.closure-choice-opens {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px;
  background: #24240f;
  border-left: 4px solid #ffc107;
  border-radius: 5px;
  padding: 10px 12px;
  margin: 12px 0 10px;
}
.closure-choice-opens-label { font-size: max(12px, var(--text-floor, 0px)); text-transform: uppercase; letter-spacing: 0.07em; color: #ffc107; font-weight: 700; }
.closure-choice-opens-date { font-size: 20px; font-weight: 800; color: #fff; }
.closure-choice-opens-away { font-size: 14px; color: #c9c9a8; }
.closure-choice-src { font-size: max(12px, var(--text-floor, 0px)); color: #9c9ca2; margin: 0 0 4px; }
.closure-choice-src a { color: #7fb5ff; }
.closure-choice-buttons { display: flex; gap: 10px; margin: 14px 0 10px; }
.closure-choice-buttons button {
  flex: 1;
  min-height: 48px;                /* a gloved thumb in a moving vehicle */
  border-radius: 8px;
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
  border: 2px solid transparent;
  padding: 10px 8px;
}
.closure-choice-avoid { background: #2e7d32; color: #fff; border-color: #2e7d32; }
/* An OUTLINED button, not a link. On the S23 and on desktop this rendered as
 * bare red text beside a solid green button, which quietly made "take it" look
 * like the lesser option rather than the other half of a real choice
 * (measured on the phone 2026-09-19). */
.closure-choice-take {
  background: rgba(255, 23, 68, 0.10);
  color: #ff8a80;
  border: 2px solid #ff1744;
}
/* "I'm going on or after {day}" (2026-10-21): its own full-width row under
 * the two, because the day makes it the longest label and it is a different
 * kind of answer (when, not which way). Blue, so it reads as neither "safe"
 * green nor "risky" red. */
.closure-choice-buttons { flex-wrap: wrap; }
/* Two classes deep, so it outranks `.closure-choice-buttons button { flex: 1 }`
 * above; one class lost to it and squeezed the label into a third column. */
.closure-choice-buttons .closure-choice-wait {
  flex: 1 1 100%;
  background: rgba(127, 181, 255, 0.10);
  color: #cfe2ff;
  border: 2px solid #7fb5ff;
}
.closure-choice-foot { font-size: max(12px, var(--text-floor, 0px)); color: #8e8e94; margin: 0; line-height: 1.4; }

@media (max-width: 380px) {
  .closure-choice-buttons { flex-direction: column; }
  .closure-choice-name { font-size: 19px; }
}

/* The trip date on the Route panel (js/trip-date.js, 2026-09-23): the day the
   closures are judged for. Same box as the AI panel's date. */
.route-date { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 10px 0 2px; }
.route-date label { font-size: 0.88rem; font-weight: 600; color: var(--text-primary); white-space: nowrap; }
.route-date input[type="date"] {
  flex: 1; min-width: 150px; min-height: 40px; padding: 0 10px;
  background: var(--bg-tertiary); border: 1px solid transparent; border-radius: var(--radius);
  color: var(--text-primary); font-family: inherit; font-size: 0.95rem;
}
.route-date input[type="date"]:focus { border-color: var(--accent); outline: none; }
.route-date-note { flex-basis: 100%; font-size: 0.82rem; line-height: 1.4; color: var(--warning, #ffab40); }
.route-date-note[hidden] { display: none; }

/* Fuel prices on the fuel station card (js/fuel-prices.js). Grey = last known
   prices, not confirmed recently; the footer line says how old. */
.fuel-prices { margin: 10px 0 4px; padding: 8px 10px; border: 1px solid var(--border, rgba(255,255,255,0.12)); border-radius: 8px; }
.fuel-prices .fp-title { font-size: max(0.8em, var(--text-floor, 0px)); font-weight: 600; margin-bottom: 4px; }
.fuel-prices .fp-table { width: 100%; border-collapse: collapse; font-size: max(0.85em, var(--text-floor, 0px)); }
.fuel-prices .fp-table td, .fuel-prices .fp-table th { padding: 2px 0; text-align: left; }
.fuel-prices .fp-table th { font-weight: 500; font-size: max(0.85em, var(--text-floor, 0px)); color: var(--text-muted); }
.fuel-prices .fp-price, .fuel-prices .fp-tm, .fuel-prices .fp-table th:not(:first-child) { text-align: right; font-variant-numeric: tabular-nums; }
.fuel-prices .fp-price { font-weight: 600; }
.fuel-prices .fp-tm { color: var(--text-muted); padding-left: 10px; }
.fuel-prices .fp-old { font-size: max(0.8em, var(--text-floor, 0px)); color: var(--text-muted); }
.fuel-prices .fp-foot { font-size: max(0.72em, var(--text-floor, 0px)); color: var(--text-muted); margin-top: 6px; line-height: 1.4; }
.fuel-prices .fp-foot a { color: var(--accent); }
.fuel-prices.fp-stale .fp-table { opacity: 0.55; }

/* ===== A place's own website and phone, first on its card (js/place-contact.js) =====
   Josh, 2026-10-02: the direct link is the one to press, so it is larger than
   the other links and filled; Booking.com and the rest stay small below it. */
.place-direct { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 12px; }
.place-direct a {
  display: flex; align-items: center; gap: 8px; flex: 1 1 190px; min-width: 0;
  min-height: 44px; padding: 8px 12px; border-radius: var(--radius-sm);
  font-size: 0.95rem; font-weight: 600; text-decoration: none;
}
.place-direct-site { background: var(--accent); color: #000; border: 1px solid var(--accent); }
.place-direct-site:hover { background: var(--accent-hover); }
.place-direct-call { color: var(--accent); border: 1px solid var(--accent); background: transparent; }
.place-direct-label { flex-shrink: 0; }
.place-direct-detail { font-size: 0.8rem; font-weight: 400; opacity: 0.85; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ===== Speed limit sign: its own cell, dead centre at the bottom (Josh, 2026-10-03)
   The sign sat in the speed box, to the right of the number, so it slid
   sideways every time the speed went from one digit to two to three. And the
   grid reset gives every child min-width: 0, which held the speed box at its
   one-digit width: on an S23 "108" drew 34px past the right edge of its box.
   Now the sign is a grid item of its own, centred on the screen in the speed's
   row with its bottom on the speed box's bottom. The speed box is one size
   whatever the digits (room for three, the unit under the number), so it can
   neither spill nor reach the sign: on a 384px S23 the box ends at 135px and
   the sign starts at 166px.
   Appended at the end on purpose: in AUS this file is several copies of itself
   and the last rule is the one the browser obeys. */
#nav-overlay:not(.hidden) > #nav-speed { min-width: max-content; grid-column: 1 / 3; }
/* Digits have no descenders, so the number's line box is cut to 0.85 of the
   font: the same size digits, 7px less height, which a 360x640 phone with every
   alert lit needs. */
#nav-speed { padding: 4px 10px; }
.nav-speed-read { flex-direction: column; align-items: center; gap: 2px; }
#nav-speed-value { font-size: 3.2rem; line-height: 0.85; min-width: 1.95em; text-align: center; }
#nav-speed-unit { font-size: max(0.75rem, var(--text-floor, 0px)); line-height: 1; }
/* One sign size at every screen size, with digits sized so a three-digit limit
   ("100") sits inside the ring's opening instead of on the red. */
#nav-speed-limit { width: 52px; height: 52px; border-width: 5px; }
#nav-speed-limit-value { font-size: 0.95rem; letter-spacing: -0.5px; }
#nav-overlay:not(.hidden) > #nav-speed-limit {
  position: relative;
  top: auto; right: auto; bottom: auto; left: auto;
  z-index: 10;
  grid-column: 1 / -1;
  grid-row: 11;
  justify-self: center;
  align-self: end;
  margin: 0 0 10px 0;
}
@media (max-width: 480px), (max-height: 500px) {
  #nav-speed-value { font-size: 2.4rem; }
}
/* On its side the bottom edge is one row: speed, sign, dock, zoom, all on one
   bottom line. The elevation readout moves up a row on the right to make the
   width: it is 5px shorter than the zoom stepper, and EU's longer off-route
   note left a 640x360 screen exactly that short of height. */
@media (min-width: 600px), (orientation: landscape) and (max-height: 500px) {
  #nav-overlay:not(.hidden) > #nav-speed { grid-column: 1; margin: 0; }
  #nav-overlay:not(.hidden) > #nav-speed-limit { grid-column: 2 / 4; grid-row: 12; margin: 0; }
  #nav-overlay:not(.hidden) > #nav-zoom { grid-column: 7; grid-row: 12; justify-self: end; margin: 0; }
  #nav-overlay:not(.hidden) > #nav-elev { grid-column: 7; grid-row: 11; justify-self: end; margin: 0 0 10px 0; }
}
/* That row costs a phone on its side height, so the off-route and radar notes
   share one row there, as worth-a-stop and camera already do: AUS at 640x360
   with every alert lit ran 34px off the bottom otherwise. */
@media (min-width: 600px) and (max-height: 500px), (orientation: landscape) and (max-height: 500px) {
  #nav-overlay:not(.hidden) > #nav-offline-note { grid-column: 1 / 5; grid-row: 6; justify-self: end; white-space: nowrap; }
  #nav-overlay:not(.hidden) > #nav-radar-note { grid-column: 5 / 8; grid-row: 6; justify-self: start; white-space: nowrap; }
}

/* ===== A phone on its side gets the landscape nav (Josh, 2026-10-03)
   Every nav rule above that says "(min-width: 600px)" also says "or a phone on
   its side": js/phone-layout.js answers width terms against a handset's SHORT
   side (384 on an S23) so the app keeps its phone layout when turned, and that
   killed the whole landscape nav. On the S23 on its side the upright layout
   ran the speed, sign and dock off the bottom of a 384px-tall screen. The
   orientation term has no width in it, so the rewrite leaves it alone. */

/* ===== Speedo top bar: each reading at its own size, clear of the X (2026-10-03)
   Readings were allowed to shrink below their own contents, so once the clock
   joined the bar the squeezed wind reading drew its arrow under the close
   button on a phone on its side (S23: arrow at 770-790px, X from 777px). Now a
   reading never shrinks, and a phone on its side buys the width back: the
   left padding (60px was only ever needed on the right, for the X), a little
   less gap, and one step smaller numbers and units. Checked at the widest real
   readings ("2228 m", "NW 315°") by scripts/qa/dash-safe-area-test.js. */
.dash-alt-reading { flex-shrink: 0; }
@media (orientation: landscape) and (max-height: 500px) {
  .dash-top-bar { padding-left: 16px; gap: 12px; }
  .dash-alt-value, .dash-alt-reading #dash-heading-value { font-size: 1.1rem; }
  .dash-alt-unit { font-size: max(0.75rem, var(--text-floor, 0px)); }
}

/* ===== Speed alert (js/speed-alert.js, 2026-10-07)
   One quiet notice when the speed is well over a posted limit, gone after
   5 s. It may not cost a row: at 360x640 with every alert lit the screen has
   7px to spare (a row of its own pushed the dock 36px off the bottom,
   scripts/qa/nav-overlap-test.js). So it takes a cell that is already there.
   Upright: the Weak GPS note's cell, just above the speed, left of the
   elevation chip. The two never want it at once: a weak fix never alerts
   (its speed is as vague as its place), and should the note come up while
   the notice shows, the note wins. On its side: row 11 over the sign, beside
   the elevation chip (the weak-GPS note lives in the top row there).
   Discreet on purpose: the dash's own dark glass, a thin amber edge, no red,
   no flashing, and taps go through it to the map. */
#nav-speed-alert {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px 6px 10px;
  background: rgba(10,14,20,0.8);
  border: 1px solid rgba(255,255,255,0.12);
  border-left: 3px solid #ffb74d;
  border-radius: 12px;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: #fff;
  font-size: 0.9rem;
  font-weight: 700;
  line-height: 1.2;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}
#nav-speed-alert i { color: #ffb74d; font-size: 0.95rem; }
/* Fades in, holds, fades out inside the 5 s; the script hides it at 5 s. */
#nav-speed-alert.sa-on { animation: nav-speed-alert-life 5s ease-in-out both; }
@keyframes nav-speed-alert-life {
  0% { opacity: 0; }
  6% { opacity: 1; }
  88% { opacity: 1; }
  100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  #nav-speed-alert.sa-on { animation: none; }
}
#nav-overlay:not(.hidden) > #nav-speed-alert {
  position: relative;
  top: auto; right: auto; bottom: auto; left: auto;
  z-index: 10;
  min-width: 0;
  max-width: 100%;
  box-sizing: border-box;
  grid-column: 1 / 3;
  grid-row: 10;
  justify-self: start;
  align-self: end;
  margin: 0 0 10px 0;
}
#nav-speed-alert-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
#nav-overlay:not(.hidden) > #nav-gps-note:not(.hidden) ~ #nav-speed-alert { display: none; }
@media (min-width: 600px), (orientation: landscape) and (max-height: 500px) {
  #nav-overlay:not(.hidden) > #nav-speed-alert { grid-column: 2 / 4; grid-row: 11; justify-self: center; }
}

/* ===== Sign-in value screen + trial day-6 card (js/value-first.js) ===== */
/* While the switch answers (at most 1.5 s), a first-time visitor's card stays
   hidden so it does not flash before the value screen. */
#login-screen.vf-wait .login-card { visibility: hidden; }
.vf-card { align-items: stretch; padding: 22px 20px; gap: 10px; }
.vf-logo { width: 48px; height: 48px; border-radius: 12px; align-self: center; }
.vf-title { margin: 0 0 4px; font-size: 1.2rem; line-height: 1.3; text-align: center; color: var(--text-primary, #fff); }
.vf-row { display: flex; align-items: center; gap: 12px; }
.vf-row img { width: 112px; height: 82px; flex-shrink: 0; object-fit: cover; border-radius: 8px; border: 1px solid var(--border, rgba(255,255,255,0.12)); background: var(--bg-tertiary, #222); }
.vf-row p { margin: 0; font-size: 0.86rem; line-height: 1.4; color: var(--text-primary, #fff); }
.vf-terms { margin: 4px 0 2px; font-size: max(0.76rem, var(--text-floor, 0px)); line-height: 1.45; color: var(--text-muted, #bbb); text-align: center; }
.vf-card .login-btn { margin-bottom: 0; }
.vf-link { background: none; border: none; color: var(--text-muted, #999); font-size: 0.8rem; text-decoration: underline; }

/* ===== Start on a searched place (js/search-go.js) =====
   Sits in the planned-route Start's own slot, with its look, and that Start
   steps aside while this one shows (body.search-go-on). Same positions as
   #route-start-btn: 154px on a desktop, 44px on a phone, 214px under 381px.
   Appended at the end: the AUS stylesheet is concatenated copies of itself,
   so only the end of the file is last in every region. */
#search-go-btn {
  position: fixed;
  bottom: 154px;
  left: calc(50% + var(--sidebar-width) / 2);
  transform: translateX(-50%);
  z-index: 600;
  min-height: 56px;
  padding: 0 38px;
  border: none;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font-size: 1.15rem;
  font-weight: 800;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  cursor: pointer;
  display: flex; align-items: center; gap: 12px;
  white-space: nowrap;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.5), 0 0 0 4px rgba(0, 200, 83, 0.18);
  transition: background var(--transition), box-shadow var(--transition), transform var(--transition);
}
#search-go-btn:hover { background: var(--accent-hover); }
#search-go-btn:active { transform: translateX(-50%) scale(0.97); }
#search-go-btn i { font-size: 1.05rem; }
#search-go-btn.hidden { display: none; }
body.search-go-on #route-start-btn,
body.dashboard-active #search-go-btn,
body.nav-active #search-go-btn { display: none !important; }
@media (max-width: 768px) {
  #search-go-btn { left: 50%; bottom: 44px; min-height: 54px; padding: 0 26px; font-size: 1.05rem; }
}
@media (max-width: 380px) {
  #search-go-btn { bottom: 214px; }
}

/* ===== Save, Email me, Share on the map (js/route-start.js, 2026-10-21) =====
   Under the route tiles (#route-hud), same left edge and the same right bound
   (the button column), so the pair reads as one block about this route. The
   top is the tiles' MEASURED bottom edge (--route-actions-top, set by
   RouteStart.placeActions); the calc is only the fallback for one row of tiles.
   Appended at the end: the AUS stylesheet is concatenated copies of itself, so
   only the end of the file is last in every region. */
#route-actions {
  position: fixed;
  top: var(--route-actions-top, 64px);
  left: calc(var(--sidebar-width, 0px) + 16px);
  right: calc(16px + var(--map-controls-w, 116px) + 8px);
  width: fit-content;
  z-index: 500;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
#route-actions.hidden { display: none !important; }
#route-actions button {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 38px;
  padding: 0 12px;
  background: var(--bg-secondary, #1c1f24);
  color: var(--text-primary, #fff);
  border: 1px solid var(--bg-tertiary, #2a2f36);
  border-radius: 999px;
  box-shadow: var(--shadow, 0 2px 10px rgba(0, 0, 0, 0.4));
  font-size: 0.8rem; font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
}
#route-actions button[hidden] { display: none; }
#route-actions button i { color: var(--accent, #00c853); font-size: 0.85rem; }
#route-actions button:hover { border-color: var(--accent, #00c853); }
#route-actions button:disabled { cursor: default; opacity: 0.85; }
#route-actions button.is-saved { border-color: var(--accent, #00c853); }
body.dashboard-active #route-actions,
body.nav-active #route-actions { display: none !important; }
@media (min-width: 769px) {
  body.sidebar-collapsed #route-actions { left: 62px; }
}
@media (max-width: 768px) {
  /* Under the phone's tiles (header + 8, 45 tall) with 6px of air. 44px tall:
     the map's own tap target on a phone. */
  #route-actions {
    top: var(--route-actions-top, calc(var(--mobile-header-height, 56px) + 59px));
    left: 10px;
    right: calc(12px + var(--map-controls-w, 116px) + 8px);
    gap: 5px;
  }
  /* Icon over word, so all three fit beside the button column on a 360px
     phone (side by side they wrapped to two rows at 390, measured). */
  #route-actions button {
    flex-direction: column; justify-content: center; gap: 2px;
    min-height: 44px; min-width: 52px; padding: 3px 10px;
    border-radius: var(--radius-sm, 6px);
    font-size: max(0.64rem, var(--text-floor, 0px)); line-height: 1.1;
  }
  #route-actions button i { font-size: 0.9rem; }
}
/* What sat under the tiles steps under the group too: the 3D compass and the
   phone's elevation chip. */
body.route-actions-on .maplibregl-ctrl-top-left { margin-top: calc(var(--route-actions-bottom, 108px) + 8px); }
body.route-actions-on #map-readouts { top: calc(var(--route-actions-bottom, 108px) + 8px); }
@media (max-width: 768px) {
  /* The compass rule for this row is in the phone chrome block below, after
     the route-hud rule it must beat (same weight, so the later one wins). */
  body.route-actions-on #map-readouts { top: calc(var(--route-actions-bottom, 163px) + 8px); }
  /* While the route card is up the tiles and the row step aside: the chip
     moves up with the compass (phone-ux-int review, 1 Nov). */
  body.route-card-open #map-readouts { top: calc(var(--mobile-header-height) + 12px); }
}

/* ===== The phone's map chrome (phone redesign steps 3 and 4) =====
   Josh, 2026-11-01 (tasks/phone-ux.md, answers 1 and 2): on a phone the map at
   rest carries a floating search bar (Menu inside it, and "Rain x" while the
   rain radar is on), Layers, Rig, Nav and Locate down the right edge, and
   Topo/Sat and 2D/3D at the bottom left. Nine controls, where the S10 counted
   nineteen. Dash, Reset view and Full screen moved to the menu's Map tools;
   the tilt and zoom pad is off until the rider switches it on there. Wired in
   js/map-chrome.js. Appended at the end so it is last in every region. The
   desktop keeps its layout: none of this shows there. */
#mobile-search, #map-tools, #btn-layers, #layers-sheet, #btn-compass { display: none; }
#rain-chip[hidden] { display: none !important; }
#mobile-search-x[hidden] { display: none !important; }

@media (max-width: 768px) {
  /* The space the bar takes: 10 above, 48 for the bar, 10 below. Every phone
     rule that cleared the old 56 px header clears this instead. */
  :root { --mobile-header-height: calc(68px + env(safe-area-inset-top, 0px)); }
  /* The map runs full-bleed under the status bar (viewport-fit=cover, and the
     home-screen app's black-translucent status bar): a dark strip the height
     of the notch keeps the clock and battery readable over a light Topo map.
     0 px tall where there is no inset (Android, a Chrome tab). */
  body::before {
    content: ''; position: fixed; z-index: 1000; top: 0; left: 0; right: 0;
    height: env(safe-area-inset-top, 0px); pointer-events: none;
    background: linear-gradient(rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0.35));
  }
  body.nav-active::before, body.dashboard-active::before { display: none; }
  /* Facebook's in-app browser bar (js/inapp.js) is fixed at the very top and
     already pads for the status bar: the floating bar goes under it. */
  body.inapp-shifted { --mobile-header-height: calc(68px + var(--inapp-bar-h, 64px)); }

  #mobile-search {
    display: flex; align-items: center; gap: 2px;
    position: fixed; z-index: 1001;
    top: calc(env(safe-area-inset-top, 0px) + 10px); left: 12px; right: 12px;
    height: 48px; padding: 0 6px 0 0;
    background: rgba(26, 38, 50, 0.96);
    border: 1px solid var(--bg-tertiary); border-radius: 24px;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
  }
  body.inapp-shifted #mobile-search { top: calc(var(--inapp-bar-h, 64px) + 10px); }
  /* The Menu / Map word under the hamburger keeps inside the bar's round left
     end (Josh, S10: "the 'menu' txt slightly breaches the top bar, move it a
     few pixels to the right so it fits properly"). At 14 px the word's box
     reached 3 px past the curve; 8 px in puts every corner of it 2 px or more
     inside (tests/map-chrome.test.js works it out from the bar's numbers). */
  #mobile-search > #sidebar-toggle { margin-left: 10px; }
  #mobile-search-box {
    flex: 1 1 auto; min-width: 0; height: 48px;
    display: flex; align-items: center; gap: 10px; padding: 0 8px;
    background: none; border: none; border-radius: 22px;
    color: var(--text-secondary); font: inherit; font-size: 0.85rem; text-align: left;
    cursor: text;
  }
  #mobile-search-box span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* A searched place on the bar (Josh, 2026-11-01, S10: "how do I clear a
     search result"): its name in the text colour, one line, and an X before
     the Rain chip that clears the search in one tap (js/map-chrome.js
     showPlace). 48 px to touch, the bar's full height. */
  #mobile-search-box.has-place { color: var(--text-primary); padding-left: 14px; }
  /* The name has the room: no magnifier beside a place, as Google Maps. */
  #mobile-search-box.has-place i { display: none; }
  #mobile-search-x {
    flex: none; width: 48px; height: 48px; padding: 0;
    display: flex; align-items: center; justify-content: center;
    background: none; border: none; border-radius: 24px;
    color: var(--text-secondary); font-size: 1.1rem;
    cursor: pointer; touch-action: manipulation;
  }
  /* The Rain chip: a 32 px pill inside a 48 px tap target, shown both ways.
     Green "Rain x" while the radar is on (.is-on) turns it off; a quiet
     "Rain" with no x while it is off turns it back on in one tap (Josh
     2026-09-16: rain radar on / off from the map). js/map-chrome.js. */
  #rain-chip {
    flex: none; height: 48px; padding: 0 4px 0 2px;
    display: inline-flex; align-items: center;
    border: none; background: none; color: inherit;
    font: inherit; cursor: pointer; touch-action: manipulation;
  }
  #rain-chip .rc-pill {
    display: inline-flex; align-items: center; gap: 5px;
    height: 32px; padding: 0 11px; border-radius: 16px;
    background: var(--bg-tertiary); color: var(--text-secondary);
    font-size: max(0.72rem, var(--text-floor, 0px)); font-weight: 700;
  }
  #rain-chip .rc-x { display: none; }
  #rain-chip.is-on .rc-pill { background: var(--accent); color: #00120a; }
  #rain-chip.is-on .rc-x { display: inline; }
  /* With the menu open the bar is only its Menu / Map button: the menu has the
     search box itself, and two boxes one above the other read as two searches. */
  body:has(#sidebar.open) #mobile-search { right: auto; padding-right: 0; }
  body:has(#sidebar.open) #mobile-search-box,
  body:has(#sidebar.open) #mobile-search-x,
  body:has(#sidebar.open) #rain-chip { display: none !important; }
  /* The guided first trip's sheet (js/first-trip.js, z-index 100010) is a
     surface too: the open menu (the bar's search box, the AI's plans) is
     never half covered by it. visibility, not display, so the guide can
     still measure its sheet to frame the map (_frame). */
  body:has(#sidebar.open) #first-trip { visibility: hidden; pointer-events: none; }

  /* Search on the map (Josh, 2026-11-01, S10: the bar opened the menu). A tap
     on the search line moves the menu's own box into the bar and its results
     into the panel under it (js/map-chrome.js). The Rain chip steps aside
     while the rider types and comes back after. Rows 48 px, text 18 px,
     nothing under 14 px. */
  #mobile-search.searching { border-color: var(--accent); }
  #mobile-search.searching #mobile-search-box,
  #mobile-search.searching #rain-chip { display: none !important; }
  #mobile-search.searching #mobile-search-x { display: none !important; }
  #mobile-search .search-box {
    flex: 1 1 auto; min-width: 0; height: 48px; gap: 4px;
    padding: 0 0 0 8px; background: none; border: none; border-radius: 22px;
  }
  #mobile-search .search-box i { font-size: 1rem; }
  #mobile-search .search-box input {
    min-width: 0; height: 48px; padding: 0 6px; font-size: 1rem;
    text-overflow: ellipsis;
  }
  #mobile-search .search-box input::placeholder { font-size: 0.85rem; }
  #mobile-search #search-clear {
    flex: none; width: 48px; height: 48px; padding: 0;
    display: flex; align-items: center; justify-content: center; font-size: 1.1rem;
  }
  #mobile-search #search-clear.hidden { display: none !important; }
  #map-search-panel {
    position: absolute; top: calc(100% + 6px); left: -1px; right: -1px;
    display: flex; flex-direction: column; gap: 6px;
    max-height: 50vh; overflow-y: auto; overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }
  #map-search-panel .search-results {
    margin-top: 0; max-height: none; overflow: visible; flex: none;
    background: rgba(26, 38, 50, 0.98);
    border: 1px solid var(--bg-tertiary); border-radius: 16px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
  }
  #map-search-panel .search-result-item {
    min-height: 48px; padding: 8px 16px; gap: 12px;
    font-size: 18px; line-height: 1.25;
  }
  #map-search-panel .search-result-item i { font-size: 16px; width: 20px; }
  #map-search-panel .search-result-item[data-dist]::after { font-size: 14px; }
  #map-search-panel .search-recent-head { padding: 12px 16px 4px; font-size: 14px; }
  #map-search-panel .search-recent-clear { min-height: 48px; padding: 0 16px; font-size: 14px; }
  /* Plan with AI's row (js/smart-search.js styles it inline, for the menu):
     solid over the map, and the sizes above. */
  #map-search-panel #smart-search-ai {
    flex: none; margin-top: 0 !important; min-height: 48px !important;
    padding: 8px 16px !important; font-size: 18px !important;
    background: #12382b !important; border-radius: 16px !important;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
  }
  #map-search-panel #smart-search-ai span span { font-size: 14px !important; }

  /* MapLibre's top-left corner (the 3D compass) clears the bar. While a route
     is up the route rules above set the same margin with more weight; with the
     map now starting at the top of the screen they no longer count the header
     twice. */
  /* ...and the slot under the elevation chip (#map-readouts, fixed at the
     same left edge just below the bar): --elev-slot is the chip's 32 px and
     8 px of air, less the control's own 10 px top margin. :where(#map) scopes
     it to the main map with no id weight. Order matters: the three rules
     below have the same weight, so the later wins. The route-actions row
     (tiles plus Save / Share / Clear) comes after the tiles alone, and the
     open route card (both step aside) last. The route-actions rule used to
     sit ABOVE the tiles rule and lost: the compass sat under the elevation
     chip whenever a route was up (phone-ux-s34 graft, phone-ux-int review). */
  :where(#map) .maplibregl-ctrl-top-left { --elev-slot: 30px; margin-top: calc(var(--mobile-header-height) + 12px + var(--elev-slot)); }
  body.route-hud-on :where(#map) .maplibregl-ctrl-top-left { margin-top: calc(var(--mobile-header-height) + 66px + var(--elev-slot)); }
  body.route-actions-on :where(#map) .maplibregl-ctrl-top-left { margin-top: calc(var(--route-actions-bottom, 163px) + 8px + var(--elev-slot, 30px)); }
  /* While the route card is up the tiles step aside: no room kept for them. */
  body.route-card-open.route-hud-on :where(#map) .maplibregl-ctrl-top-left,
  body.route-card-open.route-actions-on :where(#map) .maplibregl-ctrl-top-left { margin-top: calc(var(--mobile-header-height) + 12px + var(--elev-slot)); }
  /* Nav and the dash are full-screen views: no search bar over them (the old
     header had this rule; the overlays alone left MapChrome.topBar() saying
     it was on screen). */
  body.nav-active #mobile-search, body.dashboard-active #mobile-search { display: none !important; }

  /* The right edge: Layers, Rig, Nav, Locate. Round and 48 px, with no words
     (each keeps its title, which names it to a screen reader). Nav shows with
     no route too: there it opens "where to?" (js/nav-destination.js). */
  #btn-layers { display: flex; }
  #map-controls { gap: 10px; }
  #map-controls .map-btn {
    width: 48px; height: 48px; padding: 0;
    justify-content: center; border-radius: 50%; font-size: 1.05rem;
  }
  #map-controls .map-btn-label { display: none; }
  #btn-layers { order: 1; }
  /* The compass (js/map-chrome.js _wireCompass): first in the column, above
     Layers, only while the map is turned or tilted. 48 px round like the
     Topo/Sat and 3D buttons and the rest of the column.
     MapLibre's own compass (top left in 3D) is hidden on a phone. */
  #btn-compass.is-on { display: flex; order: 0; width: 48px; height: 48px; }
  #btn-compass .compass-needle { display: block; transition: transform 0.12s linear; }
  :where(#map) .maplibregl-ctrl-top-left .maplibregl-ctrl-compass,
  :where(#map) .maplibregl-ctrl-top-left .maplibregl-ctrl-group:has(> .maplibregl-ctrl-compass:only-child) { display: none; }
  #btn-vehicle { order: 2; }
  #btn-nav { order: 3; }
  #btn-locate { order: 4; }
  /* Off the map on a phone: Dash, Reset view and Full screen are in the menu's
     Map tools, the Rain chip stands in for Radar, and the route keeps its own
     Clear at the bottom of the map (#route-clear-btn). */
  #btn-radar, #btn-reset, #btn-fullscreen, #btn-clear-route { display: none !important; }
  /* Dash is back on the map, a fifth button on the right edge after Locate
     (Josh, 2026-11-03: "back on the map"; two taps into the menu is too deep
     for a speedo). It is in the menu's Map tools as well. */
  #btn-dash-moto { order: 5; }

  /* Topo/Sat, 2D/3D and the height slider, bottom left (Josh, 2026-11-01, S10:
     "the 3d height slider is too far up the page, it covers the map. re-work
     the sat/topo/2d/3d buttons and slider to be more elegant"). Two round
     48 px buttons in a column, 12 px from the edges plus the safe areas (the
     iPhone's home strip, a notch on a phone on its side). Topo/Sat on top is
     a picture of the OTHER map, Google Maps style: one tap goes there. 3D
     below it is lit while 3D is on. The height slider that slid out beside
     it is gone (Josh, 2026-11-01: "lock it at 1.15, remove the slider").
     One size and one origin place both, so they cannot land on each other.
     The route card's mini bar lifts them (body.route-mini, above); a card
     hides them (body.sheet-open). */
  :root {
    --mvc-size: 48px;
    --mvc-x: calc(12px + env(safe-area-inset-left, 0px));
    --mvc-y: calc(12px + env(safe-area-inset-bottom, 0px));
  }
  #basemap-switch, #mode-3d-switch { left: var(--mvc-x); }
  #mode-3d-switch { bottom: var(--mvc-y); }
  #basemap-switch { bottom: calc(var(--mvc-y) + var(--mvc-size) + 10px); }
  /* The right edge's round buttons, 4 px bigger. */
  #basemap-toggle, #mode-3d-toggle {
    position: relative; overflow: hidden;
    width: var(--mvc-size); height: var(--mvc-size); min-height: 0; padding: 0;
    justify-content: center; border-radius: 50%;
    background: var(--bg-secondary); border: 1px solid var(--bg-tertiary);
    color: var(--text-primary); box-shadow: var(--shadow);
  }
  #basemap-toggle > i, #basemap-toggle > span,
  #mode-3d-toggle > i, #mode-3d-toggle > span { display: none; }
  /* The picture alone, cropped round inside a thin white ring (no word on
     it: Josh, 2026-11-01, S10). Its aria-label names what a tap does. */
  #basemap-toggle { border: 2px solid rgba(255, 255, 255, 0.92); background: var(--bg-tertiary); }
  #basemap-toggle .map-toggle-pic {
    display: block; position: absolute; top: 0; left: 0;
    width: 100%; height: 100%; object-fit: cover; pointer-events: none;
  }
  #mode-3d-toggle::after { content: '3D'; font-size: max(15px, 0.8rem); font-weight: 800; letter-spacing: 0.5px; }
  #mode-3d-toggle[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: #00120a; }
  /* The tilt and zoom pad: off on a phone until the rider turns it on in Map
     tools (Josh, 2026-11-01, answer 2; js/map-chrome.js sets body.ptz-on). */
  #map-ptz { display: none; }
  body.ptz-on:not(.nav-active):not(.dashboard-active) #map-ptz { display: flex; }
  /* With the pad off nothing sits bottom-right but the scale ruler: it lines
     up flush with the button column's right edge (12 px) and is not slid by
     half its width, so at its widest (110 px) it stays on the screen. With the
     pad on it is centred under the pad again (tests/ruler-centred.test.js). */
  body:not(.ptz-on) .maplibregl-ctrl-bottom-right .maplibregl-ctrl-scale { margin: 0 12px 0 0 !important; transform: none; }

  /* Map tools in the menu: Dash, Reset view, Full screen and the tilt and zoom
     switch, one row, icon over word. */
  #map-tools { display: block; }
  .map-tools-row { display: flex; gap: 8px; }
  .map-tools-row button {
    flex: 1 1 0; min-width: 0; min-height: 60px;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
    padding: 6px 4px;
    background: var(--bg-tertiary); border: 1px solid transparent; border-radius: var(--radius);
    color: var(--text-primary); font: inherit; font-size: max(0.66rem, var(--text-floor, 0px)); font-weight: 600; line-height: 1.15;
    text-align: center; cursor: pointer;
  }
  .map-tools-row button i { font-size: 1rem; color: var(--accent); }
  #menu-ptz[aria-pressed="true"] { background: var(--accent); color: #00120a; }
  #menu-ptz[aria-pressed="true"] i { color: #00120a; }

  /* The layers card (phone redesign step 4): a sheet like #map-sheet (js/sheet.js
     sizes it through --sheet-h), opened by the Layers button. */
  #layers-sheet {
    display: block; position: fixed;
    top: auto; bottom: 0; left: 0; right: 0; width: 100%;
    height: var(--sheet-h, 0px);
    background: var(--bg-secondary); color: var(--text-primary);
    border-top: 1px solid var(--bg-tertiary); border-radius: 16px 16px 0 0;
    box-shadow: 0 -12px 32px rgba(0, 0, 0, 0.42);
    z-index: 600;
    padding-bottom: env(safe-area-inset-bottom, 0px);
    overflow: hidden; overscroll-behavior: contain; touch-action: pan-x;
    transition: height 0.22s ease, transform 0.22s ease;
  }
  #layers-sheet.sheet-full { overflow-y: auto; touch-action: auto; }
  #layers-sheet.sheet-dragging { transition: none; }
  @media (prefers-reduced-motion: reduce) { #layers-sheet { transition: none; } }
  #layers-sheet.hidden { transform: translateY(100%); pointer-events: none; }
  body.nav-active #layers-sheet, body.dashboard-active #layers-sheet { display: none !important; }
  #layers-sheet .sheet-close {
    position: sticky; top: 22px; float: right; z-index: 3;
    width: 48px; height: 48px; margin: 0 8px -48px 0; border-radius: 50%;
    border: none; cursor: pointer; background: var(--bg-tertiary); color: var(--text-secondary);
  }
  /* 26 px down from the handle's own 22 px: its 48 px tap area (.sheet-handle::after)
     covered the top of the Topo and Satellite pictures, and a tap there pulled the card. */
  #layers-sheet .sheet-content { padding: 28px 18px 14px; }
  /* The two pictures leave the X its corner. */
  #layers-sheet .lc-maps { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-right: 52px; }
  #layers-sheet .lc-map {
    position: relative; height: 64px; padding: 0; overflow: hidden; cursor: pointer;
    background: var(--bg-tertiary); border: 2px solid var(--bg-tertiary); border-radius: 12px;
  }
  #layers-sheet .lc-map img { display: block; width: 100%; height: 100%; object-fit: cover; }
  #layers-sheet .lc-map span {
    position: absolute; left: 8px; bottom: 5px;
    color: #fff; font-size: max(0.72rem, var(--text-floor, 0px)); font-weight: 700; text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
  }
  #layers-sheet .lc-map[aria-pressed="true"] { border-color: var(--accent); }
  #layers-sheet .lc-h {
    margin: 14px 0 8px; font-size: max(0.62rem, var(--text-floor, 0px)); font-weight: 700;
    letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-muted);
  }
  #layers-sheet .lc-chips { display: grid; grid-template-columns: repeat(auto-fit, minmax(64px, 1fr)); gap: 6px; }
  #layers-sheet .lc-chip {
    min-height: 56px; padding: 4px 2px;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
    background: var(--bg-primary); border: 1px solid var(--bg-tertiary); border-radius: 12px;
    color: var(--text-secondary); font: inherit; font-size: max(0.64rem, var(--text-floor, 0px)); font-weight: 700; cursor: pointer;
  }
  #layers-sheet .lc-chip i { font-size: 1rem; }
  #layers-sheet .lc-chip[aria-pressed="true"] { background: var(--accent-dim); border-color: var(--accent); color: var(--text-primary); }
  #layers-sheet .lc-chip[aria-pressed="true"] i { color: var(--accent); }
  /* Below the zoom its layers draw at (js/map-chrome.js chipBelow): dimmed,
     with "Zoom in" under the name, so a lit chip over an empty map is not
     read as a broken feed. */
  #layers-sheet .lc-chip.lc-zoom { opacity: 0.6; }
  #layers-sheet .lc-zoom-hint { font-size: max(0.56rem, var(--text-floor, 0px)); font-weight: 600; color: var(--text-muted); line-height: 1; }
  #layers-sheet .lc-more {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    width: 100%; min-height: 48px; margin-top: 10px; padding: 0 4px;
    background: none; border: none; color: var(--accent);
    font: inherit; font-size: 0.8rem; font-weight: 700; cursor: pointer;
  }
}

/* A phone on its side is about 300-360 px tall, and a column of four buttons
   would run into the pills and the pad, so the four line up in a row at the
   top right beside a shorter search bar. Josh, 2026-11-01: Nav, Locate,
   Layers and Rig stay in reach on a phone on its side. (Before the redesign
   this block HID the column, because the old header kept Nav, Dash and
   Locate.) Not width-gated: an S23 in landscape is ~780px wide and fails
   (max-width: 768px), see js/elevation-hud.js. The LAST #map-controls rule
   in the file (tests/mobile-ui-controls.test.js). */
@media (pointer: coarse) and (orientation: landscape) and (max-height: 500px) {
  #map-controls { flex-direction: row; top: calc(env(safe-area-inset-top, 0px) + 12px); right: calc(12px + env(safe-area-inset-right, 0px)); gap: 8px; }
  body.inapp-shifted #map-controls { top: calc(var(--inapp-bar-h, 64px) + 12px); }
  /* The compass sits under the row's right end, so the row (and the search
     bar sized beside it) keeps its width while it comes and goes. */
  #btn-compass.is-on { position: absolute; top: calc(100% + 8px); right: -2px; margin: 0; }
  /* The bar is sized by width, not by counting the buttons beside it, so a
     fifth right-edge button (Dash, if Josh asks) is a one-line change. Both
     clear a notch or camera cutout on a phone on its side (phone-ux-s34 graft). */
  #mobile-search { left: calc(12px + env(safe-area-inset-left, 0px)); right: auto; width: min(420px, calc(100vw - 280px)); }
}

/* ===== Phone targets: 48 px at least (the in-between size, 2026-11-01) =====
   Older hands tap less accurately on small targets (Leitao & Silva 2012), so
   every control a phone shows is 48 px in its short side: the menu's tabs, its
   tour and guide buttons, its X, the sign-in fields and buttons, the search
   field, a link alone on its line. Last in the file so it wins over the
   desktop rules it lifts; links inside a sentence keep their size. */
@media (max-width: 768px) {
  #sidebar-close { min-width: 48px; min-height: 48px; }
  .tab-btn, #open-tour, #open-guide, #report-problem { min-height: 48px; }
  .search-box input, #auth-email, #auth-password { min-height: 48px; }
  #auth-form .btn, #auth-form button { min-height: 48px; }
  #fb-page-link { display: inline-flex; align-items: center; min-height: 48px; }
  /* Menu > Route's controls and the route card's lower buttons (Export GPX,
     Download PDF, Share this trip), rem-sized and 10% smaller on the 18 px
     root (phone-ux-final review). Not yet the Rig menu: eight 48 px rows are
     about 410 px, and tour v3's card then covers the list at 384 x 668. */
  #plan-route, #add-waypoint, #clear-route, #save-route, #export-gpx, #save-home,
  .route-point input, #route-date, #route-sheet .btn { min-height: 48px; }
  .route-locate-btn, .route-night-btn { min-width: 48px; min-height: 48px; }
  /* Built with inline 44 px heights (the tour card, the new-look offer, the
     done card, the launch cards, "Start from here?"): lifted here. */
  #onboard-card button, #onboard-offer button, #onboard-done button,
  #install-card button, #share-card button, #fb-follow-card button, #fb-follow-card a,
  #route-start-sheet button { min-height: 48px !important; }
  #onboard-x { min-width: 48px !important; }
  /* Whole words in Route options' vehicle picker: "Motorhome" broke as
     "Motorhom" / "e" at 360 px on the 14 px floor. 1 px sides leave 72 px. */
  .route-vehicle-btn { padding: 7px 1px; }
  .route-vehicle-btn span { overflow-wrap: normal; }
}

/* Plan with AI: "Which are you taking?" with the rider's own vehicles, and "Planning for <vehicle> · change" (js/ai-route.js). */
.ai-vehicle-choices { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.ai-vehicle-choice { display: inline-flex; align-items: center; gap: 6px; padding: 8px 12px; border: 1px solid var(--bg-tertiary); border-radius: var(--radius); background: var(--bg-card); color: var(--text-primary); cursor: pointer; font: inherit; }
.ai-vehicle-choice[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); }
.ai-vehicle-line { font-size: 0.85rem; color: var(--text-secondary); padding: 2px 2px 0; }
.ai-vehicle-change { background: none; border: 0; padding: 0; color: var(--accent); text-decoration: underline; cursor: pointer; font: inherit; }

/* Far-off turn pill, phone upright: it sits in the turn card's own row, just
   under the temperature and wind pills, so all three show at once. It used to
   sit up beside the ETA box and hide temperature and wind while it showed
   (an AUS rider, 2026-11-05: "We miss the temp and wind speed ... Can the
   notification of turn outside 3KM just be lowered so we can see the Temp
   and wind"). The pill is one line, so the row costs the map about 40px. */
@media (max-width: 599px) and (orientation: portrait) {
  #nav-overlay:not(.hidden) > #nav-turn.turn-far { grid-column: 1 / -1; grid-row: 2; justify-self: center; align-self: start; margin: 0 0 10px 0; padding: 3px 10px 3px 4px; }
  #nav-overlay:not(.hidden) > #nav-turn.turn-far .nav-turn-far-road { max-width: 60vw; }
}

/* Store app, before Auth has run: the static sign-up words name the website's card trial. */
html.store-app-loading #login-request-toggle { visibility: hidden; }
