/* ── Map cursor (#120) ────────────────────────────────────────────
   Default cursor over the map canvas is a crosshair — signals that
   the map is interactive (click to extend the route, click on the
   route to drop a waypoint).  On active press / drag the cursor
   switches to `grabbing` via :active.  Contextual overrides for
   "hovering the existing route" or "dragging a waypoint" come from
   body-level classes that route_drag.js toggles. */
.maplibregl-canvas-container.maplibregl-interactive,
.maplibregl-canvas-container.maplibregl-interactive .maplibregl-canvas {
  cursor: crosshair !important;
}
.maplibregl-canvas-container.maplibregl-interactive:active,
.maplibregl-canvas-container.maplibregl-interactive:active .maplibregl-canvas {
  cursor: grabbing !important;
}
body.ps-cursor-grab .maplibregl-canvas-container.maplibregl-interactive,
body.ps-cursor-grab .maplibregl-canvas-container.maplibregl-interactive .maplibregl-canvas {
  cursor: grab !important;
}
body.ps-cursor-grabbing .maplibregl-canvas-container.maplibregl-interactive,
body.ps-cursor-grabbing .maplibregl-canvas-container.maplibregl-interactive .maplibregl-canvas {
  cursor: grabbing !important;
}

/* ── Design tokens (#133) ──────────────────────────────────────
   Single source of truth for the palette + spacing scale.  All
   subsequent rules should reference these instead of hard-coding
   hex values, so future theme tweaks (or a proper dark/light
   toggle) become one-line changes here.

   Direction B (Komoot/Strava minimal light) baseline: white app
   surfaces, a light green accent for primary actions, a coral
   accent for destructive, muted grays for supporting UI.  Same
   token names would work for a future dark palette — just swap
   values under a `[data-theme="dark"]` selector. */
:root {
  /* Backgrounds */
  --bg:            #ffffff;         /* page + panel bodies */
  --panel-bg:      #f7f7f9;         /* recessed surfaces (left panel) */
  --floating-bg:   rgba(255, 255, 255, 0.94);  /* topbar / floating chips */
  --overlay-bg:    rgba(255, 255, 255, 0.85);  /* map controls */
  --hover-bg:      #eef1f4;

  /* Foregrounds */
  --fg:            #1a1a2e;         /* body text */
  --fg-muted:      #6b7280;         /* secondary text, labels */
  --fg-subtle:     #9ca3af;         /* placeholder, disabled */
  --fg-inverse:    #ffffff;         /* text on accent surfaces */

  /* Borders + shadows */
  --border:        #e5e7eb;
  --border-strong: #cbd0d7;
  --shadow-sm:     0 1px 2px rgba(15, 23, 42, 0.06);
  --shadow-md:     0 4px 10px rgba(15, 23, 42, 0.08);

  /* Accent colours (Komoot/Strava-adjacent) */
  --accent:        #00a870;         /* primary green */
  --accent-hover:  #00915f;
  --accent-soft:   rgba(0, 168, 112, 0.10);
  --warn:          #f6a821;
  --warn-soft:     rgba(246, 168, 33, 0.12);
  --danger:        #e04f4f;
  --danger-soft:   rgba(224, 79, 79, 0.10);

  /* Shape */
  --radius-sm:     4px;
  --radius:        6px;
  --radius-lg:     10px;
  --pill:          9999px;

  /* Spacing scale (rarely referenced but useful for consistency) */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
}

/* ── Reset / base ────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
       background: var(--bg); color: var(--fg); font-size: 14px; }
/* #186 — Prevent a transient page-level scrollbar from appearing
   when a hover state (Bootstrap tooltip or popover positioned near
   the viewport edge) briefly extends past the viewport bounds.  The
   scrollbar was reclaiming ~15 px of horizontal space and shifting
   every fixed / absolute-positioned control left for one frame.
   The app shell already uses height: 100vh + overflow: hidden, so
   there's no legitimate page-level scroll to preserve. */
html, body { overflow: hidden; height: 100%; }

/* ── App shell ───────────────────────────────────────────────── */
.app-shell {
  position: relative;   /* anchor for floating topbar + stats bar */
  display: flex;
  flex-direction: column;
  height: 100vh;
  overflow: hidden;
}

/* ── Topbar (#133 Phase D) ──────────────────────────────────────
   Floats over the map as a translucent rounded capsule so the map
   canvas fills the full viewport underneath.  Direction B still
   applies inside the capsule: dark text on translucent white,
   green primary Import, muted secondary buttons.  Positioned above
   the map-ctrl-strip (z-index 1600) so popover flyouts anchor
   correctly. */
.app-topbar {
  position: absolute !important;
  top: 12px;
  left: 12px;
  right: 12px;
  z-index: 1750;
  background: var(--floating-bg) !important;
  border: 1px solid var(--border);
  border-radius: var(--pill);
  box-shadow: var(--shadow-md);
  color: var(--fg);
  backdrop-filter: blur(8px) saturate(1.1);
  padding: 4px 12px !important;
}
.app-topbar .navbar-brand {
  color: var(--fg) !important;
  font-weight: 600;
  letter-spacing: -0.2px;
}
.app-topbar .text-muted { color: var(--fg-muted) !important; }
.app-topbar .btn-primary {
  background: var(--accent) !important;
  border-color: var(--accent) !important;
  color: var(--fg-inverse) !important;
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}
.app-topbar .btn-primary:hover {
  background: var(--accent-hover) !important;
  border-color: var(--accent-hover) !important;
}
.app-topbar .btn-secondary,
.app-topbar .btn-light {
  background: var(--bg) !important;
  border: 1px solid var(--border) !important;
  color: var(--fg) !important;
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}
.app-topbar .btn-secondary:hover,
.app-topbar .btn-light:hover {
  background: var(--hover-bg) !important;
  border-color: var(--border-strong) !important;
}
.topbar-link {
  color: var(--fg-muted) !important;
  text-decoration: none;
}
.topbar-link:hover { color: var(--fg) !important; }

/* #133 — destructive-flavour topbar button (Clear all).  Same
   ghost pill shape as the other secondary buttons but with a
   danger tint so users read "this will remove things" before
   they click.  Bootstrap's `.btn-outline-danger` picks the wrong
   accent; force our own token. */
.app-topbar .btn.topbar-danger {
  background: var(--danger-soft) !important;
  border-color: var(--danger) !important;
  color: var(--danger) !important;
}
.app-topbar .btn.topbar-danger:hover {
  background: var(--danger) !important;
  border-color: var(--danger) !important;
  color: var(--fg-inverse) !important;
}

/* ── Main body: left panel + map ─────────────────────────────── */
.main-body {
  display: flex;
  flex: 1;
  overflow: hidden;
  min-height: 0;
  /* Phase D — topbar + stats-bar are now absolute-positioned, so
     main-body fills the full viewport minus a tiny top pad that
     covers plotly's default 8 px modebar top-offset.  Keeps the
     map layers underneath both floating capsules. */
  height: 100%;
}

/* ── Left panel (track upload + per-track list) ──────────────── */
.left-panel {
  width: 300px;
  min-width: 260px;
  max-width: 340px;
  overflow-y: auto;
  background: var(--panel-bg);
  border-right: 1px solid var(--border);
  flex-shrink: 0;
}

