/* ============================================================
   THEME
   ============================================================ */
:root, [data-theme="light"] {
  --bg: #f5f1e8;
  --surface: #ffffff;
  --surface-2: #faf7f0;
  --line: rgba(28,25,23,0.10);
  --line-2: rgba(28,25,23,0.18);
  --ink: #1c1917;
  --ink-2: #57534e;
  --ink-3: #a8a29e;
  --amber: #f59e0b;
  --amber-2: #d97706;
  --red: #ef4444;
  --green: #65a30d;
  --green-2: #84cc16;
  --blue: #2563eb;
  --cyan: #06b6d4;
  --sign-bg: #ffffff;
  --sign-ring: #ef4444;
  --sign-ink: #1c1917;       /* v22.27: black (was blue #2563eb) */
  --speedo-bg: #1c1917;
  --speedo-ink: #ffffff;
  --shadow: 0 2px 8px rgba(28,25,23,0.06);
}
[data-theme="dark"] {
  --bg: #0c0a09;
  --surface: #1c1917;
  --surface-2: #292524;
  --line: rgba(245,241,232,0.10);
  --line-2: rgba(245,241,232,0.20);
  --ink: #f5f1e8;
  --ink-2: #a8a29e;
  --ink-3: #57534e;
  --shadow: 0 2px 12px rgba(0,0,0,0.5);
}

/* ============================================================
   RESET & BASE
   ============================================================ */
* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { height: 100%; overflow: hidden; }
body {
  font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
  background: var(--bg);
  color: var(--ink);
  height: 100vh; height: 100dvh;
  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
  user-select: none; -webkit-user-select: none;
  font-size: 14px;
  transition: background 0.3s, color 0.3s;
}

/* ============================================================
   LAYOUT
   ============================================================ */
.app {
  max-width: 520px;
  margin: 0 auto;
  height: 100vh; height: 100dvh;
  padding: 8px;
  display: grid;
  /* v22.58: single-column outer grid. .map-row now contains map + actions
     + tools rail; the rail extends from the top of the map down to the
     bottom of the action buttons.
     v23.18.15: .route-bar moved INTO .topbar (sits parallel to the
     brand) so the standalone route-bar row is gone — back to 5 rows.
     Rows:
       1. .topbar             auto   ← brand + destination + actions
       2. .diag-strip         auto
       3. .stats              auto
       4. .map-row            1fr   ← map + actions + rail live here
       5. status line         auto */
  grid-template-rows: auto auto auto 1fr auto;
  gap: 8px;
  position: relative;
}

/* v22.58: 2-column × 2-row grid.
     col 1 row 1 = .map-wrap (1fr — fills vertical space)
     col 1 row 2 = .actions  (auto)
     col 2 rows 1-2 = .tools-rail (spans both rows on the right) */
/* v23.10.0: 3-column grid — left rail (67px, 30% narrower than the
   right) | map+actions stack | right rail (96px). The left rail shows
   opposite-direction captures; the right rail keeps the same-direction
   list + speed-limit sign. */
.map-row {
  display: grid;
  grid-template-columns: 67px 1fr 96px;
  grid-template-rows: 1fr auto;
  gap: 6px;
  align-items: stretch;
  min-height: 0;
}
.map-row .tools-rail-left { grid-column: 1; grid-row: 1 / 3; }
.map-row .map-wrap        { grid-column: 2; grid-row: 1; }
.map-row .actions         { grid-column: 2; grid-row: 2; }
.map-row .tools-rail      { grid-column: 3; grid-row: 1 / 3; }
/* v23.11.0: left rail hidden — fall back to the original 2-column
   layout (map+actions | right rail). Toggled via #btn-left-rail. */
.map-row.lr-off { grid-template-columns: 1fr 96px; }
.map-row.lr-off .tools-rail-left { display: none; }
.map-row.lr-off .map-wrap   { grid-column: 1; }
.map-row.lr-off .actions    { grid-column: 1; }
.map-row.lr-off .tools-rail { grid-column: 2; }
/* Left rail is 30% smaller: narrower column (above) + scaled-down
   entries so the emoji + label + distance fit the tighter width. */
.tools-rail-left { padding: 4px 3px 3px; }
.tools-rail-left .timeline-entry { padding: 6px 2px; font-size: 9px; }
.tools-rail-left .timeline-entry .em { font-size: 15px; }
.tools-rail-left .timeline-entry .lbl { font-size: 8px; }
.tools-rail-left .timeline-entry .dist { font-size: 8px; }
.tools-rail-left .timeline-empty { font-size: 8px; }

/* TOP BAR */
.topbar {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 4px 4px 0;
}
.brand {
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: 700;
  font-size: 14px;
}
.brand .dot {
  width: 12px; height: 12px;
  border: 2px solid var(--amber);
  border-radius: 50%;
  position: relative;
}
.brand .dot::after {
  content: '';
  position: absolute;
  inset: 2px;
  background: var(--amber);
  border-radius: 50%;
}
.brand .version {
  color: var(--amber-2);
  font-size: 11px;
  font-weight: 600;
}
/* v23.4.1: alert-engine visibility chip. Hidden in Legacy. Amber in
   Shadow. Red + slow pulse in Active so the driver always knows live
   intelligence has control. */
.brand .intel-indicator {
  display: none;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.06em;
  padding: 1px 5px;
  border-radius: 4px;
  margin-left: 4px;
  line-height: 1.2;
}
.brand .intel-indicator.shadow {
  display: inline-block;
  background: rgba(245, 158, 11, 0.18);
  color: var(--amber-2);
  border: 1px solid var(--amber-2);
}
.brand .intel-indicator.active {
  display: inline-block;
  background: rgba(239, 68, 68, 0.18);
  color: var(--red);
  border: 1px solid var(--red);
  animation: intel-active-pulse 2s ease-in-out infinite;
}
@keyframes intel-active-pulse {
  0%, 100% { box-shadow: 0 0 0 0   rgba(239,68,68,0.0); }
  50%      { box-shadow: 0 0 6px 1px rgba(239,68,68,0.55); }
}
@media (prefers-reduced-motion: reduce) {
  .brand .intel-indicator.active { animation: none; }
}
/* v23.5 Phase 4: offline / degraded chip. Hidden when fully online.
   Amber when suspected (navigator hint or single recent failure);
   red when confirmed (real fetch failures). */
.brand .offline-indicator {
  display: none;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.06em;
  padding: 1px 5px;
  border-radius: 4px;
  margin-left: 4px;
  line-height: 1.2;
}
.brand .offline-indicator.suspected {
  display: inline-block;
  background: rgba(245, 158, 11, 0.18);
  color: var(--amber-2);
  border: 1px solid var(--amber-2);
}
.brand .offline-indicator.offline {
  display: inline-block;
  background: rgba(239, 68, 68, 0.18);
  color: var(--red);
  border: 1px solid var(--red);
  animation: offline-pulse 2.4s ease-in-out infinite;
}
@keyframes offline-pulse {
  0%, 100% { box-shadow: 0 0 0 0   rgba(239,68,68,0.0); }
  50%      { box-shadow: 0 0 6px 1px rgba(239,68,68,0.4); }
}
@media (prefers-reduced-motion: reduce) {
  .brand .offline-indicator.offline { animation: none; }
}
.topbar-spacer { flex: 1; }
/* v23.18.14 — actions are a vertical stack of two right-aligned rows. */
.topbar-actions {
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: flex-end;
}
.topbar-actions-row {
  display: flex;
  gap: 4px;
  align-items: center;
}
.icon-btn {
  appearance: none;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  width: 32px; height: 32px;
  border-radius: 8px;
  cursor: pointer;
  display: grid; place-items: center;
  font-size: 14px;
  padding: 0;
  transition: transform 0.1s;
}
.icon-btn:active { transform: scale(0.92); }
/* v23.9.2: highlighted "on" state for topbar icon-buttons that act as
   toggles (e.g. #btn-drive-view). Uses the same amber accent the
   nav/pitch buttons use on the map overlay. */
.icon-btn.on {
  background: var(--amber);
  color: #0c0a09;
  border-color: var(--amber-2);
}

/* ROUTE BAR */
.route-bar {
  display: grid;
  grid-template-columns: 1fr;
  gap: 6px;
  min-width: 0;
}
.route-bar .route-current {
  width: 100%;
  min-width: 0;
  overflow: hidden;
}
.route-bar .route-current > #route-name {
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.route-bar .route-current > .arrow {
  flex: 0 0 auto;
}
/* v23.18.16 — left-column wrapper inside .topbar. Stacks the brand on
   top and the destination card directly underneath, while .topbar
   itself stays a single flex row that pushes the action stack to the
   right edge. min-width:0 lets the destination text ellipsis-truncate
   instead of expanding the column. */
.topbar .topbar-left {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  min-width: 0;
}
/* v23.18.15/16 — inline variant of the destination card. Renders
   directly under the logo + version inside .topbar-left. Compact
   font + padding so the topbar stays tight. */
.topbar .route-bar-inline {
  display: flex;
  min-width: 0;
  max-width: 240px;
  flex: 0 1 auto;
}
.topbar .route-bar-inline .route-current {
  padding: 2px 8px;
  font-size: 10px;
  border-radius: 6px;
  gap: 4px;
  line-height: 1.2;
}
.topbar .route-bar-inline .route-current .arrow { font-size: 11px; }
/* v22.12: tiny live diagnostic strip — easy to glance at while driving */
/* v23.1.4: diag-strip is now a layout shell only — each of the three
   groups is its own card (surface + border + radius), modeled on the
   Speed / Next-ahead .stat-card pair. The shell has no background, no
   border, no padding of its own. Single-line nowrap is enforced; cards
   shrink with ellipsis under pressure; the center (network) card never
   shrinks so the connection status is always visible. */
