/* ==========================================================================
   Blue Marble — Trip panel re-skin (W2b Task 1)
   Flag: web_first_light (apps/globe/views.py::_globe_web_flags). Trip MODE
   (static/js/globe/dock.js's setTripPanelVisible) sets #ag-dir's inline
   style to display:flex when [data-bm-mode="trip"] is active — an inline
   style always wins over chrome.css's `.bm-first-light #ag-dir{display:
   none}` default-hidden rule, so this file only needs to own the panel's
   LOOK, never its show/hide mechanics.

   CSS-ONLY re-skin of the legacy "Advanced Tools" panel (#ag-dir) into the
   mockup's pills-first Trip Studio layout. See task-1-report.md's W2b
   addendum for the two hard invariants this file was built to respect:
     1. Legacy id count (`grep -c 'id="ag-' templates/globe/index.html`)
        unchanged — every #ag-* selector below targets an EXISTING id/class/
        attribute; none are new markup.
     2. Flag-off byte-identical — the only markup change anywhere in this
        task is the single `bm-trip` class token on #ag-dir's root, gated by
        {% if first_light_on %}. Every visual change below is scoped under
        `.bm-first-light #ag-dir.bm-trip`, so it is entirely inert unless
        BOTH the flag is on AND this file has loaded.

   Reordering technique: the legacy markup under #ag-panel-general is ~24
   sibling rows/labels/inputs with no wrapper divs and several reused across
   the page (`.ag-row`, `.ag-label`, `.ag-toggle`), so plain :nth-child
   indices would be silently fragile if that markup is ever edited without
   updating this file. Every selector below instead anchors on something
   already globally unique — an id, an existing `for="<id>"` attribute, or
   `:has(#some-id)` / `:has(+ #some-id)` (this codebase already uses :has()
   bare, unprefixed, e.g. static/css/nav/nav.css's `body:has(#ag-nav-shell
   ...)`  #ag-dir rule and static/css/components/_shell.css's
   `.shell__search:has(...)`) — so a row keeps reordering correctly even if
   more rows are added around it later. If :has() is ever unsupported, the
   affected declaration simply fails to match and that one row falls back to
   its natural (pre-reorder) position — a soft, non-breaking degradation,
   not a broken panel.

   Tokens + LOGICAL properties only (inset-inline-*, margin-inline-*,
   padding-inline-*, etc.) — no bare left:/right:/margin-left:/etc.
   scripts/check_logical_css.sh gates this file. --bm-panel/--bm-line/
   --bm-text/--bm-dim/--bm-faint come from chrome.css's `.bm-first-light`
   scope (AA-contrast-checked dark-glass tokens for this dark chrome —
   never design-tokens.css's LIGHT-surface --ag-color-text-muted, which
   fails contrast here).

   Mockup: docs/superpowers/mockups/2026-07-06-globe-web-blue-marble/
   02-trip-studio.html.
   ========================================================================== */

/* ── 1. Panel surface: adopt the .bm-glass dark-panel recipe (chrome.css) ─
   Can't add class="bm-glass" to #ag-dir (only the gated bm-trip token is an
   allowed markup change — see file header, invariant 2), so the recipe's
   declarations are inlined here instead. Legacy .ag-dir (templates/globe/
   index.html's inline <style>, ~:1058) already owns position/size/overflow/
   scroll behavior — only the surface *look* is overridden here. */
.bm-first-light #ag-dir.bm-trip {
  background: var(--bm-panel);
  border-color: var(--bm-line);
  box-shadow: var(--ag-shadow-2xl);
}

.bm-first-light #ag-dir.bm-trip .ag-dir__title,
.bm-first-light #ag-dir.bm-trip .ag-mode-badge {
  color: var(--bm-text);
}

/* ── 2. Tips block: hidden via CSS, never deleted from the markup ────────
   The "Tips:" documentation div (templates/globe/index.html, first child of
   .ag-dir__body, right above the tab switcher) has no id and no class of
   its own beyond the widely-reused `.ag-subtle` (also used deeper inside
   #ag-panel-general for "No stops yet." / "Click Add stop..." — those must
   stay visible, so `.ag-subtle` alone would be wrong). It's precisely and
   uniquely identified by structural position instead: the only `.ag-subtle`
   that is a DIRECT child of `.ag-dir__body` — every other `.ag-subtle` on
   this panel is nested inside #ag-panel-general's .ag-dir__form. */
.bm-first-light #ag-dir.bm-trip .ag-dir__body > .ag-subtle:first-child {
  display: none;
}

/* ── 3. Pills-first flex order ────────────────────────────────────────────
   .ag-dir__form (templates/globe/index.html ~:2834) holds every control in
   #ag-panel-general as ~24 plain siblings in legacy tab order; legacy CSS
   only sets `flex:0 0 auto` on it (a flex ITEM inside .ag-dir__body's own
   flex column, but not itself a flex container). Made a flex column here so
   `order` can re-sequence its children to match mockup 02 (From/To pills →
   Add-stop/Swap → mode → routes → primary CTA → voice/share/offline row)
   WITHOUT moving a single element in the DOM — tab/reading order for
   keyboard and screen-reader users stays the original document order (a
   known trade-off of CSS `order`; see task-1-report.md's Concerns). */