/* ── Map wrapper ─────────────────────────────────────────────── */
.map-wrapper {
  position: relative;
  flex: 1;
  height: 100%;
  min-width: 0;
  overflow: hidden;
}

/* ── Map graph ───────────────────────────────────────────────── */
.map-graph {
  width: 100%;
  height: 100%;
}
.map-graph .js-plotly-plot,
.map-graph .plotly,
.map-graph .plot-container {
  height: 100% !important;
}

/* ── Plotly modebar: move from top-right to top-left ──────────────
   Keep the container full-width (Plotly's inline width:100%, right:0)
   but absolutely position the inner .modebar to the top-left of the
   map area, where the track-editing controls are conceptually grouped. */
.map-graph .modebar-container {
  /* Push below the floating topbar (12 + ~40 + 6 slack) so the
     modebar's tools don't hide under it. */
  top: 66px !important;
}
.map-graph .modebar {
  position: absolute !important;
  left: 8px !important;
  right: auto !important;
  top: 0 !important;
  background: var(--overlay-bg) !important;
  border: 1px solid var(--border) !important;
  border-radius: var(--radius) !important;
  padding: 2px !important;
  box-shadow: var(--shadow-sm);
}

/* ── Right-edge map control strip ────────────────────────────── */
.map-ctrl-strip {
  position: absolute;
  /* Sit below the floating topbar (Phase D): topbar bottom edge
     ≈ 54 px, plus a little breathing room. */
  top: 66px;
  right: 12px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  z-index: 1600;     /* above reroute banner so it can never cover them */
}

.map-ctrl-btn {
  width: 36px;
  height: 36px;
  border-radius: var(--radius);
  background: var(--overlay-bg) !important;
  border: 1px solid var(--border) !important;
  color: var(--fg) !important;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  font-size: 16px;
  padding: 0 !important;
  box-shadow: var(--shadow-sm);
  transition: background 120ms, border-color 120ms, color 120ms;
}
.map-ctrl-btn:hover {
  background: var(--hover-bg) !important;
  border-color: var(--border-strong) !important;
  color: var(--accent) !important;
}

/* Draggable start / end endpoint dots (Rule #66) — sit on top of the
   Plotly Scattermap markers so the user can grab them.  Sized a bit
   bigger than .route-waypoint so the hit target is generous; the
   visible halo + colour come from the Plotly trace below. */
.route-endpoint-drag {
  position: absolute;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;       /* hit-tested in JS via lat/lon */
  z-index: 1490;              /* below .route-waypoint, above polyline */
  border: 2px solid rgba(0, 0, 0, 0);
  cursor: grab;
}
.route-endpoint-start { background: rgba(0, 216, 106, 0.30); }
.route-endpoint-end   { background: rgba(229, 57, 53, 0.30); }

/* Pending import slice endpoints (#85 V3 step 4) — distinct square
   shape so the user reads them as "not the route's endpoints, drag
   me to cut the imported track".  Gray fill, dark border, same
   z-index family as the round route endpoints. */
