/* ==========================================================================
   Blue Marble — "First Light" chrome skeleton (W2 Task 1)
   Flag: web_first_light (apps/globe/views.py::_globe_web_flags).
   Mode dock (#bm-dock) · omni-search (#bm-omni) · instrument rail
   (#bm-rail) · content chips (#bm-chips) — dark glass overlay chrome for
   the customer globe. See docs/superpowers/mockups/
   2026-07-06-globe-web-blue-marble/01-first-light.html.

   Tokens + LOGICAL properties only (inset-inline-*, inset-block-*,
   margin-block-*, etc.) — no bare left:/right:/top:/bottom:. A later
   task's CSS gate greps this file for physical-property violations.
   (Note: never let an asterisk and a slash land back-to-back in this
   comment's prose -- that two-character sequence closes a CSS comment
   early per spec, same as it would in a real browser. An earlier draft of
   this very paragraph did exactly that by writing two logical-property
   wildcards separated only by a slash; Task 11's check_logical_css.sh
   comment-stripper caught it. Use a comma or "and" between wildcards, not
   a slash, and always leave a space before any trailing slash near a
   "*"-suffixed word.)
   ========================================================================== */

/* ── Night-flight surface tokens (dark glass panels) ────────────────────
   design-tokens.css's light-surface text tokens (e.g. --ag-color-text-muted
   = #6B7491) fail WCAG AA against these dark panels, so First Light chrome
   gets its own small token set, scoped to .bm-first-light, defined ONCE
   here for every W2 task to reuse instead of redefining its own. */
.bm-first-light {
  --bm-panel: rgba(14, 22, 46, 0.86);
  --bm-line: rgba(120, 150, 205, 0.22);
  --bm-line-soft: rgba(120, 150, 205, 0.13);
  --bm-text: #EAF0FB;
  --bm-dim: #9DAECC;
  --bm-faint: #6E7FA3;
}

/* ── Retire legacy chrome under the flag ─────────────────────────────────
   Advanced Tools (#ag-dir) is no longer default-open — Trip mode re-shows
   it (Task 2). The FAB stack / near-me chip / commerce date bar are
   superseded by the dock + omni + chips below. */
.bm-first-light #ag-dir { display: none; }
.bm-first-light #ag-nearme { display: none; }
/* The legacy commerce date bar (#ag-date-bar) + its toggle, and the legacy
   bottom-right FAB stack (#ag-map-fabs: Fit-all / List-view / Map-style), are
   fully superseded under the redesign (#bm-trip-context chip owns date/guest;
   the bm-dock + bm-rail + Layers sheet own map controls). Both were still
   LEAKING because JS opens them (adds `.is-open` / sets inline display:flex)
   which out-specifies the plain hide above — showing a SECOND date/guest
   control beneath the chip (guest counts even disagreed: chip "1 guest" vs bar
   "2") and a redundant legacy FAB cluster. !important keeps them hidden
   regardless of is-open / inline style. Flag off (no .bm-first-light): untouched. */
.bm-first-light #ag-date-bar,
.bm-first-light #ag-date-bar-toggle,
.bm-first-light #ag-map-fabs { display: none !important; }

@media (prefers-reduced-motion: reduce) {
  .bm-first-light * {
    animation: none !important;
    transition: none !important;
  }
}

/* ── Shared glass panel recipe ────────────────────────────────────────── */
.bm-glass {
  background: var(--bm-panel);
  border: 1px solid var(--bm-line);
  border-radius: var(--ag-radius-3xl);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: var(--ag-shadow-2xl);
}

/* ==========================================================================
   1. Mode dock — #bm-dock
   ========================================================================== */
#bm-dock {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: var(--ag-space-4);
  translate: 0 -50%;
  display: flex;
  flex-direction: column;
  gap: var(--ag-space-1);
  padding: var(--ag-space-2);
  z-index: 22;
}

.bm-dock__btn {
  position: relative; /* anchors the .bm-dock__badge dot to this button */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  min-inline-size: 62px;
  min-block-size: var(--ag-hit-target-compact);
  padding: var(--ag-space-2) var(--ag-space-1);
  border: none;
  border-radius: var(--ag-radius-2xl);
  background: transparent;
  color: var(--bm-dim);
  font-family: var(--ag-font-sans);
  font-size: var(--ag-font-size-xs);
  font-weight: var(--ag-font-weight-semibold);
  line-height: var(--ag-line-height-tight);
  letter-spacing: var(--ag-letter-spacing-normal);
  cursor: pointer;
  transition: var(--ag-transition-colors);
}

.bm-dock__btn:hover {
  background: var(--bm-line-soft);
  color: var(--bm-text);
}

.bm-dock__btn:focus-visible {
  outline: 2px solid var(--ag-color-border-focus);
  outline-offset: 2px;
}

.bm-dock__btn[aria-pressed="true"] {
  background: var(--ag-gradient-button);
  color: var(--ag-color-text-on-primary);
  box-shadow: var(--ag-shadow-primary);
}

.bm-dock__icon {
  font-size: var(--ag-font-size-xl);
  line-height: 1;
}

.bm-dock__sep {
  block-size: 1px;
  inline-size: auto;
  background: var(--bm-line-soft);
  margin-block: var(--ag-space-1);
  margin-inline: var(--ag-space-2);
}

/* Fresh-activity badge on the Live button (W4 Task 9, gated web_pulse). The
   dock JS (static/js/globe/dock.js) adds this dot to [data-bm-mode="live"]
   while the shared pulse store reports events newer than the user's last Live
   visit; entering Live clears it. Non-interactive + aria-hidden — the count is
   the store's, not announced here. The 2px panel-colored ring keeps it legible
   over both the resting button and the pressed (gradient) state. Tokens +
   LOGICAL properties only — the RTL gate greps this file. */
.bm-dock__badge {
  position: absolute;
  inset-block-start: var(--ag-space-1);
  inset-inline-end: var(--ag-space-1);
  inline-size: 8px;
  block-size: 8px;
  border-radius: var(--ag-radius-full);
  background: var(--ag-color-warning);
  box-shadow: 0 0 0 2px var(--bm-panel);
  pointer-events: none;
}

/* ==========================================================================
   2. Omni-search — #bm-omni
   ========================================================================== */
#bm-omni {
  position: absolute;
  inset-block-start: var(--ag-space-4);
  /* RTL-safe horizontal centering: inset-inline-start:50% + translate:-50%
     is NOT direction-safe (translate's % always resolves against the
     physical x-axis, unlike inset-inline-start). inset-inline:0 +
     margin-inline:auto centers identically in ltr/rtl by construction,
     given the definite inline-size below. */
  inset-inline: 0;
  margin-inline: auto;
  inline-size: min(520px, 52%);
  display: flex;
  align-items: center;
  gap: var(--ag-space-2);
  padding: var(--ag-space-2) var(--ag-space-4);
  border-radius: var(--ag-radius-full);
  z-index: 20;
}

#bm-omni:focus-within {
  outline: 2px solid var(--ag-color-border-focus);
  outline-offset: 2px;
}

.bm-omni__icon {
  flex: none;
  color: #9cc4ff;
  font-size: var(--ag-font-size-lg);
  line-height: 1;
}

#bm-omni-input {
  flex: 1;
  min-inline-size: 0;
  min-block-size: var(--ag-hit-target-compact);
  border: none;
  background: transparent;
  color: var(--bm-text);
  font-family: var(--ag-font-sans);
  font-size: var(--ag-font-size-md);
  font-weight: var(--ag-font-weight-medium);
}

#bm-omni-input::placeholder {
  color: var(--bm-faint);
}

#bm-omni-input:focus {
  outline: none;
}

/* Search inputs render a native clear ("x") button in WebKit/Blink; recolor
   it for the dark panel instead of leaving the OS-default dark-on-dark glyph. */
#bm-omni-input::-webkit-search-cancel-button {
  filter: invert(1) brightness(1.6);
}

#bm-omni kbd {
  flex: none;
  font-family: var(--ag-font-sans);
  font-size: var(--ag-font-size-xs);
  font-weight: var(--ag-font-weight-semibold);
  color: var(--bm-dim);
  border: 1px solid var(--bm-line);
  border-radius: var(--ag-radius-sm);
  padding-block: 2px;
  padding-inline: var(--ag-space-1);
  background: rgba(10, 17, 38, 0.6);
}

