/* ============================================================================
   Mobile-first, map-first layout: the map fills the whole screen and every
   control (search card, next-turn banner, bottom sheet, recenter button)
   floats on top of it as a rounded, shadowed card — the same visual language
   as Google Maps' mobile app — rather than stacking flat panels that push
   the map into a small strip. This is meant to be used on a phone, mounted
   or handheld, screen on — so: big touch targets, high contrast.
   ============================================================================ */

:root {
  --bg: #10151c;
  --panel: #182130;
  --panel-2: #1f2a3d;
  --text: #eef2f8;
  --text-dim: #9aabc2;
  --accent: #3d8bfd;
  --accent-2: #22c55e;
  --danger: #ef4444;
  --border: #2a3547;
  --radius: 16px;
  --shadow: 0 8px 28px rgba(0,0,0,0.5);
  --docs-body: #c3cede;
  --docs-link: #e0ac52;
}

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  -webkit-tap-highlight-color: transparent;
  overscroll-behavior: none;
}

button {
  font: inherit;
  border: none;
  border-radius: var(--radius);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

#app {
  position: relative;
  height: 100vh;
  height: 100dvh; /* dvh tracks the *actual* visible area as the mobile browser's own address bar shows/hides; plain vh doesn't, so it must be declared second to win where supported (declared first here only as the fallback for browsers with no dvh support at all) */
  overflow: hidden;
}

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

/* ---------------------------------------------------------------- top overlay */

/* A transparent flex column pinned to the top of the screen. Its own
   background is invisible so map gestures pass through the gaps between
   cards; each card inside opts back into being clickable/touchable. */
#top-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: 10px;
  /* Wider side padding than a bare safe-area inset — cards float clear of
     the screen edges instead of stretching edge to edge, and max-width +
     auto margins keep them from ballooning absurdly wide on a tablet or a
     desktop browser window. */
  padding: max(12px, env(safe-area-inset-top)) max(20px, env(safe-area-inset-right)) 0 max(20px, env(safe-area-inset-left));
  max-width: 480px;
  margin: 0 auto;
  pointer-events: none;
}
#top-overlay > * { pointer-events: auto; }

#status-banner {
  padding: 10px 14px;
  border-radius: 12px;
  font-size: 13px;
  line-height: 1.4;
  text-align: center;
  background: var(--panel);
  box-shadow: var(--shadow);
}
#status-banner.info    { background: #16324a; color: #cfe8ff; }
#status-banner.success { background: #123a26; color: #bdf2d0; }
#status-banner.error   { background: #401616; color: #ffd0d0; }
#status-banner.hidden  { display: none; }