.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-dir__form {
  display: flex;
  flex-direction: column;
  gap: var(--ag-space-4);
}

/* 3a. From/To pills (top) — Start label + #ag-start-geocoder,
   Destination label + #ag-dest-geocoder. */
.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-label:has(+ #ag-start-geocoder) { order: 10; }
.bm-first-light #ag-dir.bm-trip #ag-panel-general #ag-start-geocoder { order: 11; }
.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-label:has(+ #ag-dest-geocoder) { order: 12; }
.bm-first-light #ag-dir.bm-trip #ag-panel-general #ag-dest-geocoder { order: 13; }
/* Place Start / Place Destination — the map-click alternative to the two
   pills above; grouped immediately after them. */
.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-row:has(#ag-place-start) { order: 14; }

/* 3b. Add-stop / Swap cluster. */
.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-row:has(#ag-swap) { order: 20; }
.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-label:has(+ #ag-stops) { order: 21; }
.bm-first-light #ag-dir.bm-trip #ag-panel-general #ag-stops { order: 22; }
.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-row:has(#ag-add-stop) { order: 23; }
.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-row:has(#ag-add-stop) + .ag-subtle { order: 24; }

/* 3c. Mode row. */
.bm-first-light #ag-dir.bm-trip #ag-panel-general label[for="ag-mode"] { order: 30; }
.bm-first-light #ag-dir.bm-trip #ag-panel-general #ag-mode { order: 31; }
.bm-first-light #ag-dir.bm-trip #ag-panel-general label[for="ag-auto-route"] { order: 32; }
.bm-first-light #ag-dir.bm-trip #ag-panel-general label[for="ag-round-trip"] { order: 33; }
.bm-first-light #ag-dir.bm-trip #ag-panel-general #ag-avoid-section { order: 34; }

/* 3d. Routes — #ag-panel-general has no route-LIST container of its own
   (the live route list/#ag-routes lives in the separate #ag-results-popup
   modal, outside #ag-dir entirely — out of reach of this flex-order
   technique without moving markup across containers, which invariant 2
   forbids); the closest in-panel analog is the Get route / Fit route row. */
.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-row:has(#ag-get-route) { order: 40; }

/* 3e. Primary CTA. */
.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-row:has(#ag-start-nav) { order: 50; }

/* 3f. Voice / Share / Offline / GPX icon row (mockup's "🔊 Voice on · 📎
   Share route · ⬇ Offline · ⋯" line). */
.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-row:has(#ag-share) { order: 60; }
.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-row:has(#ag-offline) { order: 61; }
.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-row:has(#ag-gpx-import) { order: 62; }
.bm-first-light #ag-dir.bm-trip #ag-panel-general label[for="ag-voice-nav"] { order: 63; }

/* 3g. Emergency share — trailing. Mockup 02's safety cluster (share live
   trip / auto check-in / SOS) is a separate floating .safety.glass resident
   overlay, not part of the tripbar; we have no new-markup budget to spin
   one out here, so it stays inside the same reordered column, last. */
.bm-first-light #ag-dir.bm-trip #ag-panel-general label[for="ag-emergency-contact"] { order: 70; }
.bm-first-light #ag-dir.bm-trip #ag-panel-general #ag-emergency-contact { order: 71; }
.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-row:has(#ag-emergency) { order: 72; }

/* ── 4. Start/Destination pills: dark-glass surface + colored dot ────────
   #ag-start-geocoder/#ag-dest-geocoder are Mapbox Geocoder plugin MOUNT
   points — mapbox-gl-geocoder.js injects its own `.mapboxgl-ctrl-geocoder`
   markup (icon, input, clear button, all absolutely positioned by the
   plugin's own CDN stylesheet) as real DOM children at runtime. Padding the
   MOUNT to make room for an inline dot would require knowing that internal
   layout precisely (unverifiable here — no browser/live Mapbox session in
   this environment), so the colored dot is instead a small corner badge
   that straddles the pill's inline-start edge (a `border` in --bm-panel
   separates it from the pill underneath) — fully decoupled from whatever
   the plugin renders inside, and immune to `.innerHTML` replacement since
   ::before is generated content, not a real child node. */
.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-geocoder {
  position: relative;
}

.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-geocoder::before {
  content: "";
  position: absolute;
  inset-inline-start: calc(-1 * var(--ag-space-1));
  inset-block-start: 50%;
  translate: 0 -50%;
  inline-size: 10px;
  block-size: 10px;
  border-radius: var(--ag-radius-full);
  border: 2px solid var(--bm-panel);
  z-index: 3;
  pointer-events: none;
}

/* start = green, destination = red — matches mockup 02's .wpt.start/.wpt.end
   waypoint markers and .tdot pill dots. */
.bm-first-light #ag-dir.bm-trip #ag-panel-general #ag-start-geocoder::before {
  background: #059669;
}

.bm-first-light #ag-dir.bm-trip #ag-panel-general #ag-dest-geocoder::before {
  background: #dc2626;
}

/* The pill surface itself: fully-rounded dark glass. Legacy CSS already
   targets these exact selectors (templates/globe/index.html ~:1354-1381)
   with radius/border/background — only the VALUES are overridden here
   (pill radius + --bm- tokens instead of the legacy fixed 12px/rgba mix),
   never new layout-affecting properties, so the plugin's internal
   icon/input positioning (unverified — see comment above) is untouched. */
.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-geocoder .mapboxgl-ctrl-geocoder,
.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-geocoder input[type="text"] {
  border-radius: var(--ag-radius-full);
  background: var(--bm-panel);
  border-color: var(--bm-line);
}

/* ── 5. Breathing room + dark-surface AA-contrast text ────────────────────
   Legacy label/toggle/select/button text already renders near-white
   (rgba(255,255,255,*)) on the legacy panel's own dark background, so it
   already clears AA on this dark surface — restated here through the
   proper --bm-* tokens (never design-tokens.css's LIGHT-surface
   --ag-color-text-muted) so this file is the single source of truth for
   Trip panel text color, not a coincidence of two dark colors agreeing. */
.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-label {
  color: var(--bm-dim);
}

.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-toggle,
.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-select,
.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-btn,
.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-input {
  color: var(--bm-text);
}

.bm-first-light #ag-dir.bm-trip #ag-panel-general .ag-select {
  border-radius: var(--ag-radius-full);
  background: var(--bm-panel);
  border-color: var(--bm-line);
}

/* Extra inner breathing room on top of legacy .ag-dir__inner's 14px. */
.bm-first-light #ag-dir.bm-trip .ag-dir__inner {
  padding: var(--ag-space-5);
}

.bm-first-light #ag-dir.bm-trip .ag-dir__body {
  gap: var(--ag-space-4);
}

/* ── 6. Legacy #ag-mode: kept in the DOM (still the actual routing engine's
   source of truth — see static/js/globe/tripEta.js's file banner) but
   visually replaced by #bm-mode-seg (JS-inserted immediately after it, rule
   7 below). Clipped to 1x1 + clip-path rather than display:none so it stays
   focusable/in the a11y tree — legacy code paths (e.g. dock.js's
   setTripPanelVisible, which focuses #ag-dir's first FOCUSABLE input on
   entering Trip mode) can still reach it. */
.bm-first-light #ag-dir.bm-trip #ag-panel-general #ag-mode {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

/* ── 6a. Legacy Mode label: hidden. `<label for="ag-mode">` (templates/
   globe/index.html ~:2836) still targets the now visually-hidden select
   above — left alone, clicking/tapping the "Mode" text moves focus into
   that 1x1 clipped control with no visible affordance: a real
   keyboard/pointer trap now that #bm-mode-seg (rule 7 below) is the
   visible, self-labeled surface (every button carries its own aria-label,
   and the radiogroup's own aria-label is copied from THIS label's
   textContent at build time — tripEta.js's buildSeg() reads it before this
   rule ever hides it; CSS display:none never touches textContent, so the
   copy still happens correctly). display:none (not visibility:hidden) is
   safe here: the label is only ever an `order:30` item inside
   #ag-panel-general's flex-ordered column (rule 3c above) with no sibling
   positioned relative to it, so removing it from flow doesn't shift
   anything else — see task-2-report.md's "label hide" addendum. */
.bm-first-light #ag-dir.bm-trip #ag-panel-general label[for="ag-mode"] {
  display: none;
}

/* ── 7. #bm-mode-seg — mode segmented control (Task 2). JS-created
   (static/js/globe/tripEta.js's buildSeg()), inserted as #ag-mode's very
   next sibling inside #ag-panel-general's flex-ordered .ag-dir__form (rule
   3 above) — shares the select's own order:31 slot (rule 3c) since the
   now-absolute select no longer competes for flex space. Visual recipe
   ported 1:1 from mockup 02's .modes/.mode/.mode.on/.mode small (docs/
   superpowers/mockups/2026-07-06-globe-web-blue-marble/assets/marble.css)
   through this scope's own --bm- tokens (identical values — see
   chrome.css) rather than the mockup's --nf- ones, matching every other
   rule in this file. */
.bm-first-light #ag-dir.bm-trip #ag-panel-general #bm-mode-seg {
  order: 31;
  display: flex;
  gap: var(--ag-space-1);
}

.bm-first-light #ag-dir.bm-trip #ag-panel-general .bm-mode-seg__btn {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding-block: var(--ag-space-2) 6px;
  padding-inline: var(--ag-space-1);
  border-radius: var(--ag-radius-lg);
  border: 1px solid transparent;
  background: transparent;
  color: var(--bm-dim);
  font-family: inherit;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}

.bm-first-light #ag-dir.bm-trip #ag-panel-general .bm-mode-seg__btn:hover {
  background: var(--bm-line-soft);
}

.bm-first-light #ag-dir.bm-trip #ag-panel-general .bm-mode-seg__btn--on {
  background: rgba(37, 99, 235, 0.22);
  border-color: rgba(96, 165, 250, 0.5);
  color: #fff;
}

.bm-first-light #ag-dir.bm-trip #ag-panel-general .bm-mode-seg__eta {
  font-size: 9.5px;
  font-weight: 600;
  color: var(--bm-faint);
}

.bm-first-light #ag-dir.bm-trip #ag-panel-general .bm-mode-seg__btn--on .bm-mode-seg__eta {
  color: #9cc4ff;
}

.bm-first-light #ag-dir.bm-trip #ag-panel-general .bm-mode-seg__btn:focus-visible {
  outline: 2px solid #93c5fd;
  outline-offset: 2px;
}

