/* Pulse city visual — fullscreen point-cloud page (layout "pulse").
   Plain CSS on purpose: arbitrary Tailwind classes need a rebuild. */

/* Status colours live as variables (L2): the legend, the cards and the
   dossier dots all read these, so the chrome always matches the canvas.
   The day theme swaps them for the Okabe-Ito colorblind-safe palette
   further down — the two-tier meaning must survive every theme. */
.pulse-stage {
  --pulse-status-open: #ff6b4a;
  --pulse-status-complete: #f5a623;
  --pulse-status-resolved: #4ade80;
  position: fixed;
  inset: 0;
  background: #05070d;
  color: #c7d3ea;
  overflow: hidden;
}

.pulse-stage--warming {
  display: flex;
  align-items: center;
  justify-content: center;
}

.pulse-canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none;
  cursor: grab;
}

.pulse-canvas:active {
  cursor: grabbing;
}

.pulse-overlay {
  position: absolute;
  inset: 0;
  z-index: 8; /* above the story panel + finale so loading covers everything */
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(5, 7, 13, 0.55);
  backdrop-filter: blur(2px);
  pointer-events: none;
}

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

.pulse-overlay-card {
  max-width: 22rem;
  padding: 2rem 1.5rem;
  text-align: center;
  pointer-events: auto;
}

.pulse-overlay-title {
  margin: 0 0 0.5rem;
  font-size: 1.125rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: #e6edfa;
}

.pulse-overlay-text {
  margin: 0;
  font-size: 0.875rem;
  line-height: 1.5;
  color: #8fa2c4;
  font-variant-numeric: tabular-nums;
}

.pulse-overlay-link {
  display: inline-block;
  margin-top: 1rem;
  font-size: 0.875rem;
  color: #9db8e8;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.pulse-overlay-link:hover {
  color: #c3d4f2;
}

.pulse-beacon {
  width: 0.75rem;
  height: 0.75rem;
  margin: 0 auto 1rem;
  border-radius: 9999px;
  background: #9db8e8;
  animation: pulse-beacon 2s ease-in-out infinite;
}

@keyframes pulse-beacon {
  0%, 100% { box-shadow: 0 0 0 0 rgba(157, 184, 232, 0.45); }
  50% { box-shadow: 0 0 0 1.1rem rgba(157, 184, 232, 0); }
}

@media (prefers-reduced-motion: reduce) {
  .pulse-beacon { animation: none; }
}

/* --- city toggle (top centre) --- */

.pulse-cities {
  position: absolute;
  top: 1.25rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 5;
  display: flex;
  gap: 0.25rem;
  padding: 0.25rem;
  border-radius: 9999px;
  background: rgba(10, 14, 24, 0.82);
  border: 1px solid rgba(157, 184, 232, 0.18);
  backdrop-filter: blur(6px);
}

.pulse-cities-link {
  padding: 0.3rem 0.85rem;
  border-radius: 9999px;
  color: #8fa2c4;
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  text-decoration: none;
  white-space: nowrap;
}

.pulse-cities-link:hover {
  color: #e6edfa;
  background: rgba(157, 184, 232, 0.12);
}

.pulse-cities-link.is-current {
  color: #e6edfa;
  background: rgba(157, 184, 232, 0.18);
}

/* --- bottom bar: zoom group + status select + timeline scrubber --- */

/* One stable row on wide screens (G1: the chips used to wrap and crush
   the scrubber): [zoom group] [timeline pill], with the timeline pill
   flexing so the scrubber track reads like a video progress bar. Below
   1024px it wraps into two clean rows (controls, then timeline).
   pointer-events: the bar spans wider than its pills; the transparent
   gaps must never swallow canvas drags, so the wrapper is inert and the
   pills re-enable hits. */
.pulse-bottom {
  position: absolute;
  left: 50%;
  bottom: calc(1.25rem + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: 4;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  width: min(75rem, calc(100% - 2rem));
  pointer-events: none;
}

.pulse-bottom > * {
  pointer-events: auto;
}

/* Visible zoom controls: not everyone knows the wheel zooms. Docked left
   of the scrubber pill, still rendered when the timeline hasn't loaded. */
.pulse-zoom {
  flex: none;
  display: flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.3rem;
  border-radius: 9999px;
  background: rgba(10, 14, 24, 0.82);
  border: 1px solid rgba(157, 184, 232, 0.18);
  backdrop-filter: blur(6px);
}

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

.pulse-zoom-button {
  width: 1.9rem;
  height: 1.9rem;
  border-radius: 9999px;
  border: 1px solid rgba(157, 184, 232, 0.35);
  background: transparent;
  color: #c7d3ea;
  font-size: 0.95rem;
  line-height: 1;
  cursor: pointer;
}

.pulse-zoom-button:hover {
  background: rgba(157, 184, 232, 0.12);
  color: #e6edfa;
}

.pulse-zoom-button:focus-visible {
  outline: 2px solid #9db8e8;
  outline-offset: 2px;
}

/* 3D toggle: docked with the zoom pills; warm border + fill while the
   tilted mode is on, mirroring the active filter-chip language. */
.pulse-zoom-button--mode {
  width: auto;
  padding: 0 0.6rem;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.05em;
}

.pulse-zoom-button--mode.is-active {
  color: #ffb29e;
  border-color: rgba(255, 107, 74, 0.65);
  background: rgba(255, 107, 74, 0.14);
}

/* Sound toggle (Phase D): same active language as the 3D pill, plus a
   slow glow breathing while the city is audible — the sound made
   visible. The breath matches the ambience LFO's unhurried pace. */
.pulse-zoom-button--sound.is-active {
  animation: pulse-sound-breath 8s ease-in-out infinite;
}

@keyframes pulse-sound-breath {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 107, 74, 0); }
  50% { box-shadow: 0 0 10px 1px rgba(255, 107, 74, 0.35); }
}

@media (prefers-reduced-motion: reduce) {
  .pulse-zoom-button--sound.is-active {
    animation: none;
  }
}

/* Ward fly-to (Phase F) + status filter (G1): compact native selects
   restyled to match the pills. The ward select hides until the baked
   wards load. */
.pulse-ward-select,
.pulse-status-select {
  max-width: 7.5rem;
  min-width: 0;
  padding: 0.3rem 0.5rem;
  border-radius: 9999px;
  border: 1px solid rgba(157, 184, 232, 0.35);
  background: rgba(10, 14, 24, 0.82);
  color: #c7d3ea;
  font-size: 0.7rem;
  letter-spacing: 0.03em;
  cursor: pointer;
}

.pulse-ward-select:hover,
.pulse-status-select:hover {
  background: rgba(157, 184, 232, 0.12);
  color: #e6edfa;
}

.pulse-ward-select:focus-visible,
.pulse-status-select:focus-visible {
  outline: 2px solid #9db8e8;
  outline-offset: 2px;
}

.pulse-ward-select[hidden] {
  display: none;
}

.pulse-ward-select option,
.pulse-status-select option {
  background: #0a0e18;
  color: #c7d3ea;
}

/* The longest status option ("City marked complete") must fit. */
.pulse-status-select {
  max-width: none;
}

/* The timeline pill: one row — status select, play, the flexing track,
   date, live dot. min-width guarantees a real track even when the row
   gets crowded. */
.pulse-timeline {
  flex: 1;
  min-width: 320px;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.45rem 1rem;
  border-radius: 9999px;
  background: rgba(10, 14, 24, 0.82);
  border: 1px solid rgba(157, 184, 232, 0.18);
  backdrop-filter: blur(6px);
}

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

/* Two clean rows below 1024px: controls row, then the timeline row.
   (Must come after the .pulse-timeline base rule — flex: 1 resets
   flex-basis, so an earlier override would be dead.) */
@media (max-width: 1023px) {
  .pulse-bottom {
    flex-wrap: wrap;
  }

  .pulse-timeline {
    flex-basis: 100%;
    min-width: 0;
  }
}

/* The narrow end of the one-row range: trim the pill's fixed costs so
   the track keeps real travel room (the spec floor below is the last
   line of defence, not the plan). */
@media (min-width: 1024px) and (max-width: 1279px) {
  .pulse-status-caption {
    display: none; /* the select keeps its aria-label */
  }

  .pulse-timeline {
    gap: 0.45rem;
    padding: 0.45rem 0.75rem;
  }

  .pulse-date {
    min-width: 0;
  }

  .pulse-ward-select {
    max-width: 6rem;
  }
}

/* "Showing" + the status select (G1: replaces the four chips that
   wrapped on desktop). The select shares the ward select's styling. */
.pulse-status {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.pulse-status-caption {
  font-size: 0.7rem;
  letter-spacing: 0.05em;
  color: #8fa2c4;
  white-space: nowrap;
}

/* Status legend (L2): a collapsible key beside the status select. The
   summary renders as one more pill; the open list floats above the bar
   so the timeline row never reflows. Dot colours ride the status
   variables, so the key recolours with the theme (Okabe-Ito in light). */
.pulse-legend {
  position: relative;
  flex: none;
}

.pulse-legend-summary {
  list-style: none;
  padding: 0.3rem 0.6rem;
  border-radius: 9999px;
  border: 1px solid rgba(157, 184, 232, 0.35);
  font-size: 0.7rem;
  letter-spacing: 0.05em;
  color: #c7d3ea;
  cursor: pointer;
  white-space: nowrap;
}

.pulse-legend-summary::-webkit-details-marker {
  display: none;
}

.pulse-legend-summary:hover {
  background: rgba(157, 184, 232, 0.12);
  color: #e6edfa;
}

.pulse-legend-summary:focus-visible {
  outline: 2px solid #9db8e8;
  outline-offset: 2px;
}

.pulse-legend[open] .pulse-legend-summary {
  background: rgba(157, 184, 232, 0.16);
  color: #e6edfa;
}

.pulse-legend-list {
  position: absolute;
  bottom: calc(100% + 0.6rem);
  left: 0;
  margin: 0;
  padding: 0.6rem 0.85rem;
  list-style: none;
  border-radius: 0.75rem;
  background: rgba(10, 14, 24, 0.92);
  border: 1px solid rgba(157, 184, 232, 0.25);
  backdrop-filter: blur(6px);
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.45);
}

.pulse-legend-row {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.18rem 0;
  font-size: 0.75rem;
  color: #c7d3ea;
  white-space: nowrap;
}

.pulse-legend-dot {
  flex: none;
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 9999px;
}

.pulse-legend-dot[data-status="open"] {
  background: var(--pulse-status-open);
}

.pulse-legend-dot[data-status="complete"] {
  background: var(--pulse-status-complete);
}

.pulse-legend-dot[data-status="resolved"] {
  background: var(--pulse-status-resolved);
}

.pulse-play {
  flex: none;
  width: 2rem;
  height: 2rem;
  border-radius: 9999px;
  border: 1px solid rgba(157, 184, 232, 0.35);
  background: transparent;
  color: #c7d3ea;
  font-size: 0.7rem;
  line-height: 1;
  cursor: pointer;
}