.pending-endpoint {
  position: absolute;
  width: 18px;
  height: 18px;
  transform: translate(-50%, -50%) rotate(45deg);
  pointer-events: none;
  z-index: 1490;
  border: 2px solid #1a1a2e;
  border-radius: 3px;
  box-shadow: 0 0 4px rgba(0, 0, 0, 0.6);
  cursor: grab;
}
.pending-endpoint-start { background: #6fd17e; }   /* greener, "from" */
.pending-endpoint-end   { background: #d97a7a; }   /* redder, "to" */

/* Floating "+ Add this slice" pill (#85 V3 step 3) — sits at the
   midpoint of each pending track's slice.  Hover lifts opacity so it
   reads as an actionable button. */
/* Pending pill.  Two flavours:
   * solo  — single green "Add this slice" button (regular GPX import).
   * combo — two halves inside one oval: green "Add this slice" on
            the left, white "Elevation" on the right, thin vertical
            divider between (gravel candidates only).
   Outer element carries border-radius + shadow + positioning; the
   inner button halves handle click and colour. */
.pending-add-btn {
  position: absolute;
  transform: translate(-50%, -50%);
  z-index: 1495;
  border: 1px solid #1a1a2e;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
  opacity: 0.9;
  transition: opacity 120ms, transform 120ms;
  display: inline-flex;
  align-items: stretch;
  overflow: hidden;
  pointer-events: auto;      /* override .route-polyline-layer none */
}
.pending-add-btn:hover {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1.04);
}
.pending-add-half {
  padding: 0.35rem 0.7rem;
  border: 0;
  background: transparent;
  cursor: pointer;
  color: #ffffff;
  font: inherit;
  display: inline-flex;
  align-items: center;
}
.pending-add-half-add {
  background: rgba(28, 168, 80, 0.92);
  color: #ffffff;
}
.pending-add-half-add:hover { background: rgba(24, 149, 71, 0.98); }
.pending-add-half-elev {
  background: rgba(255, 255, 255, 0.94);
  color: #0d6efd;
}
.pending-add-half-elev:hover { background: #ffffff; color: #084298; }
.pending-add-divider {
  width: 1px;
  background: rgba(26, 26, 46, 0.55);
  align-self: stretch;
}
.pending-add-btn i { font-size: 14px; vertical-align: -1px; }

/* #168 — floating "dist · gain" chip above the combo pill on
   gravel candidates.  Non-interactive; provides quick stats
   without opening the Import popover or the elevation overlay. */
.pending-info-chip {
  position: absolute;
  transform: translate(-50%, -100%);
  z-index: 1494;
  padding: 2px 8px;
  background: rgba(255, 255, 255, 0.92);
  color: #1a1a2e;
  border: 1px solid rgba(26, 26, 46, 0.35);
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
  pointer-events: none;
}

/* #134 — hover-cancel button for the orphan Draw pending_start.
   Renders as a small red X to the upper-right of the green start
   marker.  Positioned absolutely by JS (m.project(lonlat)) and
   pointer-events:auto so it stays clickable through the
   pointer-events:none route-polyline-layer wrapper. */
.pending-start-cancel {
  position: absolute;
  transform: translate(-50%, -50%);
  z-index: 1500;
  width: 22px;
  height: 22px;
  padding: 0;
  border-radius: 50%;
  background: #d43a3a;
  color: #ffffff;
  border: 2px solid #ffffff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  line-height: 1;
  pointer-events: auto;
  opacity: 0.9;
  transition: opacity 120ms, transform 120ms;
}
.pending-start-cancel:hover {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1.15);
  background: #b02c2c;
}
.pending-start-cancel i {
  font-size: 14px;
  line-height: 1;
}

/* Waypoint dot (Rule #2) — Hammerhead-style: white centre, firm dark
   edge that matches the route polyline's outline so the dot reads as
   part of the route geometry. */
/* Preview line drawn from the planned-route end to the snap-cursor dot
   while the user hovers off-route.  Dimmer than the real polyline so
   it reads as a "this is where a click would extend toward" hint. */
.snap-preview-line {
  pointer-events: none;
  opacity: 0.9;
}

/* In-flight "routing…" dot — shown right after the user commits a click
   or drag-drop while the routing engine works on the new leg. */
.routing-loading-dot {
  position: absolute;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: rgba(255, 200, 60, 0.85);
  border: 2px solid rgba(0, 0, 0, 0.75);
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: 1480;            /* below .route-waypoint (1500) */
  box-shadow: 0 0 0 1.5px rgba(0, 0, 0, 0.35);
  animation: routing-pulse 900ms ease-in-out infinite;
}
@keyframes routing-pulse {
  0%   { transform: translate(-50%, -50%) scale(0.85); opacity: 0.65; }
  50%  { transform: translate(-50%, -50%) scale(1.15); opacity: 1.0;  }
  100% { transform: translate(-50%, -50%) scale(0.85); opacity: 0.65; }
}

/* Failure flash — routing came back unreachable for the dot's position.
   A short, brighter pulse in red so the rejection lands where the user
   was already looking; no side-panel error needed. */
.routing-loading-dot.error {
  background: rgba(255, 70, 70, 0.95);
  border-color: rgba(120, 0, 0, 0.9);
  animation: routing-error 600ms ease-out forwards;
}
@keyframes routing-error {
  0%   { transform: translate(-50%, -50%) scale(0.9);  opacity: 1.0; }
  30%  { transform: translate(-50%, -50%) scale(1.35); opacity: 1.0; }
  100% { transform: translate(-50%, -50%) scale(1.6);  opacity: 0.0; }
}

/* ── #138 — Topbar search ─────────────────────────────────────────
   Persistent input in the topbar with a dropdown of ranked results.
   Sits between the brand and the Import button; results overlay
   under the input so they don't shift the topbar layout. */
.topbar-search {
  position: relative;
  display: flex;
  align-items: center;
  min-width: 220px;
  max-width: 340px;
  flex: 0 1 320px;
}
.topbar-search-icon {
  position: absolute;
  left: 10px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--fg-muted);
  font-size: 14px;
  pointer-events: none;
  z-index: 1;
}
.topbar-search-input.form-control {
  padding: 4px 10px 4px 30px;
  height: 30px;
  border-radius: var(--radius-sm);
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--fg);
  font-size: 13px;
}
.topbar-search-input.form-control:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-soft);
}
.topbar-search-results {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: 2000;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.18);
  max-height: 60vh;
  overflow-y: auto;
  display: none;
}
.topbar-search-results.open {
  display: block;
}
.topbar-search-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  cursor: pointer;
  border-bottom: 1px solid var(--border);
}
.topbar-search-row:last-child { border-bottom: 0; }
.topbar-search-row:hover,
.topbar-search-row.active {
  background: var(--bg-hover);
}
.topbar-search-row-icon {
  font-size: 15px;
  color: var(--fg-muted);
  width: 18px;
  text-align: center;
  flex-shrink: 0;
}
.topbar-search-row-body {
  flex: 1 1 auto;
  min-width: 0;
}
.topbar-search-row-name {
  font-size: 13px;
  color: var(--fg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.topbar-search-row-meta {
  font-size: 11px;
  color: var(--fg-muted);
}
.topbar-search-row-dist {
  font-size: 11px;
  color: var(--fg-muted);
  flex-shrink: 0;
  margin-left: auto;
}
.topbar-search-empty {
  padding: 12px;
  color: var(--fg-muted);
  font-size: 12px;
  text-align: center;
}

/* Search-result overlay layer — sits above the chevron layer so
   the action popover isn't painted over by direction arrows on the
   route.  Pointer-events pass through by default; the pin +
   popover elements opt back in individually.  Same pattern as
   .route-polyline-layer / .chevron-layer above. */
.search-pin-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1500;
  overflow: hidden;
}

/* Result pin dropped on the map after choosing a search result.
   Positioned via m.project(); a small action popover attaches to it
   via .topbar-search-pin-popover.  Visually a red teardrop that
   matches the .pending-start-cancel red palette so all three
   map-hover overlays (add-slice pill, cancel-X, search pin) share
   the same visual family. */
.topbar-search-pin {
  position: absolute;
  transform: translate(-50%, -100%);
  width: 24px;
  height: 32px;
  pointer-events: auto;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.35));
}
/* #140 — auto-route pick markers.  Same silhouette + positioning
   convention as the topbar-search-pin (bottom-of-pin at the projected
   coord).  Coloured green for start, blue for end via inline SVG. */
.auto-route-marker {
  position: absolute;
  transform: translate(-50%, -100%);
  width: 24px;
  height: 32px;
  pointer-events: none;   /* the pin is informational; user still */
                          /* clicks the map to re-pick */
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.35));
}
.auto-route-marker .pin-body {
  width: 100%;
  height: 100%;
  background-repeat: no-repeat;
  background-position: center;
}

.topbar-search-pin .pin-body {
  width: 100%;
  height: 100%;
  background: url("data:image/svg+xml;utf8,\
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 32'>\
<path d='M12 0 C5 0 0 5 0 12 c0 8 12 20 12 20 s12-12 12-20 c0-7-5-12-12-12z' \
fill='%23d43a3a' stroke='%23ffffff' stroke-width='2'/>\
<circle cx='12' cy='12' r='4' fill='%23ffffff'/>\
</svg>") no-repeat center;
}

/* Action popover attached to the pin.  Sizing / shadow / radius /
   font weight consistent with .pending-add-btn (the "Add this slice"
   pill) — same visual family, more room for context (title + sub +
   two actions).  Primary action is the same green pill; the
   Dismiss is a slim ghost sibling so the eye lands on Route first. */
.topbar-search-pin-popover {
  position: absolute;
  transform: translate(-50%, calc(-100% - 40px));
  background: #ffffff;
  border: 1px solid #1a1a2e;
  border-radius: 12px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
  padding: 8px 10px 6px;
  min-width: 200px;
  max-width: 260px;
  pointer-events: auto;
  font-size: 12px;
  color: var(--fg);
}
.topbar-search-pin-popover .pin-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--fg);
  margin-bottom: 2px;
  padding-right: 18px;   /* room for the close X */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.topbar-search-pin-popover .pin-sub {
  font-size: 11px;
  color: var(--fg-muted);
  margin-bottom: 8px;
}
.topbar-search-pin-popover .pin-actions {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.topbar-search-pin-popover .pin-btn {
  border: 1px solid #1a1a2e;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  padding: 0.35rem 0.7rem;
  text-align: center;
  cursor: pointer;
  transition: opacity 120ms, transform 120ms;
  white-space: nowrap;
}
.topbar-search-pin-popover .pin-btn.pin-btn-primary {
  background: rgba(28, 168, 80, 0.92);
  color: #ffffff;
  opacity: 0.9;
}
.topbar-search-pin-popover .pin-btn.pin-btn-primary:hover {
  opacity: 1;
  transform: scale(1.03);
}
.topbar-search-pin-popover .pin-btn.pin-btn-secondary {
  background: #ffffff;
  color: var(--fg-muted);
  border-color: var(--border);
  font-weight: 500;
  opacity: 0.85;
}
.topbar-search-pin-popover .pin-btn.pin-btn-secondary:hover {
  color: var(--fg);
  opacity: 1;
}
.topbar-search-pin-popover .pin-close {
  position: absolute;
  top: 4px;
  right: 4px;
  background: transparent;
  border: 0;
  color: var(--fg-muted);
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  padding: 2px 4px;
}
.topbar-search-pin-popover .pin-close:hover { color: var(--fg); }

/* Snap-to-road cursor preview — translucent ghost dot showing where an
   off-route click would actually land after snap_to_road.  Smaller and
   softer than .route-waypoint so it reads as a hover hint, not a real
   marker. */
.snap-cursor-dot {
  position: absolute;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.45);
  border: 1.5px solid rgba(0, 0, 0, 0.65);
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: 1450;          /* below .route-waypoint (1500) */
  box-shadow: 0 0 4px rgba(0, 0, 0, 0.35);
}