/* v23.1.5: grid layout — left card hugs the left edge, right card
   hugs the right edge, center card sits truly in the middle of the
   row regardless of side widths. Cards are content-sized; the 1fr
   side columns absorb any extra horizontal space. */
/* v23.1.9: asymmetric columns — middle card is a small fixed-width
   slot for the network dot, side cards expand to fill the leftover
   width equally. Everything is still locked-fixed (no resizing on
   text changes) because the middle column is a pixel value and the
   sides are equal 1fr minmaxes. */
.diag-strip {
  display: grid;
  grid-template-columns: minmax(0,1fr) 48px minmax(0,1fr);
  align-items: stretch;
  gap: 6px;
  margin-top: 4px;
  font-family: var(--mono, monospace);
  font-size: 10px;
  color: var(--ink-2);
  line-height: 1.4;
  min-width: 0;
}
.diag-strip .diag-group {
  display: flex;
  align-items: center;
  gap: 4px 10px;
  flex-wrap: nowrap;
  min-width: 0;
  width: 100%;
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 4px 8px;
}
.diag-strip .diag-left   { justify-content: flex-start; }
.diag-strip .diag-center { justify-content: center; overflow: visible; } /* v23.1.6: allow the pulse ring to extend past the card border */
.diag-strip .diag-right  { justify-content: flex-end; }
.diag-strip span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.diag-strip .good { color: var(--green); font-weight: 700; }
.diag-strip .bad  { color: var(--red); font-weight: 700; }
.diag-strip .warn { color: var(--amber-2); font-weight: 700; }
/* v23.8.0: known-road indicator — non-modal, sits inside the
   diag-right cell next to ETA. Shown only when ≥ 3 observations
   are matched ahead of the driver from the global pool. */
.diag-strip .diag-known { color: var(--ink-2, var(--ink-3)); margin-left: 6px; font-size: 11px; }
.diag-strip .diag-known.good { color: var(--green); font-weight: 700; }
.diag-strip .diag-known[hidden] { display: none; }

/* v23.1.7: keep the real 🟢 emoji and add a neon-green glow ring that
   pulses outward from behind it every second, peaking at 10% larger
   than the emoji's character box (scale 1.1). When offline the emoji
   becomes 🔴 and the pulse is suppressed.
   v23.1.10: the EMOJI itself now flashes neon green once per second
   via filter: drop-shadow() keyframes. Combined with the outward
   pulse ring this gives a single bright neon-green flash that
   originates from the glyph and expands past it each beat. */
#diag-net { position: relative; display: inline-flex; align-items: center; justify-content: center; overflow: visible; line-height: 1; }
#diag-net .net-emoji { position: relative; z-index: 1; line-height: 1; font-size: 11px; }

/* v23.1.11: neon-flashing border on the network card itself. The
   .net-online / .net-offline classes are mirrored onto .diag-center
   in JS so the card border can swap colour + animation as the
   connectivity state changes. Same 1-second cadence as the emoji
   flash — the whole card pulses in unison. */
.diag-strip .diag-center.net-online {
  border-color: rgba(34,255,119,0.85);
  animation: diag-card-flash-green 1s ease-in-out infinite;
}
.diag-strip .diag-center.net-offline {
  border-color: rgba(255,51,68,0.85);
  animation: diag-card-flash-red 1s ease-in-out infinite;
}
@keyframes diag-card-flash-green {
  0%, 100% {
    border-color: rgba(34,255,119,0.55);
    box-shadow: 0 0 2px rgba(34,255,119,0.25), inset 0 0 2px rgba(34,255,119,0.15);
  }
  35% {
    border-color: rgba(34,255,119,1);
    box-shadow:
      0 0 6px rgba(34,255,119,0.95),
      0 0 14px rgba(34,255,119,0.6),
      inset 0 0 6px rgba(34,255,119,0.55);
  }
}
@keyframes diag-card-flash-red {
  0%, 100% {
    border-color: rgba(255,51,68,0.55);
    box-shadow: 0 0 2px rgba(255,51,68,0.25);
  }
  35% {
    border-color: rgba(255,51,68,1);
    box-shadow:
      0 0 6px rgba(255,51,68,0.95),
      0 0 14px rgba(255,51,68,0.5);
  }
}

/* Emoji flash — drop-shadow filters work on the rasterised emoji
   glyph so we get a true halo regardless of which font renders it. */
#diag-net.net-online .net-emoji {
  animation: diag-net-emoji-flash 1s ease-in-out infinite;
}
@keyframes diag-net-emoji-flash {
  0%, 100% {
    filter: drop-shadow(0 0 0 rgba(34,255,119,0));
  }
  35% {
    filter:
      drop-shadow(0 0 4px rgba(34,255,119,1))
      drop-shadow(0 0 10px rgba(34,255,119,0.95))
      drop-shadow(0 0 18px rgba(34,255,119,0.7));
  }
  70% {
    filter: drop-shadow(0 0 2px rgba(34,255,119,0.4));
  }
}

/* Outward ring — sits behind the emoji and expands to 10% past the
   emoji's character box each cycle. Synced to the same 1s beat. */
#diag-net.net-online::before {
  content: '';
  position: absolute;
  top: 50%; left: 50%;
  width: 1em; height: 1em;
  margin: -0.5em 0 0 -0.5em;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(34,255,119,0.85) 0%, rgba(34,255,119,0.45) 60%, rgba(34,255,119,0) 100%);
  transform: scale(0);
  transform-origin: center center;
  opacity: 0;
  animation: diag-net-pulse 1s ease-out infinite;
  pointer-events: none;
  z-index: 0;
}
@keyframes diag-net-pulse {
  0%   { transform: scale(0);   opacity: 0.0; }
  20%  { transform: scale(0.4); opacity: 0.85; }
  100% { transform: scale(1.1); opacity: 0.0; }
}

@media (prefers-reduced-motion: reduce) {
  #diag-net.net-online::before    { animation: none; }
  #diag-net.net-online .net-emoji { animation: none; }
  .diag-strip .diag-center.net-online,
  .diag-strip .diag-center.net-offline { animation: none; }
}

/* v22.58: TOOLS RAIL — vertical strip of recent captures, sits in column 2
   of .map-row beside the map. Height matches the map (align-items: stretch
   on the parent). NOT absolutely positioned over the map. */
.tools-rail {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  /* v23.5.3: sign pinned at top, list scrolls below. Rail itself no
     longer scrolls — the inner .rail-list owns scroll. */
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 6px 4px 4px;
  font-family: ui-monospace, monospace;
  min-height: 0;
  height: 100%;
  align-self: stretch;
}
/* v23.5.3: speed-limit sign in the rail. Smaller than the previous SPEED
   card variant (76px vs 80px) so it fits a 96px-wide rail with breathing
   room. Centered horizontally. margin-bottom creates the spacing the spec
   asked for so the captured-point list sits clearly BELOW the sign. */
.tools-rail .rail-sign {
  width: 76px;
  height: 76px;
  flex-shrink: 0;
  align-self: center;
  margin-bottom: 2px;
}
.tools-rail .rail-sign .sign-value { font-size: 30px; }
.tools-rail .rail-sign .sign-label { bottom: 7px; font-size: 8px; }
/* The list takes the remaining vertical space and owns the scroll. */
.tools-rail .rail-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.timeline-entry {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 9px 4px;
  border-bottom: 1px solid var(--line);
  font-size: 12px;
  color: var(--ink-2);
  line-height: 1.2;
  cursor: pointer;
  flex-shrink: 0;
}
.timeline-entry:last-child { border-bottom: none; }
.timeline-entry:active { background: var(--surface-2); }
/* v23.9.1: brief amber outline after a "locate on map" tap so the user
   can see which row the second tap will edit. Fades out after ~600ms
   when the tl-locating class is removed by the click handler. */
.timeline-entry.tl-locating {
  background: rgba(245,158,11,0.18);
  box-shadow: inset 0 0 0 2px var(--amber-2);
  transition: background 0.25s ease-out, box-shadow 0.25s ease-out;
}
/* v22.74: small × delete button on each sidebar entry. Positioned in the
   top-right corner of the entry; tap to delete (with confirm). */
