/* =============================================================================
 * Jezero Explorer — style.css
 *
 * House dark theme (same custom props as G:\2026\04_SDO\vla_viewer.html), a
 * 320 px sidebar grid on desktop, a three-detent bottom sheet under 900 px and
 * a right drawer on landscape phones. No framework, no build step.
 *
 * Layout contract for js/ui.js and the feature modules:
 *   body[data-detent="peek"|"half"|"full"]   mobile sheet height
 *   body.desktop / body.mobile               resolved breakpoint
 *   body.kiosk                               ?kiosk=1 — dock hidden, big sol
 *   body.lite                                lite mode (fewer effects)
 *   body.tour                                tour running — dock dimmed 40 %
 *   #panels > section.panel[data-panel=ID]   one registered panel
 *   [data-tab] / [aria-selected]             mobile tab strip
 * ========================================================================== */

:root {
  /* house chrome — mirrored in js/config.js PALETTE */
  --bg: #111;
  --panel: #1c1c1f;
  --panel2: #25252a;
  --border: #333;
  --text: #ddd;
  --muted: #888;
  --accent: #5ad0ff;
  --danger: #ff6060;

  /* map accents, for chrome that has to agree with the layers */
  --rover: #ff5cc8;
  --heli: #b57bff;
  --sample: #ff9f40;

  --sidebar-w: 320px;
  --topbar-h: 44px;
  --radius: 4px;
  --tap: 44px;                      /* minimum touch target */
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-t: env(safe-area-inset-top, 0px);

  /* Sheet detents. --vh is set by js/ui.js as a fallback for browsers without
     dvh; the dvh values win wherever they are supported. */
  --vh: 1vh;
  /* 148px (was 128): at 128 the sol slider's 44px row landed at y836 in an
   * 844px emulated viewport and was clipped mid-track (integration pass). */
  --peek-h: calc(148px + var(--safe-b));
  --half-h: 55vh;
  --full-h: 90vh;
}

@supports (height: 100dvh) {
  :root {
    --half-h: 55dvh;
    --full-h: 90dvh;
  }
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font: 13px/1.4 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* No rubber-banding behind the map or the sheet. */
  overscroll-behavior: none;
  -webkit-tap-highlight-color: transparent;
  -webkit-text-size-adjust: 100%;
}

body { overflow: hidden; }

h1, h2, h3 { margin: 0; font-weight: 600; }
a { color: var(--accent); }
a:hover { color: #8fe0ff; }

/* Tabular numerals everywhere a number changes in place (sol, km, elevation) —
   otherwise the readout jitters as digits change width. */
.num, #kiosk-sol, .sol-readout, .elev, #scalebar .label { font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------------------
 * Boot banner + fatal error bar (index.html paints this before any module)
 * ------------------------------------------------------------------------ */
#boot-status {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 9999;
  padding: calc(6px + var(--safe-t)) 12px 6px;
  background: #332;
  color: #fc6;
  font: 12px/1.35 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
#boot-status.fatal { background: #3a1414; color: var(--danger); }

/* ---------------------------------------------------------------------------
 * Static fallback / noscript panel (no WebGL2 — the only fatal failure)
 * ------------------------------------------------------------------------ */
.static-panel {
  max-width: 46rem;
  margin: 0 auto;
  padding: 28px 20px calc(40px + var(--safe-b));
  height: 100%;
  overflow: auto;
  font-size: 15px;
  line-height: 1.55;
}
.static-panel h1 { font-size: 26px; margin: 0 0 14px; }
.static-panel p { margin: 0 0 14px; }
.static-panel .hero {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin-bottom: 20px;
}
.static-panel .credit { color: var(--muted); font-size: 12px; }

/* ---------------------------------------------------------------------------
 * App shell — desktop: 320 px sidebar + full-bleed map
 * ------------------------------------------------------------------------ */
#app {
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  grid-template-rows: 1fr;
  /* 100dvh with a --vh fallback for older iOS. */
  height: calc(var(--vh) * 100);
  height: 100dvh;
  width: 100%;
}
#app[hidden] { display: none; }

#topbar { display: none; }

#dock {
  grid-column: 1;
  background: var(--panel);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
  transition: opacity 0.25s;
}
body.tour #dock { opacity: 0.4; }
body.tour #dock:hover, body.tour #dock:focus-within { opacity: 1; }

#dock-handle { display: none; }

#dock-head {
  padding: 12px 12px 10px;
  border-bottom: 1px solid var(--border);
  flex: 0 0 auto;
}
#dock-head h1 { font-size: 16px; letter-spacing: 0.01em; }
#dock-head .sub { margin: 3px 0 10px; color: var(--muted); font-size: 11px; }

