/*
 * Event Forecast — Global Styles
 *
 * All values reference CSS custom properties from tokens.js.
 * Never hardcode colors, spacing, sizing, typography, radii, shadows,
 * opacity, or transitions. If a value doesn't have a token yet, add
 * one to tokens.js before using it.
 */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
}

html {
  background: var(--color-bg-page);
  color: var(--color-text-primary);
  font-family: var(--typography-font-body);
  font-size: var(--typography-body);
  line-height: var(--typography-lineHeight-normal);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

body {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background:
    radial-gradient(80% 60% at 10% -10%, color-mix(in srgb, var(--color-action-primary) 10%, transparent), transparent 60%),
    radial-gradient(60% 50% at 90% 0%, color-mix(in srgb, var(--color-status-info) 6%, transparent), transparent 65%),
    var(--color-bg-page);
  transition: background-color var(--transition-base), color var(--transition-base);
}

a {
  color: var(--color-link-base);
  text-decoration: none;
  transition: color var(--transition-fast);
}

a:hover {
  color: var(--color-link-hover);
}

a:visited {
  color: var(--color-link-visited);
}

/* ────────────── Global a11y + utilities ────────────── */

/* `hidden` always wins. Several components set an author `display`
   (flex, grid) that would otherwise beat the UA stylesheet's
   display: none and leave an empty box on screen. */
[hidden] {
  display: none !important;
}

/* One keyboard focus ring for every control. Components that draw
   their own indicator (the brush grips, the chart canvas) opt out
   individually. */
:focus-visible {
  outline: 2px solid var(--color-interactive-focus);
  outline-offset: 2px;
}

/* Visually hidden, still read by assistive tech: section headings the
   layout already says visually, and short descriptions of controls. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ────────────── Header / Brand ────────────── */

.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--spacing-md) var(--spacing-lg);
  background: linear-gradient(180deg, color-mix(in srgb, var(--color-highlight) 2%, transparent), transparent 80%),
              var(--color-bg-surface);
  border-bottom: 1px solid var(--color-border-subtle);
  backdrop-filter: saturate(140%) blur(6px);
}

@media (max-width: 720px) {
  .app-header {
    padding: var(--spacing-smd) var(--spacing-md);
  }
}

.app-header__brand {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
}

.app-header__mark {
  display: inline-block;
  width: var(--spacing-md);
  height: var(--spacing-md);
  border-radius: var(--radius-sm);
  background: var(--color-action-primary);
  box-shadow: var(--shadow-glow);
}

.app-header__name {
  font-family: var(--typography-font-display);
  font-size: var(--typography-heading);
  font-weight: var(--typography-weight-semibold);
  letter-spacing: 0.005em;
  line-height: 1;
  margin: 0;   /* it is the page's h1 */
}

/* ────────────── City selector ────────────── */

.city-selector__label {
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
  margin-right: var(--spacing-sm);
}

.city-selector__input {
  background: var(--color-bg-raised);
  color: var(--color-text-primary);
  font-family: var(--typography-font-body);
  font-size: var(--typography-caption);
  padding: var(--spacing-xs) var(--spacing-sm);
  border: 1px solid var(--color-border-base);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: border-color var(--transition-fast);
}

.city-selector__input:hover,
.city-selector__input:focus-visible {
  border-color: var(--color-border-strong);
}

/* ────────────── Main layout ────────────── */

.app-main {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-lg);
  padding: var(--spacing-lg);
  max-width: 1280px;
  width: 100%;
  margin: 0 auto;
}

/*
 * Phones (max-width 720px, the one phone breakpoint in this file): the
 * page goes edge to edge. The outer gutter is dropped and each block
 * carries its own 16px inset — or none: the detail card loses its side
 * borders and radius, so the chart, the map and the event list get the
 * full width (343px instead of 278px on a 375px screen).
 */
@media (max-width: 720px) {
  .app-main {
    padding: var(--spacing-md) 0;
    gap: var(--spacing-md);
  }

  .status-banner,
  .controls,
  .forecast-calendar {
    margin-left: var(--spacing-md);
    margin-right: var(--spacing-md);
  }
}

.forecast-detail {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-lg);
  padding: var(--spacing-lg);
  min-height: var(--spacing-xxxl);
  box-shadow: var(--shadow-md);
  position: relative;
}

/* The day cards are the cards; the strip around them is bare. */
.forecast-strip {
  position: relative;
  padding: 0;
}

/* ────────────── M6 Status banner ────────────── */

/*
 * Stale-data / zero-event / budget-exhausted banner. Renders ABOVE the
 * forecast strip when the pipeline reports any of:
 *   * Ticketmaster refresh older than max_age_minutes
 *   * Per-day TM budget exhausted
 *   * Zero impactful events across the entire window
 *   * GTFS zip older than max_age_days
 *
 * Three visual kinds — info / warning / error — keyed off data-kind.
 * The dot color encodes severity at a glance.
 */
.status-banner {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-sm);
  padding: var(--spacing-md) var(--spacing-lg);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border-base);
  background: linear-gradient(180deg, color-mix(in srgb, var(--color-highlight) 2%, transparent), transparent 60%),
              var(--color-bg-raised);
  box-shadow: var(--shadow-sm);
}

.status-banner[data-kind="info"] {
  border-color: color-mix(in srgb, var(--color-status-info) 40%, transparent);
}

.status-banner[data-kind="warning"] {
  border-color: color-mix(in srgb, var(--color-status-warning) 50%, transparent);
  background: linear-gradient(180deg, color-mix(in srgb, var(--color-status-warning) 6%, transparent), transparent 70%),
              var(--color-bg-raised);
}

.status-banner[data-kind="error"] {
  border-color: color-mix(in srgb, var(--color-verdict-severe) 55%, transparent);
  background: linear-gradient(180deg, color-mix(in srgb, var(--color-verdict-severe) 8%, transparent), transparent 70%),
              var(--color-bg-raised);
}

.status-banner__dot {
  width: 10px;
  height: 10px;
  margin-top: 6px;
  border-radius: var(--radius-pill);
  background: var(--color-status-info);
  flex-shrink: 0;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-status-info) 25%, transparent);
}

.status-banner[data-kind="warning"] .status-banner__dot {
  background: var(--color-status-warning);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-status-warning) 25%, transparent);
}

.status-banner[data-kind="error"] .status-banner__dot {
  background: var(--color-verdict-severe);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-verdict-severe) 25%, transparent);
}

.status-banner__content {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
}

.status-banner__eyebrow {
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-primary);
}

.status-banner__body {
  font-size: var(--typography-caption);
  color: var(--color-text-secondary);
  line-height: 1.45;
}

/* ────────────── 7-day outlook strip ────────────── */

/*
 * One compact verdict pill per day, today first. Seven columns on wide
 * screens; on narrow screens the row scrolls sideways with snap points
 * and a right-edge fade so it's obvious more days sit off-screen.
 */
.forecast-strip__grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: var(--spacing-sm);
}

@media (max-width: 980px) {
  .forecast-strip__grid {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    padding-bottom: var(--spacing-xs);
    scrollbar-width: none;   /* the fade says there's more; no bar flashing in */
    -webkit-overflow-scrolling: touch;
  }

  .forecast-strip__grid::-webkit-scrollbar {
    display: none;
  }

  /* Pills snap to the centre, and app.js scrolls the selected one
     there, so the day either side is always on screen to tap next. */
  .forecast-strip__grid .day-pill {
    flex: 0 0 144px;
    scroll-snap-align: center;
  }

  .forecast-strip::after {
    content: "";
    position: absolute;
    top: 0;
    right: 0;
    bottom: var(--spacing-xs);
    width: var(--spacing-xl);
    pointer-events: none;
    background: linear-gradient(90deg, transparent, var(--color-bg-page));
  }
}

.forecast-strip__empty {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: var(--spacing-xxxl);
  color: var(--color-text-tertiary);
  font-family: var(--typography-font-mono);
  font-size: var(--typography-caption);
  letter-spacing: 0.04em;
}

/* Phones: the scroller runs to the screen edges with the page's 16px
   inset inside it, so the first pill lines up with the header and the
   last one can scroll fully into view. 144px pills show 2.4 days on a
   375px screen — a clear peek of the next day says "swipe". */