/* ── 8. Safety-graded alternate cards (W2b Task 3) ────────────────────────
   static/js/globe/routeCards.js's decorateRoutes() re-classes each legacy
   route row (#ag-routes .ag-chip — see templates/globe/index.html's
   renderRoutesChips()) to .bm-routecard, wraps its existing content (moved,
   never discarded — same id/click-behavior-preserving contract as every
   other re-skin in this file) into .bm-routecard__head, and appends a
   .bm-routecard__badges cluster once a bm:hike-routes event carries
   per-route safety data (routing.js's fetchHikingDirections — see that
   module's own header comment and routeCards.js's file banner for the
   documented latency: hiking-directions isn't wired into the web click flow
   yet, so the badge rules below are exercised by jest today, not by a live
   page, until a future task wires that call in).

   DIFFERENT SCOPING TECHNIQUE than rules 1-7 above, and worth calling out
   explicitly: #ag-routes does NOT live inside #ag-dir, or even inside
   #ag-globe-shell at all. It's part of #ag-results-popup, a modal rendered
   as #ag-globe-shell's OWN SIBLING (verified by literal <div>-depth count
   against the template, not indentation, which this file is not reliably
   nested by: templates/globe/index.html's #ag-globe-shell div opens at
   ~:2603 and its matching closing `</div>` is at ~:3004 — BEFORE
   #ag-results-popup even opens at ~:3023). Rule 3d above already flagged
   half of this ("the live route list/#ag-routes lives in the separate
   #ag-results-popup modal, outside #ag-dir entirely") when explaining why
   the Trip panel's flex-order technique can't reach it either; this task is
   the one that actually has to style inside that modal, so the two
   consequences below are new to this file:

     1. `.bm-first-light`'s tokens (--bm-panel/--bm-line/--bm-text/--bm-dim/
        --bm-faint, chrome.css) are declared ON #ag-globe-shell and only
        inherit DOWN its own subtree — #ag-results-popup, being a sibling,
        never receives them. var(--bm-panel) etc. would silently resolve to
        nothing there. So every color below is a LITERAL value, copied
        1:1 from chrome.css's `.bm-first-light` block (and from mockup 02's
        assets/marble.css .routecard/.rb rules) — never var(--bm-*). Generic
        --ag-* tokens (design-tokens.css's :root scope) are unaffected by
        this and are used normally — those inherit everywhere.
     2. The "double-gated under .bm-first-light so it can't leak" invariant
        can't be a plain descendant selector either, for the same reason.
        Reuses the exact body:has() cross-subtree technique
        static/css/nav/nav.css already applies to this SAME #ag-results-popup
        id for an unrelated rule (`body:has(#ag-nav-shell[data-mounted=
        "true"]) ... #ag-results-popup`) — body is a shared ancestor of both
        #ag-globe-shell and #ag-results-popup, so body:has(#ag-globe-shell
        .bm-first-light) correctly re-derives "is web_first_light on" from
        outside #ag-globe-shell's own subtree. Belt-and-suspenders alongside
        the JS-side gate (routeCards.js's decorateRoutes() is only ever
        CALLED under featureEnabled("web_first_light") — see index.js): rows
        never gain .bm-routecard at all flag-off, but this rule additionally
        can't paint it even if one somehow did. */