button, input, select { font: inherit; color: inherit; }

button.primary {
  display: block;
  width: 100%;
  min-height: var(--tap);
  background: linear-gradient(#2b3d46, #1f2c33);
  color: #d9f4ff;
  border: 1px solid #3d6675;
  border-radius: var(--radius);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.04em;
  cursor: pointer;
  touch-action: manipulation;
}
button.primary:hover { border-color: var(--accent); color: #fff; }
button.primary:disabled { opacity: 0.45; cursor: default; border-color: var(--border); }

/* mobile tab strip — hidden on desktop, where every panel is visible at once */
#tabs { display: none; }

#panels {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

section.panel { border-bottom: 1px solid var(--border); }
section.panel > .panel-title {
  margin: 0;
  padding: 9px 12px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
  background: transparent;
  border: 0;
  width: 100%;
  text-align: left;
  cursor: default;
}
section.panel > .panel-body { padding: 0 12px 12px; }
.panel-empty { color: var(--muted); font-size: 12px; padding: 2px 0 6px; }

/* Generic panel widgets — feature modules reuse these classes so the five
   panels look like one app. */
.row { display: flex; align-items: center; gap: 8px; margin: 0 0 8px; flex-wrap: wrap; }
.row.tight { gap: 5px; }
.spacer { flex: 1 1 auto; }
.hint { color: var(--muted); font-size: 11px; }
.strong { color: #fff; }

.btn {
  min-height: 30px;
  padding: 5px 9px;
  background: var(--panel2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  font-size: 12px;
  touch-action: manipulation;
}
.btn:hover { border-color: var(--accent); }
.btn[aria-pressed="true"], .btn.on { border-color: var(--accent); color: #fff; background: #223038; }
.btn:disabled { opacity: 0.4; cursor: default; border-color: var(--border); }
.btn.wide { flex: 1 1 auto; }

.link-btn {
  background: none;
  border: 0;
  padding: 4px 0;
  color: var(--accent);
  cursor: pointer;
  font-size: 11px;
  text-align: left;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.link-btn:disabled { color: var(--muted); text-decoration: none; cursor: default; }

.toggle { display: flex; align-items: center; gap: 7px; min-height: 26px; cursor: pointer; }
.toggle input[type="checkbox"] { width: 15px; height: 15px; accent-color: var(--accent); flex: 0 0 auto; }

/* Range inputs: 28 px thumb with a 44 px pseudo hit area (§4.2). */
input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: var(--tap);
  background: transparent;
  cursor: pointer;
  touch-action: manipulation;
}
input[type="range"]::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 2px;
  background: #3a3a41;
}
input[type="range"]::-moz-range-track { height: 4px; border-radius: 2px; background: #3a3a41; }
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 28px; height: 28px;
  margin-top: -12px;
  border-radius: 50%;
  background: var(--rover);
  border: 2px solid #1a1408;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.6);
}
input[type="range"]::-moz-range-thumb {
  width: 24px; height: 24px;
  border-radius: 50%;
  background: var(--rover);
  border: 2px solid #1a1408;
}
input[type="range"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

#dock-foot {
  flex: 0 0 auto;
  padding: 8px 12px calc(8px + var(--safe-b));
  border-top: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: 11px;
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
}
.badge::before {
  content: "";
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--muted);
  flex: 0 0 auto;
}
.badge.live { color: #9be08f; }
.badge.live::before { background: #7ad06a; box-shadow: 0 0 6px #7ad06a; }
.badge.amber { color: #e5b769; }
.badge.amber::before { background: #e0a63c; }

/* ---------------------------------------------------------------------------
 * Map + map chrome
 * ------------------------------------------------------------------------ */
#map-wrap { position: relative; grid-column: 2; min-width: 0; min-height: 0; }
#map { position: absolute; inset: 0; background: #0a0a0c; }
.maplibregl-canvas:focus { outline: none; }

#map-tools {
  position: absolute;
  top: 10px; right: 10px;
  z-index: 4;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
}
.tool {
  min-width: var(--tap);
  min-height: var(--tap);
  padding: 0 10px;
  background: rgba(28, 28, 31, 0.92);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  cursor: pointer;
  font-size: 12px;
  backdrop-filter: blur(3px);
  touch-action: manipulation;
}
.tool:hover { border-color: var(--accent); }
.tool[aria-pressed="true"] { border-color: var(--accent); color: #fff; background: rgba(34, 48, 56, 0.95); }

#exag-wrap {
  width: 190px;
  padding: 8px 10px 2px;
  background: rgba(28, 28, 31, 0.92);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  backdrop-filter: blur(3px);
  font-size: 11px;
}
#exag-wrap[hidden] { display: none; }
#exag-wrap label { color: var(--muted); display: block; margin-bottom: 2px; }
#exag-wrap output { display: block; text-align: right; color: var(--text); margin-top: -6px; }

/* ---------------------------------------------------------------------------
 * D10 — INTUITIVE(R) Planetarium emblem, top-left of the map.
 *
 * Top-left is the only corner nothing else claims: #map-tools owns top-right,
 * #readout owns bottom-left, MapLibre's attribution owns bottom-right.
 *
 * Sized 40 px but with a 44 px minimum box, because 44 px is the Apple/WCAG
 * touch floor and this is a link a thumb has to hit on a phone. The radial
 * halo is doing real work: the emblem is solid white with a transparent
 * background and Jezero's dunes are bright, so without it the glyph vanishes
 * over pale terrain. A gradient rather than a chip, so it reads as chrome
 * rather than a badge stuck onto the map.
 *
 * Kept visible in kiosk mode, unlike #map-tools and #readout which are hidden
 * there: those are CONTROLS, and the dome is precisely the context where the
 * planetarium's own mark belongs.
 * ------------------------------------------------------------------------ */
#brand-mark {
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 4;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  min-width: 44px;
  min-height: 44px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(0, 0, 0, 0.45) 0%, rgba(0, 0, 0, 0) 70%);
  opacity: 0.78;
  transition: opacity 0.16s ease;
  -webkit-tap-highlight-color: transparent;
}
#brand-mark img {
  width: 34px;
  height: 34px;
  display: block;
  pointer-events: none;
}
#brand-mark:hover,
#brand-mark:focus-visible { opacity: 1; }
#brand-mark:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The tour dims the chrome; the emblem goes with it rather than standing proud
 * of a darkened map. */
body.tour #brand-mark { opacity: 0.35; }

/* ---------------------------------------------------------------------------
 * D11 — *INTUITIVE*(R) wordmark treatment, from Planetarium_Brand_Kit_V7 p1:
 * INTUITIVE is set in BOLD ITALIC CAPS with a superscript (R) immediately
 * after it, PLANETARIUM upright. Prose that cannot carry italics (meta tags,
 * the map attribution string, ABOUT.md) uses "INTUITIVE(R) Planetarium".
 * ------------------------------------------------------------------------ */
.brand-intuitive {
  font-style: italic;
  font-weight: 700;
  letter-spacing: 0.01em;
}
.brand-reg {
  font-style: normal;
  font-weight: 400;
  font-size: 0.62em;
  vertical-align: super;
  line-height: 0;
  margin-left: 0.5px;
}

#readout {
  position: absolute;
  left: 10px; bottom: 10px;
  z-index: 4;
  display: flex;
  flex-direction: column;
  gap: 5px;
  pointer-events: none;
  font-size: 11px;
}
#scalebar {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--text);
  text-shadow: 0 1px 3px #000;
}
#scalebar .bar {
  height: 5px;
  width: 80px;
  border: 1px solid rgba(255, 255, 255, 0.85);
  border-top: 0;
  background: rgba(0, 0, 0, 0.25);
  transition: width 0.12s linear;
}
#elev-readout {
  align-self: flex-start;
  padding: 3px 7px;
  background: rgba(17, 17, 17, 0.78);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
}
#elev-readout:empty, #elev-readout.hidden { display: none; }