@media (max-width: 720px) {
  .forecast-strip__grid {
    padding-left: var(--spacing-md);
    padding-right: var(--spacing-md);
    scroll-padding-inline: var(--spacing-md);
  }

  .forecast-strip__grid .day-pill {
    padding: var(--spacing-smd);
  }
}

/* ────────────── Controls toolbar ────────────── */

/*
 * One row: [7 days | Month] [Sports] [Concerts] … note … [switch].
 * On phones the row reads [7 days | Month] …………… [Filters ▾] and the
 * filter group opens as a dropdown under the chip (see .event-filter),
 * with the verdict caveat repeated under the row while it is closed.
 */
.controls {
  position: relative;   /* anchors the phone dropdown */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-sm) var(--spacing-md);
}

/* The Filters chip and the closed-state note are phone-only. */
@media (min-width: 721px) {
  .controls__filters-btn,
  .controls__note {
    display: none;
  }
}

@media (max-width: 720px) {
  .controls__filters-btn {
    margin-left: auto;
  }

  .controls__note {
    flex-basis: 100%;
  }
}

/* ────────────── Outlook view switch ────────────── */

/*
 * 7 days / Month. Two .chip buttons acting as one control — .chip is the
 * project's single pill-control component, so the switch inherits its
 * sizing, focus ring and transitions rather than inventing a second
 * pill. Hidden by app.js until the archive has something to show.
 */
.outlook-switch {
  display: flex;
  align-items: center;
  gap: var(--spacing-xs);
}

.outlook-switch__chip[data-active="true"] {
  color: var(--color-text-primary);
  border-color: var(--color-text-tertiary);
  background: var(--color-bg-overlay);
}

/* ────────────── Month calendar ────────────── */

/*
 * The month view of the outlook. A calendar cell is a day pill with the
 * peak line and the wide sub-line dropped for room — same verdict
 * colours, same mini busyness graph, same selection model, so the two
 * views read as one control in two densities.
 */
.forecast-calendar {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-sm);
}

.cal-head {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
}

.cal-title {
  font-family: var(--typography-font-display);
  font-size: var(--typography-body);
  font-weight: var(--typography-weight-semibold);
  letter-spacing: -0.01em;
  color: var(--color-text-primary);
  margin: 0;
  /* Fixed width so the arrows don't shuffle between "May" and
     "September". */
  min-width: 10.5em;
}

.cal-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--spacing-xl);
  height: var(--spacing-xl);
  font-family: var(--typography-font-body);
  font-size: var(--typography-body);
  line-height: 1;
  color: var(--color-text-secondary);
  background: var(--color-bg-raised);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: color var(--transition-fast), border-color var(--transition-fast);
}

@media (hover: hover) {
  .cal-nav:hover:not(:disabled) {
    color: var(--color-text-primary);
    border-color: var(--color-border-strong);
  }
}

@media (pointer: coarse) {
  .cal-nav {
    width: var(--size-tap);
    height: var(--size-tap);
  }
}

.cal-nav:disabled {
  opacity: var(--opacity-25);
  cursor: default;
}

.cal-nav:focus-visible,
.cal-cell:focus-visible {
  outline: 2px solid var(--color-interactive-focus);
  outline-offset: 2px;
}

.cal-today-btn {
  margin-left: auto;
}

.cal-loading {
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.cal-dow {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: var(--spacing-xs);
}

.cal-dow span {
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
  text-align: center;
}

.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: var(--spacing-xs);
}

.cal-cell {
  --verdict-color: var(--color-text-tertiary);
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  width: 100%;
  min-width: 0;
  min-height: 88px;
  text-align: left;
  font-family: var(--typography-font-body);
  color: var(--color-text-primary);
  background: var(--color-bg-raised);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
  padding: var(--spacing-xs);
  overflow: hidden;
  cursor: pointer;
  transition: border-color var(--transition-fast),
              background-color var(--transition-fast);
}

/* The verdict stripe runs down the leading edge here rather than across
   the top as it does on a day pill: a calendar cell is taller than it is
   wide, and a top stripe would compete with the date. */
.cal-cell::before {
  content: "";
  position: absolute;
  inset: var(--spacing-xs) auto var(--spacing-xs) 0;
  width: 3px;
  border-radius: 0 var(--radius-xs) var(--radius-xs) 0;
  background: var(--verdict-color);
  opacity: 0.85;
}

.cal-cell--empty::before {
  display: none;
}

.cal-cell[data-verdict="quiet"]    { --verdict-color: var(--color-verdict-quiet); }
.cal-cell[data-verdict="moderate"] { --verdict-color: var(--color-verdict-moderate); }
.cal-cell[data-verdict="busy"]     { --verdict-color: var(--color-verdict-busy); }
.cal-cell[data-verdict="severe"]   { --verdict-color: var(--color-verdict-severe); }

@media (hover: hover) {
  button.cal-cell:hover {
    border-color: var(--color-border-strong);
    background: var(--color-bg-overlay);
  }
}

.cal-cell[data-selected="true"] {
  border-color: var(--verdict-color);
  box-shadow: 0 0 0 1px var(--verdict-color);
  background: var(--color-bg-overlay);
}

/* A day with no forecast behind it: rendered so the grid keeps whole
   weeks, but inert — not a button, not focusable. */
.cal-cell--empty {
  background: transparent;
  border-color: transparent;
  cursor: default;
  min-height: 88px;
}

/* Leading and trailing days of the neighbouring months. Dimmed, but
   still selectable when they carry a forecast. */
.cal-cell--outside {
  opacity: var(--opacity-50);
}

.cal-cell--today .cal-cell__num {
  color: var(--color-text-primary);
  font-weight: var(--typography-weight-semibold);
}

.cal-cell__top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--spacing-xs);
  width: 100%;
  padding-left: var(--spacing-sm);
}

.cal-cell__num {
  font-family: var(--typography-font-mono);
  font-size: var(--typography-caption);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: var(--color-text-secondary);
}

/* A day with nothing behind it keeps the same legible date — it is the
   grid's skeleton, and dimming it further drops below the contrast
   floor. What separates it from a real day is everything it lacks: no
   verdict stripe, no verdict word, no graph, no border. */

.cal-cell__today {
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-secondary);
}

.cal-cell__verdict {
  padding-left: var(--spacing-sm);
  font-size: var(--typography-caption);
  font-weight: var(--typography-weight-semibold);
  line-height: 1.2;
  color: var(--verdict-color);
}

/* Verdict dot beside the date: phones only, where a 43px cell cannot
   fit the word "Moderate" (61px). Wider screens show the word. */
.cal-cell__dot {
  display: none;
  flex: none;
  width: var(--spacing-sm);
  height: var(--spacing-sm);
  border-radius: var(--radius-pill);
  background: var(--verdict-color);
}

.cal-cell__spark {
  display: block;
  width: 100%;
  height: 26px;
  margin-top: auto;
}

/* Secondary, matching .day-pill__sub. The greys below it (tertiary,
   muted) land at 3.7:1 and 2.3:1 on the raised surface, both under the
   4.5:1 small-text floor. Hierarchy in a cell comes from size and from
   the verdict's colour and weight, not from dimming the text. */