body:has(#ag-globe-shell.bm-first-light) #ag-routes {
  flex-direction: column;
  flex-wrap: nowrap;
}

body:has(#ag-globe-shell.bm-first-light) #ag-routes .bm-routecard {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  inline-size: 100%;
  border-radius: var(--ag-radius-2xl);
  border: 1px solid rgba(120, 150, 205, 0.22); /* chrome.css --bm-line, literal — see banner */
  background: rgba(10, 17, 38, 0.5);
  padding-block: var(--ag-space-2) var(--ag-space-3);
  padding-inline: var(--ag-space-3);
  color: #eaf0fb; /* chrome.css --bm-text, literal — see banner */
}

/* Legacy-selected/recommended row (routeCards.js's pickBestIndex) —
   mockup 02's .routecard.best: green accent border + subtle tint. */
body:has(#ag-globe-shell.bm-first-light) #ag-routes .bm-routecard--best {
  border-color: rgba(52, 211, 153, 0.55);
  background: linear-gradient(180deg, rgba(5, 150, 105, 0.12), rgba(10, 17, 38, 0.5));
}

body:has(#ag-globe-shell.bm-first-light) #ag-routes .bm-routecard__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--ag-space-2);
}

/* Wraps the row's own original label text/Recommended badge (moved in by
   decorateRow, not new content) — sized/weighted per mockup 02's .rname. */