/* tour card (tour.js owns the contents) */
#tour-card {
  position: absolute;
  left: 50%;
  bottom: 18px;
  transform: translateX(-50%);
  z-index: 6;
  width: min(640px, calc(100% - 24px));
  padding: 12px 14px calc(12px + var(--safe-b));
  background: rgba(24, 24, 27, 0.95);
  border: 1px solid var(--border);
  border-radius: 6px;
  box-shadow: 0 6px 26px rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(4px);
}
#tour-card[hidden] { display: none; }
#tour-card .tour-step { color: var(--muted); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; }
#tour-card .tour-title { font-size: 17px; margin: 2px 0 6px; }
#tour-card .tour-caption { font-size: 13.5px; line-height: 1.5; margin: 0 0 10px; }
#tour-card .tour-nav { display: flex; align-items: center; gap: 8px; }

/* kiosk sol readout */
#kiosk-sol {
  position: absolute;
  top: 18px; left: 24px;
  z-index: 5;
  font-size: 54px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: #fff;
  text-shadow: 0 2px 14px rgba(0, 0, 0, 0.85);
  pointer-events: none;
}
#kiosk-sol[hidden] { display: none; }
#kiosk-sol .sub { display: block; font-size: 17px; font-weight: 400; color: #cfcfcf; }