.route-waypoint {
  position: absolute;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid rgba(0, 0, 0, 0.85);
  background: #FFFFFF;
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: 1500;
  box-shadow: 0 0 0 1.5px rgba(0,0,0,0.35),
              0 1px 4px rgba(0,0,0,0.45);
}

/* #124 — hover-only preview waypoint that follows the cursor along the
   route line.  Same shape as .route-waypoint so it reads as a real
   marker dropped at click time, but translucent and one level below in
   z so a real waypoint on top wins visually. */
.route-waypoint-ghost {
  position: absolute;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid rgba(0, 0, 0, 0.55);
  background: rgba(255, 255, 255, 0.6);
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: 1495;
  box-shadow: 0 0 0 1px rgba(0,0,0,0.2);
}
body.ps-route-hidden .route-waypoint-ghost { display: none; }

/* Thin divider between nav buttons and layer controls */
.ctrl-sep {
  width: 28px;
  margin: 2px 0;
  border-color: #3a3a5c;
  opacity: 1;
}

/* #147 — the underlying dbc.Select is hidden; picker UI lives in
   the popover instead.  Kept the class for defensive styling. */
.layer-select { display: none !important; }

/* Basemap-picker popover (opens on the LEFT of the map-ctrl-strip). */
.layer-select-popover {
  z-index: 1800 !important;
}
.layer-popover-body {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 6px;
  min-width: 140px;
}
.layer-popover-body .layer-opt-btn {
  text-align: left;
  padding: 6px 10px !important;
  font-size: 12px !important;
  border-radius: var(--radius-sm) !important;
  background: var(--bg) !important;
  border: 1px solid var(--border) !important;
  color: var(--fg) !important;
}
.layer-popover-body .layer-opt-btn:hover,
.layer-popover-body .layer-opt-btn:focus {
  background: var(--hover-bg) !important;
  border-color: var(--accent) !important;
  color: var(--accent) !important;
}

/* #180 — Overlays popover.  Same shell as layer-select-popover
   but each row is a checkbox instead of a button so the user can
   turn several overlays on at once. */
.overlays-popover {
  z-index: 1800 !important;
}
.overlays-popover-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px 10px;
  min-width: 190px;
}
.overlays-popover-body .overlay-check {
  margin: 0 !important;
  padding: 4px 8px;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.overlays-popover-body .overlay-check:hover {
  background: var(--hover-bg);
}
.overlays-popover-body .overlay-check label {
  font-size: 13px;
  color: var(--fg);
  cursor: pointer;
  user-select: none;
  margin-left: 6px;
}
/* Active badge on the toolbar button when any overlay is on. */
#overlays-btn.has-active::after {
  content: "";
  position: absolute;
  top: 4px;
  right: 4px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 1px var(--bg);
}
#overlays-btn { position: relative; }

/* #121 — reskin dbc.Switch as a 36×36 square icon button.  The
   real <input type=checkbox> is hidden; the label (which already
   carries the for=switch-id wire-up) is restyled into the visible
   button.  Active state (checkbox checked) lifts the background. */
.overlay-switch {
  background: transparent;
  border: none;
  padding: 0;
  margin: 0;
  min-height: 0;
  display: block;
}
.overlay-switch .form-check-input {
  /* visually-hidden but still focusable + clickable via label */
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap; border: 0;
  opacity: 0;
}

/* #133 — POI category checkboxes (and any other real checkboxes
   inside popovers) were invisible on the light popover body:
   Bootstrap's default checkbox border is a subtle gray-on-white
   that all but vanishes at 1 em × 1 em.  Give the input an
   explicit visible border and a green fill on checked, matching
   Direction B's accent everywhere else.  Scope excludes the
   `.overlay-switch` variant above and the dbc.Switch UI. */
.popover-body .form-check-input:not([type="radio"]),
.form-check-input:not([type="radio"]):not(.overlay-switch .form-check-input) {
  background-color: var(--bg);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
}
.popover-body .form-check-input:checked,
.form-check-input:checked:not(.overlay-switch .form-check-input) {
  background-color: var(--accent);
  border-color: var(--accent);
}
.popover-body .form-check-input:focus,
.form-check-input:focus:not(.overlay-switch .form-check-input) {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-soft);
}
.popover-body .form-check-label {
  color: var(--fg);
  cursor: pointer;
}
.overlay-switch .form-check-label {
  width: 36px;
  height: 36px;
  border-radius: var(--radius);
  background: var(--overlay-bg);
  border: 1px solid var(--border);
  color: var(--fg);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  font-size: 18px;
  margin: 0;
  padding: 0;
  transition: background 120ms, border-color 120ms, color 120ms;
  box-shadow: var(--shadow-sm);
}
.overlay-switch .form-check-label:hover {
  background: var(--hover-bg);
  border-color: var(--border-strong);
  color: var(--accent);
}
.overlay-switch .form-check-input:focus-visible + .form-check-label {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
/* Checked / active state — clearly different than hover. */
.overlay-switch .form-check-input:checked + .form-check-label {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--fg-inverse);
}
/* #123 — How-to-use modal.  Centered over the map, dark theme to
   match the rest of the app, z-index above the right-edge map
   control strip (1600) so no map UI peeks through. */
.help-modal .modal-dialog {
  z-index: 2000;
}
.help-modal ~ .modal-backdrop,
.modal-backdrop.show {
  z-index: 1990;
}
.help-modal .modal-content {
  background: #1a1a2e;
  color: #e0e0e8;
  border: 1px solid #3a3a5c;
  border-radius: 8px;
}
.help-modal .modal-header {
  border-bottom: 1px solid #2a2a4a;
  padding: 14px 18px;
}
.help-modal .modal-title {
  color: #ffffff;
  font-size: 16px;
  font-weight: 600;
}
.help-modal .btn-close {
  filter: invert(1) opacity(0.7);
}
.help-modal .btn-close:hover {
  filter: invert(1) opacity(1);
}
.help-modal .modal-body {
  padding: 16px 22px 22px;
  max-height: 75vh;
  overflow-y: auto;
}
.help-intro {
  color: #9aa0b0;
  font-size: 12px;
  margin-bottom: 14px;
}
.help-section {
  margin-bottom: 18px;
  padding-bottom: 14px;
  border-bottom: 1px solid #2a2a4a;
}
.help-section:last-of-type {
  border-bottom: none;
  margin-bottom: 0;
}
.help-section-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
}
.help-section-icon {
  font-size: 22px;
  color: #8080e0;
  flex-shrink: 0;
}
.help-section-title {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  color: #ffffff;
}
.help-section-list {
  margin: 0;
  padding-left: 22px;
  list-style: none;
}
.help-section-list li {
  position: relative;
  font-size: 12.5px;
  line-height: 1.55;
  color: #c0c5d4;
  margin-bottom: 6px;
}
.help-section-list li::before {
  content: "→";
  position: absolute;
  left: -18px;
  color: #6060c0;
  font-weight: 600;
}