.pulse-play:hover {
  background: rgba(157, 184, 232, 0.12);
}

/* The track is the bar's centrepiece: it takes every spare pixel of the
   pill so the thumb visibly travels during playback. The min-width is a
   hard floor — the track must never collapse to a dot again. */
.pulse-scrubber {
  flex: 1;
  min-width: 8rem;
  width: 100%;
  accent-color: var(--pulse-status-open);
  cursor: pointer;
}

.pulse-date {
  flex: none;
  min-width: 6.5rem;
  text-align: right;
  font-size: 0.75rem;
  color: #8fa2c4;
  font-variant-numeric: tabular-nums;
}

/* Live indicator (Phase F): a small golden dot + "Live" beside the date
   label while the live poll runs. The dot breathes on the same unhurried
   pace as the sound pill's glow. */
.pulse-live {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.68rem;
  letter-spacing: 0.05em;
  color: #ffd68c;
}

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

.pulse-live-dot {
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 9999px;
  background: #ffd68c;
  box-shadow: 0 0 6px rgba(255, 214, 140, 0.7);
  animation: pulse-live-breath 3s ease-in-out infinite;
}

@keyframes pulse-live-breath {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}

@media (prefers-reduced-motion: reduce) {
  .pulse-live-dot { animation: none; }
}

/* --- street-level walk mode (Phase 8) --- */

/* Walk approach prompt (G1): centred pill shown when the Walk button's
   fly-down lands — pointer lock needs a fresh gesture, so this click is
   the one that starts the walk. */
.pulse-walk-prompt {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 5;
  padding: 0.6rem 1.4rem;
  border-radius: 9999px;
  border: 1px solid rgba(157, 184, 232, 0.45);
  background: rgba(10, 14, 24, 0.88);
  color: #e6edfa;
  font-size: 0.85rem;
  letter-spacing: 0.03em;
  cursor: pointer;
  backdrop-filter: blur(6px);
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.45);
}

.pulse-walk-prompt:hover {
  background: rgba(157, 184, 232, 0.16);
}

.pulse-walk-prompt:focus-visible {
  outline: 2px solid #9db8e8;
  outline-offset: 2px;
}

.pulse-walk-prompt[hidden] {
  display: none;
}

/* Centre crosshair: a breathing ring that warms up + grows when a
   catchable firefly sits under it; the label pill names the target and
   how far it is. Sized for "can't miss it" (founder: "crosshair tiny"). */
.pulse-crosshair {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.8rem;
  pointer-events: none;
}

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

.pulse-crosshair-dot {
  width: 1.4rem;
  height: 1.4rem;
  border-radius: 9999px;
  border: 2px solid rgba(230, 237, 250, 0.85);
  background: rgba(230, 237, 250, 0.08);
  box-shadow: 0 0 12px rgba(157, 184, 232, 0.25);
  transition: transform 0.15s ease, border-color 0.15s ease, background 0.15s ease,
    box-shadow 0.15s ease;
  animation: pulse-crosshair-breathe 2.6s ease-in-out infinite;
}

@keyframes pulse-crosshair-breathe {
  50% { transform: scale(1.08); }
}

.pulse-crosshair.is-target .pulse-crosshair-dot {
  animation: none;
  transform: scale(1.5);
  border-color: #ff8a6b;
  background: rgba(255, 107, 74, 0.3);
  box-shadow: 0 0 22px rgba(255, 138, 107, 0.55);
}

.pulse-crosshair-label {
  padding: 0.3rem 0.8rem;
  border-radius: 9999px;
  background: rgba(10, 14, 24, 0.85);
  border: 1px solid rgba(255, 138, 107, 0.4);
  font-size: 0.85rem;
  color: #ffd9cc;
  white-space: nowrap;
}

.pulse-crosshair-label[hidden] {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .pulse-crosshair-dot { animation: none; }
}

/* Key hints sit where the scrubber pill normally lives (it hides during
   the walk). */