body.kiosk #dock, body.kiosk #topbar, body.kiosk #map-tools, body.kiosk #readout { display: none; }

/* ---------------------------------------------------------------------------
 * D4 — capture mode (`?shot=1`). Every scrap of UI off, map full-bleed, so a
 * screenshot is publication-ready without cropping a sidebar out by hand.
 *
 * Distinct from kiosk on purpose: kiosk keeps the big SOL readout and the
 * planetarium emblem because it is a STAFFED dome display. Capture keeps
 * NOTHING except the attribution, which stays because NASA/ESA/USGS credit is
 * a condition of using the imagery, not a piece of chrome we get to hide.
 * ------------------------------------------------------------------------ */
body.shot #dock,
body.shot #topbar,
body.shot #map-tools,
body.shot #readout,
body.shot #brand-mark,
body.shot #tour-card,
body.shot #sol-pill,
body.shot #kiosk-sol,
body.shot #toasts { display: none !important; }
body.shot #app { grid-template-columns: 1fr; }
body.shot #map-wrap { grid-column: 1; }
/* G3 (2026-08-25): a DANGLING SELECTOR lived here. E1 removed
   `body.shot .maplibregl-ctrl-attrib { ... }` but left the `body.shot ` prefix
   and its stale comment behind. CSS strips comments, so the parser read the
   next rule as a descendant of it — `body.shot .maplibregl-popup` — quietly
   scoping the popup's font declaration to capture mode only. Everything else
   in the popup block was a separate rule and kept working, which is exactly
   why the F6 fix verified green and this survived anyway. Found by reading the
   file for an unrelated reason. */

/* E1 (2026-08-25): the attribution styling that stood here is gone with the
   control itself. map.js no longer adds a MapLibre AttributionControl — the
   credits live in the About panel now, in full, instead of ellipsised into one
   line over the terrain.

   F6 (2026-08-25) — RESTORED BELOW, and a lesson. The E1 removal was done by
   slicing style.css between two string offsets, and the popup theming block
   sat INSIDE that span: everything from .maplibregl-popup through the .pop-*
   content classes went out with the attribution rules. MapLibre's own popup
   defaults are white-on-white against this dark theme, so every popup opened
   as the "empty window" David reported — the elevation readout, the waypoint
   popup and the sample popup all rendered their content invisibly.

   Recovered verbatim from commit 6015ac19 (the last commit before E1) with the
   attribution rules filtered out, so nothing that was deleted on purpose comes
   back. Never delete CSS by byte range again: delete by rule. */

/* ---------------------------------------------------------------------------
 * MapLibre popup + control theming (the defaults are white)
 * ------------------------------------------------------------------------ */