/* ---------------------------------------------------------------- resolver debug panel */
#resolver-debug-panel {
  position: fixed;
  left: 10px;
  right: 10px;
  bottom: 10px;
  max-height: 45vh;
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  z-index: 70;
  overflow: hidden;
}
#resolver-debug-panel.hidden { display: none; }
.resolver-debug-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
  font-weight: 600;
  flex-shrink: 0;
}
.resolver-debug-actions { display: flex; gap: 6px; }
.resolver-debug-actions button {
  background: var(--panel-2);
  color: var(--text);
  border-radius: 8px;
  padding: 4px 10px;
  font-size: 12px;
}
.resolver-debug-actions button:active { background: var(--border); }
#resolver-debug-log {
  overflow-y: auto;
  padding: 8px 10px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--text-dim);
  white-space: pre-wrap;
  word-break: break-all;
}
.resolver-debug-line.error   { color: #ffb4b4; }
.resolver-debug-line.warn    { color: #ffd97a; }
.resolver-debug-line.success { color: #bdf2d0; }
.resolver-debug-line.url     { color: var(--docs-link); }

/* ---------------------------------------------------------------- search card */

#search-card {
  background: var(--panel);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 8px 10px 10px;
}
#search-card.hidden { display: none; }

/* Collapses the whole directions editor down to one tappable summary line
   once a route is planned and the bottom sheet is expanded — see
   syncDirectionsCollapse in app.js, which drives this off #bottom-sheet's
   own .expanded class so the two panels never fight over the same screen
   space. Hidden by default; #search-directions.directions-collapsed flips
   which of the two is shown. */
#directions-summary-row {
  display: none;
  width: 100%;
  text-align: left;
  background: none;
  padding: 6px 4px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
#search-directions.directions-collapsed #directions-summary-row { display: block; }
#search-directions.directions-collapsed .directions-detail { display: none; }

.search-top { display: flex; align-items: center; gap: 6px; }
.search-fields { flex: 1; min-width: 0; }

.search-row { position: relative; display: flex; align-items: center; gap: 10px; padding: 6px 4px; }
.search-row.plain { padding: 6px 4px; }
.search-icon { flex-shrink: 0; color: var(--text-dim); }

/* Once several stops are added, the list scrolls internally instead of
   pushing the rest of the card (Get Directions, the map below it) further
   down the screen — the dvh cap scales with the device's own viewport, so
   it shrinks proportionally on shorter phones too. Declared vh first as a
   fallback, dvh second so it wins where supported (same progressive-
   enhancement pattern used for .suggestions etc. elsewhere in this file). */
#stops-container {
  max-height: 18vh;
  max-height: 18dvh;
  overflow-y: auto;
}

/* One-tap POI category chips (Google-Maps style) — a single horizontally
   scrollable row, never more, so it can't turn into the kind of permanent
   multi-section clutter a favorites/recent card would. */
.category-chips {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  padding: 2px 4px 4px;
  margin-top: 2px;
  scrollbar-width: none;
}
.category-chips::-webkit-scrollbar { display: none; }
.chip {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-radius: 999px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  color: var(--text);
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
}
.chip:active { background: var(--accent); border-color: var(--accent); color: #fff; }

.dot { flex-shrink: 0; width: 10px; height: 10px; }
.dot-origin { border-radius: 50%; background: var(--accent-2); }
.dot-dest { border-radius: 3px; background: var(--danger); }
.dot-stop { border-radius: 50%; background: #f59e0b; }

.remove-stop-btn { flex-shrink: 0; width: 24px; height: 24px; color: var(--text-dim); background: none; border: none; display: flex; align-items: center; justify-content: center; }

.stop-drag-handle {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  color: var(--text-dim);
  background: none;
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: grab;
  touch-action: none; /* dragging is vertical-only and shouldn't also scroll the page on touch */
}
.stop-unit-dragging {
  z-index: 30;
  box-shadow: 0 8px 20px rgba(0,0,0,0.45);
  border-radius: 10px;
  background: var(--panel);
}
.stop-unit-dragging .search-divider { opacity: 0; } /* the dragged unit's own divider would otherwise float mid-air */

.add-stop-btn {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 2px;
  padding: 5px 4px;
  background: none;
  color: var(--accent);
  font-size: 13px;
  font-weight: 600;
  border-radius: 8px;
}

.search-row input {
  flex: 1;
  min-width: 0;
  font-size: 16px; /* prevents iOS zoom-on-focus */
  border: none;
  background: transparent;
  color: var(--text);
  padding: 3px 0;
}
.search-row input::placeholder { color: var(--text-dim); }
.search-row input:focus { outline: none; }

.search-divider { height: 1px; background: var(--border); margin-left: 22px; }

.icon-btn {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--panel-2);
  border: 1px solid var(--border);
  color: var(--text);
  display: flex;
  align-items: center;
  justify-content: center;
}

.suggestions {
  position: absolute;
  top: 100%;
  left: -4px;
  right: -4px;
  z-index: 30;
  margin: 4px 0 0;
  padding: 4px;
  list-style: none;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  max-height: 45vh;
  max-height: 45dvh;
  overflow-y: auto;
  box-shadow: var(--shadow);
}
.suggestions.hidden { display: none; }
.suggestions li {
  padding: 10px 8px;
  border-radius: 8px;
  font-size: 14px;
  line-height: 1.3;
}
.suggestions li:active,
.suggestions li.active { background: var(--accent); color: #fff; }

.suggestions li.loading {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--text-dim);
  cursor: default;
}
.suggestions li.loading:active { background: none; }

.suggestions li.quick-option { display: flex; align-items: center; gap: 10px; color: var(--accent); font-weight: 500; }
.suggestions li.quick-option:active { background: var(--accent); color: #fff; }

/* Recent-trip / favorite rows shown inside the suggestions dropdown when an
 * empty field is focused — same dropdown, same footprint as a normal
 * search result, nothing permanent on screen. */
.quick-pick-text { display: inline-flex; align-items: center; gap: 10px; color: var(--accent); }

.spinner {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
  border-radius: 50%;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  animation: spin 0.7s linear infinite;
}
@keyframes spin {
  to { transform: rotate(360deg); }
}

/* Drive/Transit segmented control, only shown when OTP2_URL is configured. */
#travel-mode-toggle {
  display: flex;
  gap: 4px;
  background: var(--panel-2);
  border-radius: 12px;
  padding: 3px;
  margin-top: 6px;
}
#travel-mode-toggle.hidden { display: none; }
.mode-btn {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 7px;
  font-size: 13px;
  font-weight: 600;
  background: none;
  color: var(--text-dim);
  border-radius: 9px;
}
.mode-btn.active { background: var(--accent); color: #fff; }
.mode-btn.hidden { display: none; }

/* Same chip look as #travel-mode-toggle, but two independent toggles rather
   than a mutually-exclusive mode choice. */
#route-avoid-toggle {
  display: flex;
  gap: 4px;
  background: var(--panel-2);
  border-radius: 12px;
  padding: 3px;
  margin-top: 6px;
}
#route-avoid-toggle.hidden { display: none; }

#plan-route-btn {
  width: 100%;
  margin-top: 6px;
  padding: 11px;
  font-size: 15px;
  font-weight: 600;
  background: var(--accent);
  color: #fff;
}
#plan-route-btn:disabled { opacity: 0.5; }

/* ---------------------------------------------------------------- search results: save-to-favorites star */

/* Standalone (not scoped to .suggestions) so the same result-row look works
   both in a search dropdown and in the bottom sheet's "search along route"
   results list. */
.result-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0;
  border-radius: 8px;
}
.result-item:active { background: var(--panel-2); }
.result-text {
  flex: 1;
  min-width: 0;
  padding: 8px;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.result-primary {
  font-size: 14px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.result-secondary {
  font-size: 12px;
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.result-meta {
  font-size: 12px;
  color: var(--accent);
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.save-btn {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  margin-right: 4px;
  border-radius: 50%;
  background: none;
  border: none;
  color: var(--text-dim);
  display: flex;
  align-items: center;
  justify-content: center;
}
.save-btn.saved { color: #f5b942; }
.save-btn.saved svg { fill: currentColor; }

/* ---------------------------------------------------------------- place card */

#place-card {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--panel);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 12px 10px 12px 16px;
}
#place-card.hidden { display: none; }
.place-card-text { flex: 1; min-width: 0; }
#place-card-primary {
  font-size: 15px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#place-card-secondary {
  margin-top: 2px;
  font-size: 12px;
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.place-card-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
#place-directions-btn {
  padding: 10px 16px;
  font-size: 14px;
  font-weight: 600;
  background: var(--accent);
  color: #fff;
}

/* ---------------------------------------------------------------- saved-item rows (downloaded areas list) */

.saved-list { list-style: none; margin: 0; padding: 0; max-height: 40vh; max-height: 40dvh; overflow-y: auto; }
.saved-list li {
  display: flex;
  align-items: center;
  gap: 8px;
  border-radius: 10px;
}
.saved-list li:not(.empty):active { background: var(--panel-2); }
.saved-list li.empty { padding: 8px; color: var(--text-dim); font-size: 13px; }

.saved-item-body { flex: 1; min-width: 0; padding: 8px; }
.saved-item-title { font-size: 14px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.saved-item-meta { font-size: 12px; color: var(--text-dim); margin-top: 2px; }
.saved-item-link { display: block; font-size: 12px; color: var(--accent); margin-top: 2px; text-decoration: none; }
.saved-item-link:active { text-decoration: underline; }

.quick-place-body { display: flex; align-items: center; gap: 12px; }
.quick-place-icon { flex-shrink: 0; color: var(--text-dim); display: flex; }
.quick-place-unset { color: var(--text-dim); }

.icon-btn.small { width: 28px; height: 28px; flex-shrink: 0; margin-right: 4px; }
.delete-btn { color: var(--danger); }

/* .favorite-prompt-actions is shared by the two prompts below (a plain
   flex row of Cancel/Save-style buttons) — kept under its original name
   even though the prompt it was first built for (long-press-to-save) no
   longer exists, since it's just a layout utility class at this point. */
.favorite-prompt-actions { display: flex; gap: 8px; justify-content: flex-end; }

/* ---------------------------------------------------------------- save-to-list / list-name prompts */
/* These can be opened from inside the full-screen Saved panel (z-index 50,
   "move to another list") or while a search-suggestions dropdown is still
   open (z-index 30, the save-star on a result) — so they need to be
   fixed+centered with a z-index above both rather than just sitting in the
   document's flow. No dimming backdrop, though, consistent with every
   other prompt here. */

#save-to-list-prompt, #list-name-prompt {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: min(340px, calc(100vw - 32px));
  z-index: 60;
  max-height: calc(100vh - 32px);
  max-height: calc(100dvh - 32px);
  overflow-y: auto;
  background: var(--panel);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 12px 14px;
}
#save-to-list-prompt.hidden, #list-name-prompt.hidden { display: none; }

.save-to-list-place-name { font-size: 14px; font-weight: 600; margin-bottom: 8px; }
.list-name-prompt-title { font-size: 14px; font-weight: 600; margin-bottom: 8px; }
#list-name-prompt-input {
  width: 100%;
  font-size: 15px;
  padding: 10px 10px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--text);
  margin-bottom: 8px;
}

.radio-list { max-height: 30vh; max-height: 30dvh; }
.radio-list li {
  padding: 10px 8px;
  cursor: pointer;
}
.radio-list li:active { background: var(--panel-2); }
.radio-dot {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 2px solid var(--border);
  position: relative;
}
.radio-list li.selected .radio-dot { border-color: var(--accent); }
.radio-list li.selected .radio-dot::after {
  content: '';
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  background: var(--accent);
}

.save-to-list-new-row {
  display: flex;
  gap: 8px;
  align-items: center;
  padding: 8px 8px 12px;
}
.save-to-list-new-row input {
  flex: 1;
  min-width: 0;
  font-size: 14px;
  padding: 9px 10px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--text);
}