.pulse-walk-hint {
  position: absolute;
  left: 50%;
  bottom: calc(1.5rem + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: 4;
  margin: 0;
  padding: 0.45rem 1.1rem;
  border-radius: 9999px;
  background: rgba(10, 14, 24, 0.7);
  color: #8fa2c4;
  font-size: 0.75rem;
  letter-spacing: 0.02em;
  white-space: nowrap;
  pointer-events: none;
}

.pulse-walk-hint[hidden] {
  display: none;
}

/* Walk HUD: nearest ward + glows in reach, stacked just above the key
   hints. Inert like the hint pill. */
.pulse-walk-hud {
  position: absolute;
  left: 50%;
  bottom: calc(4.4rem + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: 4;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.45rem 1.1rem;
  border-radius: 9999px;
  background: rgba(10, 14, 24, 0.78);
  border: 1px solid rgba(157, 184, 232, 0.2);
  white-space: nowrap;
  pointer-events: none;
}

.pulse-walk-hud[hidden] {
  display: none;
}

/* One compact line: empty segments collapse, every later non-empty
   segment gets a middot separator (general sibling on purpose — the
   chain survives hidden segments in between). */
.pulse-walk-hud span:empty {
  display: none;
}

.pulse-walk-hud span:not(:empty) ~ span:not(:empty)::before {
  content: "\00b7\00a0";
  color: #8fa2c4;
}

.pulse-walk-hud-compass {
  min-width: 1.2rem;
  text-align: center;
  font-size: 0.85rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: #ffe3ad;
}

.pulse-walk-hud-place {
  font-size: 0.85rem;
  color: #c7d3ea;
}

.pulse-walk-hud-ward {
  font-size: 0.85rem;
  font-weight: 600;
  color: #e6edfa;
}

.pulse-walk-hud-glows {
  font-size: 0.8rem;
  color: #ffb29e;
}

/* --- wayfinding overlay: street labels + landmarks (Phase C2) --- */

/* Inert overlay above the canvas, under EVERY piece of UI chrome.
   The stage's z ladder (all chrome carries an explicit z-index — never
   rely on DOM order against the labels):
     canvas (auto) < labels 1 < bottom bar / walk hint+HUD / minimap 4
     < cities / story / odometer / present odometer+hint / tooltip
     / walk prompt / crosshair 5
     < card 6 < finale 7
     < fork / sponsor card / name-a-building card / loading/fallback 8
     (the scrim cards layer OVER an open dossier on purpose: Esc and
     the backdrop peel just them, the dossier stays underneath).
   Keep .pulse-labels the LOWEST explicit z-index on the stage so a
   street name can never paint over a panel corner. Labels are
   positioned by street_labels.js on the render path. overflow: hidden
   so a label sliding off-screen never scrolls the stage. */
.pulse-labels {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  pointer-events: none;
}

.pulse-street-label {
  position: absolute;
  left: 0;
  top: 0;
  color: rgba(196, 210, 238, 0.88);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  white-space: nowrap;
  text-shadow: 0 1px 4px rgba(5, 7, 13, 0.95), 0 0 3px rgba(5, 7, 13, 0.95);
  opacity: 0;
  transition: opacity 0.25s ease;
  will-change: transform;
}

.pulse-street-label.is-visible {
  opacity: 1;
}

/* Walking: nearby names billboard as small pills (no rotation). */
.pulse-street-label--walk {
  padding: 0.16rem 0.6rem;
  border-radius: 9999px;
  background: rgba(10, 14, 24, 0.78);
  border: 1px solid rgba(157, 184, 232, 0.25);
  color: #c7d3ea;
  text-shadow: none;
}

/* Building-name pills (L5): named buildings at close zoom in 3D/walk,
   anchored above the roofline by street_labels.js. Gold-on-dark like
   the landmarks (identity, never report-red), but quieter — a small
   pill, not a diamond marker. */
.pulse-building-label {
  position: absolute;
  left: 0;
  top: 0;
  padding: 0.18rem 0.62rem;
  border-radius: 9999px;
  background: rgba(10, 14, 24, 0.82);
  border: 1px solid rgba(255, 214, 140, 0.32);
  color: #ffe3ad;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  white-space: nowrap;
  opacity: 0;
  transition: opacity 0.25s ease;
  will-change: transform;
}

.pulse-building-label.is-visible {
  opacity: 1;
}

/* The pill is clickable like a landmark (it opens the building's
   dossier): pointer events re-enable on the pill alone — every other
   pixel of the layer stays inert. */
.pulse-building-label--link {
  pointer-events: auto;
  cursor: pointer;
}

.pulse-building-label--link:hover {
  color: #fff1d4;
  border-color: rgba(255, 214, 140, 0.55);
}

/* Landmarks: gold diamond + name — wayfinding, never report-red. */
.pulse-landmark {
  position: absolute;
  left: 0;
  top: 0;
  display: flex;
  align-items: center;
  gap: 0.32rem;
  color: #ffe3ad;
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  white-space: nowrap;
  text-shadow: 0 1px 4px rgba(5, 7, 13, 0.95);
  opacity: 0;
  transition: opacity 0.25s ease;
  will-change: transform;
}

.pulse-landmark.is-visible {
  opacity: 1;
}

.pulse-landmark-glyph {
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  background: rgba(255, 214, 140, 0.92);
  box-shadow: 0 0 7px rgba(255, 214, 140, 0.55);
  transform: rotate(45deg); /* square -> diamond */
}

/* L1: landmark markers are the ONE clickable element in the otherwise
   inert label layer — pointer events re-enable on the marker itself
   only, so every other pixel still passes clicks to the canvas. */
.pulse-landmark--link {
  pointer-events: auto;
  cursor: pointer;
}

.pulse-landmark--link:hover {
  color: #fff1d4;
}

/* --- mini-map (Phase C2: 3D + walk modes) --- */

/* Bottom-left, stacked above the bottom bar with a fixed gap so it
   never overlaps the zoom group (the Walk button lives there now). */
.pulse-minimap {
  position: absolute;
  left: 1.25rem;
  bottom: calc(5.25rem + env(safe-area-inset-bottom, 0px));
  z-index: 4;
  width: 180px;
  height: 180px;
  border-radius: 0.6rem;
  overflow: hidden;
  border: 1px solid rgba(157, 184, 232, 0.3);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
  pointer-events: none;
}

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

.pulse-minimap canvas {
  position: absolute;
  inset: 0;
  width: 180px;
  height: 180px;
}

/* The bar wraps to two rows below 1024px — lift the minimap over it. */
@media (max-width: 1023px) {
  .pulse-minimap {
    bottom: calc(8.5rem + env(safe-area-inset-bottom, 0px));
  }
}

@media (max-width: 767px) {
  .pulse-minimap {
    display: none; /* small screens: the inset costs more than it orients */
  }
}

/* --- hover tooltip --- */

.pulse-tooltip {
  position: absolute;
  z-index: 5;
  max-width: 20rem;
  padding: 0.35rem 0.6rem;
  border-radius: 0.375rem;
  background: rgba(10, 14, 24, 0.92);
  border: 1px solid rgba(157, 184, 232, 0.25);
  color: #e6edfa;
  font-size: 0.75rem;
  line-height: 1.4;
  white-space: nowrap;
  pointer-events: none;
}

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

/* --- catch card --- */

/* Brighter than the first cut (founder: "card too dim"): lighter slate,
   stronger border, a soft outer glow, larger type, photo on top. */
.pulse-card {
  position: absolute;
  top: 1.25rem;
  right: 1.25rem;
  z-index: 6;
  width: min(22rem, calc(100% - 2rem));
  padding: 1rem 1.25rem 1.1rem;
  border-radius: 0.75rem;
  background: rgba(24, 32, 50, 0.96);
  border: 1px solid rgba(157, 184, 232, 0.45);
  box-shadow: 0 0 24px rgba(157, 184, 232, 0.18), 0 8px 30px rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(6px);
}

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

.pulse-card-close {
  position: absolute;
  top: 0.5rem;
  right: 0.6rem;
  width: 1.6rem;
  height: 1.6rem;
  border: none;
  border-radius: 9999px;
  background: rgba(10, 14, 24, 0.55); /* stays legible over the photo */
  color: #c7d3ea;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}

.pulse-card-close:hover {
  color: #ffffff;
  background: rgba(10, 14, 24, 0.8);
}

.pulse-card-photo {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 0.5rem;
  margin: 0.2rem 0 0.75rem;
}

.pulse-card-photo[hidden] {
  display: none;
}

.pulse-card-title {
  margin: 0 1.25rem 0.25rem 0;
  font-size: 1.1rem;
  font-weight: 600;
  color: #f4f8ff;
}

.pulse-card-meta {
  margin: 0 0 0.5rem;
  font-size: 0.875rem;
  color: #a9bcdd;
  font-variant-numeric: tabular-nums;
}

.pulse-card-status {
  margin: 0 0 0.75rem;
  font-size: 0.9rem;
  font-weight: 600;
}

.pulse-card-status[data-status="open"] {
  color: #ff8a6b;
}

.pulse-card-status[data-status="complete"] {
  color: #f5a623;
}

.pulse-card-status[data-status="resolved"] {
  color: #4ade80;
}

.pulse-card-link {
  font-size: 0.9rem;
  color: #aec6f0;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.pulse-card-link:hover {
  color: #c3d4f2;
}

/* N3: one quiet last line on every closable card (catch card, dossier,
   private-property card) — first-time visitors kept hunting for the X. */
.pulse-card-hint {
  margin: 0.6rem 0 0;
  font-size: 0.68rem;
  color: #7a8cab;
}

/* Walk-mode catch: centred and larger — at street level the eyes sit on
   the crosshair, so a corner card goes unseen. The class stays until the
   card closes (an Esc out of the walk leaves it readable in place). */
.pulse-card--walk {
  top: 50%;
  left: 50%;
  right: auto;
  transform: translate(-50%, -50%);
  width: min(28rem, calc(100% - 2rem));
  padding: 1.25rem 1.5rem 1.35rem;
}

.pulse-card--walk .pulse-card-title {
  font-size: 1.3rem;
}

.pulse-card--walk .pulse-card-meta,
.pulse-card--walk .pulse-card-status,
.pulse-card--walk .pulse-card-link {
  font-size: 1rem;
}

/* --- construction theater (the "Solvers" fix show) --- */

/* The "See how it got fixed" link only renders on a verified-fixed (green)
   report (the controller arms it). It reads as a small green call-to-action
   under the report link, distinct from the plain blue "View this report". */
.pulse-fix-show-link {
  display: block;
  margin: 0.55rem 0 0;
  padding: 0;
  border: none;
  background: none;
  font-size: 0.9rem;
  font-weight: 600;
  color: #4ade80;
  text-align: left;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.pulse-fix-show-link[hidden] {
  display: none;
}

.pulse-fix-show-link:hover {
  color: #86efac;
}

/* The Skip control appears ONLY while the show runs — a small bottom-centre
   corner button, never on the resting card (the founder clicked an in-card
   "Skip to the fix" with no show running and nothing happened). startFixShow
   unhides it; the show's end, a skip, or a card close hides it again. A
   quiet ghost pill so the construction show stays the focus. */
.pulse-fix-skip {
  position: absolute;
  left: 50%;
  bottom: calc(1.5rem + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: 6; /* with the card layer, above the bottom bar */
  padding: 0.4rem 1.1rem;
  border: 1px solid rgba(157, 184, 232, 0.45);
  border-radius: 9999px;
  background: rgba(10, 14, 24, 0.78);
  backdrop-filter: blur(6px);
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  color: #c7d3ea;
  cursor: pointer;
}

.pulse-fix-skip[hidden] {
  display: none;
}

.pulse-fix-skip:hover {
  color: #ffffff;
  border-color: rgba(157, 184, 232, 0.7);
}

.pulse-fix-skip:focus-visible {
  outline: 2px solid #9db8e8;
  outline-offset: 2px;
}

/* --- Fix Reel: the "Play recent fixes" montage --- */

/* The trigger lives in the story panel near the business hook, shaped like
   the other panel affordances (origin/campus buttons) but in the verified-
   fix green that the show pays off in. Hidden until the controller confirms
   there is at least one verified fix to play (revealReelButton). */
.pulse-story-reel-button {
  display: block;
  width: 100%;
  margin-top: 0.75rem;
  padding: 0.55rem 0.9rem;
  border-radius: 0.6rem;
  border: 1px solid rgba(74, 222, 128, 0.45);
  background: rgba(74, 222, 128, 0.12);
  color: #d7f5e1;
  font-size: 0.82rem;
  letter-spacing: 0.02em;
  cursor: pointer;
}

.pulse-story-reel-button:hover {
  background: rgba(74, 222, 128, 0.22);
  color: #ffffff;
}

.pulse-story-reel-button[hidden] {
  display: none;
}

/* In-reel Stop control: same corner family as the theater Skip, sitting
   just above it so both can show together (Skip = next fix, Stop = end the
   reel). Shown only while the montage runs (syncReelChrome). */
.pulse-reel-stop {
  position: absolute;
  left: 50%;
  bottom: calc(3.6rem + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: 6;
  padding: 0.4rem 1.1rem;
  border: 1px solid rgba(74, 222, 128, 0.5);
  border-radius: 9999px;
  background: rgba(10, 14, 24, 0.78);
  backdrop-filter: blur(6px);
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  color: #d7f5e1;
  cursor: pointer;
}

.pulse-reel-stop[hidden] {
  display: none;
}

.pulse-reel-stop:hover {
  color: #ffffff;
  border-color: rgba(74, 222, 128, 0.8);
}

.pulse-reel-stop:focus-visible {
  outline: 2px solid #4ade80;
  outline-offset: 2px;
}

/* Closing summary: the montage's payoff, centred like the finale card but
   its own green-accented family. Count + a representative duration, then one
   honest outcome line. */
.pulse-reel-summary {
  position: absolute;
  inset: 0;
  z-index: 7;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: linear-gradient(
    to top,
    rgba(5, 7, 13, 0.82) 0%,
    rgba(5, 7, 13, 0.45) 45%,
    rgba(5, 7, 13, 0) 80%
  );
}

.pulse-reel-summary[hidden] {
  display: none;
}

.pulse-reel-summary-card {
  max-width: 22rem;
  padding: 1.9rem 1.75rem;
  text-align: center;
  border-radius: 0.75rem;
  background: rgba(10, 14, 24, 0.94);
  border: 1px solid rgba(74, 222, 128, 0.3);
}

.pulse-reel-summary-title {
  margin: 0 0 0.6rem;
  font-size: 1.15rem;
  font-weight: 600;
  color: #e6edfa;
}

.pulse-reel-summary-stats {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.55rem;
  margin: 0 0 0.9rem;
  font-size: 1.35rem;
  font-weight: 700;
  color: #8fe6ab;
}

.pulse-reel-summary-dot {
  color: rgba(143, 230, 171, 0.6);
  font-weight: 400;
}

/* No representative duration (a list with no datable spans, never in
   practice) hides both the duration line and the separator dot. */
.pulse-reel-summary-duration[hidden] {
  display: none;
}

.pulse-reel-summary-stats:has(.pulse-reel-summary-duration[hidden]) .pulse-reel-summary-dot {
  display: none;
}

.pulse-reel-summary-text {
  margin: 0 0 1.25rem;
  font-size: 0.875rem;
  line-height: 1.5;
  color: #8fa2c4;
}

.pulse-reel-summary-close {
  display: inline-block;
  padding: 0.5rem 1.3rem;
  border-radius: 9999px;
  border: 1px solid rgba(157, 184, 232, 0.4);
  background: transparent;
  color: #c3d4f2;
  font-size: 0.85rem;
  cursor: pointer;
}

.pulse-reel-summary-close:hover {
  color: #ffffff;
  border-color: rgba(157, 184, 232, 0.7);
}

/* Narrated fix timeline (construction theater): a three-step connected
   mini-timeline shown only while the show runs (startFixShow reveals the
   Reported node; the dig beat reveals the Fixed + duration nodes). The
   founder drew this — three nodes joined by a vertical rail down the left,
   like the story panel's beat rail. Hidden at rest. */
.pulse-fix-dates {
  position: relative;
  margin: 0.7rem 0 0;
  padding: 0 0 0 1.1rem; /* room for the rail + nodes on the left */
  list-style: none;
}

.pulse-fix-dates[hidden] {
  display: none;
}

/* The connecting rail: a vertical line behind the nodes, threaded between
   the first and last node centres (0.45rem is a node's vertical centre).
   Steps that are still hidden collapse out of flow, so the rail only ever
   spans the nodes that are showing — one beat lit, the rest waiting. */
.pulse-fix-dates::before {
  content: "";
  position: absolute;
  left: 0.2rem;
  top: 0.45rem;
  bottom: 0.45rem;
  width: 2px;
  border-radius: 1px;
  background: linear-gradient(to bottom, rgba(157, 184, 232, 0.4), rgba(74, 222, 128, 0.6));
}

/* A lone reported node (before the dig beat) has nothing below it to
   connect — hide the rail until a second node is showing. The :has check
   lights the rail only once the Fixed step is visible. */
.pulse-fix-dates:not(:has(.pulse-fix-step--fixed:not([hidden])))::before {
  display: none;
}

.pulse-fix-step {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 0 0 0.5rem;
  font-size: 0.82rem;
  font-variant-numeric: tabular-nums;
}

.pulse-fix-step:last-child {
  padding-bottom: 0;
}

.pulse-fix-step[hidden] {
  display: none;
}

/* Node: one dot per step, sitting on the rail in the LEFT GUTTER (never on
   the label). The node is absolute to its <li>, whose content starts at the
   ol's 1.1rem padding edge; the rail centre is 0.2rem from the ol's left, so
   relative to the li that is 0.2rem - 1.1rem = -0.9rem, and the 0.5rem dot
   centres on it at -0.9rem - 0.25rem ≈ -1.15rem (+1px to the rail's centre).
   Reported is neutral, Fixed + duration are green — the verified-fix colour. */
.pulse-fix-step-node {
  position: absolute;
  left: calc(-1.15rem + 1px);
  top: 0.32rem;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 9999px;
  background: #a9bcdd;
  box-shadow: 0 0 0 2px rgba(10, 14, 24, 0.9);
}

.pulse-fix-step--fixed .pulse-fix-step-node,
.pulse-fix-step--duration .pulse-fix-step-node {
  background: #4ade80;
  box-shadow: 0 0 8px rgba(74, 222, 128, 0.7), 0 0 0 2px rgba(10, 14, 24, 0.9);
}

.pulse-fix-step-label {
  font-size: 0.66rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #9db8e8;
}

.pulse-fix-step-value {
  color: #a9bcdd;
}

.pulse-fix-step--fixed .pulse-fix-step-value {
  color: #4ade80;
}

/* The duration node closes the rail: green + bold, the "fixed in N days"
   beat the founder asked for. */
.pulse-fix-step--duration .pulse-fix-step-value {
  color: #4ade80;
  font-weight: 600;
}

/* Crew credit, generic to match the truck name (the "Solvers"). A quiet
   line below the timeline, revealed with the finished fix. */
.pulse-fix-thanks {
  margin: 0.45rem 0 0;
  font-size: 0.78rem;
  color: #8fa2c4;
}

.pulse-fix-thanks[hidden] {
  display: none;
}

/* The card glides on its top/right/width when a show starts (the base card
   sits top-right with a 1.25rem margin; the slide pins it HARD into the very
   top-right corner and narrows it so the centre/left stage — where the truck
   and dig play — is never covered. The founder pushed back twice: the old
   slide kept a 0.5rem top gap AND a scale() transform whose origin shifted the
   painted box so it never actually reached the corner and still blocked the
   show. Now: zero top/right offset, a compact ~270px width, NO transform at
   all (the geometry IS the position — a scale only confuses where the box
   lands). The transition lives on the base card so the un-slide eases back to
   the resting corner too. */
.pulse-card {
  transition: top 0.5s ease, right 0.5s ease, width 0.5s ease;
}

.pulse-card--slid {
  top: 0; /* hard against the top edge */
  right: 0; /* flush against the viewport's right edge */
  width: min(17rem, calc(100% - 1rem)); /* ~270px: narrow enough the centre stays clear */
  padding: 0.85rem 1rem 0.95rem; /* a touch tighter than the resting card */
  border-top-left-radius: 0; /* it meets two edges: square the whole top-right corner */
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}

/* HOVER + slide: the resting walk card centres itself (top/left 50% + a
   translate) so the crosshair eye can't miss it — but that centring is exactly
   what the show must NOT have. A centred card sits dead over the truck and the
   dig, and the founder reads it as stuck: can't see the work, can't look past
   it. The slide above shares specificity with --walk and only won the props it
   names (top/right/width, by source order); it never touched --walk's left:50%
   or its transform, so in hover the card kept the centred geometry and merely
   narrowed — still planted centre-stage. This two-class rule outranks both
   single-class rules, so the corner pin wins IN HOVER TOO: auto left, no
   transform, flush into the top-right corner. Fonts drop back to the resting
   size so the corner panel reads compact, not the big centred catch card — the
   truck show owns the centre, the panel just narrates it from the corner. */
.pulse-card--walk.pulse-card--slid {
  top: 0;
  left: auto;
  right: 0;
  width: min(17rem, calc(100% - 1rem));
  transform: none;
}

.pulse-card--walk.pulse-card--slid .pulse-card-title {
  font-size: 1.1rem;
}

.pulse-card--walk.pulse-card--slid .pulse-card-meta,
.pulse-card--walk.pulse-card--slid .pulse-card-status,
.pulse-card--walk.pulse-card--slid .pulse-card-link {
  font-size: 0.9rem;
}

/* Reduced motion: the show jumps straight to the end state (no truck, no
   dust), so the card should not glide either — snap it into the corner. */
@media (prefers-reduced-motion: reduce) {
  .pulse-card {
    transition: none;
  }
}

/* --- story side panel --- */

.pulse-story-toggle {
  position: absolute;
  top: 1.25rem;
  left: 1.25rem;
  z-index: 5;
  padding: 0.4rem 0.9rem;
  border-radius: 9999px;
  border: 1px solid rgba(157, 184, 232, 0.35);
  background: rgba(10, 14, 24, 0.82);
  color: #c7d3ea;
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  cursor: pointer;
  backdrop-filter: blur(6px);
}

.pulse-story-toggle:hover {
  background: rgba(157, 184, 232, 0.12);
}

/* Replay odometer: reports visible at the current scrubber day, docked
   under the Story toggle. Inert (pointer-events none) so it never blocks
   canvas drags; the value ticks (scale keyframe) when the count changes. */
.pulse-odometer {
  position: absolute;
  top: 4rem;
  left: 1.25rem;
  z-index: 5;
  display: flex;
  flex-direction: column;
  padding: 0.55rem 0.9rem 0.6rem;
  border-radius: 0.75rem;
  background: rgba(10, 14, 24, 0.82);
  border: 1px solid rgba(157, 184, 232, 0.18);
  backdrop-filter: blur(6px);
  pointer-events: none;
}

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

.pulse-odometer-value {
  font-size: 1.9rem;
  font-weight: 700;
  line-height: 1.15;
  color: #f4f8ff;
  font-variant-numeric: tabular-nums;
  transform-origin: left center;
}

.pulse-odometer-value.is-tick {
  animation: pulse-odometer-tick 0.22s ease;
}

@keyframes pulse-odometer-tick {
  50% { transform: scale(1.07); }
}

.pulse-odometer-label {
  font-size: 0.7rem;
  letter-spacing: 0.05em;
  color: #8fa2c4;
}

@media (prefers-reduced-motion: reduce) {
  .pulse-odometer-value.is-tick { animation: none; }
}

.pulse-story {
  position: absolute;
  top: 1.25rem;
  right: 1.25rem;
  bottom: calc(7.5rem + env(safe-area-inset-bottom, 0px)); /* clear of the bottom bar at every width */
  z-index: 5;
  width: min(21rem, calc(100% - 2.5rem));
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 1rem 1.1rem;
  border-radius: 0.75rem;
  background: rgba(10, 14, 24, 0.82);
  border: 1px solid rgba(157, 184, 232, 0.18);
  backdrop-filter: blur(6px);
}

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

.pulse-story-header {
  margin-bottom: 0.75rem;
}

.pulse-story-title {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
  color: #e6edfa;
}

.pulse-story-subtitle {
  margin: 0.2rem 0 0;
  font-size: 0.75rem;
  color: #8fa2c4;
}

/* Timeline rail: a vertical line down the panel's left edge with one
   node per beat. ::before is the full track; ::after is the progress
   fill — its height comes from the --rail-fill custom property that
   story_panel.js sets to the current beat's node as the day advances. */
.pulse-story-beats {
  position: relative;
  margin: 0;
  padding: 0 0 0 1.5rem;
  list-style: none;
}

.pulse-story-beats::before,
.pulse-story-beats::after {
  content: "";
  position: absolute;
  left: 0.4rem;
  top: 0.9rem;
  width: 2px;
  border-radius: 1px;
}

.pulse-story-beats::before {
  bottom: 1.2rem;
  background: rgba(157, 184, 232, 0.18);
}

.pulse-story-beats::after {
  height: var(--rail-fill, 0px);
  background: linear-gradient(to bottom, rgba(255, 138, 107, 0.5), #ff6b4a);
  transition: height 0.4s ease;
}

.pulse-story-beat {
  position: relative;
  padding: 0.7rem 0.75rem;
  border-radius: 0.5rem;
  border: 1px solid transparent;
  margin-bottom: 0.5rem;
  opacity: 0.55;
  transition: opacity 0.3s ease, border-color 0.3s ease, background 0.3s ease,
    transform 0.3s ease, box-shadow 0.3s ease;
}

.pulse-story-beat[data-beat-day] {
  cursor: pointer;
}

.pulse-story-beat[data-beat-day]:hover {
  background: rgba(157, 184, 232, 0.08);
}

.pulse-story-beat.is-passed {
  opacity: 0.85;
}

/* Current beat: the card lifts off the panel and its node glows. */
.pulse-story-beat.is-current {
  opacity: 1;
  border-color: rgba(255, 107, 74, 0.35);
  background: rgba(255, 107, 74, 0.07);
  transform: translateY(-2px);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
}

.pulse-story-beat--ai,
.pulse-story-beat--finale {
  opacity: 0.85;
}

/* Node: one dot per beat, centred on the rail (the ol's 1.5rem padding
   minus the rail's 0.4rem offset, +1px to the line's centre). Future
   beats are hollow, passed beats fill in, the current one glows. */
.pulse-story-node {
  position: absolute;
  left: calc(-1.4rem + 1px);
  top: 0.85rem;
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 9999px;
  background: #0a0e18;
  border: 1.5px solid rgba(157, 184, 232, 0.35);
  transition: background 0.3s ease, border-color 0.3s ease,
    box-shadow 0.3s ease, transform 0.3s ease;
}

.pulse-story-beat.is-passed .pulse-story-node {
  background: rgba(157, 184, 232, 0.75);
  border-color: rgba(157, 184, 232, 0.75);
}

.pulse-story-beat.is-current .pulse-story-node {
  background: #ff6b4a;
  border-color: #ff8a6b;
  box-shadow: 0 0 10px rgba(255, 107, 74, 0.85);
  transform: scale(1.25);
}

.pulse-story-beat-date {
  margin: 0 0 0.15rem;
  font-size: 0.66rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #9db8e8;
  font-variant-numeric: tabular-nums;
}

.pulse-story-beat-title {
  margin: 0 0 0.2rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: #e6edfa;
}

.pulse-story-beat-body {
  margin: 0;
  font-size: 0.78rem;
  line-height: 1.45;
  color: #8fa2c4;
}

/* Receipt link on a beat: the press piece, post, or blog entry behind
   the chapter. Small and underlined so it reads as a citation, not a
   button. */
.pulse-story-beat-link {
  display: inline-block;
  margin-top: 0.4rem;
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  color: #9db8e8;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.pulse-story-beat-link:hover {
  color: #c9d9f5;
}

.pulse-story-beat-link::after {
  content: " \2197";
  font-size: 0.65rem;
}

/* "Jump to this day": tells the reader the dated chapters are clickable
   (the whole beat already is — this is the signpost). */
.pulse-story-beat-jump {
  display: inline-block;
  margin-top: 0.45rem;
  font-size: 0.7rem;
  letter-spacing: 0.03em;
  color: #9db8e8;
  opacity: 0.7;
}

.pulse-story-beat[data-beat-day]:hover .pulse-story-beat-jump {
  opacity: 1;
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* "See where it started": flies the camera to the origin report. Gold to
   match the origin marker's tint on the map. */
.pulse-story-origin-button {
  display: block;
  margin-top: 0.55rem;
  padding: 0.4rem 0.85rem;
  border-radius: 9999px;
  border: 1px solid rgba(255, 214, 140, 0.5);
  background: rgba(255, 214, 140, 0.1);
  color: #ffe3ad;
  font-size: 0.74rem;
  cursor: pointer;
}

.pulse-story-origin-button:hover {
  background: rgba(255, 214, 140, 0.2);
}

.pulse-story-origin-button[hidden] {
  display: none;
}

.pulse-story-counters,
.pulse-story-ai {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5rem;
  margin-top: 0.6rem;
}

.pulse-story-counters[hidden],
.pulse-story-ai[hidden] {
  display: none;
}

.pulse-story-stat {
  display: flex;
  flex-direction: column;
}

.pulse-story-stat-value {
  font-size: 1.05rem;
  font-weight: 700;
  color: #e6edfa;
  font-variant-numeric: tabular-nums;
}

.pulse-story-stat-label {
  font-size: 0.68rem;
  color: #8fa2c4;
}

.pulse-story-finale-button {
  margin-top: 0.6rem;
  padding: 0.45rem 0.9rem;
  border-radius: 9999px;
  border: 1px solid rgba(255, 107, 74, 0.55);
  background: rgba(255, 107, 74, 0.12);
  color: #ffb29e;
  font-size: 0.78rem;
  cursor: pointer;
}

.pulse-story-finale-button:hover {
  background: rgba(255, 107, 74, 0.22);
}

.pulse-story-teaser {
  margin-top: 0.75rem;
  padding: 0.7rem 0.75rem;
  border-radius: 0.5rem;
  border: 1px dashed rgba(157, 184, 232, 0.3);
}

.pulse-story-teaser p {
  margin: 0;
  font-size: 0.75rem;
  font-style: italic;
  color: #8fa2c4;
}

.pulse-story-hook {
  margin-top: 0.6rem;
  padding: 0.7rem 0.75rem;
}

.pulse-story-hook p {
  margin: 0;
  font-size: 0.75rem;
  color: #8fa2c4;
}

.pulse-story-hook a {
  color: #c3d2ee;
  text-decoration: underline;
}

/* --- "Recently solved" shortcut rows (curated featured list) --- */

/* Sits in the story panel beneath the beats: 2-3 compact rows, each a
   one-click jump to a verified-fixed report + its fix theater. Green
   family to match the "verified fixed" dot the rows celebrate. */
.pulse-story-solved {
  margin-top: 0.75rem;
  padding-top: 0.7rem;
  border-top: 1px solid rgba(157, 184, 232, 0.18);
}

.pulse-story-solved-title {
  margin: 0 0 0.45rem;
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #8fa2c4;
}

.pulse-story-solved-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.pulse-story-solved-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.4rem 0.6rem;
  border-radius: 0.5rem;
  border: 1px solid rgba(74, 222, 128, 0.3);
  background: rgba(74, 222, 128, 0.08);
  color: #d7f5e1;
  font-size: 0.74rem;
  text-align: left;
  cursor: pointer;
}

.pulse-story-solved-row:hover {
  background: rgba(74, 222, 128, 0.18);
}

.pulse-story-solved-dot {
  flex: none;
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 9999px;
  background: #4ade80;
  box-shadow: 0 0 6px rgba(74, 222, 128, 0.7);
}

.pulse-story-solved-label {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pulse-story-solved-go {
  flex: none;
  font-size: 0.66rem;
  letter-spacing: 0.02em;
  color: #8fe6ab;
  opacity: 0.85;
}

.pulse-story-solved-row:hover .pulse-story-solved-go {
  opacity: 1;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* --- campus demo boundary (G2) --- */

/* "See a campus example": gold like the dashed loop it draws, shaped
   like the origin button so the panel's affordances rhyme. */
.pulse-story-campus-button {
  display: block;
  margin-top: 0.55rem;
  padding: 0.4rem 0.85rem;
  border-radius: 9999px;
  border: 1px solid rgba(255, 214, 140, 0.5);
  background: rgba(255, 214, 140, 0.1);
  color: #ffe3ad;
  font-size: 0.74rem;
  cursor: pointer;
}

.pulse-story-campus-button:hover {
  background: rgba(255, 214, 140, 0.2);
}

.pulse-story-campus-button[hidden] {
  display: none;
}

/* The demo card: top-left, clear of the Story/Tour toggles above it and
   the story panel on the right. Gold border family to match the loop. */
.pulse-campus-card {
  position: absolute;
  top: 4.5rem;
  left: 1.25rem;
  z-index: 6;
  width: min(17rem, calc(100% - 2.5rem));
  padding: 1rem 1.25rem 1.1rem;
  border-radius: 0.75rem;
  background: rgba(24, 32, 50, 0.96);
  border: 1px solid rgba(255, 214, 140, 0.45);
  box-shadow: 0 0 24px rgba(255, 214, 140, 0.16), 0 8px 30px rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(6px);
}

.pulse-campus-card[hidden] {
  display: none;
}

.pulse-campus-title {
  margin: 0 0 0.6rem;
  padding-right: 1.4rem; /* clears the close button */
  font-size: 0.9rem;
  font-weight: 600;
  color: #ffe3ad;
}

.pulse-campus-stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5rem;
}

.pulse-campus-note {
  margin: 0.7rem 0 0;
  font-size: 0.75rem;
  color: #8fa2c4;
}

/* --- building dossier (K1) --- */

/* Same stage position as the catch card — they swap, never stack:
   picking a listed report closes the dossier and opens the card in its
   place. Gold border family to match the footprint outline. N3 widened
   the card to carry the doubled photo; on short stages it scrolls
   internally instead of running off the canvas. */
.pulse-dossier {
  position: absolute;
  top: 1.25rem;
  right: 1.25rem;
  z-index: 6;
  width: min(26rem, calc(100% - 2rem));
  max-height: calc(100% - 2.5rem);
  overflow-y: auto;
  padding: 1rem 1.25rem 1.1rem;
  border-radius: 0.75rem;
  background: rgba(24, 32, 50, 0.96);
  border: 1px solid rgba(255, 214, 140, 0.45);
  box-shadow: 0 0 24px rgba(255, 214, 140, 0.16), 0 8px 30px rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(6px);
}

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

.pulse-dossier-title {
  margin: 0 1.25rem 0.3rem 0; /* clears the close button */
  font-size: 1.05rem;
  font-weight: 600;
  color: #ffe3ad;
}

.pulse-dossier-line {
  margin: 0 0 0.75rem;
  font-size: 0.85rem;
  line-height: 1.45;
  color: #c7d3ea;
}

.pulse-dossier-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}

/* Landmark mode (L1) drops the height tile: the hidden tile leaves the
   grid and the two survivors split the row (data-tiles via dossier.js). */
.pulse-dossier-stats [hidden] {
  display: none;
}

.pulse-dossier-stats[data-tiles="2"] {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.pulse-dossier-bullets {
  margin: 0 0 0.6rem;
  padding-left: 1.1rem;
  font-size: 0.8rem;
  line-height: 1.5;
  color: #a9bcdd;
}

.pulse-dossier-bullets:empty {
  display: none;
}

/* O1 Nearby line: transit reach + cross streets, one quiet wayfinding
   sentence in the bullets' voice. */
.pulse-dossier-nearby {
  margin: 0 0 0.6rem;
  font-size: 0.8rem;
  line-height: 1.5;
  color: #a9bcdd;
}

.pulse-dossier-nearby[hidden] {
  display: none;
}

.pulse-dossier-reports {
  margin: 0 0 0.75rem;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.3rem;
}

.pulse-dossier-reports[hidden] {
  display: none;
}

.pulse-dossier-report {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.25rem 0.6rem;
  border-radius: 9999px;
  border: 1px solid rgba(157, 184, 232, 0.3);
  background: rgba(10, 14, 24, 0.55);
  color: #c7d3ea;
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}

.pulse-dossier-report:hover {
  background: rgba(157, 184, 232, 0.12);
  color: #f4f8ff;
}

/* Status dots reuse the catch card's status colours exactly. */
.pulse-dossier-dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 9999px;
  flex: none;
}

.pulse-dossier-dot[data-status="open"] {
  background: var(--pulse-status-open);
}

.pulse-dossier-dot[data-status="complete"] {
  background: var(--pulse-status-complete);
}

.pulse-dossier-dot[data-status="resolved"] {
  background: var(--pulse-status-resolved);
}

/* K2: enriched buildings open with a photo on top and a small source
   line under the summary. N3 doubled it (founder ask): full card width
   at a fixed 16/10 ratio (object-fit cover), so the card never jumps
   with the photo's own aspect; the slots stay [hidden] for buildings
   without media (K1 card unchanged). */
.pulse-dossier-photo {
  margin: 0 0 0.75rem;
  border-radius: 0.5rem;
  overflow: hidden;
  border: 1px solid rgba(255, 214, 140, 0.25);
}

.pulse-dossier-photo[hidden] {
  display: none;
}

.pulse-dossier-photo img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 10;
  height: auto;
  object-fit: cover;
}

.pulse-dossier-attribution {
  display: inline-block;
  margin: -0.35rem 0 0.75rem;
  font-size: 0.7rem;
  color: #8fa3c8;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.pulse-dossier-attribution[hidden] {
  display: none;
}

.pulse-dossier-attribution:hover {
  color: #c7d3ea;
}

/* N4 sponsor row: the live sponsored name on its building's dossier —
   gold family like the painted band, the whole row is the click target
   that opens the sponsor card. */
.pulse-dossier-sponsor {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  margin: 0.6rem 0 0.3rem;
  padding: 0.45rem 0.6rem;
  border-radius: 0.5rem;
  border: 1px solid rgba(255, 214, 140, 0.35);
  background: rgba(255, 214, 140, 0.08);
  color: #ffd68c;
  font-size: 0.8rem;
  text-align: left;
  cursor: pointer;
}

.pulse-dossier-sponsor:hover {
  background: rgba(255, 214, 140, 0.16);
  color: #ffe3ad;
}

.pulse-dossier-sponsor[hidden] {
  display: none;
}

.pulse-dossier-sponsor-logo {
  width: 1.4rem;
  height: 1.4rem;
  object-fit: contain;
  border-radius: 0.25rem;
  flex: none;
}

.pulse-dossier-sponsor-logo[hidden] {
  display: none;
}

.pulse-dossier-sponsor-name {
  font-weight: 600;
}

/* M1 name slot: one quiet gold line above the CTA and visually separate
   from it — every building and landmark dossier except live-sponsored
   buildings (the sponsor row above stands in there). */
.pulse-dossier-nameslot {
  display: block;
  margin: 0.6rem 0 0.3rem;
  font-size: 0.75rem;
  color: #ffd68c;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.pulse-dossier-nameslot:hover {
  color: #ffe3ad;
}

.pulse-dossier-nameslot[hidden] {
  display: none;
}

.pulse-dossier-cta {
  font-size: 0.9rem;
  color: #ffd68c;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.pulse-dossier-cta:hover {
  color: #ffe3ad;
}

/* Walk picks read centred, like pulse-card--walk: at street level the
   eyes sit on the crosshair, so a corner card goes unseen. The class
   stays until the card closes. */
.pulse-dossier--walk {
  top: 50%;
  left: 50%;
  right: auto;
  transform: translate(-50%, -50%);
  width: min(28rem, calc(100% - 2rem));
}

/* O1 hover dossier is info-only: the Wikipedia source line stays for
   attribution but stops acting like a link (the CTA and the name-slot
   link are hidden by the controller on the same rule). */
.pulse-dossier--walk .pulse-dossier-attribution {
  pointer-events: none;
  text-decoration: none;
}

/* Mobile: bottom sheet above the scrubber, collapsed by default (JS). */
@media (max-width: 767px) {
  .pulse-story {
    top: auto;
    left: 0.75rem;
    right: 0.75rem;
    bottom: calc(7rem + env(safe-area-inset-bottom, 0px));
    width: auto;
    max-height: 40vh;
    border-radius: 0.75rem 0.75rem 0.5rem 0.5rem;
  }
}

/* --- private-property card (M1, reworked from the L3b fork) --- */

/* The dossier CTA on a building opens this card, not the form: the
   city cannot act on private property, so there is no report path at
   all — a plain statement plus the owner invitation. Full-stage scrim
   with a centred card in the dossier's gold family, above the dossier
   and the finale so the walk variant's centred dossier never shows
   through. */
.pulse-fork {
  position: absolute;
  inset: 0;
  z-index: 8;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  background: rgba(5, 7, 13, 0.62);
}

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

.pulse-fork-card {
  position: relative; /* anchors the shared close button */
  width: min(26rem, 100%);
  max-height: calc(100% - 2rem);
  overflow-y: auto;
  padding: 1.4rem 1.5rem 1.5rem;
  border-radius: 0.75rem;
  background: rgba(10, 14, 24, 0.96);
  border: 1px solid rgba(255, 214, 140, 0.45);
  box-shadow: 0 0 24px rgba(255, 214, 140, 0.16), 0 8px 30px rgba(0, 0, 0, 0.5);
}

.pulse-fork-title {
  margin: 0 1.25rem 0.2rem 0; /* clears the close button */
  font-size: 1.05rem;
  font-weight: 600;
  color: #ffe3ad;
}

/* The statement: why there is nothing to send from here. */
.pulse-fork-text {
  margin: 0.35rem 0 0;
  font-size: 0.8rem;
  line-height: 1.5;
  color: #8fa2c4;
}

/* The invitation block, ruled off from the statement above it. */
.pulse-fork-path {
  margin-top: 0.95rem;
  padding-top: 0.85rem;
  border-top: 1px solid rgba(157, 184, 232, 0.18);
}

.pulse-fork-path-title {
  margin: 0 0 0.3rem;
  font-size: 0.9rem;
  font-weight: 600;
  color: #e6edfa;
}

.pulse-fork-path-text {
  margin: 0 0 0.75rem;
  font-size: 0.8rem;
  line-height: 1.5;
  color: #8fa2c4;
}

.pulse-fork-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.1rem;
}

.pulse-fork-link {
  font-size: 0.85rem;
  color: #ffd68c;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.pulse-fork-link:hover {
  color: #ffe3ad;
}

/* --- sponsor card (N4): the small card a painted name opens --- */

/* The fork's layering exactly (scrim at z 8, over the dossier and the
   walk variant's centred card): Esc peels just this layer, a backdrop
   click closes only it, the dossier stays underneath. */
.pulse-sponsor {
  position: absolute;
  inset: 0;
  z-index: 8;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  background: rgba(5, 7, 13, 0.62);
}

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

.pulse-sponsor-card {
  position: relative; /* anchors the shared close button */
  width: min(22rem, 100%);
  max-height: calc(100% - 2rem);
  overflow-y: auto;
  padding: 1.4rem 1.5rem 1.5rem;
  border-radius: 0.75rem;
  background: rgba(10, 14, 24, 0.96);
  border: 1px solid rgba(255, 214, 140, 0.45);
  box-shadow: 0 0 24px rgba(255, 214, 140, 0.16), 0 8px 30px rgba(0, 0, 0, 0.5);
  text-align: center;
}

/* The partner's logo never crops: a roomy square box, the image
   contained whole inside it, air above and below. The card's max-height
   scrolls if it ever runs out of room, so growing here is safe in BOTH
   the night and day themes (no day override exists).
   Founder fix: the night card is dark and a dark logo vanished on it, so
   the logo now sits on a subtle light rounded plate — cream, gently
   opaque, padded, a small radius (small enough that a circle mark or wide
   wordmark still reads whole under object-fit: contain). Tasteful, not a
   glaring white box. The plate is set HERE so it applies in night by
   default; the day override below softens it for the cream card. */
.pulse-sponsor-logo {
  display: block;
  width: 6rem;
  height: 6rem;
  margin: 0.5rem auto 0.85rem;
  padding: 0.5rem;
  box-sizing: border-box;
  object-fit: contain;
  background: rgba(247, 243, 234, 0.9); /* cream, ~90% opaque */
  border-radius: 0.6rem;
}

.pulse-sponsor-logo[hidden] {
  display: none;
}

/* The display name is the card: big, in the band's gold. */
.pulse-sponsor-name {
  margin: 0 1.25rem 0.2rem; /* clears the close button */
  font-size: 1.3rem;
  font-weight: 600;
  color: #ffe3ad;
}

.pulse-sponsor-text {
  margin: 0.35rem 0 0;
  font-size: 0.8rem;
  line-height: 1.5;
  color: #8fa2c4;
}

.pulse-sponsor-link {
  display: inline-block;
  margin-top: 0.75rem;
  font-size: 0.85rem;
  color: #ffd68c;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.pulse-sponsor-link:hover {
  color: #ffe3ad;
}

.pulse-sponsor-link[hidden] {
  display: none;
}

/* --- name-a-building card (N4): the name slot's in-page answer --- */

/* Same layer and family as the fork/sponsor cards. */
.pulse-namecard {
  position: absolute;
  inset: 0;
  z-index: 8;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  background: rgba(5, 7, 13, 0.62);
}

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

.pulse-namecard-card {
  position: relative; /* anchors the shared close button */
  width: min(26rem, 100%);
  max-height: calc(100% - 2rem);
  overflow-y: auto;
  padding: 1.4rem 1.5rem 1.5rem;
  border-radius: 0.75rem;
  background: rgba(10, 14, 24, 0.96);
  border: 1px solid rgba(255, 214, 140, 0.45);
  box-shadow: 0 0 24px rgba(255, 214, 140, 0.16), 0 8px 30px rgba(0, 0, 0, 0.5);
}

.pulse-namecard-title {
  margin: 0 1.25rem 0.2rem 0; /* clears the close button */
  font-size: 1.05rem;
  font-weight: 600;
  color: #ffe3ad;
}

.pulse-namecard-text {
  margin: 0.35rem 0 0;
  font-size: 0.8rem;
  line-height: 1.5;
  color: #8fa2c4;
}

.pulse-namecard-examples-title {
  margin: 0.95rem 0 0.3rem;
  padding-top: 0.85rem;
  border-top: 1px solid rgba(157, 184, 232, 0.18);
  font-size: 0.9rem;
  font-weight: 600;
  color: #e6edfa;
}

.pulse-namecard-examples {
  margin: 0 0 0.75rem;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.pulse-namecard-link,
.pulse-namecard-cta {
  font-size: 0.85rem;
  color: #ffd68c;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.pulse-namecard-link:hover,
.pulse-namecard-cta:hover {
  color: #ffe3ad;
}

/* --- tour (Phase F) --- */

/* Docked beside the Story toggle; shares its pill styling. The left
   offset clears "Story" at its widest rendering. */
.pulse-tour-toggle {
  left: 5.4rem;
}

.pulse-tour-toggle[hidden] {
  display: none;
}

.pulse-tour-toggle.is-active {
  color: #ffb29e;
  border-color: rgba(255, 107, 74, 0.65);
  background: rgba(255, 107, 74, 0.14);
}

/* Step caption: lower third, above the bottom bar (z 4), below the card
   (z 6). Inert except the Skip button. The hold animation is the tour's
   step clock (tour.js advances on animationend) — opacity only, so it
   keeps running under prefers-reduced-motion and the clock never stalls. */
.pulse-tour-caption {
  position: absolute;
  left: 50%;
  bottom: calc(8.5rem + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: 5;
  width: min(26rem, calc(100% - 2rem));
  padding: 0.9rem 1.1rem;
  text-align: center;
  border-radius: 0.75rem;
  background: rgba(10, 14, 24, 0.88);
  border: 1px solid rgba(157, 184, 232, 0.3);
  backdrop-filter: blur(6px);
  pointer-events: none;
}

.pulse-tour-caption[hidden] {
  display: none;
}

.pulse-tour-caption.is-holding {
  animation: pulse-tour-hold 6s linear; /* the ~6 s per-step dwell */
}

@keyframes pulse-tour-hold {
  0% { opacity: 0.6; }
  12%, 100% { opacity: 1; }
}

.pulse-tour-caption-title {
  margin: 0 0 0.25rem;
  font-size: 0.95rem;
  font-weight: 600;
  color: #e6edfa;
}

.pulse-tour-caption-body {
  margin: 0;
  font-size: 0.8rem;
  line-height: 1.45;
  color: #8fa2c4;
}

.pulse-tour-skip {
  margin-top: 0.55rem;
  padding: 0.25rem 0.8rem;
  border-radius: 9999px;
  border: 1px solid rgba(157, 184, 232, 0.35);
  background: transparent;
  color: #9db8e8;
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  cursor: pointer;
  pointer-events: auto;
}

.pulse-tour-skip:hover {
  background: rgba(157, 184, 232, 0.12);
  color: #e6edfa;
}

/* --- postcard toast (Phase F) --- */

/* Top centre, under the city pills. Lifetime is the animation itself:
   animationend hides it (controller hideToast). Opacity only, so it
   still completes under prefers-reduced-motion. */
.pulse-toast {
  position: absolute;
  top: 4.4rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 6;
  margin: 0;
  padding: 0.45rem 1.1rem;
  border-radius: 9999px;
  background: rgba(10, 14, 24, 0.9);
  border: 1px solid rgba(157, 184, 232, 0.35);
  color: #e6edfa;
  font-size: 0.78rem;
  letter-spacing: 0.03em;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
}

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

.pulse-toast.is-live {
  animation: pulse-toast-life 2.4s ease;
}

@keyframes pulse-toast-life {
  0% { opacity: 0; }
  10%, 80% { opacity: 1; }
  100% { opacity: 0; }
}

/* --- finale CTA overlay --- */

/* Card sits in the lower third so the pulled-back city (FINALE_FACTOR in
   camera_rig.js) stays visible as a glowing organism above it. The scrim
   is bottom-weighted for the same reason: legible card, undimmed city. */
.pulse-finale {
  position: absolute;
  inset: 0;
  z-index: 7;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding-bottom: calc(7vh + env(safe-area-inset-bottom, 0px));
  background: linear-gradient(
    to top,
    rgba(5, 7, 13, 0.82) 0%,
    rgba(5, 7, 13, 0.4) 34%,
    rgba(5, 7, 13, 0) 62%
  );
}

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

.pulse-finale-card {
  max-width: 24rem;
  padding: 2rem 1.75rem;
  text-align: center;
  border-radius: 0.75rem;
  background: rgba(10, 14, 24, 0.92);
  border: 1px solid rgba(157, 184, 232, 0.25);
}

.pulse-finale-title {
  margin: 0 0 0.5rem;
  font-size: 1.2rem;
  font-weight: 600;
  color: #e6edfa;
}

.pulse-finale-text {
  margin: 0 0 1.25rem;
  font-size: 0.875rem;
  line-height: 1.5;
  color: #8fa2c4;
}

.pulse-finale-cta {
  display: inline-block;
  padding: 0.55rem 1.4rem;
  border-radius: 9999px;
  background: #ff6b4a;
  color: #14060a;
  font-size: 0.875rem;
  font-weight: 600;
  text-decoration: none;
}

.pulse-finale-cta:hover {
  background: #ff8a6b;
}

.pulse-finale-close {
  display: block;
  margin: 0.9rem auto 0;
  border: none;
  background: transparent;
  color: #9db8e8;
  font-size: 0.8rem;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.pulse-finale-close:hover {
  color: #c3d4f2;
}

/* --- embeddable pulse (/pulse/embed, iframe build) --- */

/* Attribution pill: the one way out of the iframe (target _top). Bottom
   right, above the canvas, below the loading overlay. */
.pulse-embed-attribution {
  position: absolute;
  right: 0.75rem;
  bottom: calc(0.75rem + env(safe-area-inset-bottom, 0px));
  z-index: 5;
  padding: 0.3rem 0.8rem;
  border-radius: 9999px;
  background: rgba(10, 14, 24, 0.82);
  border: 1px solid rgba(157, 184, 232, 0.18);
  color: #c7d3ea;
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  text-decoration: none;
  white-space: nowrap;
}

.pulse-embed-attribution:hover {
  color: #e6edfa;
  background: rgba(157, 184, 232, 0.12);
}

/* Compact scrubber: the offered snippet is 800x450, so the timeline
   hugs the bottom-left and leaves the corner to the attribution. The
   min-width floors relax — a small iframe must never overflow. */
.pulse-stage--embed .pulse-bottom {
  left: 0.75rem;
  bottom: calc(0.75rem + env(safe-area-inset-bottom, 0px));
  transform: none;
  width: calc(100% - 11rem);
}

.pulse-stage--embed .pulse-timeline {
  min-width: 0;
  gap: 0.45rem;
  padding: 0.35rem 0.75rem;
}

.pulse-stage--embed .pulse-scrubber {
  min-width: 4rem;
}

.pulse-stage--embed .pulse-date {
  min-width: 0;
}

/* --- presentation stage mode (?present=1, the projector show) --- */

/* Chrome hides behind this ONE class; the hidden attributes underneath
   stay owned by their features, so removing the class (Esc) restores
   the page exactly. .pulse-story-toggle covers the Tour pill too (it
   shares the class); the walk/zoom/share pills live inside
   .pulse-bottom. Captions stay — only their Skip button goes. The
   finale CTA overlay never belongs on a projector loop. */
.pulse-stage--present .pulse-cities,
.pulse-stage--present .pulse-story-toggle,
.pulse-stage--present .pulse-story,
.pulse-stage--present .pulse-odometer,
.pulse-stage--present .pulse-bottom,
.pulse-stage--present .pulse-minimap,
.pulse-stage--present .pulse-finale,
.pulse-stage--present .pulse-tour-skip {
  display: none;
}

/* Cursor auto-hide: the controller adds is-cursor-idle after 3 s of
   pointer silence; !important so the canvas's inline hover cursor
   (style.cursor = "pointer") can't bring it back mid-show. */
.pulse-stage--present.is-cursor-idle,
.pulse-stage--present.is-cursor-idle * {
  cursor: none !important;
}

/* The giant odometer: centred top, inert, chrome z level (5). Sized by
   the viewport so it reads from the back row of any hall. */
.pulse-present-odometer {
  position: absolute;
  top: 1.6rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  pointer-events: none;
}

.pulse-present-odometer[hidden] {
  display: none;
}

.pulse-present-odometer-value {
  font-size: clamp(4rem, 11vw, 9rem);
  font-weight: 700;
  line-height: 1;
  color: #f4f8ff;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 0 40px rgba(157, 184, 232, 0.35);
}

.pulse-present-odometer-label {
  margin-top: 0.35rem;
  font-size: clamp(0.95rem, 2vw, 1.5rem);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #c7d3ea;
}

.pulse-present-odometer-brand {
  margin-top: 0.25rem;
  font-size: clamp(0.75rem, 1.3vw, 1rem);
  letter-spacing: 0.1em;
  color: #8fa2c4;
}

/* Live arrival mid-show: the number flashes the live layer's gold.
   Colour + glow only (no transform), so it stays calm under
   prefers-reduced-motion without a special case. */
.pulse-present-odometer.is-flash .pulse-present-odometer-value {
  animation: pulse-present-flash 1.4s ease;
}

@keyframes pulse-present-flash {
  25% {
    color: #ffd68c;
    text-shadow: 0 0 60px rgba(255, 214, 140, 0.75);
  }
}

/* One-time sound hint: lifetime is the animation (the toast pattern —
   animationend hides it). Opacity only, so the clock still completes
   under prefers-reduced-motion and the hint can never get stuck. */
.pulse-present-hint {
  position: absolute;
  bottom: calc(2.5rem + env(safe-area-inset-bottom, 0px));
  left: 50%;
  transform: translateX(-50%);
  z-index: 5;
  margin: 0;
  padding: 0.45rem 1.1rem;
  border-radius: 9999px;
  background: rgba(10, 14, 24, 0.9);
  border: 1px solid rgba(157, 184, 232, 0.35);
  color: #c7d3ea;
  font-size: 0.85rem;
  letter-spacing: 0.04em;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
}

.pulse-present-hint[hidden] {
  display: none;
}

.pulse-present-hint.is-live {
  animation: pulse-present-hint-life 8s ease;
}

@keyframes pulse-present-hint-life {
  0% { opacity: 0; }
  5%, 85% { opacity: 1; }
  100% { opacity: 0; }
}

/* --- day theme (N2, golden hour) --------------------------------------------

   Replaces the L2/L5 "Light" paper map (founder: "too white, really
   bright, not good with 3D") with an illustrated golden-hour look: warm
   pale-gold page, cream panels, warm ink text, and the same Okabe-Ito
   colorblind-safe status palette (swapped via the variables at the top,
   so the legend + cards + scrubber follow automatically). The page
   colour matches the canvas clear (pulse_controller THEMES.day).
   Scoped under data-pulse-theme="day", which the controller writes on
   the stage in applyTheme — removing the attribute restores the night
   chrome with zero per-rule cleanup. */

.pulse-stage[data-pulse-theme="day"] {
  --pulse-status-open: #e69f00;
  --pulse-status-complete: #56b4e9;
  --pulse-status-resolved: #009e73;
  background: #e8ddc8;
  color: #3b3328;
}

/* Pills + panels: the shared dark-glass surfaces flip to warm cream
   glass with warm-ink borders. One selector list per surface family
   keeps this block scannable. */
.pulse-stage[data-pulse-theme="day"] .pulse-cities,
.pulse-stage[data-pulse-theme="day"] .pulse-zoom,
.pulse-stage[data-pulse-theme="day"] .pulse-timeline,
.pulse-stage[data-pulse-theme="day"] .pulse-walk-hud,
.pulse-stage[data-pulse-theme="day"] .pulse-walk-hint,
.pulse-stage[data-pulse-theme="day"] .pulse-walk-prompt,
.pulse-stage[data-pulse-theme="day"] .pulse-present-hint,
.pulse-stage[data-pulse-theme="day"] .pulse-legend-list {
  background: rgba(255, 250, 240, 0.88);
  border-color: rgba(59, 51, 40, 0.22);
  color: #3b3328;
}

.pulse-stage[data-pulse-theme="day"] .pulse-cities-link {
  color: #6e6250;
}

.pulse-stage[data-pulse-theme="day"] .pulse-cities-link:hover,
.pulse-stage[data-pulse-theme="day"] .pulse-cities-link.is-current {
  color: #211a10;
  background: rgba(59, 51, 40, 0.1);
}

.pulse-stage[data-pulse-theme="day"] .pulse-zoom-button,
.pulse-stage[data-pulse-theme="day"] .pulse-play,
.pulse-stage[data-pulse-theme="day"] .pulse-legend-summary {
  border-color: rgba(59, 51, 40, 0.35);
  color: #3b3328;
}

.pulse-stage[data-pulse-theme="day"] .pulse-zoom-button:hover,
.pulse-stage[data-pulse-theme="day"] .pulse-play:hover,
.pulse-stage[data-pulse-theme="day"] .pulse-legend-summary:hover,
.pulse-stage[data-pulse-theme="day"] .pulse-legend[open] .pulse-legend-summary {
  background: rgba(59, 51, 40, 0.08);
  color: #261f15;
}

.pulse-stage[data-pulse-theme="day"] .pulse-zoom-button--mode.is-active {
  color: #8a5a00;
  border-color: rgba(230, 159, 0, 0.65);
  background: rgba(230, 159, 0, 0.14);
}

.pulse-stage[data-pulse-theme="day"] .pulse-ward-select,
.pulse-stage[data-pulse-theme="day"] .pulse-status-select {
  background: rgba(255, 250, 240, 0.88);
  border-color: rgba(59, 51, 40, 0.35);
  color: #3b3328;
}

.pulse-stage[data-pulse-theme="day"] .pulse-ward-select option,
.pulse-stage[data-pulse-theme="day"] .pulse-status-select option {
  background: #fffaf0;
  color: #3b3328;
}

.pulse-stage[data-pulse-theme="day"] .pulse-status-caption,
.pulse-stage[data-pulse-theme="day"] .pulse-date,
.pulse-stage[data-pulse-theme="day"] .pulse-legend-row {
  color: #6e6250;
}

/* Live stays "live", but pale gold dies on the warm page — deepen to amber. */
.pulse-stage[data-pulse-theme="day"] .pulse-live {
  color: #8a5a00;
}

.pulse-stage[data-pulse-theme="day"] .pulse-live-dot {
  background: #b8860b;
  box-shadow: 0 0 6px rgba(184, 134, 11, 0.5);
}

/* Cards: report card, campus card, dossier, CTA fork, tour caption, finale. */
.pulse-stage[data-pulse-theme="day"] .pulse-card,
.pulse-stage[data-pulse-theme="day"] .pulse-campus-card,
.pulse-stage[data-pulse-theme="day"] .pulse-dossier,
.pulse-stage[data-pulse-theme="day"] .pulse-fork-card,
.pulse-stage[data-pulse-theme="day"] .pulse-sponsor-card,
.pulse-stage[data-pulse-theme="day"] .pulse-namecard-card,
.pulse-stage[data-pulse-theme="day"] .pulse-tour-caption,
.pulse-stage[data-pulse-theme="day"] .pulse-finale-card,
.pulse-stage[data-pulse-theme="day"] .pulse-story,
.pulse-stage[data-pulse-theme="day"] .pulse-odometer,
.pulse-stage[data-pulse-theme="day"] .pulse-story-toggle,
.pulse-stage[data-pulse-theme="day"] .pulse-tooltip,
.pulse-stage[data-pulse-theme="day"] .pulse-toast,
.pulse-stage[data-pulse-theme="day"] .pulse-crosshair-label {
  background: rgba(255, 250, 240, 0.93);
  border-color: rgba(59, 51, 40, 0.25);
  color: #3b3328;
}

.pulse-stage[data-pulse-theme="day"] .pulse-card-title,
.pulse-stage[data-pulse-theme="day"] .pulse-campus-title,
.pulse-stage[data-pulse-theme="day"] .pulse-dossier-title,
.pulse-stage[data-pulse-theme="day"] .pulse-fork-title,
.pulse-stage[data-pulse-theme="day"] .pulse-fork-path-title,
.pulse-stage[data-pulse-theme="day"] .pulse-sponsor-name,
.pulse-stage[data-pulse-theme="day"] .pulse-namecard-title,
.pulse-stage[data-pulse-theme="day"] .pulse-namecard-examples-title,
.pulse-stage[data-pulse-theme="day"] .pulse-story-title,
.pulse-stage[data-pulse-theme="day"] .pulse-story-beat-title,
.pulse-stage[data-pulse-theme="day"] .pulse-tour-caption-title,
.pulse-stage[data-pulse-theme="day"] .pulse-finale-title,
.pulse-stage[data-pulse-theme="day"] .pulse-odometer-value,
.pulse-stage[data-pulse-theme="day"] .pulse-story-stat-value,
.pulse-stage[data-pulse-theme="day"] .pulse-walk-hud-ward {
  color: #261f15;
}

/* Day logo plate: the day card is itself cream, so the night plate would
   be cream-on-cream and vanish. A dark logo already reads on the cream
   card, so here the plate is near-clear with just a hairline frame —
   enough to seat the logo as a tile, never a glaring box. */
.pulse-stage[data-pulse-theme="day"] .pulse-sponsor-logo {
  background: rgba(255, 255, 255, 0.5);
  border: 1px solid rgba(59, 51, 40, 0.18);
}

.pulse-stage[data-pulse-theme="day"] .pulse-card-meta,
.pulse-stage[data-pulse-theme="day"] .pulse-dossier-line,
.pulse-stage[data-pulse-theme="day"] .pulse-dossier-bullets,
.pulse-stage[data-pulse-theme="day"] .pulse-dossier-nearby,
.pulse-stage[data-pulse-theme="day"] .pulse-fork-text,
.pulse-stage[data-pulse-theme="day"] .pulse-fork-path-text,
.pulse-stage[data-pulse-theme="day"] .pulse-sponsor-text,
.pulse-stage[data-pulse-theme="day"] .pulse-namecard-text,
.pulse-stage[data-pulse-theme="day"] .pulse-story-subtitle,
.pulse-stage[data-pulse-theme="day"] .pulse-story-beat-body,
.pulse-stage[data-pulse-theme="day"] .pulse-story-beat-date,
.pulse-stage[data-pulse-theme="day"] .pulse-story-stat-label,
.pulse-stage[data-pulse-theme="day"] .pulse-story-teaser p,
.pulse-stage[data-pulse-theme="day"] .pulse-story-hook p,
.pulse-stage[data-pulse-theme="day"] .pulse-story-solved-title,
.pulse-stage[data-pulse-theme="day"] .pulse-tour-caption-body,
.pulse-stage[data-pulse-theme="day"] .pulse-finale-text,
.pulse-stage[data-pulse-theme="day"] .pulse-campus-note,
.pulse-stage[data-pulse-theme="day"] .pulse-odometer-label {
  color: #6e6250;
}

.pulse-stage[data-pulse-theme="day"] .pulse-card-link,
.pulse-stage[data-pulse-theme="day"] .pulse-dossier-attribution,
.pulse-stage[data-pulse-theme="day"] .pulse-story-hook a,
.pulse-stage[data-pulse-theme="day"] .pulse-overlay-link,
.pulse-stage[data-pulse-theme="day"] .pulse-story-beat-link {
  color: #1f5d99;
}

/* Recently-solved rows on cream: a darker green ink + a denser fill so
   the curated shortcuts stay legible in the day theme. */
.pulse-stage[data-pulse-theme="day"] .pulse-story-solved-row {
  border-color: rgba(34, 139, 87, 0.5);
  background: rgba(34, 139, 87, 0.12);
  color: #1f6b46;
}

.pulse-stage[data-pulse-theme="day"] .pulse-story-solved-go {
  color: #2f7d55;
}

/* Status text on the catch card: ink-leaning Okabe-Ito so it stays
   readable as TEXT on cream (the raw sky blue is too pale for type). */
.pulse-stage[data-pulse-theme="day"] .pulse-card-status[data-status="open"] {
  color: #9c6c00;
}

.pulse-stage[data-pulse-theme="day"] .pulse-card-status[data-status="complete"] {
  color: #20709e;
}

.pulse-stage[data-pulse-theme="day"] .pulse-card-status[data-status="resolved"] {
  color: #00684c;
}

/* The close hint stays the quietest line on the cream cards too. */
.pulse-stage[data-pulse-theme="day"] .pulse-card-hint {
  color: #94886f;
}

/* Legend + dossier dots gain the darker outline the spec asks for —
   a pale Okabe-Ito dot needs an ink rim to read on the warm page. */
.pulse-stage[data-pulse-theme="day"] .pulse-legend-dot,
.pulse-stage[data-pulse-theme="day"] .pulse-dossier-dot {
  box-shadow: inset 0 0 0 1px rgba(38, 31, 21, 0.6);
}

/* Wayfinding labels: warm ink text, pale-gold halo instead of the night shadow. */
.pulse-stage[data-pulse-theme="day"] .pulse-street-label {
  color: rgba(59, 51, 40, 0.85);
  text-shadow: 0 1px 3px rgba(232, 221, 200, 0.95), 0 0 2px rgba(232, 221, 200, 0.95);
}

.pulse-stage[data-pulse-theme="day"] .pulse-street-label--walk {
  background: rgba(255, 250, 240, 0.88);
  border-color: rgba(59, 51, 40, 0.25);
  color: #3b3328;
  text-shadow: none;
}

/* Building-name pills follow the landmarks' amber-on-cream treatment,
   hover included (the night hover's pale gold dies on cream). */
.pulse-stage[data-pulse-theme="day"] .pulse-building-label {
  background: rgba(255, 250, 240, 0.9);
  border-color: rgba(138, 90, 0, 0.45);
  color: #6d4f00;
}

.pulse-stage[data-pulse-theme="day"] .pulse-building-label--link:hover {
  color: #3f2e00;
  border-color: rgba(138, 90, 0, 0.7);
}

.pulse-stage[data-pulse-theme="day"] .pulse-landmark {
  color: #6d4f00;
  text-shadow: 0 1px 3px rgba(232, 221, 200, 0.95);
}

.pulse-stage[data-pulse-theme="day"] .pulse-landmark--link:hover {
  color: #3f2e00;
}

.pulse-stage[data-pulse-theme="day"] .pulse-landmark-glyph {
  background: rgba(184, 134, 11, 0.95);
  box-shadow: 0 0 4px rgba(184, 134, 11, 0.45);
}

/* Loading + fallback overlays follow the warm page. */
.pulse-stage[data-pulse-theme="day"] .pulse-overlay {
  background: rgba(232, 221, 200, 0.65);
}

.pulse-stage[data-pulse-theme="day"] .pulse-overlay-title {
  color: #261f15;
}

.pulse-stage[data-pulse-theme="day"] .pulse-overlay-text {
  color: #6e6250;
}

/* Story-panel action pills: pale salmon/gold text dies on cream — they
   take ink-leaning fills with readable type in the day theme. */
.pulse-stage[data-pulse-theme="day"] .pulse-story-finale-button {
  border-color: rgba(156, 108, 0, 0.55);
  background: rgba(230, 159, 0, 0.14);
  color: #7a5500;
}

.pulse-stage[data-pulse-theme="day"] .pulse-story-origin-button,
.pulse-stage[data-pulse-theme="day"] .pulse-story-campus-button {
  border-color: rgba(138, 90, 0, 0.5);
  background: rgba(184, 134, 11, 0.1);
  color: #6d4f00;
}

.pulse-stage[data-pulse-theme="day"] .pulse-finale-cta {
  background: var(--pulse-status-open);
  color: #14110a;
}

/* The fork scrim follows the warm page, like the loading overlay; pale
   gold links — fork, name slot, report CTA — deepen to amber for
   contrast on cream. */
.pulse-stage[data-pulse-theme="day"] .pulse-fork,
.pulse-stage[data-pulse-theme="day"] .pulse-sponsor,
.pulse-stage[data-pulse-theme="day"] .pulse-namecard {
  background: rgba(232, 221, 200, 0.65);
}

.pulse-stage[data-pulse-theme="day"] .pulse-fork-link,
.pulse-stage[data-pulse-theme="day"] .pulse-dossier-nameslot,
.pulse-stage[data-pulse-theme="day"] .pulse-sponsor-link,
.pulse-stage[data-pulse-theme="day"] .pulse-namecard-link,
.pulse-stage[data-pulse-theme="day"] .pulse-namecard-cta,
.pulse-stage[data-pulse-theme="day"] .pulse-dossier-cta {
  color: #8a5a00;
}

.pulse-stage[data-pulse-theme="day"] .pulse-fork-link:hover,
.pulse-stage[data-pulse-theme="day"] .pulse-sponsor-link:hover,
.pulse-stage[data-pulse-theme="day"] .pulse-namecard-link:hover,
.pulse-stage[data-pulse-theme="day"] .pulse-namecard-cta:hover,
.pulse-stage[data-pulse-theme="day"] .pulse-dossier-cta:hover {
  color: #5c3c00;
}

/* N4 sponsor row on the cream dossier: amber rim + ink-leaning gold. */
.pulse-stage[data-pulse-theme="day"] .pulse-dossier-sponsor {
  border-color: rgba(138, 90, 0, 0.45);
  background: rgba(184, 134, 11, 0.1);
  color: #6d4f00;
}

.pulse-stage[data-pulse-theme="day"] .pulse-dossier-sponsor:hover {
  background: rgba(184, 134, 11, 0.18);
  color: #3f2e00;
}