body:has(#ag-globe-shell.bm-first-light) #ag-routes .bm-routecard__name {
  display: inline-flex;
  align-items: center;
  gap: var(--ag-space-1);
  font-size: 13px;
  font-weight: 700;
}

/* Only rendered when extractRouteMeta actually found eta/distance text —
   see routeCards.js's file banner: today's real chips never carry it, so
   this rule is exercised by jest fixtures now and by real markup only if a
   future row enrichment adds that text. Per mockup 02's .reta. */
body:has(#ag-globe-shell.bm-first-light) #ag-routes .bm-routecard__meta {
  font-size: 14px;
  font-weight: 800;
  white-space: nowrap;
}

/* bm:hike-routes badge cluster — per mockup 02's .rbadges. READY BUT LATENT
   (see file banner): populated only once fetchHikingDirections is actually
   wired into the web click flow. */
body:has(#ag-globe-shell.bm-first-light) #ag-routes .bm-routecard__badges {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ag-space-1);
  margin-block-start: var(--ag-space-2);
}

/* Badge pill base + 3 kinds — literal colors copied 1:1 from mockup 02's
   assets/marble.css .rb/.rb.safe/.rb.comm/.rb.warn (community proof =
   blue, verified/clear = green, condition warning = amber). */
body:has(#ag-globe-shell.bm-first-light) #ag-routes .bm-routecard__badge {
  border-radius: var(--ag-radius-sm);
  padding-block: 2px;
  padding-inline: 7px;
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.03em;
}

body:has(#ag-globe-shell.bm-first-light) #ag-routes .bm-routecard__badge--safe {
  background: rgba(5, 150, 105, 0.2);
  color: #34d399;
}

body:has(#ag-globe-shell.bm-first-light) #ag-routes .bm-routecard__badge--community {
  background: rgba(37, 99, 235, 0.2);
  color: #93c5fd;
}

body:has(#ag-globe-shell.bm-first-light) #ag-routes .bm-routecard__badge--warn {
  background: rgba(234, 88, 12, 0.2);
  color: #fb923c;
}

/* ── 9. Elevation story (W2b Task 4) ──────────────────────────────────────
   static/js/globe/elevStory.js's initElevStory() listens for the legacy
   planner's `bm:elev` event (dispatched from templates/globe/index.html's
   renderRouteDetail() — see that function and task-4-report.md's "Dispatch
   anchor" section) and renders #bm-elev-story under whichever row is
   currently .bm-routecard--best (falling back to #ag-routes itself if no
   row is marked best yet).

   SAME SCOPING SITUATION as rule 8 above, and this file's #bm-elev-story
   selectors reuse its exact technique for the exact same reason: #ag-routes
   lives outside #ag-globe-shell's own subtree (#ag-results-popup is its
   sibling, not its descendant — see rule 8's banner for the verified
   <div>-depth trace), so `.bm-first-light`'s custom properties don't
   inherit here (every color below is a LITERAL value, not var(--bm-*)) and
   the "can't leak flag-off" gate has to be the same body:has() cross-
   subtree technique instead of a plain descendant selector. Belt-and-
   suspenders alongside the JS-side gate: initElevStory() is only ever
   CALLED under featureEnabled("web_first_light") (see index.js), so
   #bm-elev-story never exists in the DOM flag-off at all.

   Visual recipe ported 1:1 from mockup 02's .elev/.spread (docs/
   superpowers/mockups/2026-07-06-globe-web-blue-marble/02-trip-studio.html
   + its assets/marble.css) — the sparkline fill/stroke blues (#2563eb @
   35% / #60a5fa) are this app's own primary brand blue (see design-
   tokens.css's gradient start / mobile theme.ts), not a mockup-only token;
   the dark chart backdrop (#0d1730) and dim label color (#9DAECC, ==
   chrome.css's --bm-dim, literal for the same inherit-scope reason as
   rule 8) are copied 1:1 from that same mockup CSS. */
body:has(#ag-globe-shell.bm-first-light) #ag-routes #bm-elev-story {
  display: flex;
  flex-direction: column;
  gap: var(--ag-space-2);
  margin-block-start: var(--ag-space-2);
}

body:has(#ag-globe-shell.bm-first-light) #ag-routes .bm-elev__chart {
  inline-size: 100%;
  block-size: 44px;
  border-radius: var(--ag-radius-md);
  background: #0d1730;
  overflow: hidden;
}

/* svgEl()-built <svg class="bm-elev__spark"> (elevStory.js) — fills the
   chart box exactly; the chart wrapper above owns the box look
   (background/radius/overflow) so this rule only needs to size it. */
body:has(#ag-globe-shell.bm-first-light) #ag-routes .bm-elev__spark {
  display: block;
  inline-size: 100%;
  block-size: 100%;
}