/* ---------------------------------------------------------------- saved-places panel (lists of favorites) */

.list-detail-header { display: flex; align-items: center; gap: 4px; margin-bottom: 12px; }
.list-detail-header .panel-section-title { flex: 1; min-width: 0; margin-bottom: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------------------------------------------------------------- next-turn card */

#nav-banner {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--panel);
  border-radius: var(--radius);
  padding: 12px 16px;
  box-shadow: var(--shadow);
}
#nav-banner.hidden { display: none; }

#nav-banner-icon {
  flex-shrink: 0;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
}
#nav-banner-text { min-width: 0; }
#nav-banner-instruction {
  font-size: 17px;
  font-weight: 600;
  line-height: 1.25;
  overflow-wrap: break-word;
}
#nav-banner-distance {
  margin-top: 2px;
  font-size: 14px;
  color: var(--accent);
  font-weight: 600;
}

/* Small live speed readout — a second, smaller card under the next-turn
   banner rather than a separately-positioned floating badge, so it inherits
   #top-overlay's placement/safe-area handling for free instead of needing
   its own fixed-position math around the map controls. */
#nav-speed {
  align-self: flex-start;
  background: var(--panel);
  border-radius: 12px;
  padding: 6px 12px;
  box-shadow: var(--shadow);
  font-size: 13px;
  font-weight: 700;
  color: var(--text);
}
#nav-speed.hidden { display: none; }