.maplibregl-popup { font: 13px/1.45 system-ui, sans-serif; }
.maplibregl-popup-content {
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 11px 13px;
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.6);
}
.maplibregl-popup-anchor-top .maplibregl-popup-tip { border-bottom-color: var(--panel); }
.maplibregl-popup-anchor-top-left .maplibregl-popup-tip { border-bottom-color: var(--panel); }
.maplibregl-popup-anchor-top-right .maplibregl-popup-tip { border-bottom-color: var(--panel); }
.maplibregl-popup-anchor-bottom .maplibregl-popup-tip { border-top-color: var(--panel); }
.maplibregl-popup-anchor-bottom-left .maplibregl-popup-tip { border-top-color: var(--panel); }
.maplibregl-popup-anchor-bottom-right .maplibregl-popup-tip { border-top-color: var(--panel); }
.maplibregl-popup-anchor-left .maplibregl-popup-tip { border-right-color: var(--panel); }
.maplibregl-popup-anchor-right .maplibregl-popup-tip { border-left-color: var(--panel); }
.maplibregl-popup-close-button {
  width: 30px; height: 30px;
  color: var(--muted);
  font-size: 18px;
  background: none;
  border-radius: 0 6px 0 0;
}
.maplibregl-popup-close-button:hover { background: var(--panel2); color: #fff; }
/* popup content classes used by the popup builders */
.pop-head { font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.pop-title { font-size: 15px; margin: 1px 0 5px; }
.pop-row { margin: 2px 0; }
.pop-thumbs { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; margin: 8px 0 6px; }
.pop-thumbs img, .pop-thumbs .sk {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  background: var(--panel2);
  border: 1px solid var(--border);
  border-radius: 3px;
}
.pop-links { display: flex; flex-direction: column; gap: 3px; font-size: 12px; margin-top: 6px; }
.maplibregl-ctrl-group {
  background: rgba(28, 28, 31, 0.92);
  border: 1px solid var(--border);
  box-shadow: none;
}
.maplibregl-ctrl-group button + button { border-top: 1px solid var(--border); }

/* Markers. v6 adds .maplibregl-marker-covered when a marker is behind terrain
   (opacityWhenCovered) — dim it further so it reads as "behind the hill". */
.jz-marker { display: block; cursor: pointer; will-change: transform; }
.jz-marker img { display: block; width: 100%; height: 100%; pointer-events: none; }
.maplibregl-marker-covered .jz-marker { filter: saturate(0.4); }

/* ===========================================================================
 * MOBILE — bottom sheet with three detents (§4.2)
 * ======================================================================== */
@media (max-width: 899px) {
  #app { grid-template-columns: 1fr; grid-template-rows: 1fr; }

  /* E4 (2026-08-25) — ONE row of controls across the top, nothing on the map.
     David: "if we have that menu bar up top, let's put all the clickable items
     across the top, or get rid of it and keep them all top right."

     Taking the first option. #map-tools stops being a vertical stack floating
     over the terrain and becomes a horizontal group pinned INSIDE the topbar's
     band, hard right. Together with the ☰ on the left that is a single 44 px
     strip holding every control on the screen, and the map underneath is clean.

     This reverses D7's choice of WHICH duplicate 3D button survives. D7 kept
     the topbar's one because #map-tools sat on the terrain and the topbar did
     not; now that the tools ARE the topbar, keeping them as one group beats
     splitting 3D away from its four siblings. map.js's two-button wiring is
     untouched either way — only which one is display:none changes. */
  #btn-3d-mobile { display: none; }

  #map-tools {
    position: fixed;
    top: var(--safe-t);
    right: 4px;
    height: var(--topbar-h);
    z-index: 9;                 /* above #topbar's own background */
    flex-direction: row;
    align-items: center;
    gap: 4px;
  }

  /* The exaggeration panel can no longer stack under the buttons — they are a
     row now — so it hangs below the bar as a floating card, right-aligned to
     the button that opened it. */
  #map-tools #exag-wrap {
    position: absolute;
    top: calc(var(--topbar-h) - 2px);
    right: 0;
    width: 210px;
  }

  /* The tools stay a full 44 px square — D2 measured every control on this app
     to that standard and it is not worth trading back for title width. The sums
     fit: on a 360 px phone the worst case is ☰ (44) + 5 tools (5×44) + gaps
     (~24) = 288, leaving ~72 px of title, which the ellipsis below handles.
     Only the horizontal padding and type size come down. */
  #map-tools .tool {
    padding: 0 6px;
    font-size: 11.5px;
  }

  /* The title yields first — the app name is already at the top of the sheet,
     so it is the one thing in this bar that is repeated elsewhere. */
  .topbar-title {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* D10: below the mobile topbar, which overlays the top of the map. */
  #brand-mark { top: calc(var(--topbar-h) + var(--safe-t) + 8px); }

  #topbar {
    position: fixed;
    top: 0; left: 0; right: 0;
    z-index: 8;
    height: calc(var(--topbar-h) + var(--safe-t));
    padding: var(--safe-t) 6px 0;
    display: flex;
    align-items: center;
    gap: 6px;
    background: rgba(17, 17, 17, 0.72);
    backdrop-filter: blur(6px);
    border-bottom: 1px solid rgba(255, 255, 255, 0.07);
  }
  .icon-btn {
    min-width: var(--tap);
    min-height: var(--tap);
    background: none;
    border: 0;
    color: var(--text);
    font-size: 17px;
    cursor: pointer;
    touch-action: manipulation;
  }
  .icon-btn[aria-pressed="true"] { color: var(--accent); }
  .topbar-title { flex: 1 1 auto; font-size: 14px; font-weight: 600; }

  #map-wrap { grid-column: 1; grid-row: 1; }
  /* E4: landscape keeps the same single top strip as portrait — the rule
     that used to push #map-tools below the bar would have undone it. */
  #map-tools { top: var(--safe-t); }
  /* Back to +8 (E1, 2026-08-25). This was +44 to clear the 32 px attribution
     box that used to sit at peek-h − 4; with the credit box removed from the
     map there is nothing left to clear, so the readout and scale bar reclaim
     36 px of phone screen. */
  #readout { bottom: calc(var(--peek-h) + 8px); }
  #tour-card { bottom: calc(var(--peek-h) + 10px); padding-bottom: 12px; }
  #toasts { bottom: calc(var(--peek-h) + 14px); }

  /* The sheet. Height comes from the detent; --drag is set live while the
     handle is being dragged and cleared on release. */
  #dock {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 9;
    grid-column: 1;
    height: var(--peek-h);
    max-height: var(--full-h);
    border-right: 0;
    border-top: 1px solid var(--border);
    border-radius: 12px 12px 0 0;
    box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.55);
    transform: translateY(var(--drag, 0px));
    transition: height 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);
  }
  #dock.dragging { transition: none; }
  body[data-detent="half"] #dock { height: var(--half-h); }
  body[data-detent="full"] #dock { height: var(--full-h); }

  /* touch-action:none ONLY on the handle — anywhere else it would kill
     scrolling inside the panels. */
  #dock-handle {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 26px;
    flex: 0 0 auto;
    touch-action: none;
    cursor: grab;
  }
  #dock-handle .grip {
    width: 44px; height: 4px;
    border-radius: 2px;
    background: #4a4a52;
  }
  #dock-handle:active { cursor: grabbing; }

  #dock-head { padding: 2px 12px 8px; }
  #dock-head .titles { display: none; }          /* the topbar already says it */
  #dock-head button.primary { min-height: 40px; }

  /* tab strip */
  #tabs {
    display: flex;
    gap: 4px;
    margin-top: 8px;
  }
  #tabs button {
    flex: 1 1 0;
    min-height: 38px;
    background: var(--panel2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    color: var(--muted);
    font-size: 12px;
    cursor: pointer;
    touch-action: manipulation;
  }
  #tabs button[aria-selected="true"] { color: #fff; border-color: var(--accent); background: #223038; }

  /* One tab visible at a time on mobile. */
  #panels > section.panel[hidden] { display: none; }

  #dock-foot { padding-bottom: calc(8px + var(--safe-b)); }

  /* Attribution and scale sit above the sheet. */
  .maplibregl-ctrl-bottom-right { bottom: calc(var(--peek-h) - 4px); }
  .maplibregl-ctrl-bottom-left { display: none; }   /* our own scale bar instead */
}