#bm-omni-results {
  position: absolute;
  inset-block-start: calc(100% + var(--ag-space-2));
  /* RTL-safe centering — see #bm-omni comment above. */
  inset-inline: 0;
  /* NO percentage inline-size here. This box is absolutely positioned inside
     #bm-omni, so its containing block is the SEARCH BAR (520px), not the
     shell — `min(560px, 56%)` therefore computed 0.56 x 520 = 291px, and the
     margin-inline:auto below then centred that stub inside the bar: measured
     live at 290px wide, 115px inboard of the bar's left edge. The parent's
     own min(520px, 52%) is correct because ITS containing block IS the
     shell; the pattern does not survive being copied down a level.
     inset-inline:0 + inline-size:auto stretches the panel to exactly the
     bar, which is what a suggestions list should do anyway. */
  inline-size: auto;
  max-block-size: 60vh;
  overflow-y: auto;
  border-radius: var(--ag-radius-3xl);
  background: var(--bm-panel);
  border: 1px solid var(--bm-line);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: var(--ag-shadow-2xl);
  z-index: 21;
}

#bm-omni-results[hidden] {
  display: none;
}

/* ── Trip-context chip + Edit popover (W3 Task 7) ────────────────────────────
   A compact dates/guests chip that sits inside the omni pill (flex:none, at the
   inline-end next to ⌘K). Clicking it opens the .bm-tripctx__pop dialog
   (JS-created) with two date inputs + a guests number and Apply / Clear. Logical
   properties + First-Light tokens only — see this file's header. */
.bm-tripctx {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: var(--ag-space-1);
  min-block-size: var(--ag-hit-target-compact);
  padding-block: var(--ag-space-1);
  padding-inline: var(--ag-space-3);
  border: 1px solid var(--bm-line);
  border-radius: var(--ag-radius-full);
  background: rgba(10, 17, 38, 0.6);
  color: var(--bm-text);
  font-family: var(--ag-font-sans);
  font-size: var(--ag-font-size-sm);
  font-weight: var(--ag-font-weight-medium);
  cursor: pointer;
  white-space: nowrap;
}

.bm-tripctx:hover {
  border-color: var(--bm-text);
}

.bm-tripctx:focus-visible {
  outline: 2px solid var(--ag-color-border-focus);
  outline-offset: 2px;
}

.bm-tripctx__icon {
  flex: none;
  line-height: 1;
}

.bm-tripctx__pop {
  position: absolute;
  inset-block-start: calc(100% + var(--ag-space-2));
  inset-inline-end: 0;
  inline-size: min(280px, 84vw);
  display: flex;
  flex-direction: column;
  gap: var(--ag-space-2);
  padding: var(--ag-space-4);
  z-index: 23;
}

.bm-tripctx__field {
  display: flex;
  flex-direction: column;
  gap: var(--ag-space-1);
  color: var(--bm-dim);
  font-family: var(--ag-font-sans);
  font-size: var(--ag-font-size-xs);
  font-weight: var(--ag-font-weight-semibold);
}

.bm-tripctx__field input {
  min-block-size: var(--ag-hit-target-compact);
  padding-block: var(--ag-space-1);
  padding-inline: var(--ag-space-2);
  border: 1px solid var(--bm-line);
  border-radius: var(--ag-radius-md);
  background: rgba(10, 17, 38, 0.55);
  color: var(--bm-text);
  font-family: var(--ag-font-sans);
  font-size: var(--ag-font-size-sm);
  font-weight: var(--ag-font-weight-medium);
}

.bm-tripctx__field input::-webkit-calendar-picker-indicator {
  filter: invert(1) brightness(1.4);
}

.bm-tripctx__actions {
  display: flex;
  justify-content: space-between;
  gap: var(--ag-space-2);
  margin-block-start: var(--ag-space-1);
}

.bm-tripctx__clear,
.bm-tripctx__apply {
  min-block-size: var(--ag-hit-target-compact);
  padding-block: var(--ag-space-1);
  padding-inline: var(--ag-space-4);
  border-radius: var(--ag-radius-full);
  font-family: var(--ag-font-sans);
  font-size: var(--ag-font-size-sm);
  font-weight: var(--ag-font-weight-semibold);
  cursor: pointer;
}

.bm-tripctx__clear {
  border: 1px solid var(--bm-line);
  background: transparent;
  color: var(--bm-dim);
}

.bm-tripctx__apply {
  border: none;
  background: var(--ag-gradient-primary, linear-gradient(135deg, #2563eb, #4f46e5));
  color: #fff;
}

/* ==========================================================================
   3. Instrument rail — #bm-rail
   ========================================================================== */
#bm-rail {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-end: var(--ag-space-4);
  translate: 0 -50%;
  display: flex;
  flex-direction: column;
  gap: var(--ag-space-1);
  padding: var(--ag-space-2);
  border-radius: var(--ag-radius-full);
  z-index: 22;
}

.bm-rail__btn {
  inline-size: var(--ag-hit-target-compact);
  block-size: var(--ag-hit-target-compact);
  display: grid;
  place-items: center;
  justify-content: center;
  /* Zero the global button padding (styles.css:1349 `button{padding:0 14px}`)
     — on a 36px border-box icon button it left an 8px content box, shoving the
     glyph 14px right (off-centre). place-items can't recover from that. */
  padding: 0;
  border: none;
  border-radius: var(--ag-radius-full);
  background: transparent;
  color: var(--bm-dim);
  font-size: var(--ag-font-size-xl);
  line-height: 1;
  cursor: pointer;
  transition: var(--ag-transition-colors);
}

.bm-rail__btn:hover {
  background: var(--bm-line-soft);
  color: var(--bm-text);
}

/* The rail's icons are inline stroke SVGs (see partials/_instrument_rail.html).
   They inherit `color`, so hover/active states recolour them for free — which
   the emoji glyphs they replaced could never do, being colour fonts. */
.bm-rail__btn svg {
  inline-size: 18px;
  block-size: 18px;
  display: block;
}

/* Day & night is a STATE toggle, not an action: show when the shading is off so
   the rail says what the map is currently doing. */
.bm-rail__btn[data-bm-tool="daynight"][aria-pressed="false"] {
  color: var(--bm-dim);
  opacity: .55;
}
.bm-rail__btn[data-bm-tool="daynight"][aria-pressed="true"] {
  color: var(--bm-text);
}

.bm-rail__btn:focus-visible {
  outline: 2px solid var(--ag-color-border-focus);
  outline-offset: 2px;
}

.bm-rail__sep {
  inline-size: var(--ag-hit-target-compact);
  block-size: 1px;
  background: var(--bm-line-soft);
  margin-block: var(--ag-space-1);
}

/* --------------------------------------------------------------------------
   3b. Share menu — #bm-share-menu (W7 Task 2)
   A dark-glass popover anchored just inline-start of the rail's Share button.
   -------------------------------------------------------------------------- */
#bm-share-menu {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-end: calc(var(--ag-space-4) + 52px);
  translate: 0 -50%;
  display: flex;
  flex-direction: column;
  gap: var(--ag-space-1);
  padding: var(--ag-space-2);
  border-radius: var(--ag-radius-lg);
  min-inline-size: 172px;
  z-index: 23;
}

#bm-share-menu[hidden] {
  display: none;
}

.bm-share-menu__item {
  display: flex;
  align-items: center;
  gap: var(--ag-space-2);
  inline-size: 100%;
  padding: var(--ag-space-2) var(--ag-space-3);
  border: none;
  border-radius: var(--ag-radius-md);
  background: transparent;
  color: var(--bm-text);
  font: inherit;
  font-size: var(--ag-font-size-sm);
  text-align: start;
  cursor: pointer;
  transition: var(--ag-transition-colors);
}

.bm-share-menu__item[hidden] {
  display: none;
}

.bm-share-menu__item:hover {
  background: var(--bm-line-soft);
}

.bm-share-menu__item:focus-visible {
  outline: 2px solid var(--ag-color-border-focus);
  outline-offset: -2px;
}

.bm-share-menu__icon {
  inline-size: 1.25em;
  text-align: center;
  color: var(--bm-dim);
}

/* Share-action confirmation — a brief "Link copied" / "Embed copied" pill. */
.bm-share-toast {
  position: absolute;
  inset-block-end: var(--ag-space-4);
  inset-inline: 0;
  margin-inline: auto;
  inline-size: fit-content;
  max-inline-size: 88vw;
  padding: var(--ag-space-2) var(--ag-space-4);
  border-radius: var(--ag-radius-full);
  background: var(--bm-panel, rgba(12, 18, 28, 0.92));
  color: #fff;
  font-size: var(--ag-font-size-sm);
  z-index: 40;
  pointer-events: none;
}

/* ==========================================================================
   4. Content chips — #bm-chips
   ========================================================================== */