/* ---------------------------------------------------------------- floating buttons */

/* Google-Maps-style control stack: zoom +/- above a "my location" button,
   pinned bottom-right. `bottom` is written inline by syncMapControlsClearance
   (app.js) rather than a fixed CSS value — a single hardcoded offset only
   ever matched the sheet's default "peek" height, so dragging it to the
   half/expanded stop buried these buttons underneath it (same z-index,
   #bottom-sheet later in the DOM). Kept in a ResizeObserver so it tracks
   every way the sheet's rendered height can change in one place: peek
   re-measurement, half/expanded snap, or a live drag. */
#map-controls {
  position: fixed;
  right: 14px;
  bottom: 24px;
  z-index: 20;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  transition: bottom 0.25s ease;
}
/* Suppressed during an active sheet drag (app.js toggles this alongside
   #bottom-sheet.dragging) — the eased transition would otherwise lag a
   frame behind the sheet's edge the whole way, same reason
   #bottom-sheet.dragging itself turns its own transition off. */
#map-controls.no-transition { transition: none; }

/* Mirror of #map-controls on the left, currently just the voice-mode and
   satellite-view toggles — kept as its own stack (not merged into
   #map-controls) in case anything else lands here later. Clearance is kept
   in sync with #map-controls by the same syncMapControlsClearance call. */
#map-controls-left {
  position: fixed;
  left: 14px;
  bottom: 24px;
  z-index: 20;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  transition: bottom 0.25s ease;
}
#map-controls-left.no-transition { transition: none; }

/* Weather badge — round like the .fab family, tap to force-refresh (see
   handleWeatherBadgeRefresh in app.js), so it's its own class rather than a
   .fab modifier only because it also carries emoji/temperature text .fab
   never needs. Emoji on top, temperature below in dim text, same
   panel/shadow as every other floating control. */
.weather-badge {
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: var(--panel);
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  cursor: pointer;
}
.weather-badge:active { background: var(--panel-2); }
.weather-badge.refreshing { opacity: 0.55; }
#weather-emoji { font-size: 19px; line-height: 1; }
#weather-temp {
  font-size: 10px;
  line-height: 1;
  font-weight: 600;
  color: var(--text-dim);
}

.fab {
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
  display: flex;
  align-items: center;
  justify-content: center;
}
.fab.small { width: 38px; height: 38px; }
.fab.active { background: var(--accent); color: #fff; border-color: var(--accent); }

/* markers built from plain DOM/SVG, no image assets */
.pin-marker { width: 30px; height: 40px; display: block; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.5)); }
.puck-marker { width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; }
/* Bigger footprint for the active-navigation arrow puck specifically — see
   createPuckElement vs createLocationDotElement in app.js. */
