/* The command centre shell.
   ---------------------------------------------------------------------------
   Dark throughout, on purpose. Radar Mode used to mean "dark page", which left
   nothing for it to mean once the page was already dark — it now means what it
   sounds like: dark tiles, neon pins and the sweep.

   No green anywhere, in any state. Green reads as "all clear", and an area with
   no reports is an area nobody has reported. */

:root {
  --ground: #080b12;
  --shell: #0e131d;
  --panel: rgba(20, 27, 40, 0.72);
  --line: rgba(120, 150, 200, 0.16);
  --edge: rgba(120, 150, 200, 0.3);
  --ink: #e9eef8;
  --muted: #8b9ab5;

  --fresh: #ff3b30;
  --stale: #ffcc00;
  --cool: #2b6cff;
  --warm: #ff9500;
  --glow: none;

  --command-h: 62px;
  --pulse-h: 3px;
  --dock-h: 50px;
  --time-h: 54px;
  --side-w: 292px;

  /* What the map is glowing and how fast the pulse runs. Both follow the same
     national reading, set once on <body>. */
  --accent: var(--cool);
  --pulse-ms: 4200ms;
}

body[data-level="rising"] {
  --accent: var(--stale);
  --pulse-ms: 2400ms;
}

body[data-level="hot"] {
  --accent: var(--fresh);
  --pulse-ms: 1200ms;
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  height: 100%;
  background: var(--ground);
  color: var(--ink);
  font: 15px/1.4 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
}

body {
  display: grid;
  grid-template-columns: var(--side-w) 1fr;
  grid-template-rows: var(--command-h) var(--pulse-h) var(--dock-h) 1fr var(--time-h);
  grid-template-areas:
    "command command"
    "pulse   pulse"
    "dock    dock"
    "side    screen"
    "time    time";
  height: 100dvh;
  overflow: hidden;
}

/* --- 1. command bar --------------------------------------------------------- */

.command {
  grid-area: command;
  display: grid;
  /* Three tracks rather than flex, so the title keeps its room whatever the
     readout in the middle grows to say. */
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 16px;
  padding: 0 20px;
  background: linear-gradient(180deg, #101724 0%, var(--shell) 100%);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
  position: relative;
  z-index: 30;
}

.brand {
  margin: 0;
  font-size: 20px;
  font-weight: 700;
  letter-spacing: 0.01em;
  /* Never clipped, whatever else is on the row. */
  white-space: nowrap;
  text-shadow: 0 0 22px rgba(255, 59, 48, 0.28);
}

.readout {
  margin: 0;
  justify-self: center;
  font-size: 13px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

.command-right {
  justify-self: end;
  display: flex;
  align-items: center;
  gap: 8px;
}

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 10px;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, 0.03);
  color: var(--ink);
  cursor: pointer;
  transition: border-color 160ms ease, background 160ms ease, transform 160ms ease;
}

.icon-btn:hover {
  border-color: var(--edge);
  background: rgba(255, 255, 255, 0.07);
}

.icon-btn:active {
  transform: scale(0.94);
}

.icon-btn svg {
  width: 19px;
  height: 19px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* --- 8. national pulse ------------------------------------------------------ */

.pulse {
  grid-area: pulse;
  position: relative;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.04);
  border-bottom: 1px solid var(--line);
}

/* One element sweeping across. Transform only, so it runs on the compositor and
   the speed is the only thing the activity level changes. */
.pulse-run {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--accent) 45%,
    var(--accent) 55%,
    transparent 100%
  );
  opacity: 0.85;
  animation: pulse-run var(--pulse-ms) linear infinite;
  will-change: transform;
}

@keyframes pulse-run {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(100%);
  }
}

/* --- 2. mode dock ----------------------------------------------------------- */

.dock {
  grid-area: dock;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 0 16px;
  background: var(--shell);
  border-bottom: 1px solid var(--line);
}

.mode {
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  padding: 8px 16px;
  border-radius: 9px;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, 0.03);
  color: var(--muted);
  cursor: pointer;
  transition: color 180ms ease, border-color 180ms ease, box-shadow 220ms ease,
    transform 160ms ease, background 180ms ease;
}

.mode:hover {
  color: var(--ink);
  border-color: var(--edge);
  transform: translateY(-1px);
}