.cal-cell__sub {
  width: 100%;
  padding-left: var(--spacing-sm);
  font-size: var(--typography-micro);
  line-height: 1.25;
  color: var(--color-text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Phones: the sub-line and the "Today" tag are the first things to go —
   seven columns of text at this width is unreadable, and the verdict
   colour plus the mini graph still carry the day. */
@media (max-width: 720px) {
  .cal-cell {
    min-height: 62px;
    padding: 4px;
  }

  .cal-cell--empty {
    min-height: 62px;
  }

  .cal-cell__sub,
  .cal-cell__today {
    display: none;
  }

  /* The word gives way to the dot; it stays in the tree for readers
     (and in the cell's aria-label). */
  .cal-cell__dot {
    display: block;
  }

  .cal-cell__verdict {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
  }

  .cal-cell__spark {
    height: 18px;
  }

  .cal-title {
    font-size: var(--typography-caption);
    min-width: 8.5em;
  }
}

/* ────────────── Event-type filter ────────────── */

/*
 * Chips filter the browsable view (day-card event lists, map markers +
 * heat, timeline, rail). Day verdicts stay full-model — the note chip
 * appears whenever a group is off so the divergence is explained.
 */
.event-filter {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--spacing-sm);
  margin: 0;
  flex: 1 1 auto;
}

/* Phones: the group is a dropdown hanging from the right end of the
   toolbar, under the Filters chip. app.js sets data-collapsed and
   closes it on outside click / Escape; wider screens ignore the
   attribute and show the group inline. */
@media (max-width: 720px) {
  .event-filter {
    position: absolute;
    top: calc(100% + var(--spacing-xs));
    right: 0;
    z-index: 20;
    width: min(320px, calc(100vw - var(--spacing-xl)));
    padding: var(--spacing-smd);
    background: var(--color-bg-overlay);
    border: 1px solid var(--color-border-base);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
  }

  .event-filter[data-collapsed="true"] {
    display: none;
  }

  /* The verdict switch takes its own line: label left, knob right. */
  .event-filter__toggle-wrap {
    flex-basis: 100%;
    justify-content: space-between;
    margin-left: 0;
  }
}

/* ────────────── Chip (shared component) ──────────────
 *
 * ONE component for every pill-shaped control: the view filters
 * (Sports, Concerts, Smaller venues) and the timeline controls
 * (Streetcar, GO, All day, Fit, Peak, Now). Both app.js and
 * timeline.js create these with the `chip` class; the BEM classes
 * (.event-filter__chip, .ef-timeline__chip) are hooks for layout and
 * scripting only and carry no visual rules. Three states:
 *   on        accent border + tint, primary text
 *   off       plain outline, secondary text: readable, just not lit
 *   disabled  muted and dimmed, no pointer */
.chip {
  position: relative;   /* anchors the touch halo below */
  font-family: var(--typography-font-body);
  font-size: var(--typography-caption);
  font-weight: var(--typography-weight-medium);
  line-height: 1.2;
  color: var(--color-text-secondary);
  background: var(--color-bg-raised);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-pill);
  padding: var(--spacing-xs) var(--spacing-smd);
  cursor: pointer;
  transition: border-color var(--transition-fast),
              color var(--transition-fast),
              background-color var(--transition-fast),
              opacity var(--transition-fast);
}

/* Hover only where hover exists: on a touch screen the style would
   stick to the last chip tapped. */
@media (hover: hover) {
  .chip:hover {
    color: var(--color-text-primary);
    border-color: var(--color-text-tertiary);
  }
}

/* Touch screens: the chip grows to 32px and an invisible halo pads the
   hit area out to 48px (Material's pattern), so a row of chips stays
   visually compact. Wrapped rows are 8px apart, so neighbouring halos
   meet without overlapping. */
@media (pointer: coarse) {
  .chip {
    min-height: var(--size-control);
    padding: var(--spacing-xs) var(--spacing-md);
  }

  .chip::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: calc(-1 * var(--spacing-sm));
    bottom: calc(-1 * var(--spacing-sm));
  }
}

/* Chevron drawn from two borders, so it never depends on a symbol
   font: a bordered square turned 45° points down; turned the other
   way it points up. */
.chip__caret {
  display: inline-block;
  width: var(--spacing-sm);
  height: var(--spacing-sm);
  margin-left: var(--spacing-sm);
  border-right: 2px solid var(--color-text-tertiary);
  border-bottom: 2px solid var(--color-text-tertiary);
  transform: translateY(-25%) rotate(45deg);
  transition: transform var(--transition-fast);
}

.chip[aria-expanded="true"] .chip__caret {
  transform: translateY(25%) rotate(-135deg);
}

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

.chip[aria-pressed="true"] {
  color: var(--color-text-primary);
  border-color: color-mix(in srgb, var(--color-action-primary) 70%, transparent);
  background: linear-gradient(180deg,
                color-mix(in srgb, var(--color-action-primary) 20%, transparent),
                color-mix(in srgb, var(--color-action-primary) 8%, transparent)),
              var(--color-bg-raised);
}

.chip:disabled {
  color: var(--color-text-muted);
  border-color: var(--color-border-subtle);
  background: transparent;
  opacity: var(--opacity-50);
  cursor: not-allowed;
}

.event-filter__note {
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  letter-spacing: 0.04em;
  color: var(--color-text-tertiary);
}

/* Right-side switch: re-bucket day verdicts from only the shown events.
 * Knob geometry mirrors .status-banner__dot's fixed-pixel precedent —
 * 40×20 track, 12px knob, 4px inset (all on the 4px grid). */
.event-filter__toggle-wrap {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  margin-left: auto;
  padding: var(--spacing-xs) 0;
  cursor: pointer;
}

@media (pointer: coarse) {
  .event-filter__toggle-wrap {
    min-height: var(--size-control);
  }
}

.event-filter__toggle-label {
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
}

.event-filter__toggle {
  position: relative;
  width: 40px;
  height: 20px;
  padding: 0;
  border: 1px solid var(--color-border-base);
  border-radius: var(--radius-pill);
  background: var(--color-bg-raised);
  cursor: pointer;
  transition: background-color var(--transition-fast), border-color var(--transition-fast);
}

.event-filter__toggle:hover {
  border-color: var(--color-border-strong);
}

.event-filter__toggle-knob {
  position: absolute;
  top: 50%;
  left: var(--spacing-xs);
  transform: translateY(-50%);
  width: 12px;
  height: 12px;
  border-radius: var(--radius-pill);
  background: var(--color-text-tertiary);
  transition: left var(--transition-fast), background-color var(--transition-fast);
}

.event-filter__toggle[aria-checked="true"] {
  border-color: color-mix(in srgb, var(--color-action-primary) 55%, transparent);
  background: color-mix(in srgb, var(--color-action-primary) 20%, transparent);
}

.event-filter__toggle[aria-checked="true"] .event-filter__toggle-knob {
  left: 24px; /* track 40 − knob 12 − inset 4 */
  background: var(--color-action-primary);
}

/* ────────────── Day pill ────────────── */

.day-pill {
  --verdict-color: var(--color-text-tertiary);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--spacing-xs);
  width: 100%;
  min-width: 0;
  text-align: left;
  font-family: var(--typography-font-body);
  color: var(--color-text-primary);
  background: var(--color-bg-raised);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  padding: var(--spacing-md);
  box-shadow: var(--shadow-sm);
  position: relative;
  overflow: hidden;
  cursor: pointer;
  transition: transform var(--transition-fast), border-color var(--transition-fast),
              box-shadow var(--transition-base);
  opacity: 0;
  transform: translateY(8px);
  animation: day-pill-in var(--duration-slow) var(--easing-decelerate) forwards;
  animation-delay: calc(var(--i, 0) * 50ms);
}

.day-pill::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: var(--verdict-color);
  opacity: 0.85;
}

.day-pill[data-verdict="quiet"]    { --verdict-color: var(--color-verdict-quiet); }
.day-pill[data-verdict="moderate"] { --verdict-color: var(--color-verdict-moderate); }
.day-pill[data-verdict="busy"]     { --verdict-color: var(--color-verdict-busy); }
.day-pill[data-verdict="severe"]   { --verdict-color: var(--color-verdict-severe); }

@media (hover: hover) {
  .day-pill:hover {
    transform: translateY(-2px);
    border-color: var(--color-border-base);
    box-shadow: var(--shadow-md);
  }
}

.day-pill__when {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--spacing-xs);
  width: 100%;
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  /* Tighter than the usual 0.06em: the date and the "Today" marker have to
     share one line inside a 152px pill on mobile. */
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
}

.day-pill__day,
.day-pill__today {
  white-space: nowrap;
}

.day-pill__day {
  color: var(--color-text-secondary);
}

/* "Today" sits at the far right of the date line, quieter than the date. */
.day-pill__today {
  margin-left: auto;
  color: var(--color-text-tertiary);
}

.day-pill__verdict {
  font-family: var(--typography-font-display);
  font-size: var(--typography-heading);
  font-weight: var(--typography-weight-semibold);
  line-height: 1;
  letter-spacing: -0.01em;
  color: var(--verdict-color);
  margin-top: var(--spacing-xs);
}

/* Mini busyness graph. Height and width come from CSS; the canvas
   buffer follows the rendered box (timeline.js sparkline). */
.day-pill__spark {
  display: block;
  width: 100%;
  height: 32px;
  margin-top: var(--spacing-xs);
}