.puck-marker-nav { width: 40px; height: 40px; filter: drop-shadow(0 1px 3px rgba(0,0,0,0.5)); }
.poi-marker {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--panel);
  border: 2px solid var(--accent);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 2px 6px rgba(0,0,0,0.5);
}
.poi-marker.selected { background: var(--accent); color: #fff; }

/* Name-tag bubble above each POI dot. MapLibre appends this marker element
   directly into its (statically-positioned) canvas container rather than
   absolutely positioning it, so a plain block-level div here would stretch
   to the full width of the map instead of shrinking to fit the 22px dot —
   confirmed live: the label's `left: 50%` was then landing ~900px away from
   the actual dot. display:inline-block makes the wrapper shrink-to-fit its
   one in-flow child (the dot; the label is position:absolute and doesn't
   count toward this sizing), so `left: 50%` on the label resolves against
   the dot's own width and actually centers over it. position:relative
   still gives the (absolutely positioned) label something to anchor to. */
.poi-marker-wrap { position: relative; display: inline-block; }
.poi-marker-label {
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  margin-bottom: 4px;
  max-width: 120px;
  padding: 3px 7px;
  border-radius: 8px;
  background: var(--panel);
  border: 1px solid var(--border);
  color: var(--text);
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  box-shadow: 0 2px 6px rgba(0,0,0,0.5);
  cursor: pointer;
}

/* ---------------------------------------------------------------- bottom sheet */

/* A flex column, not plain block flow: #sheet-handle/#sheet-actions/
   #route-options/#poi-results-header are flex-shrink:0 (always their full
   natural height, never compressed or clipped), and whichever list is
   currently visible (#maneuver-list or #poi-results-list) is the one
   flex:1 item that fills whatever space is left and scrolls internally.
   This used to be done with a hardcoded max-height: calc(72dvh - 118px) on
   the list — fragile, since it silently assumed the header above it was
   always exactly 118px tall, and any mismatch (a longer destination name
   wrapping to two lines, a notch's safe-area inset, etc.) clipped
   #sheet-actions (Cancel/Start/End) with no way to scroll to the rest of
   it, since it wasn't part of the scrollable region to begin with. Flexbox
   makes that whole class of bug impossible instead of computing around it. */
#bottom-sheet {
  position: fixed;
  /* Floats clear of the screen edges like the top search card, instead of
     stretching corner to corner — max-width + auto margins cap it on a
     tablet/desktop window the same way. */
  left: max(20px, env(safe-area-inset-left));
  right: max(20px, env(safe-area-inset-right));
  bottom: 0;
  max-width: 480px;
  margin: 0 auto;
  z-index: 20;
  background: var(--panel);
  border-radius: 18px 18px 0 0;
  box-shadow: 0 -8px 30px rgba(0,0,0,0.5);
  /* Overridden per-render from JS as an inline style (see
     updateSheetPeekHeight in app.js) to the actual measured height of the
     handle + route options (when shown) + action buttons, so all of that is
     always visible with no scrolling in this default landing state — this
     136px is only the fallback for the brief moment before JS first runs. */
  max-height: 136px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
/* Deliberately NOT a permanent part of #bottom-sheet's own rule — a
   transition on max-height driven by the same JS that ALSO writes
   max-height as a plain measurement (updateSheetPeekHeight) confuses
   Chrome's transition machinery (confirmed directly: the computed height
   can get stuck on a stale or partway value indefinitely instead of ever
   reaching the real target). Applied only for the moment a drag/tap
   actually changes state (see setSheetState) so that still animates
   smoothly, while every other max-height write (peek re-measurement on
   route render, resize, etc.) stays instant and reliable. */
.sheet-animate { transition: max-height 0.25s ease; }
/* vh alone can be taller than what's actually visible while the mobile
   browser's own address bar is showing (it doesn't shrink when the chrome
   does) — bottom:0-anchored content sized off it can grow tall enough to
   push #sheet-actions (Cancel/Start/End) up past the true top of the
   screen, off-screen, even though nothing is technically wrong with the
   sheet's own CSS. dvh tracks the real visible area, so it's declared
   second here to win wherever it's supported; vh stays first as the
   fallback for the rare browser with no dvh support at all. Every other
   72vh-derived max-height below (maneuver list, poi results list) needs
   the same pairing to stay in sync with this one. */
#bottom-sheet.expanded { max-height: 72vh; max-height: 72dvh; }
/* The middle stop between peek and fully expanded — see setSheetState in
   app.js — so dragging up doesn't have to mean "barely any map left". Same
   vh/dvh pairing as .expanded, for the same mobile-address-bar reason. */
#bottom-sheet.half { max-height: 42vh; max-height: 42dvh; }
#bottom-sheet.hidden { display: none; }
/* While actively being dragged, app.js sets max-height inline on every
   pointermove for 1:1 tracking — the eased transition would otherwise lag
   a frame behind the finger the whole way, which reads as "unresponsive". */
#bottom-sheet.dragging { transition: none; }

#sheet-handle {
  width: 100%;
  flex-shrink: 0;
  background: none;
  color: inherit;
  text-align: left;
  padding: 10px 16px 8px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  cursor: grab;
  touch-action: none; /* we own the vertical drag entirely — don't let the page also try to scroll/refresh */
}
#sheet-handle:active { cursor: grabbing; }
.handle-bar { width: 40px; height: 5px; border-radius: 3px; background: rgba(255,255,255,0.22); }
#sheet-summary { align-self: stretch; text-align: center; font-size: 14px; font-weight: 500; color: var(--text-dim); }