/* ---------------------------------------------------------------------------
 * PORTRAIT PHONE, PEEK detent: only the timeline shows, whatever tab is
 * selected — the sol slider is the one control that must always be a thumb
 * away. Kept out of the general mobile block so the landscape drawer (which
 * slides off-screen instead of shrinking) is unaffected.
 * ------------------------------------------------------------------------ */
@media (max-width: 899px) and (min-height: 501px) {
  /* E4 (2026-08-25): 148 -> 72. The peek row is now grip (14) + one
     44 px slider row + 8 px padding + the safe-area inset. */
  :root { --peek-h: calc(72px + var(--safe-b)); }

  body[data-detent="peek"] #dock-head button.primary,
  body[data-detent="peek"] #tabs,
  body[data-detent="peek"] #dock-foot { display: none; }
  body[data-detent="peek"] #panels { overflow: hidden; }
  /* E2 (2026-08-25): peek now hides EVERY panel instead of force-showing the
     old TIMELINE one. The sol slider moved out of the panels into #clock,
     which lives in #dock-head and is not hidden here — so the one control that
     must always be a thumb away is reachable at peek without singling out a
     vehicle. Before E2 this rule named TIMELINE explicitly, which would have
     meant peek showing Percy and not Ginny. */
  body[data-detent="peek"] #panels > section.panel { display: none; }
  body[data-detent="peek"] section.panel > .panel-title { display: none; }

  /* E4 (2026-08-25) — the closed drawer is now genuinely minimal.
     David: "the menu drawer at the bottom also takes up a lot of real estate
     and should be minimized when it isn't open."

     Peek went 148 px -> 72 px (--peek-h, mobile override below) by collapsing
     the clock from four stacked rows (SOL / date / slider / play+speed) onto
     ONE row: the sol number and the slider side by side. Everything else in
     the head — app title, subtitle, tour button, tabs, play/speed — is hidden
     until the sheet is actually opened.

     What survives is deliberate. The sol slider stays, at its full 44 px touch
     height, because it is the one control that must always be a thumb away
     (the V2 fix of 2026-08-23 established that, and dropping it here would
     undo that work rather than build on it). The rest is one drag away.

     Net: the map gets 76 px of phone screen back, and #readout follows for
     free because its offset is expressed in --peek-h. */
  body[data-detent="peek"] #dock-handle { height: 14px; }
  body[data-detent="peek"] #dock-head {
    padding: 0 10px 4px;
    border-bottom: 0;
  }
  body[data-detent="peek"] #dock-head .titles { display: none; }
  body[data-detent="peek"] #clock { margin: 0; }
  body[data-detent="peek"] #clock .clock-head {
    display: contents;          /* let SOL sit directly in the flex row below */
  }
  body[data-detent="peek"] #clock #tl-date,
  body[data-detent="peek"] #clock .clock-controls { display: none; }
  body[data-detent="peek"] #clock {
    display: flex;
    align-items: center;
    gap: 10px;
  }
  body[data-detent="peek"] #clock #tl-sol {
    font-size: 17px;
    flex: 0 0 auto;
    white-space: nowrap;
  }
  body[data-detent="peek"] #clock #tl-slider {
    flex: 1 1 auto;
    width: auto;
    margin: 0;
  }
}