/* Each mode lights in its own colour, so which one is on is readable at a
   glance rather than by reading the labels. */
.mode.heat[aria-pressed="true"] {
  color: #0b0f16;
  background: var(--stale);
  border-color: var(--stale);
  box-shadow: 0 0 18px rgba(255, 204, 0, 0.45);
}

.mode.radar[aria-pressed="true"] {
  color: #fff;
  background: var(--fresh);
  border-color: var(--fresh);
  box-shadow: 0 0 18px rgba(255, 59, 48, 0.5);
}

.mode.scan:hover {
  color: #fff;
  border-color: var(--cool);
  box-shadow: 0 0 16px rgba(43, 108, 255, 0.35);
}

/* --- 3. command panel ------------------------------------------------------- */

.sidebar {
  grid-area: side;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--panel);
  backdrop-filter: blur(14px);
  border-right: 1px solid var(--line);
  box-shadow: inset -1px 0 0 rgba(120, 150, 200, 0.08);
}

.panel {
  padding: 14px 14px 16px;
  border-bottom: 1px solid var(--line);
}

.panel h2 {
  margin: 0;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--accent);
  transition: color 400ms ease;
}

.hint {
  margin: 4px 0 10px;
  font-size: 11px;
  line-height: 1.45;
  color: var(--muted);
}

.trending,
.states,
.surge-log {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  width: 100%;
  padding: 6px 8px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
  transition: background 140ms ease;
}

.row:hover {
  background: rgba(255, 255, 255, 0.06);
}

.rank {
  width: 15px;
  flex: none;
  font-weight: 700;
  color: var(--fresh);
  font-size: 12px;
}

.name {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.count {
  flex: none;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.sub {
  flex: none;
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.empty {
  padding: 6px 8px;
  font-size: 13px;
  color: var(--muted);
}

.surge-entry {
  display: flex;
  gap: 8px;
  padding: 5px 8px;
  font-size: 12px;
  border-left: 2px solid var(--warm);
  border-radius: 0 6px 6px 0;
  background: rgba(255, 149, 0, 0.08);
}

.surge-when {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  flex: none;
}

/* --- 4. the screen ---------------------------------------------------------- */

.screen {
  grid-area: screen;
  position: relative;
  /* Boxes Leaflet in. Its panes run from z-index 200 to 800, and without a
     stacking context here they compete with the page's own chrome — which is
     how the sliding panel ended up behind the map on narrow screens. */
  z-index: 0;
  padding: 12px;
  min-width: 0;
  min-height: 0;
}

#map {
  position: relative;
  /* Same again one level down, so the overlays below stack against Leaflet's
     panes and nothing else. */
  z-index: 0;
  width: 100%;
  height: 100%;
  border-radius: 14px;
  overflow: hidden;
  background: var(--ground);
  border: 1px solid var(--edge);
  /* The glow follows the national reading, so the frame itself reports. */
  box-shadow:
    0 0 0 1px rgba(0, 0, 0, 0.5),
    0 0 26px -4px var(--accent),
    0 18px 40px rgba(0, 0, 0, 0.55);
  transition: box-shadow 700ms ease, border-color 700ms ease;
}

/* Leaflet's own chrome, dressed for the dark. */
.leaflet-control-zoom a {
  background: rgba(14, 19, 29, 0.92);
  color: var(--ink);
  border-color: var(--line);
}

.leaflet-control-zoom a:hover {
  background: rgba(30, 40, 58, 0.95);
  color: #fff;
}

.leaflet-control-attribution {
  background: rgba(8, 11, 18, 0.78);
  color: var(--muted);
}

.leaflet-control-attribution a {
  color: var(--muted);
}

/* --- 6. timeline ------------------------------------------------------------ */

.timeline {
  grid-area: time;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 0 20px;
  background: var(--shell);
  border-top: 1px solid var(--line);
  font-size: 13px;
}

.timeline-label {
  flex: none;
  font-weight: 600;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}

#window {
  flex: 1;
  min-width: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

#window-value {
  flex: none;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  min-width: 104px;
  text-align: right;
  color: var(--ink);
}

/* --- narrow screens --------------------------------------------------------- */

.only-narrow {
  display: none;
}

@media (max-width: 860px) {
  body {
    grid-template-columns: 1fr;
    grid-template-areas:
      "command"
      "pulse"
      "dock"
      "screen"
      "time";
  }

  .only-narrow {
    display: inline-flex;
  }

  .brand {
    font-size: 17px;
  }

  /* The panel slides over the map rather than squeezing it. */
  .sidebar {
    position: fixed;
    top: calc(var(--command-h) + var(--pulse-h) + var(--dock-h));
    bottom: var(--time-h);
    left: 0;
    width: min(88vw, var(--side-w));
    z-index: 40;
    transform: translateX(-102%);
    transition: transform 280ms cubic-bezier(0.4, 0, 0.2, 1);
    border-right: 1px solid var(--edge);
    box-shadow: 12px 0 40px rgba(0, 0, 0, 0.6);
  }

  body.panel-open .sidebar {
    transform: none;
  }

  .dock {
    gap: 6px;
    padding: 0 8px;
  }

  .mode {
    padding: 8px 10px;
    font-size: 12px;
  }

  .screen {
    padding: 8px;
  }

  .timeline {
    gap: 10px;
    padding: 0 12px;
  }

  .timeline-label {
    display: none;
  }

  #window-value {
    min-width: 92px;
  }
}

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

  .sidebar {
    transition: none;
  }
}