body:has(#ag-globe-shell.bm-first-light) #ag-routes .bm-elev__line {
  display: flex;
  justify-content: space-between;
  gap: var(--ag-space-2);
  font-size: 10.5px;
  color: #9daecc; /* chrome.css --bm-dim, literal — see banner above */
}

/* ── 10. Resident safety cluster (W2b Task 5) ─────────────────────────────
   templates/globe/partials/_safety_cluster.html's #bm-safety -- rendered
   as the LAST child of .ag-dir__inner (a sibling to .ag-dir__body, not
   inside its scrollable form), gated `{% if first_light_on %}`. Pinned at
   the Trip panel's bottom edge: .ag-dir__body is `flex: 1 1 auto; overflow:
   auto` (templates/globe/index.html's own inline <style>, ~:1145) and this
   cluster is `flex: 0 0 auto` below it, so it never scrolls away with the
   ~24 legacy form rows -- "always one tap, never buried" per the mockup's
   own copy (see the partial, and rule 3g above which first flagged this
   gap). Shows/hides automatically with #ag-dir itself (dock.js's
   setTripPanelVisible toggles #ag-dir's own display by id, independent of
   DOM position), i.e. Trip-mode-only with zero extra mode-tracking JS/CSS
   needed here.

   SAME SCOPING TECHNIQUE as rules 1-7 above (a plain descendant selector,
   NOT rules 8/9's cross-subtree body:has() -- #bm-safety lives INSIDE
   #ag-dir, inside #ag-globe-shell, so .bm-first-light's --bm-* custom
   properties inherit normally here; var(--bm-*) is safe to use directly,
   unlike inside #ag-results-popup). #bm-safety's own base panel look
   (background/border/border-radius/backdrop-filter/box-shadow) comes from
   the shared `.bm-glass` recipe class (chrome.css) applied directly in the
   partial's markup -- unlike #ag-dir itself (existing markup; invariant 2
   forbids adding classes beyond the one gated `bm-trip` token, so rule 1
   above has to inline the recipe's declarations instead), this partial is
   BRAND NEW markup with no such restriction, so it can just carry the
   class. Only LAYOUT (fitting inside .ag-dir__inner's flex column) and the
   cluster's own internal look are added below. */
.bm-first-light #ag-dir.bm-trip #bm-safety {
  flex: 0 0 auto;
  margin-block-start: var(--ag-space-3);
  padding: var(--ag-space-3) var(--ag-space-4);
  display: flex;
  flex-direction: column;
  gap: var(--ag-space-2);
}

.bm-first-light #ag-dir.bm-trip #bm-safety .bm-safety__heading {
  margin: 0;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--bm-faint);
}

.bm-first-light #ag-dir.bm-trip #bm-safety .bm-safety__row {
  display: flex;
  gap: var(--ag-space-2);
}

/* Share live trip / Auto check-in: ghost pills sharing the row equally.
   Background literal ported from mockup 02's .btn.ghost (assets/
   marble.css) -- no equivalent app token for this exact translucent blue
   tint, same "port the mockup literal" precedent as rule 8's badge colors
   and rule 9's chart colors above. */
.bm-first-light #ag-dir.bm-trip #bm-safety .bm-safety__btn {
  flex: 1 1 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ag-space-1);
  padding-block: var(--ag-space-2);
  padding-inline: var(--ag-space-2);
  border-radius: var(--ag-radius-lg);
  border: 1px solid var(--bm-line);
  background: rgba(154, 196, 255, 0.1);
  color: var(--bm-text);
  font-family: inherit;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  white-space: nowrap;
}

.bm-first-light #ag-dir.bm-trip #bm-safety .bm-safety__btn:hover {
  background: var(--bm-line-soft);
}

.bm-first-light #ag-dir.bm-trip #bm-safety .bm-safety__btn:focus-visible {
  outline: 2px solid #93c5fd;
  outline-offset: 2px;
}

/* SOS: fixed-width (doesn't share the row's flex-grow with the two ghost
   buttons -- matches mockup 02's own markup, where .btn.danger.sm has no
   flex:1), solid danger red. --ag-color-error/-error-hover are this app's
   REAL, existing danger tokens (design-tokens.css) -- #dc2626/#b91c1c, the
   exact same red rule 4 above already uses (as a literal) for the
   destination-pin dot on this same dark panel -- reused here through the
   tokens themselves rather than another literal, since (unlike rules 8/9)
   #bm-safety sits inside #ag-globe-shell's own subtree, where root-scoped
   --ag-* custom properties inherit normally. */
.bm-first-light #ag-dir.bm-trip #bm-safety .bm-safety__btn--sos {
  flex: 0 0 auto;
  padding-inline: var(--ag-space-4);
  border-color: var(--ag-color-error);
  background: var(--ag-color-error);
  color: #fff;
  font-weight: 800;
}

.bm-first-light #ag-dir.bm-trip #bm-safety .bm-safety__btn--sos:hover {
  background: var(--ag-color-error-hover);
}