/* ===========================================================================
 * LANDSCAPE PHONE — the sheet becomes a 300 px right drawer (§4.2)
 * ======================================================================== */
@media (max-width: 899px) and (max-height: 500px) {
  #dock {
    top: 0; bottom: 0; left: auto; right: 0;
    width: 300px;
    height: auto !important;
    max-height: none;
    border-top: 0;
    border-left: 1px solid var(--border);
    border-radius: 0;
    transform: translateX(var(--drag-x, 0px));
    transition: transform 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);
  }
  /* PEEK = closed drawer: it slides off-screen rather than shrinking, so
     nothing inside it needs collapsing. */
  body[data-detent="peek"] #dock { transform: translateX(300px); }

  #dock-handle {
    position: absolute;
    left: -26px; top: 50%;
    width: 26px; height: 64px;
    margin-top: -32px;
    background: var(--panel);
    border: 1px solid var(--border);
    border-right: 0;
    border-radius: 6px 0 0 6px;
  }
  #dock-handle .grip { width: 4px; height: 30px; }

  #readout, #toasts, #tour-card { bottom: 10px; }
  #tour-card { left: calc(50% - 150px); }
  #map-tools { right: 310px; }
  .maplibregl-ctrl-bottom-right { bottom: 0; right: 300px; }
}

/* Landscape floating sol pill. timeline.js fills it and clears [hidden]; it is
   only ever shown on a landscape phone, where the sheet is a right drawer. */
#sol-pill { display: none; }
@media (max-width: 899px) and (max-height: 500px) {
  #sol-pill:not([hidden]) {
    display: flex;
    position: absolute;
    left: 50%; bottom: 10px;
    transform: translateX(-50%);
    z-index: 5;
    align-items: center;
    gap: 8px;
    padding: 6px 12px;
    background: rgba(28, 28, 31, 0.92);
    border: 1px solid var(--border);
    border-radius: 24px;
  }
}

/* ---------------------------------------------------------------------------
 * Pointer-coarse devices: no hover affordances, bigger targets.
 *
 * D2 (2026-08-24) — audited at 390x844, 360x800 and 844x390 with real touch
 * emulation, measuring every rendered control. The 44 px floor is Apple HIG
 * and WCAG 2.5.5; every exception below was a MEASURED miss, not a guess:
 *
 *   #tabs button        38 px tall  -> the four bottom-sheet tabs
 *   button.primary      40 px tall  -> "START THE TOUR", shrunk by the mobile
 *                                      dock-head rule at line ~748
 *   .link-btn           32 px tall  -> "Where is Percy now?", "Copy link"
 *   .toggle             26 px tall  -> every layer checkbox row; the LABEL is
 *                                      the tap target, so growing the label is
 *                                      what matters, not the 15 px box
 *   .row .btn           41 px wide  -> "Fast" in the Slow/Normal/Fast segment
 *
 * Gated on (hover: none) rather than a width breakpoint on purpose: a touch
 * laptop needs the big targets and a narrow desktop window does not.
 * ------------------------------------------------------------------------ */
@media (hover: none) {
  .btn { min-height: var(--tap); min-width: var(--tap); }
  .link-btn { min-height: var(--tap); display: inline-flex; align-items: center; }
  #tabs button { min-height: var(--tap); }
  #dock-head button.primary { min-height: var(--tap); }
  body[data-detent="peek"] #dock-head button.primary { min-height: var(--tap); }
  /* The label is the hit target; the box just has to be legible next to it. */
  .toggle { min-height: var(--tap); }
  .toggle input[type="checkbox"] { width: 18px; height: 18px; }
  /* The sheet grip is a 26 px bar for looks; widen only what a thumb hits. */
  #dock-handle { position: relative; }
  #dock-handle::after {
    content: "";
    position: absolute;
    inset: -9px -9px;
    /* no paint — hit area only */
  }
}