#sheet-actions {
  display: flex;
  flex-shrink: 0;
  gap: 6px;
  padding: 4px 16px 10px;
}
#sheet-actions button {
  flex: 1;
  padding: 9px;
  font-size: 13px;
  font-weight: 600;
}
#start-nav-btn { background: var(--accent-2); color: #06240f; }
#end-nav-btn.danger { background: var(--danger); color: #fff; }
#sheet-actions button.hidden { display: none; }
#sheet-actions .icon-btn { flex: 0 0 auto; padding: 0; font-size: inherit; font-weight: normal; }

/* Alternate route cards — horizontally scrollable, one per meaningfully
   different route option. The active one is highlighted; tapping any card
   (or its matching gray line on the map) switches the active route. */
#route-options {
  display: flex;
  flex-shrink: 0;
  gap: 8px;
  overflow-x: auto;
  padding: 4px 12px 10px;
  scrollbar-width: none;
}
#route-options::-webkit-scrollbar { display: none; }
#route-options.hidden { display: none; }

.route-option-card {
  flex-shrink: 0;
  min-width: 92px;
  padding: 8px 12px;
  border-radius: 12px;
  background: var(--panel-2);
  border: 1.5px solid var(--border);
  color: var(--text);
  text-align: left;
}
.route-option-card.active { border-color: var(--accent); background: rgba(61,139,253,0.14); }
.route-option-dist { font-size: 15px; font-weight: 700; }
.route-option-tag { font-size: 11px; color: var(--accent); font-weight: 600; margin-top: 3px; }

/* Walk-mode elevation profile — static chart, computed once per route. */
#elevation-profile { flex-shrink: 0; padding: 4px 16px 8px; }
#elevation-profile.hidden { display: none; }
.elevation-title { font-size: 13px; font-weight: 700; margin-bottom: 4px; }
.elevation-summary { display: flex; justify-content: space-between; align-items: baseline; font-size: 12px; color: var(--text-dim); margin-bottom: 12px; }
.elevation-tag { font-weight: 700; color: var(--text); }

/* The chart, its dot buttons, the dashed "selected point" guideline, and
   the axis labels below all share one coordinate system: every horizontal
   position is a percentage of THIS frame's own width (see
   buildElevationChart in app.js). That only lines up correctly because
   neither .elevation-chart nor .elevation-axis carries its own horizontal
   padding — any inset belongs on the frame itself, once, via padding-top
   (which just needs to leave room for a label above the highest dot). */
.elevation-chart-frame { position: relative; padding-top: 14px; }
.elevation-chart { position: relative; }
.elevation-chart svg { display: block; width: 100%; height: 64px; }

/* Each significant-change point is a real <button> (not an SVG shape) so it
   gets proper click/tap semantics and isn't subject to the chart SVG's own
   non-uniform preserveAspectRatio="none" scaling — confirmed earlier that
   scaling distorts anything drawn inside the viewBox on a wide phone
   screen. The button's own box is a generous 22px tap target; the visible
   dot inside it is much smaller. */
.elevation-point {
  position: absolute;
  transform: translate(-50%, -50%);
  width: 22px;
  height: 22px;
  padding: 0;
  background: none;
  border: none;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.elevation-point::before {
  content: '';
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--text-dim);
  border: 1.5px solid var(--panel);
}
.elevation-point.active::before { width: 11px; height: 11px; background: var(--accent); }

.elevation-point-label {
  position: absolute;
  transform: translate(-50%, -100%);
  margin-top: -6px;
  padding: 1px 4px;
  border-radius: 4px;
  background: var(--panel);
  font-size: 9px;
  font-weight: 700;
  color: var(--text);
  white-space: nowrap;
  pointer-events: none;
}
.elevation-point-label.hidden { display: none; }

.elevation-guideline {
  position: absolute;
  top: 0;
  bottom: 0;
  border-left: 1px dashed var(--text-dim);
  pointer-events: none;
}
.elevation-guideline.hidden { display: none; }

.elevation-axis { display: flex; justify-content: space-between; font-size: 10px; color: var(--text-dim); margin-top: 4px; }

/* Marker dropped on the actual route to show where a tapped elevation point
   is — explicit width/height (not display:block auto-width) so it can't
   repeat the earlier .poi-marker-wrap bug of stretching to the map's full
   width inside MapLibre's statically-positioned canvas container. */