.bm-first-light #ag-dir.bm-trip #bm-safety .bm-safety__status {
  font-size: 10.5px;
  color: var(--bm-dim);
}

/* ── 11. Route tools popover (W2b Task 6) ─────────────────────────────────
   static/js/globe/routeTools.js's initRouteTools() JS-creates both pieces
   below at runtime (neither is server-rendered in either flag state — see
   that module's file header, same "entirely JS-created, template
   untouched" precedent as rule 7's #bm-mode-seg) and relocates five
   scattered panel tool controls (offline pack / GPX import / trip history
   / trip wizard / route compare) out of their original rows — two
   different .ag-dir__form's worth, #ag-panel-general AND #ag-panel-explore
   — into #bm-route-tools, discoverable behind one #bm-route-tools-btn
   ("⋯") trigger instead of being spread across the ~24-row legacy form and
   a second tab panel. See task-6-report.md.

   SAME SCOPING TECHNIQUE as rules 1-7/10 above (a plain descendant
   selector, not rules 8/9's cross-subtree body:has()) — both new elements
   are mounted INSIDE #ag-panel-general's .ag-dir__form (routeTools.js's
   mount(), anchored right after the #ag-share row), inside #ag-dir, inside
   #ag-globe-shell, so .bm-first-light's --bm-* custom properties inherit
   normally here.

   #bm-route-tools is deliberately kept IN-FLOW (a flex item of
   .ag-dir__form, not position:absolute) — #ag-dir's own outer box is
   overflow:hidden and .ag-dir__body (its scrollable ancestor) is
   overflow:auto (templates/globe/index.html's inline <style>, ~:1060-1080);
   either would silently clip an absolutely-positioned popover that tried
   to pop out past the panel's own edges, a real risk unverifiable without
   a live browser/layout engine in this environment (same caution rule 4's
   header above already takes with the geocoder-pill dot). Rendering
   in-flow sidesteps that risk entirely — opening it just pushes the rest
   of the already-scrollable form down, like any other row. */
.bm-first-light #ag-dir.bm-trip #ag-panel-general #bm-route-tools-btn {
  order: 64;
  align-self: flex-end;
  flex: 0 0 auto;
  inline-size: 34px;
  block-size: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--ag-radius-full);
  border: 1px solid var(--bm-line);
  background: var(--bm-line-soft);
  color: var(--bm-text);
  font-family: inherit;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}

.bm-first-light #ag-dir.bm-trip #ag-panel-general #bm-route-tools-btn:hover {
  background: var(--bm-line);
}

.bm-first-light #ag-dir.bm-trip #ag-panel-general #bm-route-tools-btn:focus-visible,
.bm-first-light #ag-dir.bm-trip #ag-panel-general #bm-route-tools-btn[aria-expanded="true"] {
  outline: 2px solid #93c5fd;
  outline-offset: 2px;
}

/* The popover itself: dark-glass card (.bm-glass, applied in
   routeTools.js's buildPopover()), stacked as its own flex column so the
   five relocated .ag-btn--wide controls (and the GPX-import <label> that
   wraps its hidden file input — moved as a whole subtree, see
   routeTools.js's relocatableUnit()) lay out one per row, same as they did
   in their original .ag-dir__form position. */
.bm-first-light #ag-dir.bm-trip #ag-panel-general #bm-route-tools {
  order: 65;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--ag-space-2);
  padding: var(--ag-space-3);
  margin-block-start: var(--ag-space-1);
}

/* [hidden] needs its own rule (not just relying on the UA default) since
   the unqualified #bm-route-tools rule above sets display:flex at equal
   ID-selector specificity — same belt-and-suspenders pattern chrome.css's
   #bm-layers-sheet / #bm-layers-sheet[hidden] pair already uses. */
.bm-first-light #ag-dir.bm-trip #ag-panel-general #bm-route-tools[hidden] {
  display: none;
}