/* --- pins ------------------------------------------------------------------ */

/* Drawn rather than loaded, so there is no image request per marker and the
   colour can change with age without fetching anything. */
.pin {
  position: relative;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.92);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
  animation: drop 220ms ease-out;
}

.pin.fresh {
  background: var(--fresh);
  color: var(--fresh);
}

.pin.stale {
  background: var(--stale);
  color: var(--stale);
}

/* Pins arrive and leave as the timeline moves rather than blinking in place. */
@keyframes drop {
  from {
    transform: scale(0.4);
    opacity: 0;
  }
  to {
    transform: scale(1);
    opacity: 1;
  }
}

/* Radar mode is the map's mode, not the page's — the page is already dark. What
   it still owns: dark tiles, the sweep, and the glow on everything drawn.
   This lived in the old page-wide radar block and went with it during the
   redesign, which left --glow permanently "none" and the pins flat. */
body.radar {
  --glow: 0 0 10px currentColor;

  /* Radar takes the console with it. Confining the mode to the map left it
     barely registering as a mode at all — the frame, the pulse and the panel
     headings all read --accent, so pinning it to the sweep's own red makes the
     mode obvious from anywhere on the page. It still is not the old trick of
     repainting light to dark; the page was already dark. */
  --accent: var(--fresh);
}

/* The frame burns rather than glows while the sweep is running. */
body.radar #map {
  border-color: rgba(255, 45, 85, 0.55);
  box-shadow:
    0 0 0 1px rgba(0, 0, 0, 0.5),
    0 0 34px -2px rgba(255, 45, 85, 0.75),
    inset 0 0 60px -30px rgba(255, 45, 85, 0.9),
    0 18px 40px rgba(0, 0, 0, 0.55);
}

body.radar .pin {
  border-color: rgba(255, 255, 255, 0.35);
  box-shadow: var(--glow);
}

/* A ring going out from anything recent. Only fresh pins pulse: a report from
   three hours ago is not breaking news. */
body.radar .pin.fresh::after {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  border: 1.5px solid currentColor;
  animation: ping 2.4s ease-out infinite;
}

@keyframes ping {
  0% {
    transform: scale(0.7);
    opacity: 0.85;
  }
  100% {
    transform: scale(3.4);
    opacity: 0;
  }
}

/* Cluster bubbles take the colour of their freshest pin: a crowd with one recent
   report in it is not stale. */
.bubble {
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.92);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
  font-weight: 700;
  font-size: 13px;
  color: #fff;
}

.bubble.fresh {
  background: rgba(255, 59, 48, 0.92);
}

.bubble.stale {
  background: rgba(255, 204, 0, 0.95);
  color: #111318;
}

body.radar .bubble {
  border-color: rgba(255, 255, 255, 0.3);
}

body.radar .bubble.fresh {
  background: rgba(255, 45, 85, 0.88);
  box-shadow: 0 0 16px rgba(255, 45, 85, 0.75);
}

body.radar .bubble.stale {
  background: rgba(255, 214, 10, 0.9);
  box-shadow: 0 0 16px rgba(255, 214, 10, 0.6);
}