.elevation-highlight-marker { display: inline-block; position: relative; width: 18px; height: 18px; }
.elevation-highlight-ring { position: absolute; inset: -6px; border-radius: 50%; border: 2px solid var(--accent); opacity: 0.5; }
.elevation-highlight-dot { position: absolute; inset: 0; border-radius: 50%; background: var(--accent); border: 2px solid #fff; box-shadow: 0 1px 4px rgba(0,0,0,0.5); }

/* "Search along the route" popover — reuses .category-chips/.chip for
   content and click handling, laid out as a vertical list of full-width
   rows so it's easy to tap one-handed while the phone is mounted driving.
   As a floating popover (opened from the search icon in #map-controls, see
   app.js) it needs its own card chrome and fixed positioning. `bottom` and
   `max-height` are set inline by app.js at open time, computed from the
   button's own live position, so it always lands just above it regardless
   of how tall the FAB stack is (Mapillary's button is conditional) and
   never runs past the top of the screen on a short phone. `max-width`
   keeps it well short of covering the screen on a narrow phone; the
   `max-height`/`overflow-y` below are a fallback in case the inline value
   isn't set, with internal scrolling once the list is taller than that. */
.route-chips-popover {
  position: fixed;
  right: 14px;
  z-index: 22;
  max-width: calc(100vw - 28px);
  width: 230px;
  max-height: min(60vh, 420px);
  max-height: min(60dvh, 420px);
  overflow-y: auto;
  overflow-x: hidden;
  display: flex;
  flex-direction: column;
  gap: 4px;
  background: var(--panel);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 6px;
  margin-top: 0;
}
.route-chips-popover.hidden { display: none; }

/* Inside the popover, chips become full-width list rows (icon + label,
   left-aligned) rather than the small centered pill shape used in the
   horizontal category rows elsewhere — a wider, taller tap target that's
   easier to hit one-handed and reads as a normal vertical menu. */
.route-chips-popover .chip {
  width: 100%;
  justify-content: flex-start;
  border-radius: 10px;
  padding: 13px 14px;
  font-size: 14.5px;
}

#poi-results-header {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  gap: 10px;
  padding: 4px 16px 8px;
}
#poi-results-header.hidden { display: none; }
#poi-results-label { font-size: 14px; font-weight: 600; }

#poi-results-list {
  list-style: none;
  margin: 0;
  padding: 0 12px calc(12px + env(safe-area-inset-bottom, 0px));
  overflow-y: auto;
  flex: 1 1 auto;
  min-height: 0; /* flex items default to a content-based min-height that can prevent shrinking to fit — without this, "fill remaining space and scroll internally" silently doesn't work */
}
#poi-results-list.hidden { display: none; }
#poi-results-list li.empty { padding: 12px 4px; color: var(--text-dim); font-size: 13px; }

#maneuver-list {
  list-style: none;
  margin: 0;
  padding: 0 0 calc(12px + env(safe-area-inset-bottom, 0px));
  overflow-y: auto;
  flex: 1 1 auto;
  min-height: 0; /* see #poi-results-list above — same flexbox gotcha */
}

#maneuver-list li {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 11px 16px;
  border-bottom: 1px solid var(--border);
}
.m-icon {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--panel-2);
  color: var(--text-dim);
  display: flex;
  align-items: center;
  justify-content: center;
}
.m-body { min-width: 0; padding-top: 2px; }
#maneuver-list li .instr { font-weight: 500; font-size: 14px; }
#maneuver-list li .meta { font-size: 12px; color: var(--text-dim); margin-top: 2px; }
#maneuver-list li.active { background: var(--panel-2); }
#maneuver-list li.active .m-icon { background: var(--accent); color: #fff; }
#maneuver-list li.done { opacity: 0.45; }

/* ---------------------------------------------------------------- Mapillary viewer */

#mapillary-viewer {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: rgba(6, 9, 14, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
#mapillary-viewer.hidden { display: none; }

.mapillary-card {
  width: min(92vw, 480px);
  background: var(--panel);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow: hidden;
}
.mapillary-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 14px;
  font-size: 14px;
  font-weight: 600;
}
.mapillary-body {
  position: relative;
  aspect-ratio: 4 / 3;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
}
#mapillary-image { width: 100%; height: 100%; object-fit: cover; }
#mapillary-image.hidden { display: none; }
.mapillary-placeholder { padding: 24px; text-align: center; color: var(--text-dim); font-size: 14px; }
.mapillary-placeholder.hidden { display: none; }

.nav-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 38px;
  height: 38px;
  background: rgba(16, 21, 28, 0.7);
}
.nav-arrow.left { left: 8px; }
.nav-arrow.right { right: 8px; }
.nav-arrow.hidden { display: none; }

.mapillary-attribution {
  padding: 8px 14px;
  font-size: 11px;
  color: var(--text-dim);
  text-align: center;
}

/* ---------------------------------------------------------------- offline maps panel */

#offline-panel, #saved-panel, #docs-panel {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: var(--bg);
  overflow-y: auto;
  padding: max(16px, env(safe-area-inset-top)) 16px calc(24px + env(safe-area-inset-bottom, 0px));
}
#offline-panel.hidden, #saved-panel.hidden, #docs-panel.hidden { display: none; }