/* #122 — dark, compact tooltip skin (overrides Bootstrap default
   black-on-grey with our app palette). */
/* Bootstrap tooltip — kept SLIGHTLY dark (fg-inverse-on-fg) so
   tooltips read as UI chrome, not as inline content. */
.ps-tooltip .tooltip-inner {
  background: var(--fg);
  color: var(--fg-inverse);
  border: 1px solid var(--fg);
  border-radius: var(--radius-sm);
  font-size: 11px;
  padding: 4px 8px;
  max-width: 240px;
  box-shadow: var(--shadow-md);
}
.ps-tooltip .tooltip-arrow::before {
  border-top-color:    var(--fg) !important;
  border-bottom-color: var(--fg) !important;
  border-left-color:   var(--fg) !important;
  border-right-color:  var(--fg) !important;
}

.overlay-switch .form-check-input:checked + .form-check-label:hover {
  background: var(--accent-hover);
}

/* ── Upload zone ─────────────────────────────────────────────── */
.upload-zone {
  border: 2px dashed var(--border-strong);
  border-radius: var(--radius-lg);
  padding: 16px;
  text-align: center;
  color: var(--fg-muted);
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
}
.upload-zone:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
}

/* ── Track cards ─────────────────────────────────────────────── */
.track-card {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px 10px 8px;
  margin-bottom: 10px;
  box-shadow: var(--shadow-sm);
}
.track-card-header {
  display: flex;
  align-items: center;
  margin-bottom: 6px;
}
.track-color-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  margin-right: 8px;
  flex-shrink: 0;
}
.track-filename {
  font-size: 12px;
  font-weight: 600;
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── Elevation graph (per track) ─────────────────────────────── */
.elev-graph {
  margin-top: 4px;
}

/* ── Defaults section (under track list) ──────────────────────── */
.defaults-sep {
  border-color: var(--border);
  margin: 16px 0 10px;
  opacity: 1;
}
.defaults-label {
  font-size: 11px;
  color: var(--fg-muted);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin-bottom: 4px;
}
/* #125 — cycling-speed input row in the Settings popover. */
.defaults-speed-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
.default-speed-input {
  width: 80px !important;
  background: var(--bg) !important;
  border: 1px solid var(--border) !important;
  color: var(--fg) !important;
  border-radius: var(--radius-sm);
}
.default-speed-input:focus {
  border-color: var(--accent) !important;
  box-shadow: 0 0 0 2px var(--accent-soft) !important;
}
.defaults-unit {
  font-size: 12px;
  color: var(--fg-muted);
}
/* #142 — day-of-week picker sits next to the time input. */
.default-start-day-select {
  width: auto !important;
  background: var(--bg) !important;
  border: 1px solid var(--border) !important;
  color: var(--fg) !important;
  border-radius: var(--radius-sm);
  padding: 3px 8px !important;
  font-size: 12px !important;
}

/* #132 — reverse route direction controls in the Settings popover. */
.reverse-controls-sep {
  border-color: var(--border);
  margin: 14px 0 10px;
  opacity: 1;
}
.reverse-controls .reverse-btn {
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--fg);
  text-align: left;
  font-size: 13px;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
}
.reverse-controls .reverse-btn:hover:not(:disabled) {
  background: var(--bg-hover);
  border-color: var(--accent);
  color: var(--accent);
}
.reverse-controls .reverse-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ── Per-track stats row (under elevation graph) ────────────── */
.track-stats-row {
  display: flex;
  gap: 6px;
  margin-top: 4px;
  flex-wrap: wrap;
  align-items: center;
}
.track-sparkline {
  display: inline-block;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg);
  flex-shrink: 0;
}
/* #179 — surface mix chip on gravel candidate cards.  Segmented
   bar of surface classes weighted by length, plus a short label
   for the dominant class. */