/* --- popups ---------------------------------------------------------------- */

.leaflet-popup-content {
  margin: 10px 12px;
  font-size: 13px;
}

.popup-age {
  font-weight: 600;
}

.popup-when {
  color: var(--muted);
  font-size: 12px;
}

/* --- route scanner --------------------------------------------------------- */

body.scanner {
  grid-template-columns: 1fr;
  grid-template-rows: var(--bar-h) auto auto 1fr auto;
  grid-template-areas:
    "bar"
    "form"
    "privacy"
    "map"
    "results";
  height: 100dvh;
}

.route-form {
  grid-area: form;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px;
  padding: 12px 16px 8px;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1 1 220px;
  min-width: 0;
}

.field-narrow {
  flex: 0 1 140px;
}

.field label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.field input,
.field select {
  font: inherit;
  font-size: 14px;
  padding: 9px 10px;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--paper);
  color: var(--ink);
  min-width: 0;
}

.field input:focus,
.field select:focus {
  outline: 2px solid var(--fresh);
  outline-offset: -1px;
}

.primary {
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  padding: 10px 18px;
  border-radius: 8px;
  border: 1px solid var(--fresh);
  background: var(--fresh);
  color: #fff;
  cursor: pointer;
  flex: none;
}

.primary:disabled {
  opacity: 0.55;
  cursor: default;
}

.privacy {
  grid-area: privacy;
  margin: 0;
  padding: 0 16px 10px;
  font-size: 12px;
  color: var(--muted);
}

body.scanner #map {
  grid-area: map;
  min-height: 260px;
}

.results {
  grid-area: results;
  max-height: 34vh;
  overflow-y: auto;
  padding: 12px 16px 14px;
  border-top: 1px solid var(--line);
  background: var(--panel);
}

.verdict {
  margin: 0 0 8px;
  font-size: 15px;
  font-weight: 600;
}

/* Never green, even for "all clear": an empty corridor is a corridor nobody has
   reported, which is not the same as a safe one. */
.verdict.clear {
  color: var(--muted);
}

.verdict.alert {
  color: var(--fresh);
}

.findings {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.finding {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 13px;
  padding: 3px 0;
}

.finding-where {
  min-width: 118px;
}

.finding-along,
.finding-when {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.cluster-row {
  font-weight: 600;
  color: var(--fresh);
}

.heat-row,
.note-row {
  color: var(--muted);
}

.soon {
  margin: 12px 0 0;
  font-size: 12px;
  color: var(--muted);
  opacity: 0.8;
}

.endpoint {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #2b6cff;
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  border: 2px solid rgba(255, 255, 255, 0.9);
}

a.toggle {
  text-decoration: none;
  display: inline-flex;
  align-items: center;
}

@media (max-width: 640px) {
  .route-form {
    flex-direction: column;
    align-items: stretch;
  }

  .primary {
    width: 100%;
  }

  .results {
    max-height: 40vh;
  }
}

/* --- radar systems --------------------------------------------------------- */
/* Everything below animates transform and opacity only, so it runs on the
   compositor and never forces layout. Nothing loops except the sweep, and the
   sweep only exists in radar mode. */

#map {
  position: relative;
}

.fx {
  position: absolute;
  inset: 0;
  /* Above the map pane, so ripples read as coming off their pins. Note this
     puts them over popups too — there is no way to sit between Leaflet's own
     panes from out here, and an 800ms ring over an open popup is not worth a
     second element inside the map to avoid. */
  z-index: 650;
  pointer-events: none;
}

/* One ripple out of a report that has just arrived, then gone. */
.shockwave,
.sonar {
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
  will-change: transform, opacity;
}

.shockwave {
  width: 26px;
  height: 26px;
  margin: -13px 0 0 -13px;
  border: 2px solid var(--fresh);
  background: rgba(255, 59, 48, 0.14);
  animation: shock 800ms linear forwards;
}

@keyframes shock {
  0% {
    transform: scale(0.3);
    opacity: 0;
  }
  12% {
    opacity: 0.9;
  }
  100% {
    transform: scale(3.4);
    opacity: 0;
  }
}

/* Wider and slower than a shockwave: a crowd growing, not a single hit. */
.sonar {
  width: 40px;
  height: 40px;
  margin: -20px 0 0 -20px;
  border: 2px solid #ff9500;
  animation: sonar 2000ms linear forwards;
}

@keyframes sonar {
  0% {
    transform: scale(0.25);
    opacity: 0;
  }
  10% {
    opacity: 0.75;
  }
  100% {
    transform: scale(4.2);
    opacity: 0;
  }
}

/* Radar mode only. A rotating gradient — one transform, no per-frame work. */
.sweep {
  position: absolute;
  inset: -30%;
  /* Above the whole map, because there is no "between" to aim at.
     .leaflet-map-pane is itself a .leaflet-pane at z-index 400 and contains
     everything — the tile pane's 200 and the marker pane's 600 only rank
     against each other inside it. Anything outside is wholly above 400 or
     wholly below, and below 400 means behind opaque tiles.
     Passing over the pins is fine: the sweep is translucent and they read
     straight through it. */
  z-index: 420;
  pointer-events: none;
  opacity: 0;
  border-radius: 50%;
  /* A hard leading edge trailing off behind it, which is what makes a sweep
     read as a sweep. The old version topped out at 16% and was invisible on a
     dark map. */
  background: conic-gradient(
    from 0deg,
    rgba(255, 45, 85, 0.6) 0deg,
    rgba(255, 45, 85, 0.26) 12deg,
    rgba(255, 45, 85, 0.08) 44deg,
    transparent 84deg,
    transparent 360deg
  );
  transition: opacity 400ms ease;
  will-change: transform;
}

.sweep.showing {
  opacity: 1;
  animation: sweep 5s linear infinite;
}

/* Range rings. Radially symmetric, so they cost nothing to rotate with the
   sweep and give it something to sweep across. */
.sweep.showing::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: repeating-radial-gradient(
    circle,
    rgba(255, 45, 85, 0.12) 0 1px,
    transparent 1px 72px
  );
}