#bm-chips {
  position: absolute;
  inset-block-start: calc(var(--ag-space-4) + 54px);
  /* RTL-safe centering — see #bm-omni comment above. Unlike #bm-omni,
     this row has no definite width of its own (it shrink-wraps to its
     chips), so inline-size must be given a non-auto value (fit-content,
     not the bare "auto" keyword) or inset-inline:0 would stretch this
     invisible wrapper to full width and dead-zone map clicks/drags on
     either side of the chip row. fit-content preserves the original
     shrink-to-fit footprint while still centering via margin-inline. */
  inset-inline: 0;
  margin-inline: auto;
  inline-size: fit-content;
  display: flex;
  gap: var(--ag-space-2);
  flex-wrap: wrap;
  justify-content: center;
  z-index: 20;
}

.bm-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--ag-space-1);
  min-block-size: var(--ag-hit-target-compact);
  padding-block: var(--ag-space-2);
  padding-inline: var(--ag-space-3);
  border-radius: var(--ag-radius-full);
  background: var(--bm-panel);
  border: 1px solid var(--bm-line);
  color: var(--bm-dim);
  font-family: var(--ag-font-sans);
  font-size: var(--ag-font-size-sm);
  font-weight: var(--ag-font-weight-semibold);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  cursor: pointer;
  transition: var(--ag-transition-colors);
}

.bm-chip:hover {
  color: var(--bm-text);
}

.bm-chip:focus-visible {
  outline: 2px solid var(--ag-color-border-focus);
  outline-offset: 2px;
}

.bm-chip[aria-pressed="true"] {
  background: var(--ag-gradient-button);
  border-color: transparent;
  color: var(--ag-color-text-on-primary);
  box-shadow: var(--ag-shadow-primary);
}

/* ==========================================================================
   5. Whisper line — #bm-whisper (W2 Task 7)
   ========================================================================== */
#bm-whisper {
  position: absolute;
  inset-block-end: var(--ag-space-4);
  /* RTL-safe horizontal centering — see #bm-omni's comment above. */
  inset-inline: 0;
  margin-inline: auto;
  inline-size: fit-content;
  max-inline-size: min(480px, 90%);
  display: flex;
  align-items: center;
  gap: var(--ag-space-2);
  padding-block: var(--ag-space-2);
  padding-inline: var(--ag-space-4);
  border-radius: var(--ag-radius-full);
  color: var(--bm-text);
  font-family: var(--ag-font-sans);
  font-size: var(--ag-font-size-sm);
  font-weight: var(--ag-font-weight-medium);
  cursor: pointer;
  z-index: 19;
}

#bm-whisper[hidden] {
  display: none;
}

#bm-whisper:hover {
  background: var(--bm-line-soft);
}

#bm-whisper:focus-visible {
  outline: 2px solid var(--ag-color-border-focus);
  outline-offset: 2px;
}

.bm-whisper__icon {
  flex: none;
  font-size: var(--ag-font-size-md);
  line-height: 1;
}

.bm-whisper__text {
  /* min-inline-size:0 overrides the flex-item default (min-width:auto),
     which otherwise refuses to shrink below the line's own intrinsic
     width -- without it, text-overflow:ellipsis never gets a chance to
     trigger and a long line just pushes #bm-whisper past its
     max-inline-size instead of truncating. */
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ==========================================================================
   6. Layers sheet — #bm-layers-sheet (W2 Task 8)
   ========================================================================== */
#bm-layers-sheet {
  position: absolute;
  inset-block-start: 50%;
  /* Pops out to the right of #bm-dock (62px min-inline-size + its own
     padding) rather than centering/backdropping the whole map -- keeps the
     open sheet visually anchored to the button that opened it. */
  inset-inline-start: calc(var(--ag-space-4) + 78px);
  translate: 0 -50%;
  inline-size: min(280px, 82%);
  max-block-size: 80vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--ag-space-3);
  padding: var(--ag-space-4);
  z-index: 25;
}

#bm-layers-sheet[hidden] {
  display: none;
}

.bm-layers__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ag-space-2);
}

.bm-layers__title {
  margin: 0;
  color: var(--bm-text);
  font-family: var(--ag-font-sans);
  font-size: var(--ag-font-size-lg);
  font-weight: var(--ag-font-weight-semibold);
}

.bm-layers__close {
  flex: none;
  inline-size: var(--ag-hit-target-compact);
  block-size: var(--ag-hit-target-compact);
  display: grid;
  place-items: center;
  border: none;
  border-radius: var(--ag-radius-full);
  background: transparent;
  color: var(--bm-dim);
  font-size: var(--ag-font-size-md);
  line-height: 1;
  cursor: pointer;
  transition: var(--ag-transition-colors);
}

.bm-layers__close:hover {
  background: var(--bm-line-soft);
  color: var(--bm-text);
}

.bm-layers__close:focus-visible {
  outline: 2px solid var(--ag-color-border-focus);
  outline-offset: 2px;
}

.bm-layers__row {
  display: flex;
  align-items: center;
  gap: var(--ag-space-3);
  min-block-size: var(--ag-hit-target-compact);
  color: var(--bm-text);
  font-family: var(--ag-font-sans);
  font-size: var(--ag-font-size-sm);
  font-weight: var(--ag-font-weight-medium);
  cursor: pointer;
}

.bm-layers__row input[type="checkbox"] {
  inline-size: 18px;
  block-size: 18px;
  accent-color: var(--ag-color-brand-primary);
  cursor: pointer;
}

.bm-layers__row input[type="checkbox"]:focus-visible {
  outline: 2px solid var(--ag-color-border-focus);
  outline-offset: 2px;
}

.bm-layers__sep {
  block-size: 1px;
  background: var(--bm-line-soft);
  margin-block: var(--ag-space-1);
}

.bm-layers__all {
  align-self: flex-start;
  border: none;
  background: transparent;
  color: #9cc4ff;
  font-family: var(--ag-font-sans);
  font-size: var(--ag-font-size-sm);
  font-weight: var(--ag-font-weight-semibold);
  padding: 0;
  cursor: pointer;
}

.bm-layers__all:hover {
  text-decoration: underline;
}

.bm-layers__all:focus-visible {
  outline: 2px solid var(--ag-color-border-focus);
  outline-offset: 2px;
}

/* ==========================================================================
   8. Price pins — .bm-pricepin (W3 Task 2, Window Shopping)
   Flag: web_window_shopping. A separate DOM-marker layer (mapboxgl.Marker
   per priced feature) from the clustered content source — see
   static/js/globe/pricePins.js. White pill showing the pre-formatted
   price_label; the selected pin flips to the brand-active fill.
   ========================================================================== */
.bm-pricepin {
  display: inline-flex;
  align-items: center;
  min-block-size: var(--ag-hit-target-compact);
  padding-block: var(--ag-space-1);
  padding-inline: var(--ag-space-3);
  border: 1px solid var(--ag-color-border-subtle);
  border-radius: var(--ag-radius-full);
  background: var(--ag-color-bg-surface);
  color: var(--ag-color-text-heading);
  font-family: var(--ag-font-sans);
  font-size: var(--ag-font-size-sm);
  font-weight: var(--ag-font-weight-semibold);
  line-height: var(--ag-line-height-tight);
  white-space: nowrap;
  box-shadow: var(--ag-shadow-md);
  cursor: pointer;
  transition: var(--ag-transition-transform), var(--ag-transition-colors);
}

.bm-pricepin:hover {
  background: var(--ag-color-bg-surface-alt);
  scale: 1.05;
}

.bm-pricepin:focus-visible {
  outline: 2px solid var(--ag-color-border-focus);
  outline-offset: 2px;
}

.bm-pricepin--sel {
  background: var(--ag-color-brand-primary-active);
  border-color: transparent;
  color: var(--ag-color-text-on-primary);
  box-shadow: var(--ag-shadow-primary);
}

/* Sponsored pin (W3 Task 9) — the amber, labeled variant of the price pill.
   ONE per viewport, browse ("explore") mode only: static/js/globe/sponsoredPin.js
   reads the #bm-sponsored-ad json_script apps/globe/views.py::index fills from
   the EXISTING ads engine (premium viewers suppressed server-side). Two stacked
   lines — a "Sponsored" eyebrow + the ad title — so it flips the base pill's
   single inline row to a column. Amber via --ag-color-warning-alt (matches
   mockup 03-window-shopping.html's #f4b45c); the base pill's dark
   --ag-color-text-heading carries the contrast. Tokens + LOGICAL properties
   only (the RTL gate greps this file). */
.bm-pricepin--sponsored {
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  padding-block: var(--ag-space-1);
  padding-inline: var(--ag-space-3);
  background: var(--ag-color-warning-alt);
  border-color: var(--ag-color-warning);
}

.bm-pricepin--sponsored:hover {
  /* the base pill flips to a surface tint on hover — keep the sponsored fill amber */
  background: var(--ag-color-warning-alt);
}