.day-pill__peak {
  font-family: var(--typography-font-mono);
  font-size: var(--typography-caption);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  color: var(--color-text-primary);
  white-space: nowrap;
}

.day-pill__sub {
  width: 100%;
  font-size: var(--typography-caption);
  color: var(--color-text-secondary);
  line-height: 1.3;
  /* Two lines, then an ellipsis. The biggest event's name is the reason
     to tap a day, and one truncated line hid most of it — on phones
     ("4 events · RÜFÜ…") and in the desktop 7-up grid alike. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  overflow-wrap: anywhere;
}

.day-pill__sub--muted {
  color: var(--color-text-tertiary);
}

.day-pill[data-selected="true"] {
  border-color: var(--verdict-color);
  box-shadow: 0 0 0 1px var(--verdict-color), var(--shadow-md);
}

.day-pill[data-selected="true"]::before {
  height: 5px;
  opacity: 1;
}

.day-pill:focus-visible {
  outline: 2px solid var(--color-interactive-focus);
  outline-offset: 2px;
}

@keyframes day-pill-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .day-pill {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

/* ────────────── Forecast detail — selected day ────────────── */

/*
 * Today-first order, top to bottom: header (date + verdict + driver),
 * stations likely packed (times lead), the busyness timeline, the map,
 * then the events active at the scrubbed time.
 */
.forecast-detail {
  padding: 0;
  overflow: hidden;
}

.forecast-detail__header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--spacing-md);
  padding: var(--spacing-md) var(--spacing-lg);
  border-bottom: 1px solid var(--color-border-subtle);
  background: linear-gradient(180deg, color-mix(in srgb, var(--color-highlight) 1.5%, transparent), transparent 80%),
              var(--color-bg-surface);
}

.forecast-detail__header-main {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-xs);
  min-width: 0;
}

.forecast-detail__title {
  --verdict-color: var(--color-text-secondary);
  font-family: var(--typography-font-display);
  font-size: var(--typography-heading);
  font-weight: var(--typography-weight-semibold);
  line-height: 1;
  color: var(--color-text-primary);
  letter-spacing: -0.005em;
  margin: 0;   /* it is an h2 */
}

.forecast-detail__title[data-verdict="quiet"]    { --verdict-color: var(--color-verdict-quiet); }
.forecast-detail__title[data-verdict="moderate"] { --verdict-color: var(--color-verdict-moderate); }
.forecast-detail__title[data-verdict="busy"]     { --verdict-color: var(--color-verdict-busy); }
.forecast-detail__title[data-verdict="severe"]   { --verdict-color: var(--color-verdict-severe); }

.forecast-detail__title-verdict {
  color: var(--verdict-color);
  margin-left: var(--spacing-sm);
}

.forecast-detail__driver {
  font-size: var(--typography-caption);
  color: var(--color-text-secondary);
  line-height: 1.4;
}

.forecast-detail__map-wrap {
  border-top: 1px solid var(--color-border-subtle);
}

.forecast-detail__map {
  width: 100%;
  height: 480px;
  background: var(--color-bg-page);
  position: relative;
}

@media (max-width: 720px) {
  .forecast-detail__map { height: 320px; }

  /* Edge to edge: no side borders or radius on phones (see .app-main). */
  .forecast-detail {
    border-radius: 0;
    border-left: 0;
    border-right: 0;
  }

  .forecast-detail__header {
    padding: var(--spacing-md);
  }
}

/* ────────────── Forecast detail — placeholder before selection ────────────── */

.forecast-detail__placeholder {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-sm);
  min-height: var(--spacing-xxxl);
  color: var(--color-text-tertiary);
  padding: var(--spacing-lg);
}

.forecast-detail__placeholder-eyebrow {
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
}

.forecast-detail__placeholder-text {
  margin: 0;
  font-size: var(--typography-body);
  color: var(--color-text-secondary);
  max-width: 56ch;
}

.forecast-detail__placeholder-text em {
  color: var(--color-text-primary);
}

/* ────────────── Footer ────────────── */

.app-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--spacing-sm) var(--spacing-md);
  padding: var(--spacing-md) var(--spacing-lg);
  border-top: 1px solid var(--color-border-subtle);
  color: var(--color-text-tertiary);
  font-size: var(--typography-micro);
}

/* <small> would otherwise shrink the 12px footer to 10px. */
.app-footer small {
  font-size: inherit;
}

.app-footer__attribution a {
  color: var(--color-text-tertiary);
  text-decoration: underline dotted;
  text-underline-offset: 3px;
}

.app-footer__attribution a:hover {
  color: var(--color-text-secondary);
}

@media (max-width: 720px) {
  .app-footer {
    padding: var(--spacing-md);
    line-height: var(--typography-lineHeight-relaxed);
  }
}

.app-footer__disclaimer {
  font-family: var(--typography-font-mono);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* Back to /apps/: the shared Clearful mark on its own centred line at
   the very bottom, same size and rest opacity as the other apps,
   expressed in this project's tokens. Its hover move is unique to this
   app (see the register in the global CLAUDE.md): a surge, the mark
   rising like a busyness spike on the timeline and settling back. */
.app-footer__home {
  flex-basis: 100%;
  display: flex;
  justify-content: center;
  padding-top: var(--spacing-sm);
}

.rw-back-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--spacing-sm);   /* 40px hit area around a 24px mark */
  border-radius: var(--radius-pill);
}

.rw-back-logo {
  display: block;
  width: var(--spacing-lg);
  height: var(--spacing-lg);
  opacity: var(--opacity-25);
  transition: opacity var(--transition-base), transform var(--transition-slow);
}

.rw-back-link:focus-visible .rw-back-logo {
  opacity: var(--opacity-75);
}

@media (hover: hover) {
  .rw-back-link:hover .rw-back-logo {
    opacity: var(--opacity-75);
    animation: rw-surge var(--duration-slower) var(--easing-emphasized);
  }
}

@keyframes rw-surge {
  0%   { transform: translateY(0); }
  35%  { transform: translateY(calc(-1 * var(--spacing-sm))); }
  100% { transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .rw-back-link:hover .rw-back-logo {
    animation: none;
  }
}

/* ────────────── Boot: load-in ────────────── */

/*
 * The page opens with data-booting on <html> (set in index.html, before
 * first paint) and every top-level block invisible. app.js removes the
 * attribute once tokens, styles, fonts and the first forecast render
 * are all in, and the blocks then fade and rise into place one after
 * another, top to bottom; --reveal-i is a block's place in that order.
 * fill-mode backwards keeps a block invisible while it waits its turn
 * and leaves no transform behind afterwards, so Leaflet and the phone
 * dropdowns keep positioning against the viewport as before. app.js
 * drops the .reveal hook once the entrance has played, so a block that
 * is hidden and shown again later doesn't replay it.
 */
html[data-booting] .reveal {
  opacity: 0;
}

html:not([data-booting]) .reveal {
  animation: reveal-in var(--duration-slow) var(--easing-decelerate) backwards;
  animation-delay: calc(var(--reveal-i, 0) * var(--duration-stagger));
}

@keyframes reveal-in {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  html:not([data-booting]) .reveal {
    animation-name: reveal-fade;
    animation-delay: 0ms;
  }
}

@keyframes reveal-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* ────────────── Placeholder loading state ────────────── */

.placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: var(--spacing-xxxl);
  color: var(--color-text-tertiary);
  font-family: var(--typography-font-mono);
  font-size: var(--typography-caption);
  letter-spacing: 0.04em;
}

.placeholder__dot {
  display: inline-block;
  width: var(--spacing-sm);
  height: var(--spacing-sm);
  margin-right: var(--spacing-sm);
  border-radius: var(--radius-pill);
  background: var(--color-action-primary);
  opacity: var(--opacity-75);
  animation: pulse var(--duration-slower) ease-in-out infinite alternate;
}

@keyframes pulse {
  from { opacity: var(--opacity-25); }
  to   { opacity: var(--opacity-90); }
}

/* ────────────── Map ────────────── */

/*
 * Leaflet's default chrome (zoom buttons, attribution box, popups) is
 * styled to fit the dark surface system below. CSS-only — we don't
 * touch Leaflet's internal class names except to retheme them.
 */

.leaflet-container {
  background: var(--color-bg-page);
  font-family: var(--typography-font-body);
  font-size: var(--typography-caption);
}