@keyframes sweep {
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .sweep.showing {
    animation: none;
  }
  .shockwave,
  .sonar {
    animation-duration: 1ms;
  }
}

/* --- surge banner and jump button ------------------------------------------ */

.surge {
  position: absolute;
  top: 12px;
  left: 50%;
  transform: translate(-50%, -14px);
  z-index: 800;
  padding: 8px 14px;
  border-radius: 999px;
  background: rgba(255, 59, 48, 0.95);
  color: #fff;
  font-size: 13px;
  font-weight: 600;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.28);
  opacity: 0;
  pointer-events: none;
  transition: opacity 350ms ease, transform 350ms ease;
}

.surge.showing {
  opacity: 1;
  transform: translate(-50%, 0);
}

.jump {
  position: absolute;
  bottom: 18px;
  left: 50%;
  transform: translate(-50%, 10px);
  z-index: 500;
  padding: 9px 16px;
  border-radius: 999px;
  border: 0;
  background: var(--ink);
  color: var(--paper);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.3);
  transition: opacity 300ms ease, transform 300ms ease;
}

.jump.showing {
  opacity: 1;
  transform: translate(-50%, 0);
  pointer-events: auto;
}

/* --- cluster temperature ---------------------------------------------------- */
/* Blue is quiet, white-hot is critical. No green at any step: green would read
   as "all clear", and a quiet bubble only means nobody has reported. */

.bubble.small {
  background: rgba(43, 108, 255, 0.9);
}

.bubble.medium {
  background: rgba(255, 214, 10, 0.95);
  color: #111318;
}

.bubble.hot {
  background: rgba(255, 149, 0, 0.95);
}

.bubble.extreme {
  background: rgba(255, 59, 48, 0.95);
}

.bubble.critical {
  background: rgba(255, 245, 235, 0.97);
  color: #b3000b;
  box-shadow: 0 0 18px rgba(255, 120, 90, 0.9);
}

/* A crowd where everything has aged past two hours keeps its temperature but
   loses its edge, so size and freshness are both readable at once. */
.bubble.cooling {
  border-color: rgba(255, 255, 255, 0.35);
  opacity: 0.72;
}

/* Slow, shallow, and only on the bubbles worth looking at. */
.bubble.hot,
.bubble.extreme,
.bubble.critical {
  animation: glow 3.4s ease-in-out infinite;
}