.timeline-entry { position: relative; }
.tl-x {
  position: absolute;
  top: 0;
  right: 0;
  width: 22px;
  height: 22px;
  background: transparent;
  border: none;
  color: var(--ink-3);
  font-size: 20px;
  font-weight: 700;
  line-height: 1;
  padding: 0;
  cursor: pointer;
  border-radius: 50%;
  z-index: 6;
  display: grid;
  place-items: center;
}
.tl-x:active { background: var(--red); color: #fff; }
.timeline-entry .em { font-size: 22px; line-height: 1; margin-bottom: 3px; }
.timeline-entry .lbl {
  font-weight: 700;
  color: var(--ink);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 12px;
}
.timeline-entry .dist {
  color: var(--amber-2);
  margin-top: 4px;
  font-weight: 800;
  font-size: 14px;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.timeline-entry .dist.no-gps { color: var(--ink-3); font-weight: 600; font-size: 11px; }
.timeline-empty {
  padding: 16px 4px;
  text-align: center;
  color: var(--ink-3);
  font-size: 9px;
  font-style: italic;
}

/* v22.65: distance-tier coloring on sidebar entries. Each entry gets a
   tier-* class based on its straight-line distance from current GPS:
     tier-far   (>= proximityStartM, default 1000 m)  →  green
     tier-mid   (between finalM and startM)            →  orange
     tier-near  (< finalM, default 200 m)              →  red
   The ahead-1/2/3 rank classes add flash + heartbeat ON TOP of the
   tier color, so the focused entry flashes in its own tier color
   (green-far → orange-mid → red-near as you close in). */

/* Tier base fills + dist text color */
.timeline-entry.tier-far  { background: rgba(34,197,94,0.15); }
.timeline-entry.tier-far  .dist { color: #16a34a; }
.timeline-entry.tier-mid  { background: rgba(245,158,11,0.22); }
.timeline-entry.tier-mid  .dist { color: var(--amber-2); }
.timeline-entry.tier-near { background: rgba(239,68,68,0.28); }
.timeline-entry.tier-near .dist { color: var(--red); }

/* Ahead-1 (closest): flashes solid tier color on every proximity ping */
.timeline-entry.ahead-1 {
  font-weight: 800;
  transition: background 0.10s linear, transform 0.10s linear, color 0.10s linear;
  position: relative;
  z-index: 5;
}
.timeline-entry.ahead-1.flash-on { transform: scale(1.06); }
.timeline-entry.ahead-1.flash-on,
.timeline-entry.ahead-1.flash-on .lbl,
.timeline-entry.ahead-1.flash-on .dist { color: #0c0a09; }
.timeline-entry.ahead-1.tier-far.flash-on  { background: #22c55e; }
.timeline-entry.ahead-1.tier-mid.flash-on  { background: #f59e0b; }
.timeline-entry.ahead-1.tier-near.flash-on { background: #ef4444; }

/* Ahead-2 / Ahead-3 (next-up): continuous heartbeat in tier color */
.timeline-entry.ahead-2.tier-far,
.timeline-entry.ahead-3.tier-far  { animation: tl-pulse-far  1.4s ease-in-out infinite; }
.timeline-entry.ahead-2.tier-mid,
.timeline-entry.ahead-3.tier-mid  { animation: tl-pulse-mid  1.4s ease-in-out infinite; }
.timeline-entry.ahead-2.tier-near,
.timeline-entry.ahead-3.tier-near { animation: tl-pulse-near 1.4s ease-in-out infinite; }
.timeline-entry.ahead-3 { animation-delay: 0.4s; opacity: 0.92; }

@keyframes tl-pulse-far  { 0%,100% { background: rgba(34,197,94,0.10);  } 50% { background: rgba(34,197,94,0.40);  } }
@keyframes tl-pulse-mid  { 0%,100% { background: rgba(245,158,11,0.12); } 50% { background: rgba(245,158,11,0.42); } }
@keyframes tl-pulse-near { 0%,100% { background: rgba(239,68,68,0.18);  } 50% { background: rgba(239,68,68,0.48);  } }
.route-current {
  appearance: none;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 3px 10px;
  text-align: left;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 6px;
  font: inherit;
  color: var(--amber-2);
  font-weight: 700;
  font-size: 11px;
  line-height: 1.3;
}
.route-current:active { background: var(--surface-2); }
.route-current .arrow { color: var(--amber); font-size: 12px; }

/* STATS ROW */
.stats {
  display: grid;
  /* v22.13 FIX: equal columns. Previously was 1fr / 1.1fr which made
     the Next-ahead card 10% wider than the Speed card — looked off
     especially when empty. */
  grid-template-columns: minmax(0,1fr) minmax(0,1fr);
  gap: 8px;
  min-height: 0;
}
.stat-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 8px 10px;      /* v22.27: tighter (was 10px 12px) */
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow: hidden;        /* v22.27: defensive — content can never escape card border */
}
.stat-label {
  font-size: 11px;
  letter-spacing: 0.1em;
  color: var(--ink-2);
  text-transform: uppercase;
  font-weight: 700;
  margin-bottom: 4px;      /* v22.27: tighter (was 6px) */
}
.speed-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  min-width: 0;          /* v22.27: lets flex children shrink within card */
}
.sign {
  width: 80px; height: 80px;          /* v22.27: bumped from 72 */
  border-radius: 50%;
  border: 6px solid var(--sign-ring);  /* v22.27: thicker ring for visibility */
  background: var(--sign-bg);
  display: grid;
  place-items: center;
  position: relative;
  flex-shrink: 0;
  cursor: pointer;
}
.sign-value {
  font-size: 34px;        /* v22.27: bumped from 26px */
  font-weight: 900;        /* v22.27: extra bold */
  color: var(--sign-ink);
  line-height: 1;
  letter-spacing: -0.04em;
}
.sign-label {
  position: absolute;
  bottom: 8px;             /* v22.27: tighter spacing inside bigger circle */
  font-size: 8px;
  font-weight: 700;
  color: var(--ink-2);
  letter-spacing: 0.04em;
}
.sign.flash { animation: signflash 0.6s ease-out; }
@keyframes signflash {
  0% { box-shadow: 0 0 0 0 rgba(239,68,68,0.8); transform: scale(1); }
  50% { box-shadow: 0 0 0 16px rgba(239,68,68,0); transform: scale(1.05); }
  100% { box-shadow: 0 0 0 0 rgba(239,68,68,0); transform: scale(1); }
}
.speedo {
  flex: 1;                 /* v22.27: fills remaining width */
  min-width: 0;            /* v22.27: lets it actually shrink */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}
.speedo-val {
  background: var(--speedo-bg);
  color: var(--speedo-ink);
  font-weight: 900;        /* v22.27: extra bold */
  font-size: 40px;          /* v22.27: bumped from 26px */
  line-height: 1;
  padding: 14px 4px;        /* v22.27: more vertical padding */
  border-radius: 8px;
  font-variant-numeric: tabular-nums;
  width: 100%;              /* v22.27: full width of speedo column */
  text-align: center;
  box-sizing: border-box;   /* v22.27: prevent overflow */
}
/* v22.27: auto-shrink for 3-digit speeds (100+) so it never overflows the card */
.speedo-val.three-digit {
  font-size: 34px;
  padding: 16px 4px;
}
.speedo-val.over { background: var(--red); animation: overpulse 0.6s infinite; }
@keyframes overpulse {
  0%,100% { box-shadow: 0 0 0 0 rgba(239,68,68,0.4); }
  50% { box-shadow: 0 0 0 6px rgba(239,68,68,0); }
}

/* v23.8.2 — dynamic Current Speed card status.
   Classes are applied by UI.renderStats from a pure UI-only function
   that reads the already-resolved `Alerts.currentLimit()` value. No
   speed-limit math, no alert thresholds, no GPS state is touched
   here — these rules are visual only.

   v23.8.3 — solid background fills + black digit text for readability
   in direct sunlight. Flashing reserved for the OVER state (the only
   one that signals a problem); SAFE and NEAR-LIMIT stay calm.

   States (mutually exclusive on the same element):
     .speed-status-unknown     · no resolved limit → neutral
     .speed-status-safe        · current speed ≤ limit - NEAR_BAND → solid green, no flash
     .speed-status-near-limit  · within NEAR_BAND km/h of the limit → solid orange, no flash
     .speed-status-over        · strictly above the limit → solid red, flashing

   .speed-status-over is intentionally distinct from .speedo-val.over
   (which fires at limit + State.settings.overBy and drives the
   existing audio alert path). Both can co-exist on .speedo-val without
   visual conflict — the red palette is consistent. */
.stat-card.speed-status-safe {
  border-color: #22c55e;
}
.speedo-val.speed-status-safe {
  background: #22c55e;
  color: #000;
}

.stat-card.speed-status-near-limit {
  border-color: #f97316;
}
.speedo-val.speed-status-near-limit {
  background: #f97316;
  color: #000;
}

.stat-card.speed-status-over {
  border-color: #ef4444;
  animation: speedOverPulse 0.8s ease-in-out infinite;
}
.speedo-val.speed-status-over {
  background: #ef4444;
  color: #000;
  animation: speedOverFlash 0.6s ease-in-out infinite;
}
@keyframes speedOverPulse {
  0%, 100% { box-shadow: inset 0 0 0 0 rgba(239, 68, 68, 0); }
  50%      { box-shadow: inset 0 0 0 3px rgba(239, 68, 68, 0.85); }
}
@keyframes speedOverFlash {
  0%, 100% { background: #ef4444; }
  50%      { background: #fca5a5; }
}

/* Neutral / unknown state has no animation — used when no limit is
   resolved so the card doesn't claim safe/danger that it can't prove. */
.stat-card.speed-status-unknown,
.speedo-val.speed-status-unknown { animation: none; }

/* Respect users who opt out of motion via OS / browser settings. */
@media (prefers-reduced-motion: reduce) {
  .stat-card.speed-status-over,
  .speedo-val.speed-status-over,
  .speedo-val.over { animation: none; }
}
.speedo-unit {
  font-size: 10px;
  color: var(--ink-2);
  letter-spacing: 0.04em;
  font-weight: 700;
}
.next-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 0;
  position: relative;
  overflow: hidden;
}
/* v22.92: oversized type-emoji rendered as a translucent watermark
   behind the name/meta/dist text. Improves at-a-glance recognition
   while driving without crowding the small card layout. */
.next-bg-emoji {
  position: absolute;
  right: -4px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 76px;
  line-height: 1;
  opacity: 0.18;
  pointer-events: none;
  z-index: 0;
  filter: saturate(1.2);
  user-select: none;
}
.next-name, .next-meta, .next-dist, .next-progress { position: relative; z-index: 1; }
/* v22.13: compact empty state — when nothing is ahead the card should
   not dominate the screen with empty space. */
.next-empty {
  font-style: italic;
  color: var(--ink-3);
  font-size: 12px;
  text-align: center;
  padding: 8px 0;
}
.next-name {
  font-weight: 700;
  font-size: 14px;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.next-meta {
  font-size: 11px;
  color: var(--ink-2);
  margin-top: 2px;
}
.next-dist {
  font-size: 22px;
  font-weight: 800;
  color: var(--amber-2);
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  margin-top: 4px;
}
.next-dist .unit { font-size: 12px; color: var(--ink-2); margin-left: 3px; font-weight: 600; }
.next-dist.urgent { color: var(--red); }
/* v22.49: proximity progress bar — fills as user approaches the next point.
   Sits at the bottom of the Next Ahead card. Color tier (data-tier) shifts
   from green far away → amber closer → red within 200m. */
.next-progress {
  margin-top: 6px;
  height: 6px;
  background: var(--surface-2);
  border-radius: 3px;
  overflow: hidden;
}
.next-progress-fill {
  height: 100%;
  background: var(--green, #22c55e);
  border-radius: 3px;
  transition: width 0.4s ease-out, background 0.4s ease;
}
.next-progress[data-tier="amber"] .next-progress-fill { background: var(--amber); }
.next-progress[data-tier="red"] .next-progress-fill { background: var(--red); }

/* v22.13: red neon flash on the Next-ahead stat card when a point is near.
   Uses outline + box-shadow only (NOT border-color). This way even if the
   class lingers or animation gets interrupted, the card border stays the
   normal tan colour — no red residue. */
.stat-card.flash-near {
  animation: neonflash 0.7s infinite;
}
@keyframes neonflash {
  0%, 100% {
    box-shadow: 0 0 0 0 rgba(239,68,68,0);
    outline: 2px solid rgba(239,68,68,0.6);
    outline-offset: -2px;
  }
  50% {
    box-shadow: 0 0 14px 3px rgba(239,68,68,0.85);
    outline: 2px solid rgba(255,107,107,1);
    outline-offset: -2px;
  }
}

/* MAP */
.map-wrap {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: hidden;
  position: relative;
  min-height: 200px;
}
#map { width: 100%; height: 100%; background: var(--surface-2); }
.map-overlay-btns {
  position: absolute;
  right: 8px;
  bottom: 8px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  z-index: 500;
  /* v23.5.6: 60% transparent (40% opaque) container. opacity applies to
     the visual layer only — taps still pass through normally because
     pointer-events default to 'auto'. On hover / touch the column
     restores to full opacity so the user sees what they're aiming at. */
  opacity: 0.4;
  transition: opacity 0.18s ease-out;
}
.map-overlay-btns:hover,
.map-overlay-btns:focus-within,
.map-overlay-btns:active {
  opacity: 1;
}
/* v23.5.6: visibility toggle for the 👁️ master eye button (#3). */
.map-overlay-btns.hidden-by-eye {
  display: none;
}
.map-btn {
  appearance: none;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--line-2);
  border-radius: 8px;
  /* v23.5.7: +20% — 36→43, 14→17. Container opacity 0.4 unchanged. */
  width: 43px; height: 43px;
  cursor: pointer;
  display: grid; place-items: center;
  font-size: 17px;
  box-shadow: var(--shadow);
}
.map-btn:active { transform: scale(0.92); }
/* v22.23: nav-mode button "on" state */
.map-btn.on { background: var(--amber); color: #0c0a09; border-color: var(--amber); }
.follow-pill {
  position: absolute;
  left: 8px;
  bottom: 8px;
  z-index: 500;
  background: var(--green-2);
  color: #0c0a09;
  border: none;
  border-radius: 999px;
  padding: 4px 10px;
  font: 600 10px ui-monospace, monospace;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 4px;
  box-shadow: var(--shadow);
}
.follow-pill.off {
  background: var(--surface);
  color: var(--ink-2);
  border: 1px solid var(--line);
}
.follow-pill .blink {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: #0c0a09;
  animation: blink 1.5s infinite;
}
@keyframes blink { 0%,100%{opacity:1} 50%{opacity:0.3} }
.follow-pill.off .blink { background: var(--ink-3); animation: none; }

/* v22.81: compass control — top-right of .map-wrap. The button is fixed
   in screen space; the .compass-rose inside rotates inversely to the
   actual map bearing so the red N tip always points to true north. */
.compass-btn {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 38px;
  height: 38px;
  z-index: 500;
  border-radius: 50%;
  border: 1px solid var(--line-2);
  background: var(--surface);
  cursor: pointer;
  display: grid;
  place-items: center;
  padding: 0;
  box-shadow: var(--shadow);
}
.compass-btn:active { transform: scale(0.92); }
/* v23.5.6: master visibility toggle (👁️). Sits directly under the
   compass, same size and surface. Stays visible regardless of whether
   .map-overlay-btns is hidden. The active class is painted by JS when
   the right-side controls are currently hidden. */
.eye-btn {
  position: absolute;
  top: 54px;   /* compass top (8) + compass height (38) + 8px gap */
  right: 8px;
  width: 38px;
  height: 38px;
  z-index: 500;
  border-radius: 50%;
  border: 1px solid var(--line-2);
  background: var(--surface);
  cursor: pointer;
  display: grid;
  place-items: center;
  padding: 0;
  font-size: 18px;
  line-height: 1;
  box-shadow: var(--shadow);
}
.eye-btn:active { transform: scale(0.92); }
.eye-btn.controls-hidden {
  background: rgba(245, 158, 11, 0.16);  /* amber tint when hiding */
  border-color: var(--amber-2);
}
.compass-rose {
  width: 30px;
  height: 30px;
  position: relative;
  /* Short linear transition so quick setBearing snaps don't jump
     instantly but rotate-during-easeTo still keeps up smoothly. */
  transition: transform 0.12s linear;
  transform-origin: 50% 50%;
}
.compass-n {
  position: absolute;
  top: 1px;
  left: 50%;
  transform: translateX(-50%);
  font: 800 9px ui-monospace, monospace;
  color: var(--red);
  letter-spacing: -0.5px;
  line-height: 1;
  pointer-events: none;
}
.compass-arrow-n {
  position: absolute;
  top: 9px;
  left: 50%;
  transform: translateX(-50%);
  width: 0; height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-bottom: 9px solid var(--red);
}
.compass-arrow-s {
  position: absolute;
  bottom: 4px;
  left: 50%;
  transform: translateX(-50%);
  width: 0; height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 8px solid var(--ink-3);
}

/* MAP MARKERS */
.ra-marker {
  width: 36px !important; height: 36px !important;
  margin-left: -18px !important; margin-top: -18px !important;
  border: 3px solid;
  border-radius: 50%;
  display: grid !important; place-items: center;
  font-size: 18px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.25);
  background: var(--surface);
  position: relative;
}
.ra-marker.passed {
  opacity: 0.45;
  filter: grayscale(0.7) saturate(0.5);
}
.ra-marker.disabled { opacity: 0.3; filter: grayscale(1); }

/* v23.19.0 — Waze-style cluster bubble. Shown when several nearby point
   markers are collapsed at low zoom; tapping zooms in to expand them.
   Neutral dark disc with a white count so it reads as a grouping, not as
   any one alert type. Three size tiers grow with the cluster count. */
.ra-cluster {
  width: 34px; height: 34px;
  margin-left: -17px; margin-top: -17px;
  border-radius: 50%;
  border: 2px solid #fff;
  background: #1c1917;       /* literal dark disc in both themes */
  color: #fff;
  display: grid; place-items: center;
  font-size: 13px;
  font-weight: 800;
  line-height: 1;
  box-shadow: 0 2px 8px rgba(0,0,0,0.35);
  cursor: pointer;
  z-index: 500;
  -webkit-user-select: none; user-select: none;
}
.ra-cluster.ra-cluster-md {
  width: 40px; height: 40px;
  margin-left: -20px; margin-top: -20px;
  font-size: 14px;
}
.ra-cluster.ra-cluster-lg {
  width: 46px; height: 46px;
  margin-left: -23px; margin-top: -23px;
  font-size: 15px;
}

/* v22.34: visual indicator for the ahead-ranking points.
   #1 (focused/closest): hard on/off flash synced with proximity ping
   #2 / #3 (next-up): heartbeat glow (continuous breathing pulse)
   Both use amber/orange caution color. */
.ra-marker.ahead-1 {
  outline: 4px solid #f59e0b;
  outline-offset: -2px;
  box-shadow: 0 0 0 4px rgba(245,158,11,0.4), 0 0 16px 6px rgba(245,158,11,0.6);
  z-index: 600;
}
.ra-marker.ahead-1.flash-on {
  outline-color: #fbbf24;
  box-shadow: 0 0 0 6px rgba(245,158,11,0.9), 0 0 24px 10px rgba(245,158,11,0.95);
  transform: scale(1.18);
  filter: brightness(1.4);
}
.ra-marker.ahead-1 { transition: transform 0.10s linear, box-shadow 0.10s linear, filter 0.10s linear; }

.ra-marker.ahead-2,
.ra-marker.ahead-3 {
  outline: 3px solid #f59e0b;
  outline-offset: -1px;
  box-shadow: 0 0 0 3px rgba(245,158,11,0.3), 0 0 10px 4px rgba(245,158,11,0.5);
  animation: heartbeat 1.4s ease-in-out infinite;
  z-index: 550;
}
.ra-marker.ahead-3 {
  /* Slightly less intense than ahead-2 */
  outline-width: 2px;
  animation-duration: 1.8s;
  opacity: 0.85;
}
@keyframes heartbeat {
  0%, 100% {
    box-shadow: 0 0 0 3px rgba(245,158,11,0.3), 0 0 10px 4px rgba(245,158,11,0.5);
    transform: scale(1);
  }
  20% {
    box-shadow: 0 0 0 6px rgba(245,158,11,0.6), 0 0 18px 8px rgba(245,158,11,0.85);
    transform: scale(1.10);
  }
  40% {
    box-shadow: 0 0 0 4px rgba(245,158,11,0.4), 0 0 12px 5px rgba(245,158,11,0.65);
    transform: scale(1.04);
  }
  60% {
    box-shadow: 0 0 0 6px rgba(245,158,11,0.55), 0 0 16px 7px rgba(245,158,11,0.8);
    transform: scale(1.08);
  }
}

/* v22.38: post-pass "still there?" confirmation popup (bottom-pinned). */
.confirm-popup {
  position: fixed;
  left: 8px;
  right: 8px;
  bottom: 12px;
  z-index: 2000;
  pointer-events: none;
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.22s ease, transform 0.22s ease;
}
.confirm-popup.show {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}
.confirm-card {
  background: var(--surface);
  border: 2px solid var(--amber);
  border-radius: 12px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.25);
  padding: 10px 12px 12px;
  position: relative;
}
/* v23.9.6: dismiss × in the top-right corner of the popup. Easy tap
   target (32 px), high contrast, never overlaps the title/meta. */
.confirm-close {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 32px;
  height: 32px;
  padding: 0;
  background: transparent;
  border: none;
  color: var(--ink-2);
  font-size: 22px;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  border-radius: 6px;
}
.confirm-close:active { background: var(--surface-2); transform: scale(0.94); }
.confirm-head { margin-bottom: 8px; }
.confirm-title {
  font-size: 15px;
  font-weight: 800;
  color: var(--ink);
  line-height: 1.2;
}
.confirm-meta {
  font-size: 11px;
  color: var(--ink-2);
  margin-top: 2px;
}
.confirm-progress {
  height: 4px;
  background: var(--line);
  border-radius: 2px;
  overflow: hidden;
  margin-bottom: 8px;
}
.confirm-progress-bar {
  height: 100%;
  width: 100%;
  background: var(--amber);
  transition: width 0.1s linear;
}
.confirm-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
.confirm-btn {
  appearance: none;
  border: none;
  border-radius: 10px;
  padding: 12px 0;
  font-size: 16px;
  font-weight: 800;
  letter-spacing: 0.04em;
  cursor: pointer;
  min-height: 48px;
}
.confirm-yes { background: var(--green-2); color: #0c0a09; }
.confirm-no  { background: var(--red);     color: #fff;     }
.confirm-btn:active { transform: scale(0.96); opacity: 0.85; }
/* v23.9.6: False Positive row sits beneath YES/NO. Secondary visual
   weight (outlined, not filled) so it doesn't compete with the
   primary YES/NO answers and won't be accidentally tapped while
   driving. */
.confirm-actions-fp {
  grid-template-columns: 1fr;
  margin-top: 8px;
}
.confirm-fp {
  background: transparent;
  color: var(--ink-2);
  border: 1.5px solid var(--ink-3);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.02em;
  min-height: 38px;
  padding: 8px 0;
}
.confirm-fp:active { background: var(--surface-2); transform: scale(0.97); }
.ra-marker.t-petrol        { border-color: #65a30d; background: #ecfccb; }
.ra-marker.t-checkpoint    { border-color: #ca8a04; background: #fef9c3; }
.ra-marker.t-speed_camera  { border-color: #dc2626; background: #fee2e2; }
.ra-marker.t-mobile_camera { border-color: #ea580c; background: #ffedd5; }
.ra-marker.t-pole_camera   { border-color: #b91c1c; background: #fecaca; }
.ra-marker.t-spider_camera { border-color: #7e22ce; background: #f3e8ff; }
.ra-marker.t-speed_change  { border-color: #0891b2; background: #cffafe; }
.ra-marker.t-gate          { border-color: #7c3aed; background: #ede9fe; }
.ra-marker.t-traffic_light { border-color: #16a34a; background: #dcfce7; }
.ra-marker.t-other         { border-color: #57534e; background: #f5f1e8; }
/* v23.7.2: speed_change observations render as road-sign style markers:
   white circular background, road-sign red border, black bold number.
   The .sign-style class is added in MapView._buildPointEl for speed_change
   points so it overrides the generic .t-speed_change colors above. */
.ra-marker.t-speed_change.sign-style {
  background: #ffffff !important;
  border-color: #dc2626 !important;
  color: #0c0a09;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-weight: 900;
  font-size: 14px;
  line-height: 1;
}
.ra-marker.t-speed_change.sign-style .sign-num {
  font-weight: 900;
  letter-spacing: -0.02em;
}
/* Confidence-driven border treatments — additive, never hides the marker. */
.ra-marker.t-speed_change.sign-style.conf-disputed {
  border-style: dashed !important;
  border-color: #b45309 !important;
}
.ra-marker.t-speed_change.sign-style.conf-stale {
  opacity: 0.7;
}
.ra-marker .side {
  position: absolute;
  bottom: -6px; right: -6px;
  min-width: 16px; height: 16px;
  padding: 0 3px;
  background: #1c1917;
  color: #fff;
  font-size: 9px;
  font-weight: 800;
  border-radius: 8px;
  border: 2px solid var(--surface);
  display: grid; place-items: center;
  line-height: 1;
  white-space: nowrap;
}
/* v22.87: feedback-count badge at top-right of each marker. Color shows
   the user's net feedback for that point:
     green  = mostly "yes, still there"
     red    = mostly "no, gone"
     amber  = tied (equal yes/no votes) */
.ra-marker .conf-badge {
  position: absolute;
  top: -6px; right: -6px;
  min-width: 16px; height: 16px;
  padding: 0 4px;
  background: var(--green);
  color: #fff;
  font-size: 9px;
  font-weight: 800;
  border-radius: 8px;
  border: 2px solid var(--surface);
  display: grid; place-items: center;
  line-height: 1;
  z-index: 2;
  font-family: ui-monospace, monospace;
}
.ra-marker .conf-badge.conf-neg     { background: var(--red); }
.ra-marker .conf-badge.conf-neutral { background: var(--amber); color: #0c0a09; }
/* v22.88: badge visible even at 0 count — grey so it's unobtrusive but
   still announces that confirmations exist as a feature. */
.ra-marker .conf-badge.conf-zero    { background: var(--ink-3); color: #fff; opacity: 0.85; }
/* v23.9.3: directional radar decoration on directional camera markers.
   SVG sits inside the existing .ra-marker wrapper, centred at the
   marker's centre, with overflow:visible so the 3 thin arc segments
   extend just past the 36 px rim. Pointer-events:none so taps still
   hit the underlying marker. Screen-aligned (no map-bearing counter-
   rotation) — the bearing is encoded in the SVG <g> rotation at build
   time, so the visual stays glued to the marker no matter what. */
.ra-marker .cam-radar {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 64px;
  height: 64px;
  margin: -32px 0 0 -32px;
  overflow: visible;
  pointer-events: none;
  z-index: 0;  /* below the emoji/badges of the marker */
}
/* v23.18.9 — thicker, more opaque arcs with a dark drop-shadow halo so
   the bearing direction is readable on light or busy map tiles. */
.ra-marker .cam-radar-arc {
  fill: none;
  stroke-width: 3;
  stroke-linecap: round;
  filter: drop-shadow(0 0 1.5px rgba(0,0,0,0.7))
          drop-shadow(0 1px 1.5px rgba(0,0,0,0.5));
}
.ra-marker.t-speed_camera  .cam-radar-arc { stroke: #dc2626; }
.ra-marker.t-mobile_camera .cam-radar-arc { stroke: #ea580c; }
.ra-marker.t-pole_camera   .cam-radar-arc { stroke: #b91c1c; }
.ra-marker.t-spider_camera .cam-radar-arc { stroke: #7e22ce; }
.ra-marker .cam-radar-arc-1 { stroke-opacity: 1;    stroke-width: 3.4; }
.ra-marker .cam-radar-arc-2 { stroke-opacity: 0.85; stroke-width: 3;   }
.ra-marker .cam-radar-arc-3 { stroke-opacity: 0.65; stroke-width: 2.6; }
/* v23.9.1: missed-feedback red SQUARE in the top-left corner of every
   marker that has unresolved missed-feedback entries. Distinct shape +
   color from the round green/red feedback-count badge in the top-right
   so the two can never be confused at a glance. */
.ra-marker .missed-badge {
  position: absolute;
  top: -6px; left: -6px;
  min-width: 16px; height: 16px;
  padding: 0 4px;
  background: #dc2626;
  color: #fff;
  font-size: 9px;
  font-weight: 800;
  border-radius: 3px;
  border: 2px solid var(--surface);
  display: grid; place-items: center;
  line-height: 1;
  z-index: 3;
  font-family: ui-monospace, monospace;
  box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}
/* v23.12.0: prior-capture count badge in the BOTTOM-LEFT corner. Bold,
   circled, color-coded by how many times the point was confirmed:
     0       → orange
     1       → red
     2 or 3+ → green */
.ra-marker .dep-badge {
  position: absolute;
  bottom: -6px; left: -6px;
  width: 16px; height: 16px;
  background: var(--amber);
  color: #0c0a09;
  font-size: 9px;
  font-weight: 900;
  border-radius: 50%;
  border: 2px solid var(--surface);
  display: grid; place-items: center;
  line-height: 1;
  z-index: 3;
  font-family: ui-monospace, monospace;
  box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}
.ra-marker .dep-badge.dep-one { background: var(--red);   color: #fff; }
.ra-marker .dep-badge.dep-ok  { background: var(--green); color: #fff; }
/* v23.18.5 — live distance countdown rendered on top-center of every
   alertable marker. Sits above the marker, never moves stored coords.
   Refreshed by MapView._updateMarkerDistances on every GPS tick.
   v23.18.6 — transparent background; text-shadow keeps the number
   legible against light or dark map tiles.
   v23.18.7 — moved to BOTTOM-center and tiered color (far/mid/near).
   v23.18.8 — wider color bands, real text stroke + heavier shadow for
   clarity on busy map tiles, brighter saturated greens/oranges so
   tiers are visually distinct (the shared --green is olive and reads
   muddy on top of map textures). */
.ra-marker .ra-dist-label {
  position: absolute;
  left: 50%;
  top: auto;
  bottom: -22px;
  transform: translateX(-50%);
  background: transparent;
  color: #22c55e;
  font-family: ui-monospace, monospace;
  font-size: 12px;
  font-weight: 800;
  line-height: 1;
  padding: 0 3px;
  border-radius: 4px;
  white-space: nowrap;
  pointer-events: none;
  -webkit-text-stroke: 0.6px #000;
  text-shadow:
    -1px  0 0 #000,  1px  0 0 #000,
     0 -1px 0 #000,  0  1px 0 #000,
     0 0 4px rgba(0,0,0,0.85),
     0 1px 3px rgba(0,0,0,0.9);
  z-index: 4;
}
.ra-marker .ra-dist-label.ra-dist-far  { color: #22c55e; }
.ra-marker .ra-dist-label.ra-dist-mid  { color: #fb923c; }
.ra-marker .ra-dist-label.ra-dist-near { color: #f87171; }
.ra-marker.passed .ra-dist-label,
.ra-marker.disabled .ra-dist-label { display: none; }
/* v22.82: Google Maps-style directional triangle marker.
   .ra-current is now a wrapper holding two children:
     .ra-current-halo  - small blue dot + pulsing accuracy halo (always shown)
     .ra-current-tri   - the directional triangle (rotated by JS)
   The triangle's transform-origin (50% 65%) sits near its base so
   rotation pivots around the user's location, not the apex. */
.ra-current {
  width: 40px !important;
  height: 40px !important;
  position: relative;
  pointer-events: none;
}
.ra-current-halo {
  position: absolute;
  top: 50%; left: 50%;
  width: 12px; height: 12px;
  margin: -6px 0 0 -6px;
  background: #2563eb;
  border: 2px solid #fff;
  border-radius: 50%;
  box-shadow: 0 0 0 4px rgba(37,99,235,0.3);
  animation: pulsedot 2s infinite;
  z-index: 1;
}
.ra-current-tri {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0; height: 0;
  border-left: 9px solid transparent;
  border-right: 9px solid transparent;
  border-bottom: 22px solid #2f80ff;
  filter: drop-shadow(0 2px 4px rgba(0,0,0,0.35));
  transform-origin: 50% 65%;
  transform: translate(-50%, -50%) rotate(0deg);
  transition: transform 0.15s linear;
  pointer-events: none;
  z-index: 2;
}
/* Red when no heading source is available (neither device orientation
   nor GPS movement) — visible signal that direction is unknown. */
.ra-current-tri.no-heading {
  border-bottom-color: var(--red);
}
@keyframes pulsedot {
  0%,100% { box-shadow: 0 0 0 4px rgba(37,99,235,0.3); }
  50%     { box-shadow: 0 0 0 12px rgba(37,99,235,0); }
}
.ra-dest {
  width: 36px !important; height: 36px !important;
  margin-left: -18px !important; margin-top: -18px !important;
  background: var(--green-2);
  border: 3px solid #fff;
  border-radius: 50%;
  display: grid !important; place-items: center;
  font-size: 18px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.4);
}

/* ACTIONS */
.actions {
  display: grid;
  /* v22.58: Capture is wider, Start GPS narrower. */
  grid-template-columns: 1.4fr 1fr;
  gap: 6px;
}
.btn {
  appearance: none;
  border: 1px solid var(--line-2);
  background: var(--surface);
  color: var(--ink);
  font: 700 13px ui-monospace, monospace;
  padding: 10px 16px;
  border-radius: 10px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  transition: transform 0.1s;
}
.btn:active { transform: scale(0.97); }
/* v22.93: button busy state — inline spinner on the right edge of the
   label, no layout jump. Used by Backup now / Restore handlers while a
   network request is in flight. Buttons become non-interactive. */
.btn.loading {
  pointer-events: none;
  opacity: 0.78;
  position: relative;
  padding-right: 32px;
}
.btn.loading::after {
  content: '';
  position: absolute;
  right: 10px;
  top: 50%;
  width: 14px;
  height: 14px;
  margin-top: -7px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: btn-spin 0.75s linear infinite;
}
@keyframes btn-spin { to { transform: rotate(360deg); } }
.btn-capture { background: var(--red); color: #fff; border-color: var(--red); }
.btn-go { background: var(--green-2); color: #0c0a09; border-color: var(--green-2); }
.btn-go.active { background: var(--surface); color: var(--red); border-color: var(--red); }
.btn-square { width: 48px; padding: 14px 0; }

/* STATUS LINE */
.status-line {
  font-size: 10px;
  color: var(--ink-3);
  text-align: center;
  letter-spacing: 0.04em;
  display: flex;
  justify-content: center;
  gap: 8px;
  padding: 0 4px;
  flex-wrap: wrap;
}
.status-line strong { color: var(--ink-2); margin-right: 3px; }
.status-line .live { color: var(--green); }
.status-line .demo { color: var(--cyan); }
/* v22.68: auto-backup ticker on the main status line. */
.status-line #status-backup { color: var(--green); font-weight: 700; }
.status-line #status-backup.off { color: var(--ink-3); font-weight: 400; }
.status-line #status-backup.fail { color: var(--red); font-weight: 700; }

/* MODALS - improved backdrop visibility */
.modal {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.65);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  display: none;
  align-items: flex-end;
  z-index: 1000;
}
.modal.open { display: flex; }
.modal-card {
  width: 100%;
  max-width: 520px;
  margin: 0 auto;
  background: var(--bg);
  border-top: 2px solid var(--amber);
  border-radius: 16px 16px 0 0;
  padding: 16px;
  max-height: 90vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-bottom: calc(16px + env(safe-area-inset-bottom));
  box-shadow: 0 -8px 24px rgba(0,0,0,0.3);
}
/* v22.67: modal head sticks to the top of the scrollable modal card so the
   close button stays in reach when the user scrolls through long modals
   (e.g. Settings). Background fill prevents content from showing through. */
.modal-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  position: sticky;
  top: -16px;
  margin: -16px -16px 0;
  padding: 16px 16px 10px;
  background: var(--bg);
  z-index: 10;
  border-bottom: 1px solid var(--line);
}
.modal-title { font-weight: 700; font-size: 16px; }
/* v22.67: bigger, higher-contrast close button. Uses an SVG-like × that
   renders identically across browsers — some mobile browsers were not
   rendering the smaller 16px × glyph reliably. */
.modal-close {
  appearance: none;
  background: var(--surface);
  border: 1px solid var(--line-2);
  color: var(--ink);
  width: 40px; height: 40px;
  border-radius: 50%;
  cursor: pointer;
  font-size: 24px;
  font-weight: 700;
  line-height: 1;
  display: grid;
  place-items: center;
  padding: 0;
  flex-shrink: 0;
}
.modal-close:active { background: var(--surface-2); transform: scale(0.94); }
.section-title {
  font-size: 10px;
  letter-spacing: 0.14em;
  color: var(--ink-3);
  text-transform: uppercase;
  font-weight: 700;
  margin-top: 6px;
  padding: 0 4px;
}
/* v23.8.1 — Collapsible Settings sections. The Settings modal wraps
   every `.section-title` in a tappable header + a hideable body so
   advanced/technical groups stay compact by default. Visual rules
   only — wiring lives in UI.installCollapsibleSettings(). */
.settings-section-head {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  margin: 10px 0 4px;
  padding: 11px 14px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--ink);
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  cursor: pointer;
  text-align: left;
  min-height: 40px;
}
.settings-section-head:active { background: var(--surface-2); transform: scale(0.997); }
.settings-section-head:focus-visible {
  outline: 2px solid var(--amber, #f5a623);
  outline-offset: 1px;
}
.settings-section-head .settings-section-chev {
  display: inline-block;
  width: 12px;
  text-align: center;
  color: var(--ink-3);
  font-weight: 700;
  transition: transform 0.15s ease-out;
}
.settings-section-head[aria-expanded="true"] .settings-section-chev {
  transform: rotate(90deg);
  color: var(--amber-2, var(--amber, #f5a623));
}
.settings-section-head .settings-section-title-text {
  flex: 1;
  color: var(--ink-2, var(--ink));
}
.settings-section-head[aria-expanded="true"] .settings-section-title-text {
  color: var(--ink);
}
.settings-section-body {
  padding: 0;
  margin: 0 0 6px;
}
.settings-section-body[hidden] { display: none; }
.row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  font-size: 13px;
}
.row .label { flex: 1; color: var(--ink); }
.row input, .row select {
  background: var(--bg);
  border: 1px solid var(--line-2);
  color: var(--ink);
  font: inherit;
  padding: 6px 8px;
  border-radius: 6px;
  text-align: right;
}
.row input[type=number] { width: 72px; text-align: center; }
.row input[type=text]   { flex: 2; text-align: left; }
/* v22.90: small explanatory text shown directly below a settings row. */
.row-hint {
  font-size: 10px;
  color: var(--ink-3);
  line-height: 1.45;
  padding: 4px 4px 8px 12px;
  margin: -2px 0 4px 4px;
  border-left: 2px solid var(--line-2);
}
.row-hint code { font-family: ui-monospace, monospace; color: var(--ink-2); }
.row-hint strong { color: var(--ink-2); }

/* v23.6.6 — Sound Alerts table.
   Stacked flexbox per row. NO inner scroll — the modal-card already
   scrolls the whole Settings page, and nesting a second scroll
   inside it squeezed the table to ~one-row-tall in real iOS Safari
   (the user's "very tiny service area" report). Letting the table
   grow naturally lets the user scroll through all 18 rows via the
   modal's own overflow-y. */
.sound-alerts-table {
  display: block;
  padding: 4px 0;
  /* explicitly NOT max-height + NOT overflow-y so the table
     expands to its content height; the parent .modal-card owns
     scrolling. flex-shrink: 0 keeps the table from being
     squeezed when the modal is also a flex column. */
  flex-shrink: 0;
}
.sa-row {
  display: block;
  padding: 10px 10px;
  margin-bottom: 6px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  font-size: 13px;
  min-height: 40px;
  flex-shrink: 0;
}
.sa-row:last-child { margin-bottom: 0; }
.sa-row .sa-name {
  display: block;
  font-weight: 700;
  color: var(--ink);
  margin-bottom: 8px;
  font-size: 14px;
  word-break: break-word;
}
.sa-row .sa-try-wrap {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
}
.sa-row .sa-try {
  appearance: none;
  background: var(--amber);
  color: #0c0a09;
  border: 1px solid var(--line-2);
  border-radius: 6px;
  padding: 6px 14px;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  min-width: 70px;
  flex: 0 0 auto;
}
.sa-row .sa-try:active { transform: scale(0.96); }
.sa-row .sa-try:disabled { opacity: 0.5; cursor: not-allowed; }
.sa-row .sa-status {
  font-size: 11px;
  color: var(--ink-3);
  line-height: 1.2;
  min-height: 14px;
  flex: 1 1 auto;
}
.sa-row .sa-status.playing { color: var(--amber-2); }
.sa-row .sa-status.played  { color: var(--green); }
.sa-row .sa-status.failed  { color: var(--red); }
.sa-row .sa-freq,
.sa-row .sa-usedfor {
  display: block;
  width: 100%;
  font-size: 12px;
  padding: 6px 8px;
  border-radius: 6px;
  background: var(--surface-2, var(--surface));
  border: 1px solid var(--line);
  color: var(--ink);
  margin-bottom: 6px;
  appearance: auto;          /* keep native select styling so the
                                user can tell it's tappable */
}
.sa-row .sa-usedfor { margin-bottom: 0; }
/* v23.0.1: user toggle to hide every explanatory paragraph at once.
   Driven by the Settings → "Show help text" toggle via a body attribute. */
body[data-hide-hints="true"] .row-hint { display: none; }
/* v22.17: chip-style markers for alert distances */
.marker-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--amber);
  color: #0c0a09;
  font-weight: 800;
  font-size: 12px;
  padding: 6px 10px;
  border-radius: 14px;
  cursor: pointer;
  border: none;
  font-family: var(--mono, monospace);
}
.marker-chip::after {
  content: '×';
  font-size: 14px;
  opacity: 0.7;
}
.marker-chip:active { transform: scale(0.92); opacity: 0.7; }
.opts {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  justify-content: flex-end;
}
.opts button {
  appearance: none;
  background: var(--bg);
  border: 1px solid var(--line-2);
  color: var(--ink-2);
  padding: 5px 10px;
  border-radius: 6px;
  font: 600 11px ui-monospace, monospace;
  cursor: pointer;
}
.opts button.active {
  background: var(--amber);
  color: #0c0a09;
  border-color: var(--amber);
}
.toggle {
  width: 36px; height: 20px;
  background: var(--bg);
  border: 1px solid var(--line-2);
  border-radius: 999px;
  position: relative;
  cursor: pointer;
}
.toggle::after {
  content: '';
  position: absolute;
  left: 2px; top: 2px;
  width: 14px; height: 14px;
  background: var(--ink-3);
  border-radius: 50%;
  transition: transform 0.15s, background 0.15s;
}
.toggle.on { background: var(--amber); border-color: var(--amber); }
.toggle.on::after { transform: translateX(16px); background: #0c0a09; }

/* CAPTURE GRID */
.cap-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
.cap-btn {
  appearance: none;
  border: 2px solid var(--line-2);
  background: var(--surface);
  color: var(--ink);
  padding: 16px 8px;
  border-radius: 12px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  font: 700 12px ui-monospace, monospace;
  min-height: 80px;
}
.cap-btn:active { transform: scale(0.95); }
.cap-btn .em { font-size: 28px; }
.cap-btn.t-petrol        { border-color: #65a30d; }
.cap-btn.t-checkpoint    { border-color: #ca8a04; }
.cap-btn.t-speed_camera  { border-color: #dc2626; }
.cap-btn.t-mobile_camera { border-color: #ea580c; }
.cap-btn.t-pole_camera   { border-color: #b91c1c; }
.cap-btn.t-spider_camera { border-color: #7e22ce; }
.cap-btn.t-speed_change  { border-color: #0891b2; }
.cap-btn.t-gate          { border-color: #7c3aed; }
.cap-btn.t-traffic_light { border-color: #16a34a; }
.cap-btn.t-destination   { border-color: var(--green-2); }
.cap-btn.t-other         { border-color: #57534e; }

/* SHEET BUTTONS */
.sheet-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.sheet-btn {
  appearance: none;
  background: var(--surface);
  border: 1px solid var(--line-2);
  color: var(--ink);
  padding: 18px;
  border-radius: 12px;
  font: 700 14px ui-monospace, monospace;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}
.sheet-btn:active { transform: scale(0.97); }
.sheet-btn .em { font-size: 26px; }

.limit-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
}
.limit-pick {
  appearance: none;
  border: 3px solid var(--red);
  background: #fff;
  color: #1c1917;
  font: 800 20px ui-monospace, monospace;
  aspect-ratio: 1;
  border-radius: 50%;
  cursor: pointer;
  display: grid;
  place-items: center;
}
.limit-pick:active { transform: scale(0.94); }

/* LISTS */
.list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-height: 50vh;
  overflow-y: auto;
}
.list-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  font-size: 12px;
}
.list-row .em { font-size: 20px; flex-shrink: 0; }
.list-row .info { flex: 1; min-width: 0; }
.list-row .name {
  font-weight: 700;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.list-row .meta { font-size: 10px; color: var(--ink-3); }
.list-row.disabled .name { text-decoration: line-through; opacity: 0.5; }
.list-row .row-btn {
  appearance: none;
  border: 1px solid var(--line-2);
  background: var(--surface);
  color: var(--ink);
  width: 28px; height: 28px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 12px;
  display: grid; place-items: center;
}
.list-row .row-btn.del { color: var(--red); border-color: var(--red); }
.list-row .row-btn.keep { color: var(--green); border-color: var(--green); }
.list-row .row-btn.active.keep { background: var(--green); color: #fff; }
.list-row .row-btn.active.del { background: var(--red); color: #fff; }
.empty {
  text-align: center;
  padding: 24px 12px;
  color: var(--ink-3);
  font-size: 13px;
  font-style: italic;
}

/* v22.79: debug log rows. Newest at top, scrollable. Type colored
   (amber for default, red for err, green for ok). */
.debug-row {
  display: flex;
  gap: 8px;
  padding: 3px 6px;
  border-bottom: 1px solid var(--line);
  font-family: ui-monospace, monospace;
  font-size: 10px;
  line-height: 1.4;
  white-space: pre-wrap;
  word-break: break-word;
}
.debug-row:last-child { border-bottom: none; }
.debug-row .ts { color: var(--ink-3); flex-shrink: 0; font-variant-numeric: tabular-nums; min-width: 62px; }
.debug-row .ty { color: var(--amber-2); font-weight: 700; flex-shrink: 0; min-width: 54px; }
.debug-row .msg { flex: 1; color: var(--ink); min-width: 0; }
.debug-row.err .ty { color: var(--red); }
.debug-row.ok  .ty { color: var(--green); }

/* TABS */
.tabs {
  display: flex;
  gap: 4px;
  padding: 4px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  flex-wrap: wrap;
}
.tab {
  appearance: none;
  background: transparent;
  border: none;
  color: var(--ink-2);
  padding: 6px 10px;
  border-radius: 6px;
  font: 600 11px ui-monospace, monospace;
  cursor: pointer;
  text-transform: uppercase;
  flex: 1;
  min-width: max-content;
}
.tab.active { background: var(--amber); color: #0c0a09; }

/* TOAST */
.toast {
  position: fixed;
  bottom: calc(80px + env(safe-area-inset-bottom));
  left: 50%;
  transform: translateX(-50%) translateY(20px);
  background: var(--ink);
  color: var(--bg);
  padding: 10px 16px;
  border-radius: 999px;
  font: 600 12px ui-monospace, monospace;
  opacity: 0;
  pointer-events: none;
  z-index: 2000;
  transition: opacity 0.2s, transform 0.2s;
  box-shadow: var(--shadow);
  max-width: 90%;
  text-align: center;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
.toast.good { background: var(--green); color: #fff; }
.toast.bad { background: var(--red); color: #fff; }

/* v22.51: MapLibre overrides */
.maplibregl-map { background: var(--surface-2); font-family: ui-monospace, monospace; }
.maplibregl-ctrl-attrib {
  background: rgba(255,255,255,0.7) !important;
  font-size: 9px !important;
  color: var(--ink-2) !important;
}
.maplibregl-ctrl-attrib a { color: var(--amber-2) !important; }
.maplibregl-popup-content {
  background: var(--surface) !important;
  color: var(--ink) !important;
  border-radius: 10px !important;
  border: 1px solid var(--line-2);
  margin: 0 !important;
  padding: 10px 14px !important;
  font-size: 12px;
}
.maplibregl-popup-tip { border-top-color: var(--surface) !important; border-bottom-color: var(--surface) !important; }
.maplibregl-popup-close-button {
  color: var(--ink) !important;
  font-size: 18px !important;
  padding: 4px 8px !important;
}
.maplibregl-popup-content .pop-name { font-weight: 700; margin-bottom: 4px; font-size: 13px; }
.maplibregl-popup-content .pop-meta { font-size: 10px; color: var(--ink-2); margin-bottom: 6px; }
.maplibregl-popup-content button {
  appearance: none;
  border: none;
  background: var(--amber);
  color: #0c0a09;
  padding: 10px 16px;
  border-radius: 8px;
  font-weight: 800;
  font-size: 13px;
  cursor: pointer;
  margin-right: 6px;
  margin-top: 6px;
  min-height: 38px;
  min-width: 70px;
}
.maplibregl-ctrl-group button {
  background: var(--surface) !important;
  color: var(--ink) !important;
  border-color: var(--line) !important;
}
/* v23.5.6: zoom controls — softened (50% transparent) and 25% larger so
   they're still easy to tap while driving but visually less heavy than
   the default. transform-origin top-left so the scaled group stays
   inside the MapLibre top-left anchor. Background opacity applied to
   the BUTTON (not the wrapper) so .map-overlay-btns and other map
   children retain their own opacity rules.
   v23.5.7: bumped scale 1.25 → 1.50 (+20% of original) per spec.
   Opacity 0.5 unchanged. */
.maplibregl-ctrl-top-left .maplibregl-ctrl-group {
  transform: scale(1.50);
  transform-origin: top left;
  opacity: 0.5;
  background: transparent !important;
  box-shadow: none !important;
}
.maplibregl-ctrl-top-left .maplibregl-ctrl-group:hover,
.maplibregl-ctrl-top-left .maplibregl-ctrl-group:focus-within,
.maplibregl-ctrl-top-left .maplibregl-ctrl-group:active {
  opacity: 1; /* restore on interaction so the tap target is fully visible */
}
/* MapLibre wraps custom marker DOM in .maplibregl-marker — ensure our
   .ra-marker / .ra-current styling isn't broken by the wrapper */
.maplibregl-marker { cursor: pointer; }

/* Tight phone screens */
@media (max-height: 700px) {
  .sign { width: 66px; height: 66px; border-width: 5px; }
  .sign-value { font-size: 28px; }
  .sign-label { bottom: 7px; font-size: 8px; }
  /* v23.5.3: rail-sign tracks the same scale-down at narrow heights. */
  .tools-rail .rail-sign { width: 64px; height: 64px; border-width: 4px; }
  .tools-rail .rail-sign .sign-value { font-size: 26px; }
  .tools-rail .rail-sign .sign-label { bottom: 6px; font-size: 7px; }
  .speedo-val { font-size: 32px; padding: 10px 4px; }
  .speedo-val.three-digit { font-size: 26px; padding: 12px 4px; }
  .next-dist { font-size: 18px; }
}

/* v22.75: ultra-compact layout for very short viewports — car-screen
   browsers in landscape, iPhone in landscape, etc. Triggers below 600px
   tall. iPhone in PORTRAIT is 750+ px tall so it's unaffected. */
@media (max-height: 600px) {
  .app { gap: 4px; padding: 4px; }
  /* Topbar slimmer */
  .topbar { padding: 2px 4px 0; gap: 6px; }
  .icon-btn { width: 28px; height: 28px; font-size: 12px; border-radius: 6px; }
  .brand { font-size: 12px; gap: 4px; }
  .brand .dot { width: 10px; height: 10px; }
  .brand .version { font-size: 9px; }
  /* v23.18.3: destination card extra-compact on narrow screens. */
  .route-current { padding: 2px 8px; font-size: 10px; gap: 4px; }
  .route-current .arrow { font-size: 11px; }
  .diag-strip { font-size: 9px; padding: 2px 5px; line-height: 1.2; }
  /* Speed + Next-ahead cards much smaller */
  .stat-card { padding: 4px 6px; }
  .stat-label { font-size: 9px; margin-bottom: 2px; }
  .sign { width: 48px; height: 48px; border-width: 3px; }
  .sign-value { font-size: 20px; }
  .sign-label { display: none; }
  /* v23.5.3: rail-sign at ultra-narrow heights — matches the tight
     SPEED-card sizing so the rail stays usable in landscape phones. */
  .tools-rail .rail-sign { width: 48px; height: 48px; border-width: 3px; }
  .tools-rail .rail-sign .sign-value { font-size: 20px; }
  .tools-rail .rail-sign .sign-label { display: none; }
  .speedo-val { font-size: 22px; padding: 6px 4px; }
  .speedo-val.three-digit { font-size: 18px; padding: 8px 4px; }
  .speedo-unit { font-size: 8px; }
  .next-name { font-size: 12px; }
  .next-meta { font-size: 9px; }
  .next-dist { font-size: 14px; margin-top: 2px; }
  .next-dist .unit { font-size: 9px; }
  .next-empty { font-size: 10px; padding: 4px 0; }
  /* v22.92: shrink the watermark proportionally on short viewports */
  .next-bg-emoji { font-size: 52px; right: -2px; opacity: 0.16; }
  /* Map can be smaller too */
  .map-wrap { min-height: 100px; }
  /* Action buttons compact but still touch-friendly */
  .btn { padding: 6px 10px; font-size: 11px; }
  /* v22.89: sidebar gets bigger fonts in compact mode too — was too tiny
     to read at a glance on a car-screen browser. Slightly wider rail
     (80px) accommodates the larger text.
     v23.10.0: 3-column compact layout — left rail 56px (≈30% smaller
     than the 80px right rail), map, right rail 80px. */
  .map-row { grid-template-columns: 56px 1fr 80px; gap: 4px; }
  .map-row.lr-off { grid-template-columns: 1fr 80px; }
  .timeline-entry { padding: 6px 3px; }
  .timeline-entry .em { font-size: 18px; margin-bottom: 2px; }
  .timeline-entry .lbl { font-size: 11px; }
  .timeline-entry .dist { font-size: 13px; margin-top: 3px; }
  .timeline-entry .dist.no-gps { font-size: 10px; }
  .tl-x { width: 18px; height: 18px; font-size: 16px; }
  /* Left rail entries stay ~30% smaller than the right even in compact. */
  .tools-rail-left .timeline-entry .em { font-size: 13px; }
  .tools-rail-left .timeline-entry .lbl { font-size: 8px; }
  .tools-rail-left .timeline-entry .dist { font-size: 9px; }
  /* Status line tiny */
  .status-line { font-size: 9px; padding: 0 2px; gap: 6px; }
  /* Map overlay buttons smaller so they don't eat the small map */
  .map-btn { width: 36px; height: 36px; font-size: 14px; } /* v23.5.7: +20% (30→36, 12→14) */
  .follow-pill { padding: 2px 7px; font-size: 9px; }
}

/* v23.6.0 merge: removed parallel agent's duplicate Sound Alerts
   CSS. The 18-row table lives inside #m-sound-alerts modal and is
   styled by the earlier .sound-alerts-table / .sa-row rules above. */

/* v23.14.0: read-only Capture Metadata block in the Edit Point modal.
   Purely informational; no inputs, no editable controls. */
.capmeta {
  margin: 8px 0;
  padding: 8px 10px;
  border: 1px solid var(--line-2);
  border-radius: 8px;
  background: var(--surface-2);
}
.capmeta-title {
  font: 700 11px ui-monospace, monospace;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-2);
  margin-bottom: 6px;
}
.capmeta-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  padding: 2px 0;
  border-bottom: 1px solid var(--line);
  font-size: 11px;
}
.capmeta-row:last-child { border-bottom: none; }
.capmeta-k { color: var(--ink-3); flex: 0 0 auto; }
.capmeta-v {
  color: var(--ink);
  font: 600 11px ui-monospace, monospace;
  text-align: right;
  word-break: break-word;
  overflow-wrap: anywhere;
}