/* ---- docs: single scrollable page, one flat grouped list per section ----
   Uses its own --docs-body/--docs-link tokens rather than the app-wide
   --text-dim/--accent: those are reused in ~30 other places (buttons, active
   states, chips), so retuning them here for reading comfort would ripple
   into unrelated UI. --docs-link intentionally avoids the app's blue accent
   so a hyperlink in a paragraph of prose doesn't read as a tappable button. */
.docs-section-title {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-dim);
  font-weight: 700;
  margin: 20px 2px 6px;
}
.docs-section-first .docs-section-title { margin-top: 0; }
.docs-blurb { margin: 0 2px 10px; font-size: 13px; color: var(--docs-body); line-height: 1.45; }

.docs-group { background: var(--panel); border-radius: var(--radius); overflow: hidden; }

.docs-item { border-bottom: 1px solid var(--border); }
.docs-item:last-child { border-bottom: none; }
.docs-item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 16px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  cursor: pointer;
  list-style: none;
}
.docs-item summary::-webkit-details-marker { display: none; }
.docs-item .docs-chevron { flex-shrink: 0; color: var(--text-dim); transition: transform 0.15s ease; }
.docs-item[open] .docs-chevron { transform: rotate(90deg); }
.docs-item-body { padding: 0 16px 16px; font-size: 14px; line-height: 1.6; color: var(--docs-body); }
.docs-item-body p { margin: 0; }
.docs-item-body p + p { margin-top: 10px; }

/* ---- developer tools: a real interactive control amid otherwise-static docs content ---- */
.docs-toggle-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
}
.docs-toggle-text { flex: 1 1 auto; min-width: 0; }
.docs-toggle-label { font-size: 14px; font-weight: 600; color: var(--text); }
.docs-toggle-hint { margin-top: 4px; font-size: 13px; line-height: 1.5; color: var(--docs-body); }
.docs-toggle-hint code { background: var(--panel-2); border-radius: 4px; padding: 1px 5px; font-size: 12px; }
.toggle-switch {
  flex-shrink: 0;
  position: relative;
  width: 46px;
  height: 26px;
  border-radius: 13px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  transition: background 0.15s ease;
}
.toggle-switch::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--text);
  transition: transform 0.15s ease;
}
.toggle-switch.active { background: var(--accent); border-color: var(--accent); }
.toggle-switch.active::after { transform: translateX(20px); background: #fff; }

/* ---- credits: static reference list, not an accordion ---- */
.docs-credits { padding: 16px; display: flex; flex-direction: column; gap: 18px; }
.docs-credits-block .panel-section-title { margin-bottom: 10px; }
.docs-credits-block p { margin: 0; font-size: 14px; line-height: 1.6; color: var(--docs-body); }
.credits-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; font-size: 14px; line-height: 1.6; color: var(--docs-body); }
.credits-list a, .docs-credits-block a, .docs-item-body a { color: var(--docs-link); font-weight: 600; text-decoration: none; }
.credits-list a:hover, .credits-list a:focus-visible,
.docs-credits-block a:hover, .docs-credits-block a:focus-visible,
.docs-item-body a:hover, .docs-item-body a:focus-visible { text-decoration: underline; }

.panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
}
.panel-title { font-size: 20px; font-weight: 700; }

.panel-section {
  background: var(--panel);
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 16px;
}
.panel-section-title { font-size: 15px; font-weight: 600; margin-bottom: 12px; }

.field-label {
  display: block;
  font-size: 12px;
  color: var(--text-dim);
  margin-bottom: 12px;
}
.field-label input {
  display: block;
  width: 100%;
  margin-top: 6px;
  font-size: 15px;
  padding: 10px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--text);
}
.zoom-range-row { display: flex; gap: 12px; }
.zoom-range-row .field-label { flex: 1; }

.tile-estimate { font-size: 13px; color: var(--text-dim); margin-bottom: 12px; }

#download-area-btn, #cancel-download-btn {
  width: 100%;
  padding: 13px;
  font-size: 15px;
  font-weight: 600;
  background: var(--accent);
  color: #fff;
}
#download-area-btn:disabled { opacity: 0.5; }
button.secondary { background: var(--panel-2); color: var(--text); border: 1px solid var(--border); }

#download-progress { margin-top: 12px; }
#download-progress.hidden { display: none; }
.progress-bar { height: 8px; border-radius: 4px; background: var(--panel-2); overflow: hidden; margin-bottom: 8px; }
#download-progress-fill { height: 100%; width: 0%; background: var(--accent); transition: width 0.2s ease; }
#download-progress-text { font-size: 13px; color: var(--text-dim); margin-bottom: 10px; text-align: center; }

.storage-estimate { font-size: 12px; color: var(--text-dim); margin-top: 8px; }

/* ---------------------------------------------------------------- misc */

.hidden { display: none !important; }