/* The map itself is focusable (Leaflet's keyboard panning); the ring
   sits inside the box so the card's clipping doesn't eat it. */
.leaflet-container:focus-visible {
  outline-offset: -2px;
}

.leaflet-control-attribution {
  background: color-mix(in srgb, var(--color-bg-surface) 78%, transparent) !important;
  color: var(--color-text-tertiary) !important;
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  letter-spacing: 0.02em;
  padding: 2px var(--spacing-xs) !important;
  border-top-left-radius: var(--radius-sm);
}

.leaflet-control-attribution a {
  color: var(--color-text-secondary) !important;
  text-decoration: underline dotted;
  text-underline-offset: 2px;
}

.leaflet-bar {
  border: 1px solid var(--color-border-base) !important;
  box-shadow: var(--shadow-md) !important;
  border-radius: var(--radius-sm) !important;
  overflow: hidden;
}

.leaflet-bar a,
.leaflet-bar a:hover {
  background: var(--color-bg-raised) !important;
  color: var(--color-text-primary) !important;
  border-bottom-color: var(--color-border-subtle) !important;
}

@media (hover: hover) {
  .leaflet-bar a:hover {
    background: var(--color-bg-overlay) !important;
  }
}

/* Touch screens: finger-sized zoom buttons (Leaflet's own are 30px). */
@media (pointer: coarse) {
  .leaflet-touch .leaflet-bar a {
    width: var(--size-tap);
    height: var(--size-tap);
    line-height: var(--size-tap);
    font-size: var(--typography-title);
  }
}

/* Heat canvas — sits above tiles, below markers. */
.ef-heat-canvas {
  position: absolute;
  inset: 0;
  pointer-events: none;
  mix-blend-mode: screen;
}

/* Markers — subtle pulse on hover, no Leaflet default cyan. */
.leaflet-interactive.ef-marker {
  transition: opacity var(--transition-fast), transform var(--transition-fast);
}

.leaflet-interactive.ef-marker:hover {
  opacity: 1;
}

/* Popup */

.leaflet-popup-content-wrapper {
  background: var(--color-bg-overlay) !important;
  color: var(--color-text-primary) !important;
  border: 1px solid var(--color-border-base);
  border-radius: var(--radius-md) !important;
  box-shadow: var(--shadow-lg) !important;
}

.leaflet-popup-tip {
  background: var(--color-bg-overlay) !important;
  border: 1px solid var(--color-border-base);
}

.leaflet-popup-content {
  margin: var(--spacing-sm) var(--spacing-md) !important;
  font-family: var(--typography-font-body);
}

.leaflet-popup-close-button {
  color: var(--color-text-tertiary) !important;
  padding: 6px 8px !important;
}

.ef-popup {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-xs);
  min-width: 200px;
  max-width: 280px;
}

.ef-popup__name {
  font-weight: var(--typography-weight-semibold);
  font-size: var(--typography-caption);
  line-height: 1.3;
}

.ef-popup__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-xs);
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  color: var(--color-text-tertiary);
  letter-spacing: 0.02em;
}

.ef-popup__venue { color: var(--color-text-secondary); }
.ef-popup__sep   { opacity: 0.5; }

.ef-popup__link {
  font-size: var(--typography-micro);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-family: var(--typography-font-mono);
  padding-top: var(--spacing-xs);
  border-top: 1px dashed var(--color-border-subtle);
}

.ef-popup__attribution {
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  letter-spacing: 0.04em;
  color: var(--color-text-tertiary);
  margin-top: 2px;
}

/* Legend */

/* One wrapping line: [Quiet ramp Severe] · In / Out / Several ·
   "Modeled, not measured". The long wording is the tooltip; the
   footer and the timeline carry the modeled line in full. */
.ef-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-xs) var(--spacing-sm);
  background: color-mix(in srgb, var(--color-bg-surface) 88%, transparent);
  backdrop-filter: blur(6px) saturate(140%);
  border: 1px solid var(--color-border-base);
  border-radius: var(--radius-md);
  padding: var(--spacing-xs) var(--spacing-sm);
  box-shadow: var(--shadow-md);
  max-width: 360px;
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
}

.ef-legend__heat {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-xs);
}

.ef-legend__ramp {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  width: 48px;
  height: 8px;
  border-radius: var(--radius-pill);
  overflow: hidden;
  border: 1px solid var(--color-border-subtle);
}

.ef-legend__stop { display: block; height: 100%; }

.ef-legend__end {
  color: var(--color-text-tertiary);
}

.ef-legend__note {
  color: var(--color-text-tertiary);
}

/* (i) opens the long wording as a line under the legend — the hover
   title is unreachable on touch. */
.ef-legend__info {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--spacing-lg);
  height: var(--spacing-lg);
  padding: 0;
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-border-strong);
  background: transparent;
  color: var(--color-text-secondary);
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  font-weight: var(--typography-weight-semibold);
  line-height: 1;
  cursor: pointer;
  transition: color var(--transition-fast), border-color var(--transition-fast),
              background-color var(--transition-fast);
}

.ef-legend__info[aria-expanded="true"] {
  color: var(--color-text-primary);
  border-color: var(--color-text-tertiary);
  background: var(--color-bg-overlay);
}

@media (pointer: coarse) {
  .ef-legend__info::after {
    content: "";
    position: absolute;
    inset: calc(-1 * var(--spacing-sm));
  }
}

.ef-legend__detail {
  flex-basis: 100%;
  margin: 0;
  padding-top: var(--spacing-xs);
  border-top: 1px dashed var(--color-border-subtle);
  font-family: var(--typography-font-body);
  font-size: var(--typography-micro);
  letter-spacing: 0;
  text-transform: none;
  line-height: 1.4;
  color: var(--color-text-secondary);
}

/* Wheel-zoom hint. A bare wheel over the map scrolls the page instead of
   zooming (see gateScrollZoom in map.js); this says so for a beat when it
   happens. z-index clears Leaflet's panes (max 700) and stays under its
   controls (800) so +/− keeps working while the hint is up. */

.ef-zoom-hint {
  position: absolute;
  inset: 0;
  z-index: 750;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--spacing-md);
  pointer-events: none;
  background: color-mix(in srgb, var(--color-bg-page) 60%, transparent);
  backdrop-filter: blur(2px);
  color: var(--color-text-primary);
  font-family: var(--typography-font-body);
  font-size: var(--typography-body);
  text-align: center;
  opacity: 0;
  transition: opacity var(--transition-base);
}

.ef-zoom-hint[data-show="true"] {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .ef-zoom-hint { transition: none; }
}

/* M6: map-level transit empty-state notice. Renders top-left as a
   subdued chip — enough presence to explain why no station markers
   are visible without crowding the heat field or the legend. */

.ef-transit-empty {
  background: color-mix(in srgb, var(--color-bg-surface) 88%, transparent);
  backdrop-filter: blur(6px) saturate(140%);
  border: 1px solid color-mix(in srgb, var(--color-status-info) 35%, var(--color-border-base));
  border-radius: var(--radius-md);
  padding: var(--spacing-xs) var(--spacing-sm);
  box-shadow: var(--shadow-md);
  max-width: 260px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  color: var(--color-text-secondary);
}

.ef-transit-empty__eyebrow {
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-status-info);
}

.ef-transit-empty__body {
  font-size: var(--typography-micro);
  color: var(--color-text-secondary);
  line-height: 1.4;
}

/* M4: small inline transit-station legend row. */
.ef-legend__stations {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--spacing-sm);
}

.ef-legend__station {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-xs);
}

.ef-legend__station-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  transform: rotate(45deg);
  border-radius: var(--radius-xs);
  background: color-mix(in srgb, var(--color-status-info) 75%, var(--color-bg-overlay));
  border: 1px solid var(--color-status-info);
}

.ef-legend__station-dot[data-kind="dispersal"] {
  background: color-mix(in srgb, var(--color-status-warning) 75%, var(--color-bg-overlay));
  border-color: var(--color-status-warning);
}

.ef-legend__station-dot[data-state="multi"] {
  background: var(--color-verdict-severe);
  border-color: color-mix(in srgb, var(--color-verdict-severe) 60%, var(--color-text-primary));
}

/* ────────────── Timeline (M3) ────────────── */