.bm-pricepin__sponsor {
  font-size: var(--ag-font-size-xs);
  font-weight: var(--ag-font-weight-bold);
  line-height: var(--ag-line-height-tight);
  letter-spacing: var(--ag-letter-spacing-wide);
  text-transform: uppercase;
}

.bm-pricepin__title {
  font-size: var(--ag-font-size-sm);
  font-weight: var(--ag-font-weight-semibold);
  line-height: var(--ag-line-height-tight);
}

/* ==========================================================================
   9. Entity card — .bm-ecard (W3 Task 4, Window Shopping)
   Flag: web_window_shopping. ONE dark-glass card docked inset-inline-end that
   renders + acts on any focused feature (stay/activity/tour/event/trail) —
   JS-created (static/js/globe/entityCard.js), template stays zero-diff. See
   mockup 03-window-shopping.html.

   Self-gating tokens: web_window_shopping can be ON while web_first_light is
   OFF, so the .bm-first-light ancestor that defines --bm-panel/... may be
   absent under this flag. The card therefore RE-declares the night-flight
   tokens on .bm-ecard itself (identical values — they just shadow the
   ancestor's when it IS present) so its .bm-glass background, border and text
   resolve either way. Same reason section 8's .bm-pricepin leans on --ag-*
   globals. Tokens + LOGICAL properties only — the RTL gate greps this file.
   ========================================================================== */
.bm-ecard {
  --bm-panel: rgba(14, 22, 46, 0.86);
  --bm-line: rgba(120, 150, 205, 0.22);
  --bm-line-soft: rgba(120, 150, 205, 0.13);
  --bm-text: #eaf0fb;
  --bm-dim: #9daecc;
  --bm-faint: #6e7fa3;
  position: absolute;
  inset-block-start: var(--ag-space-4);
  inset-inline-end: var(--ag-space-4);
  inline-size: min(344px, 92%);
  max-block-size: calc(100% - var(--ag-space-4) * 2);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  color: var(--bm-text);
  font-family: var(--ag-font-sans);
  z-index: 24;
}

.bm-ecard:focus-visible {
  outline: 2px solid var(--ag-color-border-focus);
  outline-offset: 2px;
}

/* Image header (real thumb) or scene-gradient placeholder — same footprint. */
.bm-ecard__img,
.bm-ecard__scene {
  display: block;
  inline-size: 100%;
  block-size: 150px;
  object-fit: cover;
  border-start-start-radius: var(--ag-radius-3xl);
  border-start-end-radius: var(--ag-radius-3xl);
}

.bm-ecard__scene {
  background: linear-gradient(145deg, #155e75, #0b3444);
}

.bm-ecard__close {
  position: absolute;
  inset-block-start: var(--ag-space-2);
  inset-inline-end: var(--ag-space-2);
  inline-size: var(--ag-hit-target-compact);
  block-size: var(--ag-hit-target-compact);
  display: grid;
  place-items: center;
  border: none;
  border-radius: var(--ag-radius-full);
  background: rgba(7, 13, 31, 0.6);
  color: var(--bm-text);
  font-size: var(--ag-font-size-md);
  line-height: 1;
  cursor: pointer;
  z-index: 1;
  transition: var(--ag-transition-colors);
}

.bm-ecard__close:hover {
  background: rgba(7, 13, 31, 0.85);
}

.bm-ecard__close:focus-visible {
  outline: 2px solid var(--ag-color-border-focus);
  outline-offset: 2px;
}

.bm-ecard__body {
  display: flex;
  flex-direction: column;
  gap: var(--ag-space-2);
  padding: var(--ag-space-4);
}

.bm-ecard__title {
  margin: 0;
  color: var(--bm-text);
  font-size: var(--ag-font-size-xl);
  font-weight: var(--ag-font-weight-semibold);
  line-height: var(--ag-line-height-tight);
}

.bm-ecard__price {
  color: var(--bm-text);
  font-size: var(--ag-font-size-lg);
  font-weight: var(--ag-font-weight-bold);
}

/* Trip-context total (W3 Task 7) — "$74 × 4 nights = $296" under the per-night
   price when a trip context (both dates) is set. Dimmer than the headline. */
.bm-ecard__total {
  margin-block-start: calc(var(--ag-space-1) * -1);
  color: var(--bm-dim);
  font-size: var(--ag-font-size-sm);
  font-weight: var(--ag-font-weight-medium);
}

.bm-ecard__meta {
  display: flex;
  align-items: center;
  gap: var(--ag-space-2);
  color: var(--bm-dim);
  font-size: var(--ag-font-size-sm);
  font-weight: var(--ag-font-weight-medium);
}

.bm-ecard__meta-icon {
  flex: none;
  line-height: 1;
}

.bm-ecard__actions {
  display: flex;
  gap: var(--ag-space-2);
  margin-block-start: var(--ag-space-2);
}

.bm-ecard__btn {
  flex: 1;
  min-block-size: var(--ag-hit-target-compact);
  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-3);
  border: 1px solid var(--bm-line);
  border-radius: var(--ag-radius-2xl);
  background: transparent;
  color: var(--bm-text);
  font-family: var(--ag-font-sans);
  font-size: var(--ag-font-size-sm);
  font-weight: var(--ag-font-weight-semibold);
  cursor: pointer;
  transition: var(--ag-transition-colors);
}

.bm-ecard__btn:hover {
  background: var(--bm-line-soft);
}

.bm-ecard__btn:focus-visible {
  outline: 2px solid var(--ag-color-border-focus);
  outline-offset: 2px;
}

.bm-ecard__btn--primary {
  border-color: transparent;
  background: var(--ag-gradient-button);
  color: var(--ag-color-text-on-primary);
  box-shadow: var(--ag-shadow-primary);
}

/* ==========================================================================
   9b. Reel peek — .bm-reelpeek + .bm-crosslink (W3 Task 6, Window Shopping)
   Flag: web_window_shopping. A focused REEL renders this VARIANT of the entity
   card: the JS root carries `bm-reelpeek bm-ecard bm-glass`, so it reuses
   section 9's dock/position/glass/tokens for free (static/js/globe/reelPeek.js;
   template stays zero-diff). Adds a thumb with a centered ▶ play overlay (opens
   the reel page in a new tab) and a cross-link to the nearest stay. The reel's
   "🛏 stay" line and the stay card's inverse "🎬 reel" line share .bm-crosslink
   (one affordance, both directions). Tokens + LOGICAL properties only — the RTL
   gate greps this file. See mockup 03-window-shopping.html + task-6-report.md.
   ========================================================================== */
.bm-reelpeek__thumb {
  position: relative;
  display: block;
  inline-size: 100%;
}

.bm-reelpeek__play {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  transform: translate(-50%, -50%);
  inline-size: 56px;
  block-size: 56px;
  display: grid;
  place-items: center;
  padding-inline-start: 4px; /* optically center the ▶ glyph */
  border: none;
  border-radius: var(--ag-radius-full);
  background: rgba(7, 13, 31, 0.62);
  color: var(--bm-text);
  font-size: var(--ag-font-size-xl);
  line-height: 1;
  cursor: pointer;
  transition: var(--ag-transition-colors);
}

.bm-reelpeek__play:hover {
  background: rgba(7, 13, 31, 0.85);
}

.bm-reelpeek__play:focus-visible {
  outline: 2px solid var(--ag-color-border-focus);
  outline-offset: 2px;
}

.bm-crosslink {
  inline-size: 100%;
  margin-block-start: var(--ag-space-1);
  padding-block: var(--ag-space-2);
  padding-inline: var(--ag-space-3);
  display: flex;
  align-items: center;
  gap: var(--ag-space-2);
  border: 1px solid var(--bm-line);
  border-radius: var(--ag-radius-2xl);
  background: var(--bm-line-soft);
  color: var(--bm-text);
  font-family: var(--ag-font-sans);
  font-size: var(--ag-font-size-sm);
  font-weight: var(--ag-font-weight-medium);
  text-align: start;
  cursor: pointer;
  transition: var(--ag-transition-colors);
}

.bm-crosslink:hover {
  background: rgba(120, 150, 205, 0.2);
}

.bm-crosslink:focus-visible {
  outline: 2px solid var(--ag-color-border-focus);
  outline-offset: 2px;
}