.surface-mix-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.surface-mix-bar {
  display: inline-flex;
  width: 48px;
  height: 8px;
  border: 1px solid var(--border);
  border-radius: 2px;
  overflow: hidden;
  flex-shrink: 0;
}
.surface-mix-seg {
  display: inline-block;
  height: 100%;
}
/* #175 — post-discovery filter bar in the Import popover. */
.track-filter-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 0 6px;
  flex-wrap: wrap;
}
.track-filter-bar .filter-check {
  margin-right: 4px !important;
}
.track-filter-bar .filter-num {
  width: 68px;
  padding: 2px 6px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg);
  font-size: 12px;
}
.track-card-hidden { display: none !important; }
.track-filter-bar .filter-sort {
  width: 160px;
  font-size: 12px;
}
.track-filter-bar .filter-sort .Select-control {
  min-height: 26px;
  height: 26px;
}
.track-filter-bar .filter-sort .Select-value,
.track-filter-bar .filter-sort .Select-value-label,
.track-filter-bar .filter-sort .Select-placeholder {
  line-height: 24px !important;
  font-size: 12px !important;
}
.track-stat {
  font-size: 11px;
  color: var(--fg-muted);
  background: var(--panel-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 2px 8px;
  white-space: nowrap;
}
/* #88 — pre-import warning when a pending track contains stretches of
   HC_HARD or harder.  Amber background stands out against the
   muted standard chips. */
.track-stat-warn {
  color: #8a5a10;
  background: var(--warn-soft);
  border-color: var(--warn);
}

/* ── Queue panel (inside left-route) ─────────────────────────── */
.queue-panel-inner {
  display: flex;
  flex-direction: column;
  gap: 6px;
  overflow-y: auto;
  flex: 1 1 0;
  min-height: 0;
}

/* ── Queue segment card ──────────────────────────────────────── */
.queue-segment {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px 10px;
  font-size: 12px;
  cursor: default;
  transition: border-color 0.1s, background 0.1s;
}
.queue-segment:hover {
  border-color: var(--accent);
  background: var(--hover-bg);
}

/* ── Queue gap card ──────────────────────────────────────────── */
.queue-gap {
  background: var(--warn-soft);
  border: 1px dashed var(--warn);
  border-radius: var(--radius);
  padding: 8px 10px;
  font-size: 12px;
}
.gap-icon-row {
  display: flex;
  gap: 4px;
  margin-top: 4px;
}
.gap-icon-btn {
  width: 28px;
  height: 24px;
  padding: 0 !important;
  font-size: 13px;
  border-radius: var(--radius-sm);
  color: var(--fg-muted) !important;
  background: var(--bg) !important;
  border: 1px solid var(--border) !important;
  transition: color 0.12s, border-color 0.12s, background 0.12s;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.gap-icon-btn:hover {
  color: var(--fg) !important;
  border-color: var(--border-strong) !important;
  background: var(--hover-bg) !important;
}
.gap-icon-btn.active {
  color: var(--accent) !important;
  border-color: var(--accent) !important;
  background: var(--accent-soft) !important;
}

/* ── Stats bar (#133 Phase D) ───────────────────────────────────
   Floats over the map like the topbar: a translucent white card
   pinned to the bottom of the viewport with a subtle shadow.
   `.elev-scrubber-line` positioning still works because the card
   is `position: absolute` — its own children can be absolute
   against it. */
.stats-bar {
  position: absolute;
  bottom: 12px;
  left: 12px;
  right: 12px;
  z-index: 1750;
  background: var(--floating-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  backdrop-filter: blur(8px) saturate(1.1);
  padding: 8px 14px !important;
  max-height: 45vh;
  overflow-y: auto;
}
.elev-scrubber-line {
  position: absolute;
  width: 0;
  border-left: 2px dotted #FFFF00;
  pointer-events: none;
  z-index: 5;
}
.stat-chips-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
}
.stat-chip {
  font-size: 12px;
  color: var(--fg);
  background: var(--panel-bg);
  border: 1px solid var(--border);
  border-radius: var(--pill);
  padding: 3px 10px;
  display: inline-flex;
  align-items: center;
}

/* ── View-mode picker (above the elevation chart) ────────────────── */
.view-mode-picker {
  display: inline-flex;
  gap: 4px;
  margin-left: auto;   /* push to the right end of the chips row */
}
.view-mode-btn {
  font-size: 11px !important;
  padding: 3px 10px !important;
  color: var(--fg-muted) !important;
  border-radius: var(--pill);
  background: var(--panel-bg);
  border: 1px solid var(--border);
  transition: background 120ms, color 120ms, border-color 120ms;
}
.view-mode-btn:hover { color: var(--fg) !important; background: var(--hover-bg); }
.view-mode-btn.active {
  color: var(--accent) !important;
  background: var(--accent-soft) !important;
  border: 1px solid var(--accent) !important;
}
.view-mode-btn.disabled, .view-mode-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* ── Composition stats (per active view mode) ────────────────── */
.comp-stats {
  display: inline-flex;
  gap: 10px;
  font-size: 11px;
  color: var(--fg-muted);
  margin-left: 8px;
}
.comp-stat { display: inline-flex; align-items: center; gap: 4px; }
.comp-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  border: 1px solid rgba(0, 0, 0, 0.6);
}
.route-elev-graph { display: block; }
.route-elev-graph .js-plotly-plot,
.route-elev-graph .plotly,
.route-elev-graph .plot-container { height: 90px !important; }

/* ── Label strip above the elevation graph ────────────────────────────── */
.label-strip {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: 0;
  margin-top: 4px;
  margin-bottom: 2px;
  /* Match the plotly chart's left/right margin so each label cell sits
     directly above the curve segment for that section. */
  padding-left: 38px;
  padding-right: 8px;
  height: 16px;
  user-select: none;
}
.label-strip-cell {
  position: relative;             /* anchor for the absolute merge button */
  border-left: 1px dotted var(--border-strong);
  /* Tiny sections still get a clickable target. */
  min-width: 4px;
  cursor: pointer;
  padding: 0 4px;
  background: var(--bg);
  transition: background 80ms ease-out;
  /* overflow:hidden would clip the merge button — clip name with the
     inner span's text-truncate instead. */
}
.label-strip-cell:first-child { border-left: none; }
.label-strip-cell:hover { background: var(--hover-bg); }
.label-strip-cell.selected {
  background: var(--accent-soft);
  border-left: 1px solid var(--accent);
}
.label-strip-name {
  font-size: 10px;
  font-weight: 600;
  color: var(--fg-muted);
  pointer-events: none;
}
/* ── Merge-with-next button, sits straddling the right edge of a cell.
   Hidden until the user hovers either cell at the joint, then fades in
   so the affordance only appears when relevant. */
.label-strip-merge-btn {
  position: absolute;
  top: 50%;
  right: -8px;                    /* half-overlap with the next cell */
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--accent);
  background: var(--bg);
  color: var(--accent);
  font-size: 10px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  opacity: 0;
  transition: opacity 100ms ease-out, background 100ms ease-out;
  z-index: 30;
  box-shadow: var(--shadow-sm);
}
.label-strip-cell:hover > .label-strip-merge-btn,
.label-strip-cell + .label-strip-cell:hover > .label-strip-merge-btn,
.label-strip-merge-btn:hover {
  opacity: 1;
}
.label-strip-merge-btn:hover {
  background: var(--accent);
  color: var(--fg-inverse);
}

/* Inspector — shows controls for whichever clip is currently selected. */
.inspector {
  margin-top: 4px;
  padding: 6px 10px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  min-height: 32px;
  box-shadow: var(--shadow-sm);
}
.inspector-empty {
  margin-top: 4px;
  padding: 6px 12px;
  background: var(--panel-bg);
  border-radius: var(--radius);
  text-align: center;
  color: var(--fg-muted);
}
.inspector-stats {
  flex-shrink: 0;
  font-size: 12px;
  color: var(--fg);
}
.inspector-stat {
  font-size: 11px;
  color: var(--fg-muted);
}
.inspector-section-name {
  cursor: text;
  border-bottom: 1px dashed transparent;
  padding-bottom: 1px;
  transition: border-bottom-color 0.15s;
}
.inspector-section-name:hover {
  border-bottom-color: var(--border-strong);
}
/* ── Route-name editor in the top bar (Route metadata #44). */
.route-name-input {
  width: 240px;
  background: var(--bg) !important;
  border: 1px solid var(--border) !important;
  color: var(--fg) !important;
}
.route-name-input::placeholder {
  color: var(--fg-subtle);
}
.route-name-input:focus {
  background: var(--bg) !important;
  border-color: var(--accent) !important;
  box-shadow: 0 0 0 2px var(--accent-soft) !important;
  color: var(--fg) !important;
}

.inspector-section-name-input {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--fg);
  padding: 2px 6px;
  font-size: inherit;
  font-weight: 600;
  font-family: inherit;
  outline: none;
  margin-left: 8px;
  min-width: 160px;
}
.inspector-section-name-input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-soft);
}
.inspector-label {
  font-size: 10px;
  color: var(--fg-muted);
  text-transform: uppercase;
  letter-spacing: 0.4px;
}
.inspector-btn { padding: 2px 6px !important; font-size: 12px !important; }
/* #133 — destructive section-card buttons (queue-remove / gap-remove)
   share the topbar-danger token: light-red fill + border, filled
   danger on hover.  Matches Clear all visually so the "removes
   things" affordance is consistent. */
.inspector-btn.text-danger {
  background: var(--danger-soft) !important;
  border: 1px solid var(--danger) !important;
  color: var(--danger) !important;
  border-radius: var(--radius-sm);
  padding: 3px 8px !important;
}
.inspector-btn.text-danger:hover {
  background: var(--danger) !important;
  color: var(--fg-inverse) !important;
}

/* #133 — inline rerouting spinner.  Sits inline at the right edge
   of the section-card row so it drops in/out without pushing the
   elevation chart around.  Hidden by default; the clientside
   callbacks in app.py add `.rerouting` on click of any per-segment
   preference button and clear it when store-queue updates. */