.forecast-detail__timeline {
  padding: var(--spacing-md) var(--spacing-lg) var(--spacing-sm);
  border-top: 1px solid var(--color-border-subtle);
  background: linear-gradient(180deg, color-mix(in srgb, var(--color-highlight) 1.5%, transparent), transparent 50%),
              var(--color-bg-surface);
}

.ef-timeline {
  position: relative;   /* the details popover is placed against this box */
  display: flex;
  flex-direction: column;
  gap: var(--spacing-xs);
}

.ef-timeline__head {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--spacing-md);
  flex-wrap: wrap;
}

/* Chip row: Range All day Fit · [Stations Streetcar GO · Jump to Peak Now].
   On wide screens the bracketed options panel is display: contents, so
   its groups sit in the row like the others. On a phone the row is
   [All day Fit …………… Options ▾] and the panel is a dropdown under the
   Options chip. */
.ef-timeline__head {
  position: relative;   /* anchors the phone dropdown */
}

.ef-timeline__chips {
  margin-left: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--spacing-xs);
}

.ef-timeline__options {
  display: contents;
}

.ef-timeline__group {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-xs);
}

/* Group caption ("Stations", "Range"): says what the chips beside it
   do, which the hover titles alone could not on touch. */
.ef-timeline__cap {
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
  margin: 0 var(--spacing-xs) 0 var(--spacing-sm);
  user-select: none;
}

.ef-timeline__chips > .ef-timeline__group:first-child .ef-timeline__cap {
  margin-left: 0;
}

.ef-timeline__options-btn {
  display: none;
}

@media (max-width: 720px) {
  .ef-timeline__chips {
    margin-left: 0;
    width: 100%;
    justify-content: flex-start;
  }

  /* The row: the two range chips (their caption is implied), then the
     Options chip at the far right. */
  .ef-timeline__group--range .ef-timeline__cap {
    display: none;
  }

  .ef-timeline__options-btn {
    display: inline-block;
    margin-left: auto;
  }

  /* The dropdown: one group per line, captions leading. */
  .ef-timeline__options {
    position: absolute;
    top: calc(100% + var(--spacing-xs));
    right: 0;
    z-index: 6;   /* over the brush and lanes, under nothing that matters */
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--spacing-sm);
    padding: var(--spacing-smd);
    background: var(--color-bg-overlay);
    border: 1px solid var(--color-border-base);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
  }

  .ef-timeline__options[data-collapsed="true"] {
    display: none;
  }

  .ef-timeline__options .ef-timeline__cap {
    margin-left: 0;
    min-width: var(--spacing-xxxl);   /* captions align, chips line up */
  }

  .ef-timeline__sep {
    display: none;
  }
}

.ef-timeline__canvas {
  display: block;
  width: 100%;
  height: 160px;
  cursor: ew-resize;
  /* Vertical swipes scroll the page; the scrubber only takes over once
     a touch moves sideways (see attachPointerEvents in timeline.js). */
  touch-action: pan-y;
  user-select: none;
  outline: none;
}

.ef-timeline__canvas:focus-visible {
  box-shadow: inset 0 0 0 1px var(--color-interactive-focus);
}

@media (max-width: 720px) {
  .ef-timeline__canvas { height: 130px; }
}

.ef-timeline__legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-md);
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
  padding-top: var(--spacing-xs);
  border-top: 1px dashed var(--color-border-subtle);
}

.ef-timeline__legend-item {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-xs);
}

.ef-timeline__legend-swatch {
  display: inline-block;
  width: 12px;
  height: 8px;
  border-radius: var(--radius-xs);
  border: 1px solid var(--color-border-subtle);
}

.ef-timeline__legend-swatch--arrival  { background: color-mix(in srgb, var(--color-status-info) 30%, transparent); }
.ef-timeline__legend-swatch--dispersal{ background: color-mix(in srgb, var(--color-status-warning) 30%, transparent); }
.ef-timeline__legend-swatch--peak     { background: color-mix(in srgb, var(--color-verdict-severe) 30%, transparent); }

.ef-timeline__legend-note {
  margin-left: auto;
  text-transform: none;
  letter-spacing: 0.04em;
  color: var(--color-text-tertiary);
}

@media (max-width: 720px) {
  .forecast-detail__timeline {
    padding: var(--spacing-md) var(--spacing-md) var(--spacing-sm);
  }
}

/* ────────────── Timeline chips + station lanes ────────────── */

/* Kind toggles (Streetcar / GO) and the Overnight switch share the chip
   style; aria-pressed carries the on/off look. */
.ef-timeline__toggles {
  display: inline-flex;
  gap: var(--spacing-xs);
}

.ef-timeline__toggles:empty {
  display: none;
}

/* Dot between the chip groups: Streetcar / GO · All day / Fit · Peak / Now.
   The first dot goes with the toggles when a city has none. */
.ef-timeline__sep {
  align-self: center;
  color: var(--color-text-muted);
  font-size: var(--typography-caption);
  line-height: 1;
  user-select: none;
}

/* Line pills. With a configured agency colour (city.json line_colors)
   the pill is painted inline as a line badge; the class colours below
   are the fallback for cities without one. */
.line-pill {
  display: inline-block;
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  font-weight: var(--typography-weight-semibold);
  letter-spacing: 0.02em;
  line-height: 1.3;
  padding: 0 var(--spacing-sm);
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-border-base);
  color: var(--color-text-secondary);
  white-space: nowrap;
}

.line-pill--subway {
  color: var(--color-status-info);
  border-color: color-mix(in srgb, var(--color-status-info) 40%, transparent);
  background: color-mix(in srgb, var(--color-status-info) 10%, transparent);
}

.line-pill--go {
  color: var(--color-status-success);
  border-color: color-mix(in srgb, var(--color-status-success) 40%, transparent);
  background: color-mix(in srgb, var(--color-status-success) 10%, transparent);
}

/* Range brush: a miniature of the whole day above the chart with two
   handles. Same side margins as the plot so it lines up. */
.ef-brush {
  position: relative;
  height: 48px;
  margin: var(--spacing-md) 0 var(--spacing-xs);
  touch-action: pan-y;   /* as the chart: sideways drags only */
  user-select: none;
}

.ef-brush__canvas {
  display: block;
  width: 100%;
  height: 28px;
}

.ef-brush__sel {
  position: absolute;
  top: 0;
  height: 28px;
  cursor: grab;
  background: color-mix(in srgb, var(--color-text-accent) 6%, transparent);
}

.ef-brush--dragging .ef-brush__sel {
  cursor: grabbing;
}

/* The handle's hit area is the full 48px brush height and 32px wide;
   the visible grip stays an 8px bar in the middle of it. */
.ef-brush__handle {
  position: absolute;
  top: 0;
  width: var(--spacing-xl);
  height: 100%;
  margin-left: calc(-1 * var(--spacing-md));
  padding: 0;
  background: transparent;
  border: 0;
  cursor: ew-resize;
  touch-action: pan-y;
}

.ef-brush__grip {
  position: absolute;
  top: 2px;
  height: var(--spacing-lg);
  left: var(--spacing-smd);
  width: 8px;
  border-radius: var(--radius-xs);
  background: var(--color-bg-overlay);
  border: 1px solid var(--color-border-strong);
  box-shadow: var(--shadow-sm);
  transition: background var(--transition-fast), border-color var(--transition-fast);
}

.ef-brush__handle:hover .ef-brush__grip,
.ef-brush__handle:focus-visible .ef-brush__grip {
  background: var(--color-bg-modal);
  border-color: var(--color-text-secondary);
}

.ef-brush__handle:focus-visible {
  outline: none;
}

.ef-brush__handle:focus-visible .ef-brush__grip {
  box-shadow: 0 0 0 2px var(--color-interactive-focus);
}

/* Handle times sit INSIDE the range, flush with the grip (left edge
   of the start handle, right edge of the end handle), so nothing hangs
   past the handles. On a narrow range they'd meet, so they flip out. */
.ef-brush__label {
  position: absolute;
  top: 30px;
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  font-variant-numeric: tabular-nums;
  color: var(--color-text-secondary);
  white-space: nowrap;
  pointer-events: none;
}

.ef-brush__handle--from .ef-brush__label { left: var(--spacing-smd); }
.ef-brush__handle--to   .ef-brush__label { right: var(--spacing-smd); }