/* ── Small screens: the map stays in charge, not the chrome ─────────────── */
@media (max-width: 820px) {
  #bm-dock {
    inset-inline-start: var(--ag-space-2);
  }

  #bm-rail {
    inset-inline-end: var(--ag-space-2);
  }

  #bm-omni {
    inline-size: min(520px, 86%);
  }

  /* #bm-omni-results deliberately has NO width override here: it inherits
     inset-inline:0 + inline-size:auto and so tracks whatever width #bm-omni
     takes at this breakpoint. The old min(560px, 90%) had the same
     wrong-containing-block defect as the base rule (0.90 x the bar). */

  #bm-whisper {
    max-inline-size: min(480px, 92%);
  }

  #bm-layers-sheet {
    inset-inline-start: var(--ag-space-2);
    inline-size: min(280px, 78%);
  }

  .bm-ecard {
    inset-block-start: var(--ag-space-2);
    inset-inline-end: var(--ag-space-2);
    inline-size: min(344px, calc(100% - var(--ag-space-2) * 2));
  }
}

/* ==========================================================================
   7. Mobile-web layout — <=720px (W2 Task 11, wave gate)
   ========================================================================== */
/* Phones: the dock becomes a bottom tab bar, omni spans (almost) full
   width, chips scroll horizontally, and the rail sheds everything but
   zoom + locate. The persistent chrome that reduces map area is dock
   (bottom) + omni (top) + the always-on chips row: #bm-chips ships with
   no [hidden] attribute and is visible the instant First Light chrome
   renders, same as dock/omni. #bm-layers-sheet and #bm-whisper are the
   only genuinely transient pieces here -- both start `hidden` in their
   partial's own markup and are only shown later by JS. (An earlier
   version of this comment miscategorized chips alongside layers-sheet/
   whisper as "transient overlays that don't count against the map's
   share" -- corrected; see task-11-report.md's gate-integrity fix.)

   omni is pinned top, ~54px tall (padding-block var(--ag-space-2) x2 +
   the input's --ag-hit-target-compact). The dock is pinned bottom; a
   real-browser harness measurement, not a plain-text content-box
   estimate, drove its padding here -- emoji glyphs render taller than
   their nominal font-size, and "My planet" wraps to two lines under
   ~360px, so its height varies: ~61px unwrapped, ~80px wrapped at the
   narrowest tested width. chips is forced to one horizontal-scrolling
   row at this breakpoint (flex-wrap:nowrap + overflow-x:auto below), so
   it never grows past one row's height (~39px) regardless of viewport
   width.

   Real Chromium measurements (task-11-report.md's harness; re-verified
   via Playwright when this comment was corrected): dock+omni ALONE --
   320x568 (narrowest+shortest tested, "My planet" wrapped) ~76.4% map
   share, 360x740 ~84.4%, 375x667 ~82.7%. Counting chips too, as the
   correction above says this section should (its ~39px band sits
   directly under omni, not overlapping it): 320x568 drops to ~69.6% --
   BELOW the >=75% floor -- while 360x740 (~79.1%) and 375x667 (~76.9%)
   stay clear (dock is shorter, unwrapped, at those two sizes).

   NOT fixed here: chips' ~36-39px is set by the same
   --ag-hit-target-compact (36px) floor every First Light control uses
   for tap-target accessibility, and #bm-chips carries no extra container
   padding to trim the way #bm-dock's own mobile fix did (that trimmed
   the DOCK CONTAINER's padding, independent of its buttons' 36px floor;
   #bm-chips has no equivalent container padding -- the 36px sits
   directly on each .bm-chip). Zeroing .bm-chip's padding-block entirely
   -- the only even theoretically "minimal" trim available -- recovers
   ~3px (measured) against a ~31px shortfall at 320x568; the rest is the
   accessibility floor itself, which this fix will not shrink just to
   chase a layout number. Flagging as an open concern for a follow-up
   task rather than force-fitting an inadequate trim. */
@media (max-width: 720px) {
  #bm-dock {
    inset-block-start: auto;
    inset-block-end: 0;
    inset-inline: 0;
    translate: none;
    flex-direction: row;
    align-items: center;
    justify-content: space-around;
    gap: 0;
    padding-block: var(--ag-space-1);
    padding-inline: var(--ag-space-2);
  }

  .bm-dock__btn {
    /* A little narrower than the vertical-stack minimum (62px) so all 5
       buttons plus the dock's own inline padding fit without overflow on
       a 320px-wide phone (5 x 56px + 16px padding = 296px, 24px to
       spare). At exactly 320px "My planet" (the longest label) still
       wraps to two lines -- measured harmless (no overflow, no clipping,
       see task-11-report.md's screenshot) but it grows the dock enough to
       leave the ">=75% map share" budget with almost no margin, so
       block-axis padding is trimmed here too (8px total instead of the
       vertical stack's 16px) to buy that margin back in both the wrapped
       and unwrapped case. */
    padding-block: var(--ag-space-1);
    min-inline-size: 56px;
  }

  .bm-dock__sep {
    /* Flips from a horizontal rule (sits between rows in the vertical
       stack) to a vertical divider (sits between columns in the row
       #bm-dock is now) now that the dock itself lays out as a row. */
    inline-size: 1px;
    block-size: 24px;
    margin-block: 0;
    margin-inline: var(--ag-space-1);
  }

  #bm-omni {
    inline-size: auto;
    inset-inline: var(--ag-space-2);
  }

  #bm-chips {
    inline-size: auto;
    max-inline-size: calc(100% - 24px);
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    /* flex-start, not center: centering an OVERFLOWING flex container can
       leave the start of its content unreachable by scrolling in some
       browsers -- flex-start guarantees the natural, scrollable-from-the-
       first-chip behavior every native horizontal chip/pill row uses.
       Direction-safe by construction: flex-start already resolves against
       the inline-start edge (the right in rtl), so this needs no separate
       rtl override. */
    justify-content: flex-start;
  }

  #bm-chips::-webkit-scrollbar {
    /* scrollbar-width:none (above) is the Firefox/standards knob; Safari
       and Chrome need the -webkit pseudo-element hidden too, same
       cross-browser pairing this file already uses for backdrop-filter. */
    display: none;
  }

  .bm-chip {
    /* flex:none = flex:0 0 auto -- chips never shrink to fit. Overflowing
       (and thus the horizontal scroll just turned on above) is the point,
       not squished or truncated chip text. */
    flex: none;
  }

  .bm-rail__extra {
    /* Compass, projection, fullscreen, and the two separators either side
       of them (see templates/globe/partials/_instrument_rail.html) --
       zoom in/out + locate are the three instruments a one-handed phone
       visit actually needs; the rest stay one tap away on a wider
       viewport instead of crowding a floating rail over the map. */
    display: none;
  }

  #bm-whisper {
    /* #bm-whisper's base rule anchors it var(--ag-space-4) (16px) above
       the viewport's bottom edge -- correct when nothing else lives down
       there, but #bm-dock now permanently occupies that same bottom band
       (this block's header comment: ~61-80px tall depending on whether
       "My planet" wraps) as a bottom bar, so an unmodified whisper would
       render UNDER/overlapping the dock instead of clear above it. First
       measured this with a real browser (see task-11-report.md) rather
       than trusting a hand-rolled icon+label content-box estimate -- that
       estimate (~56px) undershot the real, emoji-inflated dock height by
       13px and would have left whisper and the dock visibly overlapping.
       var(--ag-space-2) + 80px (88px total) clears the dock at every
       tested width, including its tallest (wrapped, ~80px at 320x568,
       an 8px margin) -- reconfirmed overlap=false in the same harness at
       320x568, 360x740, and 375x667 after this fix. */
    inset-block-end: calc(var(--ag-space-2) + 80px);
  }
}

/* ==========================================================================
   10. Booking slide-over — .bm-booksheet (W3 Task 5, Window Shopping)
   Flag: web_window_shopping (mounted on demand from entityCard.js's stay
   Reserve seam — no template diff). A docked panel over the globe hosting the
   stay's REAL booking flow in an iframe (catalog/stay_detail_embed_book.html);
   its success page posts "ag-booking-complete" back up and bookSheet.js toasts
   + closes. Unlike the dark-glass chrome, the panel is a LIGHT surface because
   it frames the light booking form. Tokens + LOGICAL properties only — the RTL
   gate greps this file.
   ========================================================================== */
.bm-booksheet {
  position: absolute;
  inset: 0;
  z-index: var(--ag-z-modal);
  display: flex;
  justify-content: flex-end;
}

.bm-booksheet__backdrop {
  position: absolute;
  inset: 0;
  background: var(--ag-color-bg-overlay);
  cursor: pointer;
}

.bm-booksheet__panel {
  position: relative;
  inline-size: min(440px, 100%);
  max-inline-size: 100%;
  block-size: 100%;
  display: flex;
  flex-direction: column;
  background: var(--ag-color-bg-surface);
  border-start-start-radius: var(--ag-radius-3xl);
  border-end-start-radius: var(--ag-radius-3xl);
  box-shadow: var(--ag-shadow-2xl);
  overflow: hidden;
}