.stats-inspector-loading-parent {
  position: relative;
}
.inspector-reroute-spinner {
  display: none;                 /* hidden until .rerouting fires */
  position: absolute;
  right: 14px;
  top: 8px;
  width: 24px;
  height: 24px;
  font-size: 18px;
  line-height: 24px;
  text-align: center;
  color: var(--accent);
  animation: ps-spin 900ms linear infinite;
  pointer-events: none;
  z-index: 5;
}
.stats-inspector-loading-parent.rerouting .inspector-reroute-spinner {
  display: inline-block;
}
@keyframes ps-spin {
  to { transform: rotate(360deg); }
}
.inspector-icon-btn {
  padding: 3px 8px !important;
  font-size: 12px !important;
  color: var(--fg-muted) !important;
  border-radius: var(--radius-sm);
  transition: background 120ms, color 120ms;
}
.inspector-icon-btn:hover {
  background: var(--hover-bg) !important;
  color: var(--fg) !important;
}
.inspector-icon-btn.active {
  color: var(--accent) !important;
  background: var(--accent-soft) !important;
  border-radius: var(--radius-sm);
}

/* ── Drag-to-reroute anchors (HTML overlay on map) ───────────── */
.drag-anchor {
  position: absolute;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid #000;
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: 1500;
  box-shadow: 0 0 0 1px rgba(255,255,255,0.4), 0 0 6px rgba(0,0,0,0.5);
}

/* ── Route polylines (SVG overlay on map) ────────────────────── */
/* Sits BELOW the chevron layer so direction arrows draw on top of
   the route line.  pointer-events: none so MapLibre still receives
   pan / zoom / click; the polyline JS hooks mousemove directly on
   the MapLibre instance. */
.route-polyline-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1300;
  overflow: hidden;
}
.route-polyline-layer svg {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
.route-hover-tooltip {
  position: absolute;
  pointer-events: none;
  background: var(--fg);
  color: var(--fg-inverse);
  padding: 4px 8px;
  border-radius: var(--radius-sm);
  font-size: 11px;
  border: 1px solid var(--fg);
  box-shadow: var(--shadow-md);
  z-index: 1600;
  white-space: nowrap;
  transform: translate(8px, 8px);
}

/* ── Direction chevrons (HTML overlay on map) ────────────────── */
.chevron-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1400;
  overflow: hidden;
}
.chevron {
  position: absolute;
  width: 0;
  height: 0;
  border-left:  6px solid transparent;
  border-right: 6px solid transparent;
  border-bottom: 10px solid #fff;     /* color set per element via JS */
  filter: drop-shadow(0 0 1px rgba(0,0,0,0.85));
  pointer-events: none;
  will-change: transform;
}

/* ── Cursor snap indicator (when in Draw / Reroute mode) ─────────── */
.snap-indicator {
  position: absolute;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: rgba(0, 216, 106, 0.30);
  border: 2px solid #00D86A;
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.6), 0 0 8px rgba(0, 216, 106, 0.6);
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: 1500;     /* above chevrons */
  display: none;
  will-change: transform, left, top;
}

/* ── Elevation-bar scrubber dot (HTML overlay on map) ─────────── */
.scrubber-dot {
  position: absolute;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #FFF200;
  border: 2px solid #1a1a2e;
  box-shadow: 0 0 8px rgba(255, 242, 0, 0.7);
  pointer-events: none;
  z-index: 1500;
  transform: translate(-50%, -50%);
  transition: opacity 0.1s;
}


/* POI hover label (#101) — small absolutely-positioned card pinned
   above the hovered POI dot.  poi_hover.js sets its left/top via
   MapLibre.project() on every map move + hover. */
.poi-hover-label {
  position: absolute;
  transform: translate(-50%, calc(-100% - 14px));
  background: var(--fg);
  color: var(--fg-inverse);
  padding: 6px 10px;
  border-radius: var(--radius);
  border: 1px solid var(--fg);
  box-shadow: var(--shadow-md);
  font-size: 12px;
  line-height: 1.4;
  pointer-events: none;
  white-space: nowrap;
  max-width: 280px;
  z-index: 1700;
}
.poi-hover-name  { font-weight: 600; }
.poi-hover-cat   { opacity: 0.78; }
.poi-hover-hours { opacity: 0.65; font-size: 11px; margin-top: 2px; }


/* ── Dash range slider overrides ─────────────────────────────── */
.rc-slider-track { background-color: var(--accent) !important; }
.rc-slider-handle { border-color: var(--accent) !important; background: var(--accent) !important; }

/* Modals must clear the route-overlay z-index stack (waypoints at
   1500, polylines + chevrons up to 1600).  Bootstrap defaults
   (1050 / 1055) would let our SVG and HTML overlays bleed through. */
.modal-backdrop { z-index: 2000 !important; }
.modal          { z-index: 2001 !important; }
.toast-container, .toast { z-index: 2100 !important; }
/* Bootstrap's default popover z-index (1070) sits below our
   map-ctrl-strip (1600) AND the floating stats-bar (1750), so
   topbar popovers get covered by both.  Lift above everything
   with 1800.  #140: this became critical when the Generate button
   ended up at the bottom of the Settings popover — with a route
   loaded the stats-bar's top overlapped it. */
.popover { z-index: 1800 !important; }

/* ── #154 — Map scale bar (bottom-left) ───────────────────────────
   Custom drop-in for MapLibre's ScaleControl (see
   assets/map_scale.js).  The container IS the bar — its ``width``
   is the "nice" metres in pixels, and its label sits centered
   inside it.  Semi-transparent chip so the map underneath stays
   partly visible.

   ``!important`` on padding / border / background beats MapLibre's
   own ``.maplibregl-ctrl`` reset (same 1-class specificity, loaded
   later in the cascade). */

/* ── #155 — Section overview floating overlay ─────────────────────
   Anchored above the elevation-chart notification marker the user
   tapped.  Positioning is set inline by section_detail.js — CSS
   only owns the shell + typography. */
.section-detail-overlay {
  position: absolute;
  z-index: 1780;                   /* above stats-bar (1750) */
  /* 760 px on desktop for more chart breathing room; clamped by
     viewport minus 24 px so tight phones still fit. */
  width: min(760px, calc(100vw - 24px));
  padding: 10px 12px 8px;
  background: var(--floating-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  backdrop-filter: blur(10px) saturate(1.1);
  color: var(--fg);
  font-size: 12px;
}
/* Tail pointing at the notification marker.  ``--tail-x`` is set
   by the positioning callback so the tail tracks the marker even
   when the overlay is clamped to the viewport edge (in which case
   the overlay body shifts sideways but the tail should keep
   pointing straight down at the marker). */
.section-detail-overlay::after {
  content: "";
  position: absolute;
  left: var(--tail-x, 50%);
  bottom: -8px;
  margin-left: -8px;
  width: 0; height: 0;
  border-left:  8px solid transparent;
  border-right: 8px solid transparent;
  border-top:   8px solid var(--floating-bg);
  filter: drop-shadow(0 1px 0 var(--border));
  transition: left 120ms ease-out;
}
.section-detail-overlay.anchored-below::after {
  bottom: auto;
  top: -8px;
  border-top: none;
  border-bottom: 8px solid var(--floating-bg);
}
.section-detail-close {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 24px;
  height: 24px;
  border: none;
  background: transparent;
  color: var(--fg-muted);
  border-radius: var(--radius-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  font-size: 12px;
  padding: 0;
}
.section-detail-close:hover {
  background: var(--hover-bg);
  color: var(--fg);
}
.section-detail-title {
  font-weight: 600;
  font-size: 13px;
  margin-right: 26px;
  margin-bottom: 6px;
}
.section-detail-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 6px;
  align-items: center;
  margin-bottom: 6px;
}
.section-detail-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 11px;
  padding: 2px 8px;
}
.section-detail-chip-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  border: 1px solid rgba(0, 0, 0, 0.5);
  margin-right: 4px;
}
.section-detail-graph .js-plotly-plot,
.section-detail-graph .plotly,
.section-detail-graph .plot-container {
  height: 280px !important;
}