.ef-brush__handle--tight.ef-brush__handle--from .ef-brush__label { left: auto; right: calc(var(--spacing-md) + var(--spacing-xs)); }
.ef-brush__handle--tight.ef-brush__handle--to   .ef-brush__label { right: auto; left: calc(var(--spacing-md) + var(--spacing-xs)); }

/* Lanes: a chip anchored at each window, over the same axis as the
   chart. The event lane sits above the curve (one row, stacked when
   events overlap); station lanes sit under it, one row per station.
   No side margins, like the canvas plot, so chips line up with the
   axis. Positions are percentages of the visible range, so a resize
   never needs a re-measure. */
.ef-lanes {
  /* Chip height and the gap between stacked rows. timeline.js reads
     both when it stacks overlapping chips, so touch sizing below
     needs no JS change. */
  --lane-chip-h: var(--spacing-lg);
  --lane-row-gap: var(--spacing-xs);
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--lane-row-gap);
  margin: var(--spacing-xs) 0 0;
}

@media (pointer: coarse) {
  .ef-lanes {
    --lane-chip-h: var(--size-control);
    --lane-row-gap: var(--spacing-sm);
  }
}

.ef-lanes--events {
  margin-bottom: 0;
}

.ef-lane {
  position: relative;
  height: var(--lane-chip-h);
}

.ef-lane__chip {
  position: absolute;
  top: 0;
  height: var(--lane-chip-h);
  max-width: 100%;
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-xs);
  padding: 0 var(--spacing-xs);
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border-base);
  background: var(--color-bg-raised);
  font-family: var(--typography-font-body);
  font-size: var(--typography-micro);
  font-weight: var(--typography-weight-semibold);
  color: var(--color-text-primary);
  white-space: nowrap;
  cursor: pointer;
  transition: box-shadow var(--transition-fast), border-color var(--transition-fast);
}

.ef-lane__chip[data-phase="arrival"] {
  border-color: color-mix(in srgb, var(--color-status-info) 55%, transparent);
  background: color-mix(in srgb, var(--color-status-info) 16%, var(--color-bg-raised));
}

.ef-lane__chip[data-phase="dispersal"] {
  border-color: color-mix(in srgb, var(--color-status-warning) 55%, transparent);
  background: color-mix(in srgb, var(--color-status-warning) 16%, var(--color-bg-raised));
}

.ef-lane__chip[data-phase="both"] {
  border-color: color-mix(in srgb, var(--color-verdict-severe) 55%, transparent);
  background: color-mix(in srgb, var(--color-verdict-severe) 16%, var(--color-bg-raised));
}

/* Event chips take the curve's accent so they read as "the event",
   not as an In / Out window. */
.ef-lane__chip[data-phase="event"] {
  border-color: color-mix(in srgb, var(--color-text-accent) 45%, transparent);
  background: color-mix(in srgb, var(--color-text-accent) 12%, var(--color-bg-raised));
}

/* Start-time badge inside an event chip: same footprint as a line
   badge, neutral colour. */
.ef-lane__badge {
  flex: none;
  font-family: var(--typography-font-mono);
  font-size: var(--typography-tiny);
  font-weight: var(--typography-weight-semibold);
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
  padding: 0 var(--spacing-xs);
  border-radius: var(--radius-xs);
  color: var(--color-text-secondary);
  background: color-mix(in srgb, var(--color-text-primary) 8%, transparent);
}

@media (hover: hover) {
  .ef-lane__chip:hover {
    border-color: var(--color-border-strong);
    z-index: 2;
  }
}

.ef-lane__chip:focus-visible {
  border-color: var(--color-border-strong);
  z-index: 2;
}

.ef-lane__chip[data-active="true"] {
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-text-primary) 35%, transparent);
  z-index: 1;
}

.ef-lane__name {
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 160px;
}

/* Event chips span a whole crowd window, so the name gets more room. */
.ef-lane--events .ef-lane__name {
  max-width: 320px;
}

/* Streetcar stops carry long GTFS names ("King St West at Bay St");
   the chip shows a compact form (app.js compactStopName) and caps it
   tighter than a subway name. The popover keeps the full name. */
.ef-lane[data-kind="streetcar"] .ef-lane__name {
  max-width: 112px;
}

/* Badge inside a lane chip: one step smaller than elsewhere. */
.ef-lane__chip .line-pill {
  font-size: var(--typography-tiny);
  line-height: 1.2;
  padding: 0 var(--spacing-xs);
}

/* Phones: the axis is short, so chips keep the line badge (the number
   is the fastest way to tell stations apart) but cap the name tighter.
   Overlapping chips are stacked into extra rows by the timeline itself,
   measured. */
@media (max-width: 720px) {
  .ef-lane__name,
  .ef-lane--events .ef-lane__name { max-width: 160px; }
}

.ef-lanes__scrub {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: color-mix(in srgb, var(--color-text-primary) 45%, transparent);
  pointer-events: none;
  z-index: 0;
}

.ef-lanes__empty {
  padding: var(--spacing-sm) var(--spacing-md);
  border: 1px dashed var(--color-border-subtle);
  border-radius: var(--radius-md);
  color: var(--color-text-tertiary);
  font-size: var(--typography-caption);
  line-height: 1.45;
}

/* Details popover for a chip: the full time, phase, cause. z-index
   clears Leaflet's panes (max 700) and controls (800): the lanes sit
   right above the map and a popover under a low chip overlaps it. */
.ef-pop {
  position: absolute;
  z-index: 900;
  min-width: 240px;
  max-width: min(320px, 100%);
  padding: var(--spacing-sm) var(--spacing-md);
  display: flex;
  flex-direction: column;
  gap: var(--spacing-xs);
  background: var(--color-bg-overlay);
  border: 1px solid var(--color-border-base);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  color: var(--color-text-primary);
}

.ef-pop__head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--spacing-xs);
}

.ef-pop__name {
  font-size: var(--typography-body);
  font-weight: var(--typography-weight-semibold);
}

.ef-pop__kind {
  margin-left: auto;
  font-size: var(--typography-micro);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
}

.ef-pop__when {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--spacing-xs) var(--spacing-sm);
}

.ef-pop__time {
  font-family: var(--typography-font-mono);
  font-size: var(--typography-title);
  font-weight: var(--typography-weight-semibold);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  line-height: 1.1;
}

.ef-pop__phase {
  font-size: var(--typography-micro);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-status-info);
}

.ef-pop__phase[data-phase="dispersal"] { color: var(--color-status-warning); }
.ef-pop__phase[data-phase="both"]      { color: var(--color-verdict-severe); }

.ef-pop__cause {
  font-size: var(--typography-caption);
  color: var(--color-text-secondary);
  line-height: 1.35;
}

.ef-pop__via {
  font-size: var(--typography-micro);
  color: var(--color-text-tertiary);
}

.ef-pop__fine {
  font-size: var(--typography-micro);
  color: var(--color-text-tertiary);
  padding-top: var(--spacing-xs);
  border-top: 1px dashed var(--color-border-subtle);
}

/* Close button and scrim exist only for the phone sheet below. */
.ef-pop__close,
.ef-pop-scrim {
  display: none;
}

/* Phones: the popover becomes a sheet at the bottom of the screen over
   a scrim (timeline.js showPop sets data-sheet), so it can never hide
   under the map. Tap the scrim or the x to close. */