.bm-booksheet__close {
  position: absolute;
  inset-block-start: var(--ag-space-3);
  inset-inline-end: var(--ag-space-3);
  inline-size: var(--ag-hit-target-compact);
  block-size: var(--ag-hit-target-compact);
  display: grid;
  place-items: center;
  border: 1px solid var(--ag-color-border-subtle, rgba(46, 116, 184, 0.18));
  border-radius: var(--ag-radius-full);
  background: var(--ag-color-bg-surface);
  color: var(--ag-color-text-body);
  font-size: var(--ag-font-size-md);
  line-height: 1;
  cursor: pointer;
  z-index: 1;
  box-shadow: var(--ag-shadow-soft, 0 6px 18px rgba(2, 8, 20, 0.12));
  transition: var(--ag-transition-colors);
}

.bm-booksheet__close:hover {
  background: var(--ag-color-bg-surface-alt);
}

.bm-booksheet__close:focus-visible {
  outline: 2px solid var(--ag-color-border-focus);
  outline-offset: 2px;
}

.bm-booksheet__frame {
  flex: 1 1 auto;
  inline-size: 100%;
  block-size: 100%;
  border: 0;
  background: var(--ag-color-bg-surface);
}

/* Success toast — dark chip, docked bottom-center over the globe. Self-styled
   (no dependence on the flag-scoped glass tokens) so it reads either way. */
.bm-booksheet-toast {
  position: absolute;
  inset-block-end: var(--ag-space-6);
  inset-inline: 0;
  margin-inline: auto;
  inline-size: max-content;
  max-inline-size: min(420px, 90%);
  z-index: var(--ag-z-popover);
  padding-block: var(--ag-space-3);
  padding-inline: var(--ag-space-5);
  border-radius: var(--ag-radius-full);
  background: rgba(14, 22, 46, 0.92);
  color: #eaf0fb;
  font-family: var(--ag-font-sans);
  font-size: var(--ag-font-size-sm);
  font-weight: var(--ag-font-weight-semibold);
  box-shadow: var(--ag-shadow-2xl);
}

@media (max-width: 820px) {
  .bm-booksheet__panel {
    inline-size: 100%;
    border-radius: 0;
  }
}

/* ==========================================================================
   11. Activity ripples — .bm-ripple (W4 Task 2, The Pulse)
   Flag: web_pulse. Expanding-ring mapboxgl.Marker overlays dropped where
   public activity is happening (static/js/globe/pulse/ripples.js polls
   /globe/api/activity-events/ every 60s, visibility- + mode-gated). Each ring
   plays ONCE and its JS owns the lifetime (removed after ~2.6s), so the
   animation is finite by construction. Ambient + non-interactive
   (pointer-events:none; the element is aria-hidden). Amber-neutral brand ring
   over the dark globe. Reduced motion -> a static glow dot, no animation —
   guaranteed twice over: ripples.js renders .bm-ripple--static, AND the
   @media (prefers-reduced-motion: reduce) block below forces it even on a
   plain .bm-ripple (this layer can render with web_first_light OFF, so the
   section-0 `.bm-first-light *` reduced-motion reset does not cover it).
   Tokens + LOGICAL properties only — the RTL gate greps this file.
   ========================================================================== */
.bm-ripple {
  inline-size: 16px;
  block-size: 16px;
  border-radius: var(--ag-radius-full);
  border: 2px solid var(--ag-color-brand-primary);
  background: transparent;
  pointer-events: none;
  will-change: transform, opacity;
  animation: bm-ripple 2.6s ease-out both;
}

@keyframes bm-ripple {
  0% {
    transform: scale(0.3);
    opacity: 0.9;
  }
  60% {
    opacity: 0.35;
  }
  100% {
    transform: scale(3.2);
    opacity: 0;
  }
}

/* Static glow dot — the reduced-motion fallback ripples.js opts into (and the
   @media guarantee below mirrors). A soft filled dot instead of an expanding,
   fading ring; no animation. */
.bm-ripple--static {
  border-color: transparent;
  background: radial-gradient(circle, var(--ag-color-brand-primary) 0%, transparent 70%);
  opacity: 0.85;
  transform: none;
  animation: none;
}

@media (prefers-reduced-motion: reduce) {
  .bm-ripple {
    border-color: transparent;
    background: radial-gradient(circle, var(--ag-color-brand-primary) 0%, transparent 70%);
    opacity: 0.85;
    transform: none;
    animation: none;
  }
}

/* ==========================================================================
   12. "Happening Now" feed — .bm-pulse-feed (W4 Task 3, The Pulse)
   Flag: web_pulse (pulse_on). A dark-glass panel docked inset-inline-end that,
   in the dock's Live mode, lists recent public activity (rows populated by
   static/js/globe/pulse/feed.js from the shared store) with kind-filter pills.
   When there's no activity it shows the HONEST empty state — "Quiet right now /
   Popular places instead" listing top hotspots labeled "Popular" (the planet
   never fakes liveness). Hidden until Live mode (the [hidden] attribute the
   partial ships with; feed.js toggles it). See mockup 04-the-pulse.html.

   The partial only renders inside the First Light chrome block, so the
   .bm-first-light ancestor that defines --bm-panel/... is always present; the
   panel re-declares those night-flight tokens on itself anyway (identical
   values — they just shadow the ancestor's) for the same defensive reason
   section 9's .bm-ecard does. Tokens + LOGICAL properties only — the RTL gate
   greps this file.
   ========================================================================== */
.bm-pulse-feed {
  --bm-panel: rgba(14, 22, 46, 0.86);
  --bm-line: rgba(120, 150, 205, 0.22);
  --bm-line-soft: rgba(120, 150, 205, 0.13);
  --bm-text: #eaf0fb;
  --bm-dim: #9daecc;
  --bm-faint: #6e7fa3;
  position: absolute;
  inset-block-start: var(--ag-space-4);
  inset-block-end: var(--ag-space-4);
  inset-inline-end: var(--ag-space-4);
  inline-size: min(316px, 92%);
  display: flex;
  flex-direction: column;
  padding: var(--ag-space-4);
  color: var(--bm-text);
  font-family: var(--ag-font-sans);
  z-index: 23;
}

.bm-pulse-feed[hidden] {
  display: none;
}

.bm-pulse-feed__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--ag-space-2);
}

.bm-pulse-feed__title {
  color: var(--bm-text);
  font-size: var(--ag-font-size-md);
  font-weight: var(--ag-font-weight-bold);
  line-height: var(--ag-line-height-tight);
}

.bm-pulse-feed__sub {
  color: var(--bm-faint);
  font-size: var(--ag-font-size-xs);
  font-weight: var(--ag-font-weight-medium);
}

/* Kind-filter pills (All/Reels/Events/Trails/Rifts). */
.bm-pulse-feed__tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ag-space-1);
  margin-block: var(--ag-space-2);
}

.bm-pulse-feed__pill {
  min-block-size: var(--ag-hit-target-compact);
  padding-block: var(--ag-space-1);
  padding-inline: var(--ag-space-2);
  border: 1px solid var(--bm-line);
  border-radius: var(--ag-radius-full);
  background: rgba(18, 28, 56, 0.7);
  color: var(--bm-dim);
  font-family: var(--ag-font-sans);
  font-size: var(--ag-font-size-xs);
  font-weight: var(--ag-font-weight-semibold);
  cursor: pointer;
  transition: var(--ag-transition-colors);
}

.bm-pulse-feed__pill:hover {
  color: var(--bm-text);
  border-color: var(--bm-line);
}

.bm-pulse-feed__pill.is-on {
  background: var(--ag-gradient-button);
  border-color: transparent;
  color: var(--ag-color-text-on-primary);
}

.bm-pulse-feed__pill:focus-visible {
  outline: 2px solid var(--ag-color-border-focus);
  outline-offset: 2px;
}

/* Scrollable rows region — takes the remaining panel height. */
.bm-pulse-feed__rows {
  flex: 1;
  overflow-y: auto;
  min-block-size: 0;
}

.bm-pulse-feed__row {
  display: flex;
  align-items: center;
  gap: var(--ag-space-2);
  inline-size: 100%;
  padding-block: var(--ag-space-2);
  padding-inline: var(--ag-space-1);
  border: none;
  border-block-end: 1px solid var(--bm-line-soft);
  background: transparent;
  color: var(--bm-text);
  font-family: var(--ag-font-sans);
  text-align: start;
  cursor: pointer;
  transition: var(--ag-transition-colors);
}

.bm-pulse-feed__row:last-child {
  border-block-end: none;
}