/* ── 12. Live-nav HUD parity (W2b Task 7) ─────────────────────────────────
   Re-skins BOTH live-nav HUDs to mockup 02's dark-glass "HUD ball" look
   (arrow tile + next-turn line + arrival/remaining row — docs/superpowers/
   mockups/2026-07-06-globe-web-blue-marble/02-trip-studio.html's .hudball /
   .hud .harrow / .spread block). NO JS behavior change: AgNav's
   maneuverBanner/etaCard (static/js/nav/components/) already render
   .ag-nav-banner*/.ag-nav-eta* into #ag-nav-shell during live navigation,
   and the legacy inline planner shows #ag-navbox the same way — this task
   only adds a gated `bm-hud` class to the two STATIC containers (templates/
   globe/index.html, {% if first_light_on %}, so flag-off stays byte-
   identical and adds no id) and owns their LOOK below. Every rule is scoped
   under `.bm-hud`, which is self-gating (that class only exists flag-on).
   Baseline styles overridden here live in static/css/nav/nav.css (AgNav
   shell) and templates/globe/index.html's inline <style> ~:1578 (navbox).

   TWO CONTAINERS, TWO CSS-SCOPE REALITIES (the key gotcha, same split rules
   1-7/10 vs 8/9 already navigate in this file):

     A. #ag-nav-shell (AgNav) lives INSIDE #ag-globe-shell, so
        .bm-first-light's --bm-* custom properties inherit down to it —
        rules 12a-12b below use var(--bm-*) directly, like rules 1-7/10.

     B. #ag-navbox (legacy) lives inside #ag-results-popup, which is
        #ag-globe-shell's SIBLING (opens ~:3023, AFTER #ag-globe-shell's
        matching </div> at ~:3005), so --bm-* do NOT inherit there —
        var(--bm-panel) etc. would resolve to nothing. Rule 12c below uses
        LITERAL values mirrored 1:1 from chrome.css's `.bm-first-light`
        block (--bm-panel rgba(14,22,46,.86) / --bm-line rgba(120,150,205,
        .22) / --bm-text #EAF0FB / --bm-dim #9DAECC), never var(--bm-*) —
        the exact trap rule 8's routeCards banner documents for this same
        modal. Generic --ag-* tokens (design-tokens.css :root) inherit
        everywhere and are used normally.

   Neither HUD exists at initial render (#ag-nav-shell is empty until nav
   mounts; #ag-navbox is display:none until the legacy planner starts) — so
   these rules are latent-but-correct, exercised only once live nav is
   active (unverifiable in this env: flag off, ships dark). The bm-hud class
   itself IS present at render on both containers (pytest asserts it). */

/* ── 12a. AgNav maneuver banner — the next-turn card (--bm-* inherit here) */
#ag-nav-shell.bm-hud .ag-nav-banner {
  align-items: center;
  background: var(--bm-panel);
  border-block-end-color: var(--bm-line);
}

/* Arrow glyph → gradient tile, ported from mockup 02's .hud .harrow. The
   brand gradient (#2563eb→#4f46e5, design-tokens.css's button gradient) has
   no --bm-* token, so it's a literal here even though this subtree could
   otherwise use var(--bm-*). */
#ag-nav-shell.bm-hud .ag-nav-banner-arrow {
  inline-size: 44px;
  block-size: 44px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: var(--ag-radius-lg);
  background: linear-gradient(145deg, #2563eb, #4f46e5);
  color: #fff;
  font-size: 21px;
  font-weight: 700;
  line-height: 1;
}

#ag-nav-shell.bm-hud .ag-nav-banner-distance {
  color: var(--bm-text);
}

#ag-nav-shell.bm-hud .ag-nav-banner-instruction {
  color: var(--bm-dim);
}

/* ── 12b. AgNav ETA card — the arrival/remaining row (--bm-* inherit here) */
#ag-nav-shell.bm-hud .ag-nav-eta-card {
  background: var(--bm-panel);
  border-block-start-color: var(--bm-line);
}

#ag-nav-shell.bm-hud .ag-nav-eta-stat-value {
  color: var(--bm-text);
}

/* Arrival time: mockup 02's green (#34d399) instead of nav.css's dark
   #137333 (unreadable on dark glass). Declared AFTER .ag-nav-eta-stat-value
   above — the arrival element carries BOTH classes (etaCard.js's
   makeStat("ag-nav-eta-time-val")), and both overrides are equal
   specificity, so source order lets green win on the arrival value only. */
#ag-nav-shell.bm-hud .ag-nav-eta-time-val {
  color: #34d399;
}

#ag-nav-shell.bm-hud .ag-nav-eta-stat-label {
  color: var(--bm-faint);
}

/* ── 12c. Legacy navbox — LITERAL values (outside #ag-globe-shell; see
   banner scope B). The container itself carries `bm-hud` (added to its
   existing `ag-navbox` class), so `.bm-hud.ag-navbox` is a compound on the
   panel element; its next-turn / status descendants are addressed by the
   id-qualified selectors the brief names, guaranteeing they outweigh the
   inline <style> baseline. */
.bm-hud.ag-navbox {
  background: rgba(14, 22, 46, 0.86); /* chrome.css --bm-panel, literal */
  border-color: rgba(120, 150, 205, 0.22); /* chrome.css --bm-line, literal */
}

/* "Active" live-status pill: retinted to mockup 02's arrival green for the
   new navy panel (was nav.css's emerald on a green-tinted card). */
.bm-hud.ag-navbox .ag-navbox__pill#ag-nav-pill {
  border-color: rgba(52, 211, 153, 0.35);
  background: rgba(52, 211, 153, 0.16);
  color: #34d399;
}

/* Next-turn instruction line — bumped toward the mockup's next-turn `b`
   prominence (16px there; 15px here, proportionate to this compact card). */
.bm-hud.ag-navbox .ag-navbox__main#ag-nav-instruction {
  color: #eaf0fb; /* chrome.css --bm-text, literal */
  font-size: 15px;
  font-weight: 700;
}

.bm-hud.ag-navbox .ag-navbox__sub#ag-nav-sub {
  color: #9daecc; /* chrome.css --bm-dim, literal */
}