@keyframes glow {
  0%,
  100% {
    filter: brightness(1);
  }
  50% {
    filter: brightness(1.22);
  }
}

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

/* --- streaks ---------------------------------------------------------------- */

.streaks {
  margin: 6px 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.streak {
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

/* --- danger score ----------------------------------------------------------- */

.score {
  flex: none;
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  padding: 1px 6px;
  border-radius: 5px;
  color: #fff;
}

.score.quiet {
  background: rgba(127, 127, 127, 0.75);
}

.score.medium {
  background: #ffcc00;
  color: #111318;
}

.score.hot {
  background: #ff9500;
}

.score.extreme {
  background: #ff3b30;
}

/* --- cluster story ---------------------------------------------------------- */

.story {
  position: absolute;
  right: 12px;
  top: 12px;
  bottom: 12px;
  width: min(300px, calc(100% - 24px));
  z-index: 900;
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.25);
  opacity: 0;
  transform: translateX(12px);
  pointer-events: none;
  transition: opacity 220ms ease, transform 220ms ease;
}

.story.showing {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.story-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line);
}

.story-head h2 {
  margin: 0;
  font-size: 13px;
  flex: 1;
  min-width: 0;
}

.story-btn {
  font: inherit;
  font-size: 12px;
  padding: 4px 8px;
  border-radius: 6px;
  border: 1px solid var(--line);
  background: var(--paper);
  color: var(--ink);
  cursor: pointer;
}

.story-means {
  margin: 0;
  padding: 8px 12px 0;
  font-size: 12px;
  color: var(--muted);
}

.story-list {
  margin: 0;
  padding: 10px 12px 14px;
  list-style: none;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 0;
}

/* A line down the left, with each moment hung off it. */
.story-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  font-size: 12px;
  padding: 5px 0 5px 14px;
  border-left: 2px solid var(--line);
  position: relative;
}

.story-row::before {
  content: "";
  position: absolute;
  left: -5px;
  top: 10px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--line);
}

.story-row.fresh::before {
  background: var(--fresh);
}

.story-row.stale::before {
  background: var(--stale);
}

.story-row.formed::before,
.story-row.heat::before {
  background: #ff9500;
}

.story-row.formed .story-what,
.story-row.heat .story-what {
  font-weight: 600;
}

.story-when {
  flex: none;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.story-what {
  flex: 1;
  min-width: 0;
}

@media (max-width: 720px) {
  .story {
    left: 12px;
    width: auto;
  }
}

/* --- route scanner shell ---------------------------------------------------- */
/* The main page's command bar was rebuilt as a three-slot grid; the scanner
   keeps a simpler bar of its own rather than being bent into that shape. Same
   dark ground, same accents. */

body.scanner .bar {
  grid-area: bar;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 0 20px;
  background: linear-gradient(180deg, #101724 0%, var(--shell) 100%);
  border-bottom: 1px solid var(--line);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
}

body.scanner .title h1 {
  margin: 0;
  font-size: 19px;
  font-weight: 700;
  white-space: nowrap;
  text-shadow: 0 0 22px rgba(255, 59, 48, 0.28);
}

body.scanner .status {
  margin: 2px 0 0;
  font-size: 12px;
  color: var(--muted);
}

body.scanner .controls {
  display: flex;
  gap: 8px;
  flex: none;
}

body.scanner .toggle {
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  padding: 8px 14px;
  border-radius: 9px;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, 0.03);
  color: var(--muted);
  cursor: pointer;
  transition: color 180ms ease, border-color 180ms ease, box-shadow 220ms ease;
}

body.scanner .toggle:hover {
  color: var(--ink);
  border-color: var(--edge);
}

body.scanner .toggle[aria-pressed="true"] {
  color: #0b0f16;
  background: var(--stale);
  border-color: var(--stale);
  box-shadow: 0 0 18px rgba(255, 204, 0, 0.45);
}

body.scanner #map {
  border-radius: 12px;
  border: 1px solid var(--edge);
  box-shadow: 0 0 26px -6px var(--cool), 0 14px 34px rgba(0, 0, 0, 0.5);
  overflow: hidden;
  margin: 0 12px;
  width: auto;
}

body.scanner .results {
  background: var(--panel);
  backdrop-filter: blur(14px);
}