.bm-pulse-feed__row:hover {
  background: rgba(120, 150, 205, 0.08);
}

.bm-pulse-feed__row:focus-visible {
  outline: 2px solid var(--ag-color-border-focus);
  outline-offset: -2px;
}

.bm-pulse-feed__ic {
  flex: none;
  inline-size: 32px;
  block-size: 32px;
  display: grid;
  place-items: center;
  border-radius: var(--ag-radius-md);
  background: rgba(24, 36, 74, 0.9);
  font-size: var(--ag-font-size-sm);
  line-height: 1;
}

.bm-pulse-feed__row-body {
  flex: 1;
  min-inline-size: 0;
}

.bm-pulse-feed__row-title {
  display: block;
  color: var(--bm-text);
  font-size: var(--ag-font-size-xs);
  font-weight: var(--ag-font-weight-semibold);
  line-height: var(--ag-line-height-tight);
}

.bm-pulse-feed__ago {
  flex: none;
  margin-inline-start: auto;
  color: var(--bm-faint);
  font-size: var(--ag-font-size-xs);
  font-weight: var(--ag-font-weight-semibold);
}

/* "Popular" badge on empty-state hotspot rows — labeled as popular, NOT as
   live activity (the emptiness-honesty rule). */
.bm-pulse-feed__badge {
  flex: none;
  margin-inline-start: auto;
  padding-block: 2px;
  padding-inline: var(--ag-space-1);
  border-radius: var(--ag-radius-sm);
  background: rgba(120, 150, 205, 0.16);
  color: var(--bm-dim);
  font-size: var(--ag-font-size-xs);
  font-weight: var(--ag-font-weight-semibold);
}

/* Honest empty state. */
.bm-pulse-feed__empty {
  padding-block: var(--ag-space-2);
}

.bm-pulse-feed__empty-title {
  color: var(--bm-text);
  font-size: var(--ag-font-size-sm);
  font-weight: var(--ag-font-weight-semibold);
}

.bm-pulse-feed__empty-sub {
  margin-block-end: var(--ag-space-2);
  color: var(--bm-dim);
  font-size: var(--ag-font-size-xs);
  font-weight: var(--ag-font-weight-medium);
}

@media (max-width: 820px) {
  .bm-pulse-feed {
    inline-size: min(280px, 88%);
  }
}

/* ==========================================================================
   13. Time machine — .bm-timemachine (W4 Task 7, The Pulse)
   Flag: web_pulse (pulse_on). A dark-glass scrubber panel docked
   inset-inline-start / inset-block-end that replays the map through time via the
   existing premium /globe/timelapse/ endpoint (gate() from W4 Task 6). Ships
   hidden (the [hidden] attribute the partial carries); the Layers-sheet's
   `data-bm-timemachine-open` row reveals it. Free / admin users get the honest
   client-side "last 3 h preview" — never a padlock wall. See mockup
   04-the-pulse.html.

   Same defensive token re-declaration as .bm-pulse-feed (section 12): the
   partial only renders inside the First Light chrome block so .bm-first-light is
   always present, but the panel re-declares its night-flight tokens anyway.
   Tokens + LOGICAL properties only — the RTL gate greps this file.
   ========================================================================== */
.bm-timemachine {
  --bm-panel: rgba(14, 22, 46, 0.86);
  --bm-line: rgba(120, 150, 205, 0.22);
  --bm-text: #eaf0fb;
  --bm-dim: #9daecc;
  --bm-faint: #6e7fa3;
  position: absolute;
  inset-block-end: var(--ag-space-4);
  inset-inline-start: var(--ag-space-4);
  inline-size: min(400px, 92%);
  display: flex;
  flex-direction: column;
  gap: var(--ag-space-2);
  padding: var(--ag-space-4);
  color: var(--bm-text);
  font-family: var(--ag-font-sans);
  z-index: 23;
}

.bm-timemachine[hidden] {
  display: none;
}

.bm-timemachine__head {
  display: flex;
  align-items: center;
  gap: var(--ag-space-2);
}

.bm-timemachine__title {
  color: var(--bm-text);
  font-size: var(--ag-font-size-md);
  font-weight: var(--ag-font-weight-semibold);
}

.bm-timemachine__badge {
  padding-block: 2px;
  padding-inline: var(--ag-space-1);
  border-radius: var(--ag-radius-sm);
  background: rgba(139, 92, 246, 0.18);
  color: #c4b5fd;
  font-size: var(--ag-font-size-xs);
  font-weight: var(--ag-font-weight-bold);
}

.bm-timemachine__close {
  margin-inline-start: auto;
  border: 0;
  background: transparent;
  color: var(--bm-dim);
  font-size: var(--ag-font-size-md);
  line-height: 1;
  cursor: pointer;
}

.bm-timemachine__close:focus-visible {
  outline: 2px solid var(--bm-text);
  outline-offset: 2px;
}

.bm-timemachine__scrubrow {
  display: flex;
  align-items: center;
  gap: var(--ag-space-2);
}

.bm-timemachine__play {
  flex: none;
  inline-size: 34px;
  block-size: 34px;
  border: 1px solid var(--bm-line);
  border-radius: var(--ag-radius-pill);
  background: rgba(120, 150, 205, 0.12);
  color: var(--bm-text);
  font-size: var(--ag-font-size-sm);
  cursor: pointer;
}

.bm-timemachine__play:hover {
  background: rgba(120, 150, 205, 0.2);
}

.bm-timemachine__play:focus-visible {
  outline: 2px solid var(--bm-text);
  outline-offset: 2px;
}

.bm-timemachine__scrub {
  flex: 1 1 auto;
  inline-size: 100%;
  accent-color: #2563eb;
}

.bm-timemachine__ticks {
  display: flex;
  justify-content: space-between;
  color: var(--bm-faint);
  font-size: var(--ag-font-size-xs);
  font-weight: var(--ag-font-weight-semibold);
}

.bm-timemachine__status {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.bm-timemachine__day {
  color: var(--bm-text);
  font-size: var(--ag-font-size-sm);
  font-weight: var(--ag-font-weight-semibold);
}

.bm-timemachine__note {
  color: var(--bm-dim);
  font-size: var(--ag-font-size-xs);
  font-weight: var(--ag-font-weight-medium);
}

/* --- Task 8: time-layer tab (tabs + panes + Past/Present/Future modes +
   the active-mode `bm-timelayer` chip). Tokens + logical properties only —
   the RTL gate greps this file. See templates/globe/partials/_time_machine.html
   and static/js/globe/pulse/timeMachine.js. --- */
.bm-timemachine__tabs {
  display: flex;
  gap: var(--ag-space-1);
}

.bm-timemachine__tab {
  flex: 1 1 auto;
  padding-block: var(--ag-space-1);
  padding-inline: var(--ag-space-2);
  border: 1px solid var(--bm-line);
  border-radius: var(--ag-radius-pill);
  background: rgba(120, 150, 205, 0.1);
  color: var(--bm-dim);
  font-size: var(--ag-font-size-xs);
  font-weight: var(--ag-font-weight-bold);
  cursor: pointer;
}

.bm-timemachine__tab[aria-selected="true"] {
  background: #2563eb;
  border-color: #2563eb;
  color: #fff;
}

.bm-timemachine__tab:focus-visible {
  outline: 2px solid var(--bm-text);
  outline-offset: 2px;
}

.bm-timemachine__pane {
  display: flex;
  flex-direction: column;
  gap: var(--ag-space-2);
}

.bm-timemachine__pane[hidden] {
  display: none;
}

.bm-timemachine__modes {
  display: flex;
  gap: var(--ag-space-1);
}

.bm-timemachine__mode {
  flex: 1 1 auto;
  padding-block: var(--ag-space-2);
  padding-inline: var(--ag-space-2);
  border: 1px solid var(--bm-line);
  border-radius: var(--ag-radius-md);
  background: rgba(120, 150, 205, 0.12);
  color: var(--bm-text);
  font-size: var(--ag-font-size-sm);
  font-weight: var(--ag-font-weight-semibold);
  cursor: pointer;
}

.bm-timemachine__mode:hover {
  background: rgba(120, 150, 205, 0.2);
}

.bm-timemachine__mode[aria-pressed="true"] {
  background: rgba(37, 99, 235, 0.9);
  border-color: #2563eb;
  color: #fff;
}

.bm-timemachine__mode:focus-visible {
  outline: 2px solid var(--bm-text);
  outline-offset: 2px;
}

/* Active-mode chip in the header — a blue accent distinct from the static
   purple "Globe Plus" badge; hidden until a Past/Present/Future mode is on. */
.bm-timelayer {
  padding-block: 2px;
  padding-inline: var(--ag-space-1);
  border-radius: var(--ag-radius-sm);
  background: rgba(37, 99, 235, 0.2);
  color: #bfdbfe;
  font-size: var(--ag-font-size-xs);
  font-weight: var(--ag-font-weight-bold);
}

.bm-timelayer[hidden] {
  display: none;
}

@media (max-width: 820px) {
  .bm-timemachine {
    inline-size: min(320px, 88%);
  }
}

/* ===========================================================================
   11. First-Light coexistence with the legacy globe chrome (flag-ON only).
   The Blue Marble overlay chrome (#bm-omni / #bm-rail / #bm-whisper) mounts
   as a sibling of the legacy controls, which previously kept rendering at
   the same coordinates: the legacy search pill (z=85) sat ON TOP of the
   omni (z=20), the legacy FAB stack (z=140, top:198px) occluded the rail's
   upper buttons, the whisper (z=19) was buried by #ag-date-bar (z=130),
   and the native Mapbox zoom/compass/fullscreen duplicated the rail one
   half-overlapped column to its right. Real user clicks hit the top layer,
   so "buttons did nothing". Everything below is scoped to .bm-first-light
   (stamped on #ag-globe-shell only when web_first_light is ON) — flag-off
   markup and layout stay byte-identical.
   ======================================================================== */

/* The omni + mode dock own search/filtering under First Light. */
.bm-first-light .ag-service-filters-floating {
  display: none !important;
}

/* ...including in fullscreen. index.html carries a generic
   `#ag-globe-shell:fullscreen .ag-service-filters-floating { display: block }`
   for hero-embed mode, which re-showed the legacy bar on top of the omni — two
   overlapping search fields. index.html already countered that, but only inside
   its `{% if embed_hero %}` block, so the plain /globe/ page never got the fix.
   Placing it here makes it unconditional wherever First Light is on.
   The ID is required, not stylistic: the rule it must beat is
   `#ag-globe-shell:fullscreen ...` and both carry !important, so specificity
   decides — a class-only selector would lose. */
#ag-globe-shell.bm-first-light:fullscreen .ag-service-filters-floating,
#ag-globe-shell.bm-first-light:-webkit-full-screen .ag-service-filters-floating {
  display: none !important;
}