@media (prefers-reduced-motion: reduce) {
  #dock, #scalebar .bar { transition: none; }
}

/* ===========================================================================
 * E2 (2026-08-25) — the mission clock, and the SHARED vehicle-panel shape.
 *
 * David: "Perseverance and Ingenuity are almost treated like different modes.
 * They should both be treated in the same way in the menu."
 *
 * The `.veh-*` classes are the mechanism. Percy's panel and Ginny's panel are
 * literally the same CSS — header, scrollable list, click-a-row, profile chart
 * — so the two cannot drift apart again by someone styling one of them. The
 * ONLY thing that differs is the accent, taken from each vehicle's own map
 * colour by scoping on the panel id. Same structure, own identity.
 * ======================================================================== */

/* --- the clock: neutral ground, owned by neither vehicle ---------------- */
#clock { margin: 10px 0 4px; }
.clock-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
#clock #tl-sol { font-size: 26px; font-weight: 700; color: #fff; line-height: 1.15; }
#clock #tl-date { margin: 0; }
#clock #tl-slider { width: 100%; margin: 4px 0 2px; }
.clock-controls { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.clock-controls #tl-play { flex: 0 0 auto; min-width: 84px; }
.clock-controls .seg { display: flex; gap: 0; flex: 1 1 auto; }
.clock-controls .seg .btn {
  flex: 1 1 0;
  border-radius: 0;
  min-width: 0;
  padding-left: 4px;
  padding-right: 4px;
}
.clock-controls .seg .btn:first-child {
  border-top-left-radius: var(--radius, 4px);
  border-bottom-left-radius: var(--radius, 4px);
}
.clock-controls .seg .btn:last-child {
  border-top-right-radius: var(--radius, 4px);
  border-bottom-right-radius: var(--radius, 4px);
}
/* G1 (2026-08-25, David on a Pixel 8 Pro: "normal and fast buttons do not have
   their full outline drawn when selected"). This was `border-left: 0` on every
   button after the first, so the segments would butt together without a double
   border. Slow is :first-child and kept all four sides; Normal and Fast were
   missing their left edge, so the pressed highlight drew as three sides.

   Overlap the borders instead of deleting one: every button keeps a complete
   box, adjacent edges are pulled onto each other by -1px so it still reads as
   one control, and the pressed button is lifted above its neighbours so its
   full outline is the one that survives the overlap. */
.clock-controls .seg .btn + .btn { margin-left: -1px; }
.clock-controls .seg .btn[aria-pressed="true"] { position: relative; z-index: 1; }
.clock-controls .seg .btn:focus-visible { position: relative; z-index: 2; }

/* --- the shared vehicle-panel shape ------------------------------------- */
.veh-header { margin: 2px 0 10px; font-size: 12.5px; }

.veh-list {
  max-height: 260px;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  border: 1px solid var(--border, #333);
  border-radius: var(--radius, 4px);
  margin-bottom: 10px;
}
.veh-row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: 44px;          /* touch target — never shrink this */
  padding: 0 8px;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--border, #333);
  color: var(--text, #ddd);
  font: inherit;
  text-align: left;
  cursor: pointer;
  touch-action: manipulation;
}
.veh-row:last-child { border-bottom: 0; }
.veh-row:hover, .veh-row.sel { background: var(--panel2, #25252a); }
.veh-row.sel { color: #fff; }
.veh-num { flex: 0 0 58px; font-weight: 600; }
.veh-mid { flex: 1 1 auto; color: var(--muted, #888); font-size: 11px; }
.veh-a, .veh-b { flex: 0 0 auto; font-size: 11.5px; }

/* The one per-vehicle difference: each panel wears its own map colour, so the
   list you are reading always matches the line you are looking at. */
section.panel[data-panel="PERCY"] .veh-num { color: var(--rover, #ff5cc8); }
section.panel[data-panel="PERCY"] .veh-row.sel { box-shadow: inset 2px 0 0 var(--rover, #ff5cc8); }
section.panel[data-panel="GINNY"] .veh-num { color: var(--heli, #b57bff); }
section.panel[data-panel="GINNY"] .veh-row.sel { box-shadow: inset 2px 0 0 var(--heli, #b57bff); }

/* --- the shared profile chart ------------------------------------------- */
.veh-chart { margin: 6px 0 10px; }
.veh-chart-svg {
  display: block;
  width: 100%;
  height: auto;
  background: rgba(255, 255, 255, 0.03);
  border-radius: 3px;
}
.veh-chart-ax { display: flex; justify-content: space-between; margin-top: 2px; }