@keyframes ef-pop-sheet-in {
  from { opacity: 0; transform: translateY(var(--spacing-md)); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (max-width: 720px) {
  .ef-pop-scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 1000;   /* over the map (Leaflet controls top out at 800) */
    background: color-mix(in srgb, var(--color-bg-page) 60%, transparent);
  }

  .ef-pop[data-sheet="true"] {
    position: fixed;
    top: auto;
    left: var(--spacing-md);
    right: var(--spacing-md);
    bottom: max(var(--spacing-md), env(safe-area-inset-bottom));
    z-index: 1001;
    min-width: 0;
    max-width: none;
    max-height: 70vh;
    overflow-y: auto;
    padding: var(--spacing-md) var(--spacing-lg);
    padding-right: calc(var(--spacing-lg) + var(--size-tap));
    gap: var(--spacing-sm);
    background: var(--color-bg-modal);
    animation: ef-pop-sheet-in var(--duration-fast) var(--easing-decelerate);
  }

  .ef-pop[data-sheet="true"] .ef-pop__close {
    display: flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    top: var(--spacing-xs);
    right: var(--spacing-xs);
    width: var(--size-tap);
    height: var(--size-tap);
    padding: 0;
    border: 0;
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--color-text-secondary);
    font-size: var(--typography-title);
    line-height: 1;
    cursor: pointer;
  }

  .ef-pop[data-sheet="true"] .ef-pop__close::before {
    content: "\00D7";
  }

  .ef-pop[data-sheet="true"] .ef-pop__close:focus-visible {
    outline: 2px solid var(--color-interactive-focus);
    outline-offset: -4px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ef-pop[data-sheet="true"] { animation: none; }
}

/* ────────────── Detail rail (M3) ────────────── */

.forecast-detail__rail {
  padding: var(--spacing-md) var(--spacing-lg) var(--spacing-lg);
  border-top: 1px solid var(--color-border-subtle);
  background: var(--color-bg-surface);
}

@media (max-width: 720px) {
  .forecast-detail__rail {
    padding: var(--spacing-md);
  }
}

.rail__head {
  display: flex;
  align-items: baseline;
  gap: var(--spacing-sm);
  margin: 0 0 var(--spacing-sm);   /* it is an h3 */
}

.rail__eyebrow {
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
}

.rail__time {
  font-family: var(--typography-font-display);
  font-size: var(--typography-title);
  font-weight: var(--typography-weight-semibold);
  line-height: 1;
  color: var(--color-text-primary);
}

.rail__empty {
  padding: var(--spacing-md);
  border: 1px dashed var(--color-border-subtle);
  border-radius: var(--radius-md);
  color: var(--color-text-tertiary);
  font-size: var(--typography-caption);
}

/* v2: one compact row per event — phase · name + venue/time · Tickets.
   The event lane above the chart carries the detail. */
.rail__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-xs);
}

.rail__item {
  background: var(--color-bg-raised);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  padding: var(--spacing-xs) var(--spacing-sm);
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--spacing-sm);
}

.rail__main {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.rail__phase {
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 2px var(--spacing-xs);
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-border-subtle);
  background: var(--color-bg-overlay);
  white-space: nowrap;
  flex-shrink: 0;
}

.rail__phase--arrival   { color: var(--color-status-info);    border-color: color-mix(in srgb, var(--color-status-info) 35%, transparent); }
.rail__phase--during    { color: var(--color-text-accent);    border-color: color-mix(in srgb, var(--color-text-accent) 30%, transparent); }
.rail__phase--dispersal { color: var(--color-status-warning); border-color: color-mix(in srgb, var(--color-status-warning) 35%, transparent); }

.rail__name {
  font-size: var(--typography-caption);
  font-weight: var(--typography-weight-semibold);
  color: var(--color-text-primary);
  line-height: 1.3;
  /* Two lines before the ellipsis, as on the day pills. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  overflow-wrap: anywhere;
}

.rail__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-xs);
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  color: var(--color-text-tertiary);
  letter-spacing: 0.04em;
}

.rail__venue    { color: var(--color-text-secondary); }
.rail__sep      { opacity: 0.45; }
.rail__category { text-transform: uppercase; letter-spacing: 0.06em; }

.rail__people { color: var(--color-text-tertiary); }

.rail__note {
  color: var(--color-text-secondary);
}

/* A small pill, not bare text: 24px tall on desktop (the WCAG 2.5.8
   floor), 32px on touch screens. */
.rail__link {
  display: inline-flex;
  align-items: center;
  min-height: var(--spacing-lg);
  padding: 0 var(--spacing-sm);
  border-radius: var(--radius-pill);
  border: 1px solid color-mix(in srgb, var(--color-link-base) 35%, transparent);
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  transition: border-color var(--transition-fast), color var(--transition-fast);
}

.rail__link:hover {
  border-color: var(--color-link-hover);
}

@media (pointer: coarse) {
  .rail__link {
    min-height: var(--size-control);
    padding: 0 var(--spacing-smd);
  }
}

/* ────────────── M4 Transit station markers ────────────── */

/* Wrapper class on the L.divIcon. Leaflet adds .leaflet-div-icon by
   default; we strip its background/border via the empty .ef-station-marker
   selector so only our inner dot renders. */
.leaflet-div-icon.ef-station-marker {
  background: transparent;
  border: 0;
}

.ef-station-marker__dot {
  display: block;
  width: 18px;
  height: 18px;
  /* Rotated square reads as "this is not the same shape as an event
     marker" without going pictogram. */
  transform: rotate(45deg);
  border-radius: var(--radius-xs);
  /* Dormant default — barely-there in the candidate set so the eye
     locks onto the active ones. */
  background: color-mix(in srgb, var(--color-bg-overlay) 85%, var(--color-text-primary) 15%);
  border: 1px solid var(--color-border-base);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
  opacity: 0.45;
  transition:
    background var(--transition-base),
    border-color var(--transition-base),
    opacity var(--transition-base),
    transform var(--transition-base),
    box-shadow var(--transition-base);
}

.ef-station-marker__dot[data-state="dormant"] {
  /* Hide dormant markers entirely so the map only foregrounds the
     stations actually being impacted at the current scrubber bucket. */
  opacity: 0;
  pointer-events: none;
  transform: rotate(45deg) scale(0.6);
}

.ef-station-marker__dot[data-state="single"] {
  opacity: 1;
  background: color-mix(in srgb, var(--color-status-info) 75%, var(--color-bg-overlay));
  border-color: var(--color-status-info);
  transform: rotate(45deg) scale(1);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-status-info) 20%, transparent),
              0 1px 4px rgba(0, 0, 0, 0.5);
}

.ef-station-marker__dot[data-state="single"][data-kind="dispersal"] {
  background: color-mix(in srgb, var(--color-status-warning) 75%, var(--color-bg-overlay));
  border-color: var(--color-status-warning);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-status-warning) 20%, transparent),
              0 1px 4px rgba(0, 0, 0, 0.5);
}

.ef-station-marker__dot[data-state="multi"] {
  /* Multi-event flag — reserve the brightest accent so the eye finds it
     even on a saturated Severe day. */
  opacity: 1;
  background: var(--color-verdict-severe);
  border-color: color-mix(in srgb, var(--color-verdict-severe) 60%, var(--color-text-primary));
  transform: rotate(45deg) scale(1.15);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-verdict-severe) 25%, transparent),
              0 2px 6px rgba(0, 0, 0, 0.6);
}

/* Shape per station kind: subway keeps the rotated square, GO reads as
   a ring, streetcar stops are smaller squares. */
.ef-station-marker__dot[data-transit="go"] {
  border-radius: var(--radius-pill);
}

.ef-station-marker__dot[data-transit="streetcar"] {
  width: 12px;
  height: 12px;
  margin: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .ef-station-marker__dot { transition: none; }
}

/* Phones: one line — ramp, modeled note, (i) — so the legend covers as
   little of a 320px map as possible. The ramp's end words go (the
   colours speak for themselves and the detail line explains them); the
   station glyphs are explained by the lane chips. */
@media (max-width: 720px) {
  .ef-legend {
    max-width: 280px;
  }
  .ef-legend__stations,
  .ef-legend__end {
    display: none;
  }
  .ef-legend__note {
    white-space: nowrap;
  }
}

/* Popup specifics for stations */
.ef-popup--station .ef-popup__eyebrow {
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
}

.ef-popup__lines {
  display: inline-flex;
  gap: 4px;
  flex-wrap: wrap;
}

.ef-popup__line {
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  font-weight: var(--typography-weight-semibold);
  letter-spacing: 0.02em;
  padding: 1px var(--spacing-xs);
  border-radius: var(--radius-pill);
  border: 1px solid color-mix(in srgb, var(--color-status-info) 35%, transparent);
  color: var(--color-status-info);
  background: color-mix(in srgb, var(--color-status-info) 8%, transparent);
}

.ef-popup__lines-empty {
  font-family: var(--typography-font-mono);
  font-size: var(--typography-micro);
  color: var(--color-text-tertiary);
}

.ef-popup__fine {
  font-family: var(--typography-font-body);
  font-size: var(--typography-micro);
  color: var(--color-text-tertiary);
  margin-top: var(--spacing-xs);
  padding-top: var(--spacing-xs);
  border-top: 1px dashed var(--color-border-subtle);
}