/* Legacy FABs keep their unique tools (fit-all, list view, style, more) but
   move to the free bottom-right zone below the rail (rail ends ~y588; the
   date bar spans x273-927 so x~1146 stays clear). */
.bm-first-light .ag-map-fab-group {
  top: auto !important;
  bottom: 90px !important;
}
/* "Go to my location" duplicates the rail's Locate button. */
.bm-first-light #ag-fab-my-location {
  display: none !important;
}

/* Lift the whisper clear of the legacy date bar (54px tall, bottom-anchored). */
.bm-first-light #bm-whisper {
  bottom: 84px;
}

/* The rail provides zoom / compass / fullscreen — hide the duplicated native
   Mapbox control groups (keep any custom controls, e.g. rotation toggle). */
.bm-first-light .mapboxgl-ctrl-top-right .mapboxgl-ctrl-group:has(.mapboxgl-ctrl-zoom-in),
.bm-first-light .mapboxgl-ctrl-top-right .mapboxgl-ctrl-group:has(.mapboxgl-ctrl-compass),
.bm-first-light .mapboxgl-ctrl-top-right .mapboxgl-ctrl-group:has(.mapboxgl-ctrl-fullscreen) {
  display: none;
}

/* The legacy Drift toggle mounts on camera-idle at the same right-edge slot
   the relocated FABs use (it wasn't in the initial overlap map). Move it one
   column inboard so both stay fully hit-testable. */
.bm-first-light .ag-drift-btn {
  inset-inline-end: 76px !important;
}

/* ── The rotate/stop toggle, on the FULL page ────────────────────────────────
   It is a MapLibre control, so with no restyle it renders MapLibre's stock
   chrome: a white .maplibregl-ctrl-group with a 4px radius, carrying a white
   icon at opacity .55 — white-on-white, parked beside the dark-glass
   instrument rail. An identical restyle already existed in
   templates/globe/index.html but was scoped inside `{% if embed_hero %}`, so
   only the hero embed ever received it and the page people actually use kept
   the default widget. Same values, applied where they were always needed.
   (Embeds now match by construction rather than by a second copy drifting.) */
.bm-first-light .maplibregl-ctrl-top-right > .ag-rotate-ctrl {
  margin: 0;
  padding: 4px;
  background: var(--bm-panel);
  border: 1px solid var(--bm-line);
  border-radius: var(--ag-radius-full);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: var(--ag-shadow-2xl);
  overflow: visible;
}

/* The global .ag-rotate-toggle--active is #0f172a — near-black, invisible on
   this panel — so both states are restated against the rail's own colours. */
.bm-first-light .maplibregl-ctrl-top-right .ag-rotate-toggle {
  inline-size: 30px;
  block-size: 30px;
  background: transparent;
  border: none;
  border-radius: var(--ag-radius-full);
  color: var(--bm-dim);
}
.bm-first-light .maplibregl-ctrl-top-right .ag-rotate-toggle:hover {
  background: var(--bm-line-soft);
  color: var(--bm-text);
}
.bm-first-light .maplibregl-ctrl-top-right .ag-rotate-toggle--active {
  color: var(--bm-text);
}

/* ── Omni results: readable rows (they previously inherited the page ink →
   dark-on-dark over the globe) + paint ABOVE the mode dock. ─────────────── */
#bm-omni-results {
  /* Above #ag-dir (the Plan-a-trip panel, z-index 90). Below ~1300px the
     centred omni bar overlaps that panel — at 1200px the bar runs 340->860
     against #ag-dir's 18->378 — so at z-index 30 the trip panel painted over
     the open suggestions and clipped "PLACES" to "ACES". The bar's own
     overlap there is a separate, pre-existing layout collision; this just
     guarantees that an OPEN listbox is never occluded, which is the normal
     contract for one. */
  z-index: 95;
  padding: 6px;
  /* HANGS OFF THE BAR. This used to be calc(100% + 56px) to clear the mode
     dock, which left the listbox floating 55px under the field that owns it
     with the chips row and a stripe of globe in between — measured on
     production: bar bottom 138, panel top 193. A combobox listbox has to be
     attached to its input; the chips are what yields instead (rule below). */
  inset-block-start: calc(100% + var(--ag-space-2));
  /* A dropdown is a SURFACE, not a film. --bm-panel is .86 alpha, which is
     right for chrome that floats over the globe but not for rows of text you
     have to read: clouds showed straight through the suggestions. */
  background: rgba(11, 18, 38, 0.97);
}

/* While suggestions are open the category chips step aside — they are
   siblings of #bm-omni under #ag-globe-shell, so the relationship is
   expressed from the common ancestor. Nothing to read there mid-search, and
   it is what buys the panel the right to sit against the bar. */
#ag-globe-shell:has(#bm-omni-results:not([hidden])) #bm-chips {
  opacity: 0;
  pointer-events: none;
}
#bm-omni-results [role="option"] {
  padding: 9px 12px;
  border-radius: 10px;
  color: var(--bm-text);
  font-size: 13.5px;
  line-height: 1.35;
  cursor: pointer;
}
#bm-omni-results [role="option"]:hover,
#bm-omni-results [role="option"][aria-selected="true"] {
  background: rgba(120, 150, 205, 0.16);
}
#bm-omni-results .bm-omni__group {
  padding: 10px 12px 3px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bm-dim);
}

/* The "Advanced Tools" fab (#ag-dir-fab) OWNS the planner (#ag-dir) under
   First Light — the redundant dock "Trip" button was removed (2026-07-11,
   user request: keep Advanced Tools, drop the conflicting duplicate). The fab
   keeps its top-left slot; the mode dock is vertically centred on the left
   edge (#bm-dock top:50%), so they occupy different zones.
   Base .ag-dir__fab is display:none and only unhides via a sibling-class rule
   the First-Light planner (display:none, this file line ~40) never triggers —
   so force it available, and swap fab<->planner on a .bm-planner-open state
   toggled by a small inline controller in index.html (full-page first light
   only; it adds .bm-adv-tools so embeds keep the fab hidden). The
   .bm-planner-open !important flex beats both line-40 display:none and dock.js's
   stale inline display:none from an earlier mode switch. */
.bm-first-light.bm-adv-tools #ag-dir-fab { display: inline-flex !important; align-items: center; justify-content: center; }
.bm-first-light.bm-adv-tools.bm-planner-open #ag-dir-fab { display: none !important; }
.bm-first-light.bm-planner-open #ag-dir { display: flex !important; }