.ps-scale-bar {
  display: flex !important;
  align-items: center;
  justify-content: center;
  min-width: 60px;
  height: 20px;
  padding: 0 8px !important;
  margin: 0 0 8px 8px !important;
  /* Partially transparent (~78% white) — the map still bleeds
     through, but enough contrast that the label stays legible
     against dense city basemaps as well as pale rural / water
     tiles.  A blur helps low-contrast backgrounds further. */
  background: rgba(255, 255, 255, 0.78) !important;
  border-left:   2px solid var(--fg) !important;
  border-right:  2px solid var(--fg) !important;
  border-bottom: 2px solid var(--fg) !important;
  border-top:    none !important;
  border-radius: 0;
  /* Subtle shadow lifts the chip off dark map tiles (e.g. satellite
     imagery, night mode) so the border edges never disappear. */
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15) !important;
  backdrop-filter: blur(6px) saturate(1.15);
  -webkit-backdrop-filter: blur(6px) saturate(1.15);
  font-size: 12px;
  font-weight: 600;
  color: var(--fg);
  line-height: 1;
  letter-spacing: 0.2px;
  white-space: nowrap;
  user-select: none;
  pointer-events: none;
  box-sizing: border-box;
}

/* ── Popover shell (#133 Phase B) ────────────────────────────────
   Bootstrap's default popover is dark inherited from BS 5's tooltip
   scheme.  Reskin to Direction B light: white body, subtle border +
   shadow, matches the topbar chip aesthetic. */
.popover {
  background: var(--bg) !important;
  border: 1px solid var(--border) !important;
  box-shadow: var(--shadow-md);
  border-radius: var(--radius-lg);
  color: var(--fg);
}
.popover-body {
  color: var(--fg);
  padding: 12px 14px;
}
.popover-header {
  background: var(--panel-bg);
  border-bottom: 1px solid var(--border);
  color: var(--fg);
  font-weight: 600;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
/* Bootstrap paints the popover arrow with its own colour scheme —
   retint the outer + inner triangles so they match the light body
   instead of leaking a dark edge into a light popover. */
.popover .popover-arrow::before { border-color: var(--border) !important; }
.popover .popover-arrow::after  { border-color: transparent !important; }
.bs-popover-bottom > .popover-arrow::after,
.bs-popover-auto[data-popper-placement^="bottom"] > .popover-arrow::after {
  border-bottom-color: var(--bg) !important;
}
.bs-popover-top > .popover-arrow::after,
.bs-popover-auto[data-popper-placement^="top"] > .popover-arrow::after {
  border-top-color: var(--bg) !important;
}
.bs-popover-start > .popover-arrow::after,
.bs-popover-auto[data-popper-placement^="left"] > .popover-arrow::after {
  border-left-color: var(--bg) !important;
}
.bs-popover-end > .popover-arrow::after,
.bs-popover-auto[data-popper-placement^="right"] > .popover-arrow::after {
  border-right-color: var(--bg) !important;
}

/* ── Menu rows (#85 / #133) ──────────────────────────────────────
   Full-width menu items used inside both Export and Import
   dropdowns.  Works on dbc.Button, html.Button, AND the html.Div
   wrapped by dcc.Upload — the tag varies but the visual is the
   same left-aligned icon + label row that fills the popover width
   and hovers to a hover-bg tint. */
.export-row {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  width: 100%;
  padding: 0.45rem 0.6rem;
  margin: 0;
  color: var(--fg) !important;
  text-decoration: none !important;
  background: transparent;
  border: none;
  border-radius: var(--radius-sm);
  font-size: 14px;
  cursor: pointer;
  text-align: left;
  transition: background 100ms ease-out;
}
.export-row:hover {
  background: var(--hover-bg);
}
.export-row[disabled],
.export-row:disabled {
  color: var(--fg-subtle) !important;
  cursor: not-allowed;
}
.export-row[disabled]:hover,
.export-row:disabled:hover {
  background: transparent;
}

/* ── Integrations toolbar (#85) ──────────────────────────────────
   Single horizontal row of icon-only sources at the top of the
   left panel: file upload, URL paste, Hammerhead, Ride with GPS.
   Each .toolbar-icon-btn is a square button — focused state gets
   a brighter border so keyboard nav reads. */
.integrations-toolbar {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
}
.toolbar-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: var(--radius);
  background: var(--panel-bg);
  border: 1px solid var(--border);
  color: var(--fg);
  font-size: 18px;
  padding: 0;
  cursor: pointer;
  transition: background 120ms, border-color 120ms, color 120ms;
}
.toolbar-icon-btn:hover {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent);
}
.toolbar-icon-btn:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-soft);
}

/* ── Browse Hammerhead tile grid (#84) ───────────────────────────
   Auto-fitting grid: tiles are ~220 px wide minimum, columns add as
   the modal grows.  Each tile shows the route thumbnail, name,
   distance, and an Add button. */
.hh-tile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 0.75rem;
  padding: 0.25rem 0;
}
.hh-tile {
  display: flex;
  flex-direction: column;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 0.5rem;
  box-shadow: var(--shadow-sm);
  transition: border-color 120ms, box-shadow 120ms;
}
.hh-tile:hover {
  border-color: var(--border-strong);
  box-shadow: var(--shadow-md);
}
.hh-tile-img {
  display: block;
  width: 100%;
  height: 130px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  background: var(--panel-bg);
}
.hh-tile-meta {
  flex: 1;
  margin: 0.5rem 0;
  overflow: hidden;
}
.hh-tile-name {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.hh-tile-add {
  align-self: flex-end;
}

/* ── #89 — Hold Shift to fade the route overlay ────────────────
   When shift_hide.js detects Shift-down on document, it toggles
   `ps-route-hidden` on <body>.  We fade the SVG polyline layer,
   chevrons, waypoints, endpoint handles, and pending-import dots
   so the user can peek at what's under the route.  Pointer-events
   off so the underlying MapLibre canvas takes clicks during the
   peek. */
body.ps-route-hidden #route-polyline-layer svg,
body.ps-route-hidden .chevron-layer,
body.ps-route-hidden .chevron,
body.ps-route-hidden .route-endpoint-drag,
body.ps-route-hidden .route-waypoint,
body.ps-route-hidden .pending-endpoint,
body.ps-route-hidden .pending-add-btn {
  opacity: 0 !important;
  pointer-events: none !important;
  transition: opacity 0.1s linear;
}
#route-polyline-layer svg,
.chevron-layer,
.chevron,
.route-endpoint-drag,
.route-waypoint,
.pending-endpoint,
.pending-add-btn {
  transition: opacity 0.1s linear;
}
