/* ======================================================================
   BUSPULS · Daylight Edition
   ----------------------------------------------------------------------
   Bright, transit-poster aesthetic. Warm paper background, white cards
   with soft drop shadows, signage-orange accent. Same information
   density as the v1 glass build – completely re-laid-out.
   Fonts: IBM Plex Sans Condensed (display – formerly DM Serif Display),
          DM Sans (UI), JetBrains Mono (figures)
   ====================================================================== */

:root {
  /* Type stack — --serif token is now condensed grotesque, not a serif.
     Name kept for backward-compat with existing var(--serif) references. */
  --serif: 'IBM Plex Sans Condensed', 'Inter', system-ui, -apple-system, "Segoe UI", sans-serif;
  --sans:  'DM Sans', system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
  --mono:  'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Paper / ink — soft blue-tinted off-white canvas */
  --paper:       #eef4fa;             /* light-blue tinted off-white */
  --paper-deep:  #dde8f2;             /* slightly deeper, used behind tiles */
  --surface:     #ffffff;
  --surface-2:   #f4f8fc;
  --ink:         #14202c;             /* deep blue-tinged primary text */
  --ink-2:       #3a4a5c;             /* secondary text */
  --muted:       #5e6c7d;             /* darkened 2026-06-09 for WCAG AA (≥4.5:1 on paper & white) */
  --dim:         #b3c0cd;             /* non-text only (bars, separators, placeholders) */
  --hairline:    rgba(20, 40, 64, 0.10);
  --hairline-2:  rgba(20, 40, 64, 0.06);
  --hl:          rgba(20, 40, 64, 0.045);
  --hl-hover:    rgba(20, 40, 64, 0.07);

  /* Brand — Stadtwerke cyan, locked to the BusPuls.MS mark */
  --brand:       #00B4F0;
  --brand-2:     #0091C2;
  --brand-soft:  #e1f7fe;
  --brand-line:  rgba(0, 180, 240, 0.32);

  /* Accent — coral, used by the heart/pulse in the mark and by .MS in the wordmark */
  --accent:      #FF4757;
  --accent-soft: #ffe9eb;
  --accent-line: rgba(255, 71, 87, 0.32);

  /* Signal palette (legible on white) */
  --good:        #15803d;
  --good-soft:   #dcfce7;
  --warn:        #b45309;
  --warn-soft:   #fef0c7;
  --bad:         #c2410c;
  --bad-soft:    #fee4cf;
  --crit:        #b91c1c;
  --crit-soft:   #fee2e2;
  --info:        #1d4ed8;
  --info-soft:   #dbeafe;
  --leicht:      #ca8a04;          /* golden yellow — Leichte Verzögerungen */
  --leicht-soft: #fef9c3;
  --early:       #1d4ed8;

  --radius-s:  8px;
  --radius:    12px;
  --radius-l:  18px;

  --shadow-sm: 0 1px 2px rgba(20,40,64,0.06), 0 1px 1px rgba(20,40,64,0.04);
  --shadow-md: 0 4px 18px rgba(20,40,64,0.08), 0 2px 4px rgba(20,40,64,0.05);
  --shadow-lg: 0 12px 36px rgba(20,40,64,0.12), 0 4px 12px rgba(20,40,64,0.07);

  --topbar-h: 82px;
  --drawer-bottom: 82px;             /* updated in JS via ResizeObserver */
  --footer-h: 28px;                  /* single-line strip; bumped automatically when wrapped on narrow viewports */
}

* { box-sizing: border-box; }
html, body {
  margin: 0; height: 100%;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
button { font-family: inherit; }
button, [role="button"] { -webkit-tap-highlight-color: transparent; }
:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 4px; }

a { color: var(--brand-2); text-decoration: none; }
a:hover { text-decoration: underline; }

#map {
  position: absolute;
  inset: var(--drawer-bottom) 0 var(--footer-h) 0;
  background: var(--paper-deep);
  transition: inset .35s cubic-bezier(.2,.7,.2,1);
}

/* ===== Leaflet integration ===== */
.leaflet-container { font-family: var(--sans); background: var(--paper-deep); }
.leaflet-control-zoom a, .leaflet-control-layers-toggle {
  background: var(--surface) !important;
  color: var(--ink-2) !important;
  border: 1px solid var(--hairline) !important;
  box-shadow: var(--shadow-sm) !important;
}
.leaflet-control-zoom a { font-weight: 600; }
.leaflet-control-zoom a:hover { background: var(--brand-soft) !important; color: var(--brand-2) !important; }
.leaflet-control-layers {
  border-radius: var(--radius) !important;
  border: 1px solid var(--hairline) !important;
  box-shadow: var(--shadow-md) !important;
  background: var(--surface) !important;
  color: var(--ink) !important;
  padding: 6px 8px !important;
}
.leaflet-control-layers-expanded { padding: 10px 12px !important; }
.leaflet-control-attribution {
  background: rgba(255,255,255,0.75) !important;
  color: var(--ink-2) !important;
  font-size: 10.5px;
  border-radius: 6px 0 0 0;
  backdrop-filter: blur(6px);
}
.leaflet-control-attribution a { color: var(--brand-2) !important; }
.leaflet-top.leaflet-left {
  top: calc(var(--drawer-bottom) + 12px);
  left: 14px;
  transition: top .35s cubic-bezier(.2,.7,.2,1);
}
/* Lift Leaflet attribution snug above the site footer */
.leaflet-bottom.leaflet-right { right: 0 !important; }
.leaflet-bottom              { bottom: var(--footer-h) !important; }
.leaflet-bottom .leaflet-control { margin-bottom: 0 !important; }

/* ===== Topbar (header bar) ===== */
#topbar {
  position: fixed;
  z-index: 800;
  top: 0; left: 0; right: 0;
  height: var(--topbar-h);
  background: rgba(252, 254, 255, 0.92);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
          backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid var(--hairline);
  display: flex; align-items: center;
  padding: 0 24px;
  gap: 28px;
}

#brand { display: flex; align-items: center; gap: 12px; flex: 0 0 auto; min-width: 0; }
#brand .mark {
  width: auto; height: 46px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent;     /* no enclosing shape */
  border: 0;
  flex-shrink: 0;
}
#brand .mark img,
#brand .mark svg { width: auto; height: 46px; display: block; }
#brand .brand-text {
  display: flex; flex-direction: column; line-height: 1;
}
#brand h1 {
  font-family: var(--serif);
  font-size: 23px;
  font-weight: 600;          /* Plex Condensed 600 ≈ visual weight of old DM Serif 400 */
  margin: 0;
  letter-spacing: 0.1px;
  color: var(--brand);       /* cyan, matches the wordmark in the locked lockup */
}
#brand h1 .brand-suffix { color: var(--accent); }

@keyframes pulse-good {
  0%   { box-shadow: 0 0 0 0   rgba(21,128,61,0.55); }
  60%  { box-shadow: 0 0 0 7px rgba(21,128,61,0); }
  100% { box-shadow: 0 0 0 0   rgba(21,128,61,0); }
}

/* KPI strip – pills sit left-aligned next to the brand, capped at a tighter
   max-width so they don't span the chrome. Stretching to align all tiles
   to the same height (Auslastung's bus icon currently sets the tallest). */
#kpi-strip {
  flex: 1 1 auto;
  display: flex;
  flex-wrap: nowrap;
  gap: 10px;
  align-items: stretch;
  justify-content: flex-start;
  min-width: 0;
}
.kpi {
  /* basis 144 px (uniform width across pills), can shrink to min-width on
     narrower viewports, never grows beyond basis. */
  flex: 0 1 144px;
  min-width: 96px;
  display: flex; flex-direction: column;
  justify-content: flex-start;
  padding: 9px 13px 8px 13px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  position: relative;
  transition: border-color .15s, transform .15s;
}
/* Trend arrow — coloured badge centred vertically on the right edge.
   Hidden when stable; green pill = improving, red pill = worsening. */
.kpi-trend {
  position: absolute;
  right: 9px;
  top: 50%;
  transform: translateY(-50%);
  display: flex; align-items: center; justify-content: center;
  width: 20px; height: 20px;
  border-radius: 5px;
  opacity: 0;
  transition: opacity .35s;
  pointer-events: none;
}
.kpi-trend.trend-good,
.kpi-trend.trend-bad  { opacity: 1; }
.kpi-trend.trend-good { color: var(--good); background: var(--good-soft); }
.kpi-trend.trend-bad  { color: var(--crit); background: var(--crit-soft); }
.kpi-trend svg { width: 12px; height: 12px; display: block; }

/* Auslastung bus icon inside the topbar KPI tile.
   The bus silhouette fills from the bottom; the fill height encodes the
   weighted fleet-average load. Color of fill AND outline tighten as the
   fleet gets fuller (low=green, med=amber, hi=red) to give the "different
   weights" feel for full vs medium vs empty. */
.bus-fill-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  margin: 4px 0 3px 0;
}
.bus-fill-icon {
  width: 76px; height: 28px;
  flex-shrink: 0;
  color: var(--ink-2);
  overflow: visible;
  transition: color .35s ease;
}
.bus-fill-icon .bus-fill-level {
  fill: var(--dim);
  transition: y .55s cubic-bezier(.2,.7,.2,1),
              height .55s cubic-bezier(.2,.7,.2,1),
              fill .3s ease;
}
.bus-fill-icon[data-load="low"]                       { color: var(--good); }
.bus-fill-icon[data-load="low"] .bus-fill-level       { fill:  var(--good); }
.bus-fill-icon[data-load="med"]                       { color: var(--warn); }
.bus-fill-icon[data-load="med"] .bus-fill-level       { fill:  var(--warn); }
.bus-fill-icon[data-load="hi"]                        { color: var(--crit); }
.bus-fill-icon[data-load="hi"]  .bus-fill-level       { fill:  var(--crit); }
.bus-fill-icon[data-load="hi"]  rect[stroke]          { stroke-width: 1.7; }  /* heavier outline when full */
.bus-fill-icon[data-load="unk"] .bus-fill-level       { fill:  var(--dim); }

.bus-fill-num {
  display: none;
  font-family: var(--mono);
  font-size: 17px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  line-height: 1;
  transition: color .3s ease;
}
.bus-fill-num.tone-low { color: var(--good); }
.bus-fill-num.tone-med { color: var(--warn); }
.bus-fill-num.tone-hi  { color: var(--crit); font-weight: 600; }

.kpi-label {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.3px;          /* was 1.1px – the wide tracking pushed the
                                     longest labels ("Busauslastung",
                                     "Verzögerungen") past the tile width */
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 2px;
  /* Wrap rather than clip: with the tighter tracking the labels fit on one
     line at every width where their tiles are shown, but should anything
     still overflow (locale, zoom, font fallback) it now wraps instead of
     being cut off with an ellipsis. */
  white-space: normal;
}
.kpi-value {
  font-family: var(--mono);
  font-size: 26px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  line-height: 1.05;
  white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.kpi-value .unit { font-size: 13px; color: var(--muted); margin-left: 3px; font-weight: 400; }
.kpi-value .sub  { font-size: 13px; color: var(--muted); margin-left: 4px; font-family: var(--sans); }
.kpi-value.good { color: var(--good); }
.kpi-value.warn { color: var(--warn); }
.kpi-value.bad  { color: var(--bad);  }
.kpi-value.crit { color: var(--crit); }
.kpi-value.compact { font-size: 15px; font-family: var(--sans); }

.line-tag {
  display: inline-flex; align-items: center; gap: 6px;
}
.line-tag .line-pill {
  font-size: 10.5px; min-width: 26px; height: 19px; padding: 0 6px;
}
.stoer-strip {
  display: inline-flex; align-items: center; gap: 7px;
  flex-wrap: nowrap;
  overflow: hidden;
}
.stoer-tag {
  display: inline-flex; align-items: center;
}
.stoer-tag .line-pill {
  font-size: 13px; min-width: 30px; height: 22px; padding: 0 8px;
  font-weight: 600;
  border-radius: 6px;
}

/* Right-side controls in topbar */
.topbar-actions { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }

/* Merged status pill: dot + system-health label + hover-revealed timestamp.
   The pill takes a tone class (.tone-good / warn / bad / info) that paints
   both the dot and the soft background. Network-state classes (.err on dot
   for fetch failure, .load on dot during the very first poll) override
   the dot color but keep the pill quietly neutral. */
#status-pill {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 6px 14px 6px 11px;
  border-radius: 999px;
  border: 1px solid var(--hairline);
  background: var(--surface);
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-2);
  cursor: default;
  box-shadow: var(--shadow-sm);
  transition: background .25s, border-color .25s, color .25s;
  /* div replaces former button – suppress default outline in favour of
     the :focus-visible rule below, which already shows a brand outline */
  outline: none;
}
#status-pill:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 999px; }
#status-pill.tone-good { background: var(--good-soft); border-color: rgba(21,128,61,0.25);  color: var(--good); }
#status-pill.tone-warn { background: var(--warn-soft); border-color: rgba(180,83,9,0.25);   color: var(--warn); }
#status-pill.tone-bad  { background: var(--crit-soft); border-color: rgba(185,28,28,0.25);  color: var(--crit); }
#status-pill.tone-info  { background: var(--info-soft);   border-color: rgba(29,78,216,0.25);  color: var(--info); }
#status-pill.tone-leicht{ background: var(--leicht-soft); border-color: rgba(202,138,4,0.25); color: var(--leicht); }
#status-pill.tone-off  { background: var(--surface-2); border-color: var(--hairline);       color: var(--muted); }

#status-pill .dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--good);
  box-shadow: 0 0 0 0 rgba(21,128,61,0.45);
  animation: pulse-good 2s infinite;
}
#status-pill.tone-warn .dot { background: var(--warn); animation: pulse-warn 2.4s ease-out infinite; }
#status-pill.tone-bad  .dot { background: var(--crit); animation: none; }
#status-pill.tone-info   .dot { background: var(--info); }
#status-pill.tone-leicht .dot { background: var(--leicht); }
#status-pill.tone-off  .dot { background: var(--muted); animation: none; box-shadow: none; }
#status-pill .dot.err  { background: var(--crit); animation: none; }
#status-pill .dot.load { background: var(--warn); animation: pulse-warn 1.2s infinite; }
@keyframes pulse-warn {
  0%   { box-shadow: 0 0 0 0   rgba(180,83,9,0.45); }
  70%  { box-shadow: 0 0 0 7px rgba(180,83,9,0); }
  100% { box-shadow: 0 0 0 0   rgba(180,83,9,0); }
}

/* Status pill: inline expansion reveals the last-update timestamp on hover/
   focus (no JS layout math, pure CSS max-width transition). The reasons for
   the current verdict live in the separate #reasonsCard below the chrome. */
#status-pill .last-update-wrap {
  display: inline-flex; align-items: center; gap: 6px;
  max-width: 0;
  opacity: 0;
  overflow: hidden;
  white-space: nowrap;
  transition: max-width .25s ease, opacity .2s ease;
  font-weight: 500;
  color: currentColor;
}
#status-pill .last-update-wrap .sep { opacity: 0.5; }
#status-pill:hover .last-update-wrap,
#status-pill:focus-within .last-update-wrap { max-width: 240px; opacity: 0.85; margin-left: 2px; }

/* The verdict label can carry a full + abbreviated form (rendered as two
   spans). Desktop always shows the full label; the mobile block swaps to the
   short one so long verdicts still fit the expanded pill. */
#status-pill .txt-short { display: none; }

/* Floating reasons card – snug against the chrome's bottom edge at the
   top-right corner. The Leaflet zoom moved to top-left so this can have
   the corner. Visible only when the verdict is non-Reibungsloser Betrieb (and
   only when the user hasn't dismissed it for the current verdict). */
#reasonsCard {
  position: fixed;
  z-index: 700;
  top: calc(var(--drawer-bottom) + 8px);
  right: 14px;
  width: 280px;
  max-width: calc(100vw - 28px);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-left: 3px solid var(--muted);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  padding: 11px 14px 13px 13px;
  transition: top .35s cubic-bezier(.2,.7,.2,1), border-color .25s ease;
  font-family: var(--sans);
}
#reasonsCard[hidden] { display: none; }
#reasonsCard.tone-info   { border-left-color: var(--info); }
#reasonsCard.tone-leicht { border-left-color: var(--leicht); }
#reasonsCard.tone-warn { border-left-color: var(--warn); }
#reasonsCard.tone-bad  { border-left-color: var(--crit); }
#reasonsCard.tone-off  { border-left-color: var(--muted); }
.reasons-card-title {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 8px 0;
  padding-right: 22px;       /* clear the X button */
}
.reasons-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.reasons-list li {
  padding-left: 14px;
  position: relative;
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--ink);
}
.reasons-list li::before {
  content: "•";
  position: absolute;
  left: 2px;
  top: 0;
  color: var(--muted);
}
/* Temporal context line under the reasons (trend + typical-day comparison). */
.reasons-context {
  margin: 9px 0 0 0;
  padding-top: 8px;
  border-top: 1px solid var(--hairline);
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--muted);
}
.reasons-context[hidden] { display: none; }
.reasons-context .rc-trend-up   { color: var(--crit); font-weight: 600; }
.reasons-context .rc-trend-down { color: var(--good); font-weight: 600; }
#reasonsClose {
  position: absolute;
  top: 7px;
  right: 7px;
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  border-radius: 6px;
  color: var(--muted);
  cursor: pointer;
  padding: 0;
  transition: background .12s ease, color .12s ease;
}
#reasonsClose:hover, #reasonsClose:focus-visible {
  background: var(--hl);
  color: var(--ink);
  outline: none;
}
#reasonsClose svg { width: 12px; height: 12px; }

@media (max-width: 560px) {
  #reasonsCard {
    width: auto;
    left: 14px;
    right: 14px;
    top: calc(var(--drawer-bottom) + 8px);
  }
}

.icon-btn {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 10px;
  color: var(--ink-2);
  cursor: pointer;
  padding: 7px 11px;
  font-size: 12px;
  font-weight: 600;
  display: inline-flex; align-items: center; gap: 6px;
  box-shadow: var(--shadow-sm);
  transition: all .15s ease;
  white-space: nowrap;
}
.icon-btn svg { width: 14px; height: 14px; }
.icon-btn:hover {
  border-color: var(--brand-line);
  color: var(--brand-2);
  background: var(--brand-soft);
}
.icon-btn.active {
  border-color: var(--brand-line);
  color: var(--brand-2);
  background: var(--brand-soft);
  box-shadow: var(--shadow-sm), inset 0 0 0 1px rgba(10,143,200,0.18);
}

/* Drawer toggle: tab protruding from the bottom-center of the chrome.
   Anchored to the --drawer-bottom CSS variable so it rides down with the
   drawer when it expands and back up when it collapses. Chevron rotates
   180° to point upward when the drawer is open. */
#drawer-toggle {
  position: fixed;
  z-index: 820;
  /* Hangs entirely BELOW the chrome's bottom edge (with a 1px overlap so the
     chrome's border-bottom blends into the tab's missing top border). Pills
     above are never visually crossed by the tab. */
  top: calc(var(--drawer-bottom) - 1px);
  left: 50%;
  transform: translateX(-50%);

  width: 68px; height: 22px;
  margin: 0; padding: 0;
  border: 1px solid var(--hairline);
  border-top-color: transparent;
  border-radius: 0 0 14px 14px;
  background: rgba(252, 254, 255, 0.95);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
          backdrop-filter: saturate(180%) blur(14px);
  color: var(--muted);
  cursor: pointer;
  box-shadow: 0 4px 12px rgba(20,40,64,0.08);
  display: flex; align-items: center; justify-content: center;
  transition: top .35s cubic-bezier(.2,.7,.2,1),
              color .15s ease,
              background .15s ease,
              height .15s ease,
              box-shadow .15s ease;
}
#drawer-toggle:hover,
#drawer-toggle:focus-visible {
  color: var(--brand-2);
  background: var(--brand-soft);
  height: 24px;
  box-shadow: 0 6px 16px rgba(20,40,64,0.12);
  outline: none;
}
#drawer-toggle .chevron {
  width: 16px; height: 16px;
  transition: transform .3s ease;
}
#drawer-toggle[aria-expanded="true"] .chevron {
  transform: rotate(180deg);
}

/* ===== Drawer (analytics) ===== */
#drawer {
  position: fixed;
  z-index: 750;
  top: var(--topbar-h); left: 0; right: 0;
  background: rgba(252, 254, 255, 0.94);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
          backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid var(--hairline);
  max-height: 0;
  overflow: hidden;
  transition: max-height .4s cubic-bezier(.2,.7,.2,1);
}
#topbar.expanded ~ #drawer { max-height: 640px; box-shadow: var(--shadow-md); }
#drawer-inner {
  padding: 16px 22px 20px 22px;
  max-width: 1600px;
  margin: 0 auto;
}

/* Banner row: title + Datenqualität (donut + hover-popover) on the left,
   service-phase chip on the right */
.drawer-banner {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 12px;
  padding-bottom: 12px;
  margin-bottom: 14px;
  border-bottom: 1px solid var(--hairline-2);
}
@media (max-width: 700px) {
  .drawer-banner { grid-template-columns: 1fr 1fr; }
  #drawerTabs { grid-column: 1 / -1; justify-self: start; }
}
.banner-left {
  display: flex; align-items: center; gap: 24px;
  min-width: 0;
}
.drawer-title {
  display: flex; flex-direction: column; min-width: 0;
}
.title-eyebrow {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--muted);
}
.title-main {
  font-family: var(--serif);
  font-size: 18px;
  font-weight: 600;          /* Plex Condensed 600 keeps drawer-card titles legible */
  line-height: 1.1;
  color: var(--ink);
  margin-top: 2px;
  letter-spacing: 0.1px;
}
.drawer-phase {
  display: flex; align-items: center; gap: 10px;
  justify-self: end;
}
.phase-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--muted);
}
.phase-chip-lg {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 7px 14px;
  border-radius: 999px;
  background: var(--surface-2);
  border: 1px solid var(--hairline);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.2px;
  white-space: nowrap;
}
.phase-chip-lg .dot {
  width: 9px; height: 9px;
  border-radius: 50%;
  background: var(--muted);
}
.phase-chip-lg.tone-good .dot { background: var(--good); }
.phase-chip-lg.tone-warn .dot { background: var(--warn); }
.phase-chip-lg.tone-info .dot { background: var(--info); }
.phase-chip-lg.tone-bad  .dot { background: var(--bad);  }

/* 4-column drawer grid:
     Pünktlichkeit · Haltestellenauslastung · Haltestellen-Verzögerung · Buslinien-Verzögerung */
.drawer-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
  align-items: stretch;
}

.drawer-card {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: 14px 16px 14px 16px;
  min-width: 0;
}
.drawer-card h3 {
  font-family: var(--sans);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  margin: 0 0 10px 0;
  color: var(--muted);
  display: flex; align-items: center; gap: 8px;
}
.drawer-card h3 .accent {
  width: 14px; height: 2px;
  background: var(--brand);
  border-radius: 2px;
}

/* ---- Statistics drawer tabs --------------------------------------------- */
.drawer-tabs {
  display: flex;
  gap: 6px;
  justify-self: center;
}
.dtab {
  padding: 5px 15px;
  border-radius: 20px;
  border: 1.5px solid var(--brand);
  background: transparent;
  color: var(--brand);
  font: 600 12px/1.2 var(--sans);
  cursor: pointer;
  transition: background .15s, color .15s;
  white-space: nowrap;
}
.dtab.is-active {
  background: var(--brand);
  color: #fff;
}
.dtab:hover:not(.is-active) { background: var(--brand-soft); }
.dtab:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* ---- Drawer panes -------------------------------------------------------- */
.drawer-pane         { display: none; }
.drawer-pane.is-active { display: block; }
/* the live pane just reveals the existing grid — don't change grid behaviour */
#pane-live.is-active { display: contents; }

/* ---- History chart grid -------------------------------------------------- */
.hist-charts {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
  align-items: stretch;
}
.hchart-card {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: 14px 16px 12px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.hchart-card h3 {
  font-family: var(--sans);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  margin: 0;
  color: var(--muted);
  display: flex;
  align-items: center;
  gap: 8px;
}
.hchart-card h3 .hc-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}
.hchart-wrap {
  position: relative;
  height: 150px;
  flex-shrink: 0;
}
.hchart-foot {
  display: flex;
  align-items: baseline;
  gap: 3px;
}
.hchart-val {
  font: 700 22px/1 var(--sans);
  color: var(--ink);
}
.hchart-suffix {
  font: 500 13px/1 var(--sans);
  color: var(--muted);
  margin-left: 3px;
}
.hchart-unit {
  font: 500 11px/1 var(--sans);
  color: var(--muted);
}
.hchart-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 96px;
  color: var(--muted);
  font: 500 12px/1.4 var(--sans);
  text-align: center;
}
.hchart-since {
  grid-column: 1 / -1;
  font: 400 11px var(--sans);
  color: var(--muted);
  font-style: italic;
  text-align: center;
  padding-top: 2px;
}
@media (max-width: 900px) {
  .hist-charts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 540px) {
  .hist-charts { grid-template-columns: 1fr; }
}
/* ---- Linien tab: per-line punctuality table ------------------------------ */
#lineStats,
#stopStats { max-width: 920px; margin: 0 auto; }
.lstats-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px 14px;
  margin-bottom: 10px;
}
.lstats-title {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--muted);
}
.lstats-filters {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 10px;
}
.lstats-divider {
  width: 1px;
  height: 16px;
  background: var(--hairline);
  flex-shrink: 0;
}
.lstats-period { display: flex; gap: 5px; }
.lstats-empty {
  display: block;
  padding: 16px 14px;
  font-size: 12px;
  font-style: italic;
  color: var(--muted);
  text-align: center;
}
.lstats-search {
  width: 180px;
  padding: 5px 11px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink);
  font: 500 11.5px/1.3 var(--sans);
  outline: none;
  transition: border-color .15s;
}
.lstats-search:focus { border-color: var(--brand-line); }
.lstats-search::placeholder { color: var(--muted); }
/* 5-column variant for the Haltestellen table (extra Abf./Tag column) */
.lstats-row.ls5 { grid-template-columns: minmax(0, 1fr) 72px 86px 86px 86px; }
.ls-hubdot {
  display: inline-block;
  width: 9px; height: 9px;
  border-radius: 50%;
  flex-shrink: 0;
}
.ls-stopname { font-size: 12.5px; color: var(--ink); }
@media (max-width: 540px) {
  /* drop the Abf./Tag column on phones; it stays in the row tooltip */
  .lstats-row.ls5 { grid-template-columns: minmax(0, 1fr) 70px 72px 70px; }
  .lstats-row.ls5 .ls-deps { display: none; }
  .lstats-search { width: 100%; }
  .lstats-filters { width: 100%; }
}
.lstats-chip {
  padding: 4px 11px;
  border-radius: 999px;
  border: 1px solid var(--hairline);
  background: var(--surface);
  color: var(--ink-2);
  font: 600 11px/1.2 var(--sans);
  cursor: pointer;
  transition: background .15s, color .15s, border-color .15s;
}
.lstats-chip:hover { border-color: var(--brand-line); color: var(--brand-2); }
.lstats-chip.is-active {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}
.lstats-table {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  /* Desktop: the drawer is capped at 640px, so the table scrolls inside it
     instead of being cut off at the drawer's bottom edge. */
  max-height: min(430px, calc(100vh - 320px));
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* Header stays visible while the rows scroll beneath it. */
.lstats-header {
  position: sticky;
  top: 0;
  z-index: 2;
}
@media (max-width: 720px) {
  /* The mobile stats overlay scrolls as a whole page – no inner cap needed. */
  .lstats-table { max-height: none; overflow-y: visible; }
}
.lstats-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 86px 86px 86px;
  align-items: center;
  gap: 10px;
  padding: 7px 14px;
  min-height: 36px;
}
.lstats-row + .lstats-row { border-top: 1px solid var(--hairline-2); }
.lstats-row:not(.lstats-header):hover { background: var(--hl); }
.lstats-header {
  background: var(--surface-2);
  border-bottom: 1px solid var(--hairline);
  padding-top: 5px;
  padding-bottom: 5px;
  min-height: 32px;
}
.lstats-th {
  background: transparent;
  border: 0;
  padding: 2px 0;
  margin: 0;
  font: 700 9.5px/1.2 var(--sans);
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--muted);
  text-align: right;
  cursor: pointer;
  white-space: nowrap;
  transition: color .12s;
}
.lstats-th:first-child { text-align: left; }
.lstats-th:hover, .lstats-th:focus-visible { color: var(--brand-2); outline: none; }
.lstats-th[aria-sort="ascending"], .lstats-th[aria-sort="descending"] { color: var(--ink-2); }
.lstats-line {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
}
.lstats-route {
  font-size: 11.5px;
  color: var(--ink-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.lstats-v {
  font-family: var(--mono);
  font-size: 12.5px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  text-align: right;
  white-space: nowrap;
}
.lstats-v .lstats-u {
  font-family: var(--sans);
  font-size: 9.5px;
  color: var(--muted);
  margin-left: 3px;
  font-weight: 400;
}
.lstats-v.good { color: var(--good); }
.lstats-v.warn { color: var(--warn); }
.lstats-v.bad  { color: var(--bad);  }
.lstats-v.crit { color: var(--crit); }
.lstats-note {
  margin: 8px 2px 0;
  font-size: 10.5px;
  color: var(--muted);
  text-align: center;
}
@media (max-width: 540px) {
  .lstats-row { grid-template-columns: minmax(0, 1fr) 70px 72px 70px; gap: 6px; padding: 7px 10px; }
  .lstats-route { display: none; }   /* line pill + tooltip carry the identity */
}

.drawer-card h3 .h3-meta {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.4px;
  text-transform: none;
  color: var(--ink-2);
}

/* --- Pünktlichkeit hero --- */
.card-hist .hist-frame {
  position: relative;
  padding: 4px 2px 0 2px;
}
.delay-hist {
  display: block;
  width: 100%;
  height: 90px;
}
.hist-axis {
  display: flex; justify-content: space-between;
  font-family: var(--mono);
  font-size: 10px;
  color: var(--muted);
  margin-top: 6px;
  padding: 0 2px;
  font-variant-numeric: tabular-nums;
}
.hist-caption {
  font-size: 10.5px;
  color: var(--muted);
  margin-top: 4px;
  letter-spacing: 0.2px;
}
.hist-stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px 14px;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--hairline-2);
}
.hs { display: flex; flex-direction: column; min-width: 0; }
.hs-l {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--muted);
  /* Allow multi-line wrap (e.g. "STARK VERSPÄTET" / "→ STADTEINWÄRTS") so
     longer labels aren't truncated inside the 4-column drawer grid. */
  line-height: 1.2;
  min-height: 2.4em;            /* reserve room for two lines so columns align */
  display: flex;
  align-items: flex-end;
}
.hs-v {
  font-family: var(--mono);
  font-size: 14px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  margin-top: 2px;
  line-height: 1.15;
}
.hs-v .hs-sub {
  font-family: var(--sans);
  font-size: 10px;
  color: var(--muted);
  margin-left: 4px;
  font-weight: 400;
}
.hs-v.good { color: var(--good); }
.hs-v.warn { color: var(--warn); }
.hs-v.bad  { color: var(--bad);  }
.hs-v.crit { color: var(--crit); }

/* --- Datenqualität donut (lives in the banner now) --- */
.dq-donut { width: 100%; height: 100%; display: block; flex-shrink: 0; }
.dq-bg { fill: none; stroke: var(--hairline-2); stroke-width: 4; }
.dq-fg { fill: none; stroke: var(--muted); stroke-width: 4; stroke-linecap: round;
         transform: rotate(-90deg); transform-origin: 50% 50%;
         transition: stroke-dasharray .3s ease, stroke .2s ease; }
.dq-fg.good { stroke: var(--good); }
.dq-fg.warn { stroke: var(--warn); }
.dq-fg.bad  { stroke: var(--bad);  }
.dq-fg.crit { stroke: var(--crit); }
.dq-center {
  position: relative;
  width: 56px; height: 56px;
}
.dq-num-inner {
  position: absolute;
  inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  pointer-events: none;
  letter-spacing: -0.5px;
}
/* Banner-sized donut (smaller variant of .dq-center) */
.dq-center.dq-banner { width: 44px; height: 44px; }
.dq-banner .dq-num-inner { font-size: 11.5px; }

/* Datenqualität button in the banner – donut on the right of its label,
   with a dark hover/focus popover revealing Bus Bunching + Leerfahrten */
.banner-dq {
  position: relative;
  display: flex; align-items: center; gap: 12px;
  background: transparent;
  border: 0;
  padding: 4px 6px;
  margin: 0;
  cursor: help;
  font-family: var(--sans);
  border-radius: 8px;
  transition: background .15s ease;
}
.banner-dq:hover, .banner-dq:focus-visible {
  background: var(--hl);
  outline: none;
}
.banner-dq-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
.banner-dq-popover {
  position: absolute;
  top: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  background: var(--ink);
  color: var(--surface);
  font-size: 11.5px;
  font-family: var(--sans);
  padding: 10px 12px;
  border-radius: 9px;
  min-width: 220px;
  box-shadow: var(--shadow-lg);
  opacity: 0;
  pointer-events: none;
  transition: opacity .15s ease;
  z-index: 760;
  text-align: left;
  letter-spacing: 0;
  text-transform: none;
  font-weight: 400;
}
.banner-dq:hover .banner-dq-popover,
.banner-dq:focus-visible .banner-dq-popover { opacity: 1; }
.dqd-row {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 18px;
  padding: 3px 0;
}
.dqd-lbl {
  color: rgba(255, 255, 255, 0.65);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.4px;
  text-transform: uppercase;
}
.dqd-v {
  font-family: var(--mono);
  font-size: 12.5px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: #ffffff;
}
/* Lighter semantic colours so they read on the dark popover surface */
.dqd-v.good { color: #4ade80; }
.dqd-v.warn { color: #fbbf24; }
.dqd-v.bad  { color: #fb923c; }
.dqd-v.crit { color: #f87171; }

/* --- Haltestellenauslastung an Knotenpunkten --- */
.occ-bar {
  width: 100%; height: 14px;
  border-radius: 7px;
  background: var(--hl);
  overflow: hidden;
  display: flex;
  margin: 4px 0 8px;
}
.occ-bar .seg-low { background: var(--good); }
.occ-bar .seg-med { background: var(--warn); }
.occ-bar .seg-hi  { background: var(--crit); }
.occ-bar .seg-unk { background: var(--dim); }
.occ-legend {
  /* Single horizontal row across all three known buckets (k.A. dropped). */
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 11px;
  color: var(--ink-2);
  flex-wrap: wrap;
}
.occ-legend span { display: inline-flex; align-items: center; gap: 5px; }
.occ-legend .sw {
  display: inline-block; width: 9px; height: 9px; border-radius: 2px; flex-shrink: 0;
}
.occ-legend .num {
  font-family: var(--mono);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  margin-left: 3px;
}

/* Top-3 busiest hubs by weighted load score (= per-hub equivalent of the
   topbar bus-icon score). Compact mini-bars between hub name and percent. */
.busy-hubs {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--hairline-2);
}
.busy-hubs:empty {
  display: none;
}
.busy-hub-row {
  display: grid;
  grid-template-columns: 16px 1fr 60px 38px;
  align-items: center;
  gap: 8px;
  font-size: 11.5px;
}
.busy-hub-rank {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 600;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.busy-hub-name {
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.busy-hub-bar {
  height: 6px;
  background: var(--hl);
  border-radius: 3px;
  overflow: hidden;
}
.busy-hub-fill {
  display: block;
  height: 100%;
  border-radius: 3px;
  transition: width .35s ease, background .25s ease;
  background: var(--good);
}
.busy-hub-fill.med { background: var(--warn); }
.busy-hub-fill.hi  { background: var(--crit); }
.busy-hub-pct {
  font-family: var(--mono);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-2);
  text-align: right;
}
/* "Calm" hub variant – when the weighted load is ≤ 30 %, the bar/percent
   carry no useful nuance, so collapse them into a single green check. The
   grid is reshaped on the row so the check sits flush right next to the
   name without leaving a gap where the bar used to be. */
.busy-hub-row.is-calm {
  grid-template-columns: 16px 1fr 18px;
}
.busy-hub-check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px; height: 16px;
  color: var(--good);
}
.busy-hub-check svg {
  width: 16px; height: 16px;
  display: block;
}

/* Abfahrten/h number below the legend – smaller per user feedback. */
.abf-rate {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--hairline-2);
  cursor: help;
}
.abf-rate-num {
  font-family: var(--mono);
  font-size: 20px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  line-height: 1;
}
.abf-rate-label {
  font-size: 10.5px;
  letter-spacing: 0.4px;
  color: var(--muted);
  margin-top: 3px;
}

/* --- Problematische Haltestellen ---
   Now lives as the 4th card on the main grid row. To keep card heights
   even (this list grows with severity), the list scrolls inside the card
   instead of pushing the card taller. */
.problemstops-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 218px;
  overflow-y: auto;
  padding-right: 2px;
}
.ps-row {
  display: flex; align-items: center;
  gap: 10px;
  padding: 7px 11px;
  background: var(--surface-2);
  border: 1px solid var(--hairline-2);
  border-radius: 9px;
  min-width: 0;
  min-height: 38px;                  /* uniform row height across stops + lines lists */
  transition: border-color .15s, background .15s;
}
.ps-row.crit { border-color: rgba(185,28,28,0.22); background: rgba(254,226,226,0.55); }
.ps-row.bad  { border-color: rgba(194,65,12,0.22); background: rgba(254,228,207,0.55); }
.ps-row.warn { border-color: rgba(180,83,9,0.22);  background: rgba(254,240,199,0.55); }
.ps-name {
  flex: 1; min-width: 0;
  font-size: 12.5px;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 500;
}
.ps-delay {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  font-size: 13px;
  flex-shrink: 0;
  line-height: 1;
}
.ps-delay .ps-unit {
  font-family: var(--sans);
  font-size: 10px;
  color: var(--muted);
  margin-left: 2px;
  font-weight: 400;
}
.ps-delay.crit { color: var(--crit); }
.ps-delay.bad  { color: var(--bad); }
.ps-delay.warn { color: var(--warn); }
.ps-count {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--muted);
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
  min-width: 28px;
  text-align: right;
}
.ps-empty {
  font-size: 12.5px;
  color: var(--muted);
  font-style: italic;
  padding: 14px 6px;
  text-align: center;
  background: var(--surface-2);
  border: 1px dashed var(--hairline);
  border-radius: 9px;
}

.info-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 14px; height: 14px;
  margin-left: 4px;
  padding: 0;
  background: transparent;
  border: none;
  color: var(--muted);
  cursor: help;
  vertical-align: middle;
  transition: color .15s;
  position: relative;
}
.info-icon:hover, .info-icon:focus-visible { color: var(--brand-2); outline: none; }
.info-icon .popover {
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  background: var(--ink);
  color: var(--surface);
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  padding: 7px 10px;
  border-radius: 8px;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity .15s;
  z-index: 50;
  max-width: 260px; white-space: normal; min-width: 160px;
  box-shadow: var(--shadow-lg);
}
.info-icon:hover .popover,
.info-icon:focus .popover { opacity: 1; }

/* ===== Controls panel (left) =====
   Unified panel containing four sections: Einfärben nach (always open), Legende
   (collapsible, closed by default), Linien filtern (collapsible, closed by
   default), and a foot row with a Karten-Stil gear button that opens a
   popover. The standalone bottom-right legend and Leaflet's layers control
   are gone – everything lives here. */
#controls {
  position: fixed;
  z-index: 700;
  bottom: calc(14px + var(--footer-h));
  left: 14px;
  width: 290px;
  /* Leave the top portion of the map clear; cap how tall the panel can grow
     so an expanded "Linien filtern" can't reach into the chrome. */
  max-height: calc(100vh - var(--drawer-bottom) - 32px);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-l);
  box-shadow: var(--shadow-md);
  padding: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: max-height .35s cubic-bezier(.2,.7,.2,1);
}

.ctl-section {
  padding: 12px 16px;
  border-top: 1px solid var(--hairline-2);
  min-height: 0;
}
.ctl-section:first-child { border-top: none; }
.ctl-section.ctl-scroll { overflow: auto; }

.ctl-title {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 9px 0;
  display: flex; align-items: center; gap: 8px;
}

.ctl-header {
  width: 100%;
  margin: 0;
  padding: 0;
  background: transparent;
  border: 0;
  cursor: pointer;
  font-family: var(--sans);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--muted);
  display: flex; align-items: center; gap: 8px;
  text-align: left;
  transition: color .15s ease;
}
.ctl-header:hover { color: var(--ink-2); }
.ctl-header:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 4px; }
.ctl-h-title { flex: 1; }
.ctl-h-meta {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink-2);
}
.ctl-header .chevron {
  width: 13px; height: 13px;
  color: var(--muted);
  transition: transform .25s ease;
  flex-shrink: 0;
}
.ctl-collapsible[data-open="true"] .ctl-header .chevron { transform: rotate(180deg); }

.ctl-body {
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  padding-top: 0;
  transition: max-height .3s cubic-bezier(.2,.7,.2,1),
              opacity .2s ease,
              padding-top .25s ease;
}
.ctl-collapsible[data-open="true"] .ctl-body {
  max-height: 480px;
  opacity: 1;
  padding-top: 10px;
}
.ctl-collapsible[data-open="true"] .ctl-body.ctl-body-scroll { overflow: auto; }

/* Layer-toggle row inside the controls panel ("Ebenen" section). Visually
   sits closer to the row-line filter rows than to the segmented controls. */
.ctl-toggle {
  display: flex; align-items: center; gap: 10px;
  padding: 5px 6px;
  margin: -5px -6px;                /* visually flush with section padding */
  border-radius: 7px;
  cursor: pointer;
  transition: background .12s ease;
  font-size: 12px;
  color: var(--ink-2);
}
.ctl-toggle:hover { background: var(--hl); }
.ctl-toggle input[type="checkbox"] {
  margin: 0; accent-color: var(--brand);
  width: 13px; height: 13px;
  cursor: pointer;
}
.ctl-toggle-label { flex: 1; min-width: 0; }

/* Congestion overlay hour scrubber. Sits under the "Stauzonen" toggle and
   is revealed (hidden attribute removed) only while that layer is on. The
   slider accent uses brand coral to tie it to the coral overlay it drives. */
.congest-hour { margin-top: 8px; padding: 0 2px; }
.congest-hour[hidden] { display: none; }
.congest-hour-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; margin-bottom: 4px;
}
.congest-hour-label {
  font-size: 12px; font-variant-numeric: tabular-nums;
  color: var(--ink-2);
}
.congest-now-btn {
  font: inherit; font-size: 11px; line-height: 1;
  padding: 3px 8px; border-radius: 6px; cursor: pointer;
  color: var(--accent);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  transition: opacity .12s ease;
}
.congest-now-btn:disabled { opacity: .4; cursor: default; }
.congest-hour input[type="range"] {
  width: 100%; margin: 0; cursor: pointer;
  accent-color: var(--accent);
}

/* (i) info button: top-right corner of the controls panel; opens the
   Legende popover on click/hover. Overlays content so it doesn't take
   vertical space inside the panel. */
#ctl-legend-info {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 5;
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  border-radius: 6px;
  color: var(--muted);
  cursor: pointer;
  padding: 0;
  transition: background .12s, color .12s;
}
#ctl-legend-info:hover,
#ctl-legend-info:focus-visible,
#ctl-legend-info[aria-expanded="true"] {
  background: var(--brand-soft);
  color: var(--brand-2);
  outline: none;
}
#ctl-legend-info svg { width: 14px; height: 14px; }

/* Legend popover: dialog-style next to the (i) button, with a small mode
   name chip inside the heading so the user knows which mode the legend
   is for. */
#legendPopover h3 {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 10px 0;
}
#legendPopover .lp-mode {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink-2);
  background: var(--paper-deep);
  border-radius: 4px;
  padding: 1px 6px;
}
#legendPopover #legend-body-inner {
  display: flex;
  flex-direction: column;
  gap: 6px;
}


/* Karten-Stil popover anchored above the gear button */
.ctl-popover {
  position: fixed;
  z-index: 750;
  width: 248px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  box-shadow: var(--shadow-lg);
  padding: 12px 14px 14px 14px;
}
.ctl-popover[hidden] { display: none; }
.ctl-popover h3 {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 10px 0;
}
.ctl-popover .radio-group { display: flex; flex-direction: column; gap: 2px; }
.ctl-popover label {
  display: flex; align-items: center; gap: 9px;
  font-size: 12.5px;
  color: var(--ink);
  cursor: pointer;
  padding: 5px 4px;
  border-radius: 6px;
  transition: background .12s ease;
}
.ctl-popover label:hover { background: var(--hl); }
.ctl-popover input[type="radio"],
.ctl-popover input[type="checkbox"] {
  accent-color: var(--brand);
  width: 14px; height: 14px;
  margin: 0;
}
.ctl-popover hr {
  border: 0;
  border-top: 1px solid var(--hairline-2);
  margin: 8px -4px 6px -4px;
}

.segmented {
  display: flex;
  background: var(--paper-deep);
  border: 1px solid var(--hairline);
  border-radius: 10px;
  padding: 3px;
  gap: 2px;
}
.segmented label {
  flex: 1;
  text-align: center;
  padding: 7px 3px;
  border-radius: 7px;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ink-2);
  cursor: pointer;
  transition: background .15s ease, color .15s ease;
  user-select: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.segmented input { display: none; }
.segmented label:hover { color: var(--brand-2); }
.segmented label.active {
  background: var(--surface);
  color: var(--brand-2);
  box-shadow: 0 1px 2px rgba(0,0,0,0.06), inset 0 0 0 1px var(--brand-line);
}

.btn-row { display: flex; gap: 6px; margin-top: 10px; }
.btn {
  flex: 1;
  padding: 7px 10px;
  background: var(--paper-deep);
  border: 1px solid var(--hairline);
  border-radius: 8px;
  color: var(--ink-2);
  cursor: pointer;
  font-size: 12px;
  font-weight: 600;
  transition: all .15s ease;
}
.btn:hover {
  border-color: var(--brand-line);
  color: var(--brand-2);
  background: var(--brand-soft);
}

.line-search {
  position: relative;
  margin: 8px 0 4px;
}
.line-search input {
  width: 100%;
  padding: 7px 10px 7px 30px;
  background: var(--paper-deep);
  border: 1px solid var(--hairline);
  border-radius: 8px;
  color: var(--ink);
  font-family: var(--sans);
  font-size: 12.5px;
  outline: none;
  transition: border-color .15s, background .15s;
}
.line-search input:focus { border-color: var(--brand-line); background: var(--surface); }
.line-search input::placeholder { color: var(--muted); }
.line-search svg {
  position: absolute;
  left: 9px; top: 50%; transform: translateY(-50%);
  width: 14px; height: 14px; color: var(--muted);
  pointer-events: none;
}

#line-list {
  margin-top: 6px;
  max-height: 340px;
  overflow: auto;
  padding-right: 2px;
}
.row-line {
  display: flex; align-items: center; gap: 10px;
  padding: 5px 6px;
  border-radius: 7px;
  cursor: pointer;
  transition: background .12s ease;
}
.row-line:hover { background: var(--hl); }
.row-line.is-focused { background: var(--brand-soft); }
.row-line.is-hidden { opacity: 0.45; }
.row-line input[type="checkbox"] {
  margin: 0; accent-color: var(--brand);
  width: 13px; height: 13px;
}
.row-line .line-name {
  flex: 1;
  min-width: 0;
  font-size: 11.5px;
  color: var(--ink-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.row-line .count {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  margin-left: 4px;
}
.row-line .solo-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px;
  border: none;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  border-radius: 4px;
  opacity: 0;
  transition: opacity .12s, color .12s, background .12s;
}
.row-line:hover .solo-btn { opacity: 1; }
.row-line .solo-btn:hover { color: var(--brand-2); background: var(--brand-soft); }
.row-line.is-focused .solo-btn { opacity: 1; color: var(--brand); }

/* Line pill (used in many places) */
.line-pill {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 30px; height: 22px; padding: 0 8px;
  border-radius: 6px;
  font-family: var(--mono);
  font-weight: 600;
  font-size: 12px;
  color: #fff;
  background: #5e6675;
  letter-spacing: 0.2px;
  text-shadow: 0 1px 1px rgba(0,0,0,0.18);
  box-shadow: inset 0 -1px 0 rgba(0,0,0,0.12);
  flex-shrink: 0;
}

/* Legend rows now live inside the controls panel's Legende section */
#legend-body-inner {
  display: flex; flex-direction: column; gap: 6px;
}
.legend-row {
  display: flex; align-items: center; gap: 8px;
  font-size: 11.5px;
  color: var(--ink-2);
}
.legend-row .sw {
  display: inline-block; width: 10px; height: 10px; border-radius: 3px;
  flex-shrink: 0;
}

/* ===== Vehicle markers =====
   Each marker shows the line number and the actual compass direction of travel.
   A 30×30 square bounding box (= Leaflet click area) contains:
     · .bm-dir  — the colored pill shape, rotated by the computed bearing so
                  its "nose" (more-rounded end) points in the direction of travel.
                  Falls back to a rounded square when bearing is unknown.
     · .bm-label — the line number, absolutely centered over the pill, never
                  rotates, so it stays legible regardless of bearing.
   Bearing is computed from akthst → nachhst stop catalog entries (route
   geometry, stable at rest) with a GPS-delta fallback when catalog entries
   are unavailable. */
.bus-marker {
  position: relative;
  width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  user-select: none;
  transition: transform .2s ease;
}
.bus-marker.is-focused {
  transform: scale(1.18);
  z-index: 999 !important;
}
.bus-marker.is-dimmed { opacity: 0.32; }

/* Shape: always a vertical pill (nose at top), rotated to bearing. */
.bm-dir {
  position: absolute;
  width: 20px; height: 28px;
  /* Asymmetric radius: more rounded at top (leading end/nose),
     less at bottom (trailing end) — keeps direction readable. */
  border-radius: 10px 10px 5px 5px;
  border: 1.5px solid rgba(255,255,255,0.88);
  box-shadow: 0 1px 3px rgba(0,0,0,0.32), 0 0 0 1px rgba(0,0,0,0.08);
  transform-origin: center center;
}
.bm-dir.no-bearing {
  /* No known direction yet → neutral rounded square */
  width: 24px; height: 24px;
  border-radius: 8px;
}
.bus-marker.is-focused .bm-dir {
  box-shadow: 0 2px 6px rgba(0,0,0,0.4), 0 0 0 2.5px var(--brand);
}

/* Label: centered on top of the shape, never participates in rotation. */
.bus-marker .bm-label {
  position: relative; z-index: 1;
  display: block;
  color: #fff;
  font-family: var(--mono);
  font-weight: 700;
  font-size: 10.5px;
  letter-spacing: -0.3px;
  line-height: 1;
  text-shadow: 0 1px 2px rgba(0,0,0,0.5);
  pointer-events: none;
  white-space: nowrap;
}
.leaflet-marker-icon { background: transparent; border: none; }

/* ===== Hub clouds =====
   Translucent coloured "clouds" that mark the overall area of each of the 5
   polled hubs (Hbf, Bült, Domplatz, Ludgeriplatz, Coesfelder Kreuz). Each
   hub gets its own hue so they're distinguishable at a glance. Real-geography
   L.circle radius derived from the platform spread, sitting on the custom
   "hubsPane" (z 400) below the bus markers. The CSS blur softens the edges
   so the circles read as ambient zones rather than sharp boundaries. */
.hub-cloud {
  filter: blur(6px);
  transition: opacity .25s ease;
  opacity: 0.55;
  cursor: help;
  pointer-events: auto;
}
.hub-cloud:hover {
  opacity: 0.85;
}

/* ===== Stress clouds =====
   Ambient coloured circles (amber/orange/red) marking areas of sustained
   network stress. Rendered on stressPane (z 390, below hub clouds at 400).
   Larger blur than hub clouds for a softer "heat-map" feel.
   pointer-events: none so they never intercept map clicks. */
/* Stress clouds are now L.polygon ellipses – interactive so hover/tap
   shows the tooltip. pointer-events:none removed so events reach the shape. */
.stress-cloud {
  filter: blur(10px);
  cursor: default;
}
/* Tooltip shown when hovering / tapping a stress cloud */
.leaflet-tooltip.stress-tooltip {
  background: var(--ink);
  color: #fff;
  border: 0;
  border-radius: 8px;
  font-family: var(--sans);
  font-size: 12px;
  font-weight: 500;
  padding: 6px 11px;
  box-shadow: var(--shadow-md);
  max-width: 240px;
  white-space: normal;
  line-height: 1.4;
}
.leaflet-tooltip.stress-tooltip::before { border-top-color: var(--ink); }
.st-name  { font-weight: 600; }
.st-delay { color: #fbbf24; font-family: var(--mono); font-variant-numeric: tabular-nums; }
.st-title { display: block; font-size: 10px; font-weight: 700; letter-spacing: 0.8px; text-transform: uppercase; color: rgba(255,255,255,0.6); margin-bottom: 1px; }
.st-hint  { display: block; margin-top: 4px; font-size: 11px; font-weight: 400; color: rgba(255,255,255,0.72); }

.leaflet-tooltip.hub-tooltip {
  background: var(--ink);
  color: #fff;
  border: 0;
  border-radius: 6px;
  font-family: var(--sans);
  font-size: 11.5px;
  font-weight: 500;
  padding: 4px 9px;
  box-shadow: var(--shadow-md);
  white-space: nowrap;
}
.leaflet-tooltip.hub-tooltip::before {
  border-top-color: var(--ink);
}

/* ===== Vehicle hover tooltip =====
   Dark pill: [line] direction · delay. Delay is colour-coded on the dark
   background using the lighter semantic palette used elsewhere on dark
   surfaces (e.g. the DQ popover). */
.leaflet-tooltip.veh-tooltip {
  background: var(--ink);
  color: #fff;
  border: 0;
  border-radius: 8px;
  font-family: var(--sans);
  font-size: 12px;
  font-weight: 400;
  padding: 0;
  box-shadow: var(--shadow-md);
  white-space: nowrap;
  pointer-events: none;
}
.leaflet-tooltip.veh-tooltip::before { border-top-color: var(--ink); }
.veh-tip {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 5px 10px 5px 8px;
}
.veh-tip-line {
  font-family: var(--mono);
  font-weight: 700;
  font-size: 11px;
  color: #fff;
  padding: 2px 6px;
  border-radius: 4px;
  letter-spacing: 0.2px;
  flex-shrink: 0;
  text-shadow: 0 1px 1px rgba(0,0,0,0.2);
}
.veh-tip-dir {
  color: rgba(255,255,255,0.85);
  font-size: 12px;
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.veh-tip-sep {
  color: rgba(255,255,255,0.25);
  font-size: 11px;
  flex-shrink: 0;
}
.veh-tip-delay {
  font-family: var(--mono);
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  flex-shrink: 0;
}
/* Delay colours on dark background – lighter variants so they read well */
.veh-tip-ok        { color: #4ade80; }  /* green  */
.veh-tip-early     { color: #93c5fd; }  /* blue   */
.veh-tip-late      { color: #fbbf24; }  /* amber  */
.veh-tip-very-late { color: #f87171; }  /* red    */

/* ===== Stop name tooltip =====
   Minimalist: just the name, same dark background as hub clouds. */
.leaflet-tooltip.stop-tooltip {
  background: var(--ink);
  color: #fff;
  border: 0;
  border-radius: 6px;
  font-family: var(--sans);
  font-size: 11.5px;
  font-weight: 500;
  padding: 4px 9px;
  box-shadow: var(--shadow-md);
  white-space: nowrap;
}
.leaflet-tooltip.stop-tooltip::before { border-top-color: var(--ink); }

/* ===== Stop markers =====
   Off by default; toggle via Karten-Stil → "Haltestellen anzeigen".
   Rendered to a shared canvas on the custom "stopsPane" (z 500), between
   hubsPane (400) and the default markerPane (600), so hub clouds sit below
   and bus markers sit above. White-fill / ink-ring dots are the classic
   transit-map idiom; tiny enough to coexist with the existing chrome.
   At low zoom we show the ~200 consolidated Haltestellen-Areale; at z≥15
   we switch to the ~361 individual Steige so multi-platform stops like
   Hbf or Coesfelder Kreuz become distinguishable. */
.stops-pane { /* the .leaflet-pane-stopsPane container, addressed by class for fade */
  transition: opacity .18s ease;
}

/* Popups */
.leaflet-popup-content-wrapper {
  border-radius: var(--radius) !important;
  box-shadow: var(--shadow-lg) !important;
  background: var(--surface) !important;
  color: var(--ink) !important;
  border: 1px solid var(--hairline) !important;
}
.leaflet-popup-tip { background: var(--surface) !important; box-shadow: 0 2px 4px rgba(0,0,0,0.08) !important; }
.leaflet-popup-content { margin: 14px 16px !important; line-height: 1.4; font-family: var(--sans); }
.leaflet-popup-close-button {
  color: var(--muted) !important;
  font-size: 18px !important;
  padding: 6px 8px 0 0 !important;
}
/* ===== Bus popup ===== */
.bus-popup { min-width: 240px; max-width: 320px; }

/* Header: line pill + destination in one row */
.bus-popup-head {
  display: flex; align-items: center; gap: 8px;
  padding-bottom: 10px; margin-bottom: 9px;
  border-bottom: 1px solid var(--hairline-2);
  min-width: 0;
}
.bus-popup-destination {
  font-family: var(--serif); font-weight: 600; font-size: 15px;
  color: var(--ink); line-height: 1.2;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  flex: 1; min-width: 0;
}

/* Delay band — the visual centrepiece of the popup */
.bus-delay-band {
  margin-bottom: 9px; padding: 7px 10px;
  border-radius: 7px; background: var(--paper);
  border-left: 3px solid transparent;
}
.bus-delay-band.bdb-ok        { border-left-color: var(--good); }
.bus-delay-band.bdb-slight    { border-left-color: var(--warn); }
.bus-delay-band.bdb-late      { border-left-color: var(--crit); }
.bus-delay-band.bdb-very-late {
  border-left-color: var(--crit);
  animation: bdb-pulse 1s ease-in-out infinite;
}
.bus-delay-band.bdb-early     { border-left-color: var(--info); }
.bus-delay-value {
  font-family: var(--sans); font-size: 13px; font-weight: 600;
}
.bus-delay-band.bdb-ok        .bus-delay-value { color: var(--good); }
.bus-delay-band.bdb-slight    .bus-delay-value { color: var(--warn); }
.bus-delay-band.bdb-late      .bus-delay-value,
.bus-delay-band.bdb-very-late .bus-delay-value { color: var(--crit); }
.bus-delay-band.bdb-early     .bus-delay-value { color: var(--info); }
@keyframes bdb-pulse {
  0%, 100% { background: var(--paper); }
  50%       { background: var(--crit-soft); }
}
@media (prefers-reduced-motion: reduce) { .bus-delay-band { animation: none !important; } }

/* Fact rows */
.bus-popup-dl {
  margin: 0; display: grid;
  grid-template-columns: 80px 1fr;
  row-gap: 5px; column-gap: 10px; font-size: 12px;
}
.bus-popup-dl dt {
  color: var(--muted); font-size: 10.5px; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; align-self: center;
}
.bus-popup-dl dd {
  margin: 0; font-family: var(--mono); font-variant-numeric: tabular-nums;
  color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.bus-popup-dl dd.text  { font-family: var(--sans); }
.bus-popup-dl dd.wrap  { white-space: normal; line-height: 1.35; }
.bus-speed-halt        { color: var(--muted); font-style: italic; font-family: var(--sans) !important; }

/* Occupancy: 3 person-silhouette icons */
.bus-occ-icons { display: inline-flex; align-items: center; gap: 3px; vertical-align: middle; }

/* Clickable next-stop name */
.goto-stop-btn {
  background: none; border: none; padding: 0; margin: 0;
  font: inherit; color: var(--brand-2);
  cursor: pointer; text-align: left;
  text-decoration: underline; text-decoration-style: dotted;
  text-underline-offset: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: 100%;
}
.goto-stop-btn:hover { color: var(--brand); text-decoration-style: solid; }
.goto-stop-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 2px; }

/* Stop popup: lighter sibling of .bus-popup. Now carries live departures
   and the classic H glyph; widened to fit five rows comfortably. */
/* max-width removed: the Leaflet popup's own maxWidth option controls the
   outer constraint (540 px for hub stops, 380 px for regular stops).
   The content div fills whatever the popup allows. */
.stop-popup { min-width: 280px; }
.stop-popup .stop-name {
  font-family: var(--serif);
  font-weight: 600;
  font-size: 15px;
  line-height: 1.25;
  margin-bottom: 10px;
  display: flex;
  align-items: center;
  gap: 9px;
}
.stop-popup .stop-name-text { flex: 1; min-width: 0; }
.hst-h-icon {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  display: block;
}
.stop-popup .stop-richtung {
  font-size: 11px;
  color: var(--muted);
  margin-bottom: 10px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--hairline-2);
}
.stop-popup dl {
  margin: 0;
  display: grid;
  grid-template-columns: max-content 1fr;
  column-gap: 10px;
  row-gap: 3px;
}
.stop-popup dt {
  color: var(--muted);
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
}
.stop-popup dd {
  margin: 0;
  font-family: var(--mono);
  font-size: 11.5px;
}
.stop-popup dd.text { font-family: var(--sans); }
.stop-popup .steig-suffix {
  font-family: var(--mono);
  font-weight: 500;
  font-size: 12.5px;
  color: var(--muted);
  margin-left: 4px;
}
/* (Old Bussteige disclosure CSS removed – the in-popup Steig list was
   dropped in favour of departure-only popups; spatial detail lives on
   the map via the "Alle Bussteige auf Karte anzeigen" reveal.) */

/* Departures section in the area popup. Mirrors the muted "Richtung Hbf"
   small caps used elsewhere; rows are dense to keep the popup compact. */
.stop-dep {
  margin: 8px 0 10px 0;
  padding: 8px 10px;
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: 7px;
}
.stop-dep-title {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--muted);
  margin-bottom: 6px;
}
.stop-dep-row {
  display: flex; align-items: center; gap: 8px;
  padding: 3px 0;
  font-size: 12px;
  color: var(--ink);
  min-height: 22px;
}
.stop-dep-row + .stop-dep-row { border-top: 1px dashed var(--hairline-2); }
/* Button form: reset native button chrome so the row keeps the flex layout
   above, but adds a click affordance. */
button.stop-dep-row {
  width: 100%;
  background: transparent;
  border: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: default;
  border-radius: 5px;
  transition: background .12s ease;
}
button.stop-dep-row.is-clickable { cursor: pointer; }
button.stop-dep-row.is-clickable:hover { background: var(--hl); }
button.stop-dep-row.is-clickable:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 1px;
}
/* Flashes briefly when the user clicks a departure whose fahrzeugid isn't
   in the live /fahrzeuge feed – e.g. bus is scheduled but hasn't left a
   terminal yet, so there's nowhere to fly to. Gives visible feedback so
   the click doesn't feel ignored. */
button.stop-dep-row.dep-row-miss {
  background: #fef3c7;
  transition: background .25s ease;
}
button.stop-dep-row.dep-row-miss .dep-when {
  color: #b45309;
  font-weight: 600;
}
.stop-dep-row .dep-dir {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.stop-dep-row .dep-when {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 11.5px;
  color: var(--ink-2);
  flex-shrink: 0;
}
/* Status dot before the line pill. Color encodes delay magnitude; the
   numeric figure now lives in the row's title tooltip, removing the
   "2 Min +5" ambiguity people kept reading as "7 Min total". Orange and
   red pulse to draw attention to actual problems; green and blue stay
   steady so on-time departures don't compete visually. */
.dep-status-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex-shrink: 0;
  background: #16a34a;
}
.dep-status-dot.dep-status-ok     { background: #16a34a; }
.dep-status-dot.dep-status-early  { background: #1d4ed8; }
.dep-status-dot.dep-status-late {
  background: #f59e0b;
  /* Slower period (2.4 s) + smaller, softer shadow = less aggressive */
  animation: dep-pulse-orange 2.4s ease-in-out infinite;
}
.dep-status-dot.dep-status-very-late {
  background: #dc2626;
  /* Slower period (2 s) + softer shadow */
  animation: dep-pulse-red 2s ease-in-out infinite;
}
@keyframes dep-pulse-orange {
  0%, 100% { box-shadow: 0 0 0 0   rgba(245, 158, 11, 0.35); }
  50%      { box-shadow: 0 0 0 4px rgba(245, 158, 11, 0); }
}
@keyframes dep-pulse-red {
  0%, 100% { box-shadow: 0 0 0 0   rgba(220, 38, 38, 0.40); }
  50%      { box-shadow: 0 0 0 5px rgba(220, 38, 38, 0); }
}

/* ===== Flight-board boarding indicator =====
   Shown instead of the status dot when countdown = "jetzt".
   Two stacked circles fill sequentially on a 2-second loop:
   top fills 0→1 s, snaps empty; bottom fills 1→2 s, snaps empty.
   Achieved by running the same keyframes on both dots with a -1 s delay
   on the bottom circle. */
.dep-status-boarding {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  flex-shrink: 0;
  width: 10px;
  height: 14px;
}
.board-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  border: 1.5px solid #16a34a;
  background: rgba(22, 163, 74, 0);   /* hollow by default */
}
/* Clean alternating toggle: top filled → bottom filled → repeat.
   Both dots run the same keyframes; the bottom is offset by half a
   cycle so they are always in opposite states (one filled, one hollow).
   step-end makes the switch instant – no gradual fade. */
.board-dot-top {
  background: rgba(22, 163, 74, 1);   /* starts filled */
  animation: dep-board-switch 1.6s step-end infinite;
}
.board-dot-bottom {
  background: rgba(22, 163, 74, 0);   /* starts hollow */
  animation: dep-board-switch 1.6s step-end infinite;
  animation-delay: -0.8s;             /* half-cycle offset = always opposite */
}
@keyframes dep-board-switch {
  0%, 100% { background: rgba(22, 163, 74, 1); }   /* filled  */
  50%      { background: rgba(22, 163, 74, 0); }   /* hollow  */
}

/* ===== Hub departure grid (2 columns) =====
   Wraps departure rows for hub stops (14 entries) in a two-column CSS grid.
   grid-auto-flow: column fills column-first so items 1-7 go to col 1,
   items 8-14 to col 2. grid-template-rows is set inline to ⌈n/2⌉ auto rows.
   Borders between rows are replaced by row-gap so the column boundary
   doesn't produce an orphan top border on the first item of column 2. */
.dep-rows-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-auto-flow: column;
  column-gap: 8px;
  row-gap: 1px;
}
.dep-rows-grid .stop-dep-row {
  border-top: 0 !important;   /* suppress the + sibling border; gap handles spacing */
  padding-top: 3px;
  padding-bottom: 3px;
  min-height: 20px;
  min-width: 0;               /* grid items default to min-width:auto which lets them
                                  blow past their 1fr column; 0 allows shrinking so
                                  .dep-dir's text-overflow:ellipsis actually fires */
}

@media (prefers-reduced-motion: reduce) {
  .dep-status-dot   { animation: none !important; }
  .board-dot-top,
  .board-dot-bottom { animation: none !important; background: rgba(22,163,74,1); }
}
.stop-dep-loading,
.stop-dep-empty {
  font-size: 11.5px;
  color: var(--muted);
  font-style: italic;
}
.stop-dep-empty { padding-top: 2px; }
/* External / regional lines: slightly muted row; outlined pill set inline */
.stop-dep-row.dep-row-ext { opacity: 0.72; }

/* ===== Loading veil (first paint) ===== */
#loading-veil {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex; align-items: center; justify-content: center;
  background: var(--paper);
  transition: opacity .4s ease;
}
#loading-veil .veil-inner {
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  text-align: center;
}
#loading-veil h2 {
  font-family: var(--serif);
  font-weight: 600;
  font-size: 28px;
  letter-spacing: 0.2px;
  color: var(--ink);
  margin: 0;
}
#loading-veil p {
  font-size: 12px;
  color: var(--muted);
  letter-spacing: 0.6px;
  text-transform: uppercase;
  margin: 0;
}
#loading-veil .veil-bar {
  width: 200px; height: 3px;
  background: var(--paper-deep);
  border-radius: 999px;
  overflow: hidden;
  position: relative;
}
#loading-veil .veil-bar::before {
  content: ""; position: absolute;
  top: 0; left: -40%;
  width: 40%; height: 100%;
  background: linear-gradient(90deg, transparent, var(--brand), transparent);
  animation: veil-slide 1.4s infinite;
}
@keyframes veil-slide { to { left: 100%; } }
#loading-veil.is-hidden { opacity: 0; pointer-events: none; }

/* ===== Responsive =====
   Strategy: KPI metric sizes stay at their full desktop values. As the
   viewport narrows we first hide the percentage next to the bus icon,
   then drop entire pills from the right one by one. Topbar stacks only
   at the narrowest widths. */
@media (max-width: 1280px) {
  #kpi-strip { gap: 8px; }
}


/* Drawer steps down 4 → 2 → 1 column as width drops */
@media (max-width: 1280px) {
  .drawer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .problemstops-list { max-height: 240px; }
}
@media (max-width: 1100px) {
  .delay-hist { height: 100px; }
  .hchart-wrap { height: 120px; }
  #brand h1 { font-size: 19px; }
}
@media (max-width: 760px) {
  .drawer-grid { grid-template-columns: 1fr; }
  .problemstops-list { max-height: 200px; }
}

/* Compress topbar at medium-narrow widths: tighten padding/gap and collapse
   the status pill to dot + background only. This frees ~140 px that the full
   "Reibungsloser Betrieb" label would otherwise consume, preventing KPI tiles
   from being squeezed into each other in the 660–820 px range. */
@media (max-width: 820px) {
  #topbar { padding: 0 16px; gap: 18px; }
  #status-pill .txt,
  #status-pill .last-update-wrap { display: none; }
  #status-pill { padding: 6px 10px; }
}

/* Tile hide cascade (right-to-left as viewport narrows):
   Verzögerungen → Auslastung → Verspätung → Pünktlich.
   Verzögerungen (k-stoer) carries up-to-three line-number pills, so it is the
   first to run out of room: hidden at ≤1280px (raised from 1180) so it drops
   BEFORE the strip shrinks the tiles below their 144px basis and clips the
   pills / truncates the "Busauslastung" label. The remaining tiles then keep
   their regular size. */
@media (max-width: 1280px) {
  .kpi.k-stoer { display: none; }
}
@media (max-width: 1050px) {
  .kpi.k-auslastung { display: none; }
}
@media (max-width: 790px) {
  .kpi.k-delay { display: none; }
}
@media (max-width: 660px) {
  .kpi.k-ontime { display: none; }
}

/* Narrowest desktop – stack the topbar (brand on its own row, KPIs below) */
@media (max-width: 560px) {
  :root { --topbar-h: auto; }
  #topbar {
    flex-wrap: wrap;
    padding: 10px 12px;
    gap: 10px 12px;
  }
  #brand { width: 100%; }
  #kpi-strip {
    order: 3;
    width: 100%;
    gap: 6px;
  }
  .topbar-actions { margin-left: auto; }
  #drawer-inner { padding: 14px; }
  .drawer-banner { gap: 10px; }
  .hist-stats { grid-template-columns: repeat(2, 1fr); }
  .delay-hist { height: 100px; }
  .hchart-wrap { height: 100px; }
  #map { top: 0; }
}

/* Mobile-narrow – controls dock to bottom, Verspätung tile also drops */
@media (max-width: 420px) {
  /* k-delay was already hidden at 790 px; only Aktive Busse remains here */
  #kpi-strip { grid-template-columns: 1fr; }
  #controls {
    top: auto !important;
    left: 8px; right: 8px;
    width: auto;
    max-height: 42vh;
    bottom: calc(14px + var(--footer-h));
  }
}

/* ===== Site footer (legal strip) =====
   A slim single-line strip pinned to the bottom of the viewport. Sources /
   API attributions live on the standalone /quellen.html page linked from
   here, so this strip carries only the © line, the legal links, and the
   short disclaimer. The map shrinks to leave space for it (#map uses
   --footer-h as its bottom inset); the Leaflet attribution row, the
   controls panel, and the Karten-Stil gear are all lifted by the same
   variable. Height is auto-measured in JS via ResizeObserver so it adapts
   when the line wraps on narrow viewports. */
#site-footer {
  position: fixed;
  z-index: 650;
  left: 0; right: 0; bottom: 0;
  background: rgba(252, 254, 255, 0.94);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
          backdrop-filter: saturate(180%) blur(14px);
  border-top: 1px solid var(--hairline);
  padding: 5px 18px 6px;
  font-family: var(--sans);
  font-size: 11px;
  color: var(--ink-2);
  line-height: 1.5;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
#site-footer a { color: var(--brand-2); text-decoration: none; }
#site-footer a:hover { text-decoration: underline; }
#site-footer .footer-row { display: inline; }
#site-footer .sep { color: var(--dim); padding: 0 6px; }
#site-footer .heart { color: #e11d48; }   /* signage rose, deliberately a touch warmer than --crit */
#site-footer .muted-note { color: var(--muted); }
@media (max-width: 980px) {
  /* Below ~980 px the single line can no longer fit – allow it to wrap and
     drop the right-hand disclaimer onto its own row to keep the line height
     small instead of stretching the strip. */
  #site-footer { white-space: normal; padding: 5px 12px 6px; font-size: 10.5px; }
  #site-footer .muted-note { display: block; margin-top: 1px; }
  #site-footer .sep:last-of-type { display: none; }
}
@media (max-width: 540px) {
  #site-footer { font-size: 10px; }
  #site-footer .sep { padding: 0 4px; }
}
/* ===== Keyboard-shortcut help dialog (toggled with "?") ===== */
#kbd-help {
  position: fixed;
  z-index: 1500;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: 340px;
  max-width: calc(100vw - 32px);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  padding: 16px 18px 14px;
  font-family: var(--sans);
}
#kbd-help[hidden] { display: none; }
#kbd-help h3 {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 10px 0;
}
#kbd-help dl { margin: 0; display: flex; flex-direction: column; gap: 6px; }
#kbd-help .kbd-row { display: flex; align-items: baseline; gap: 12px; }
#kbd-help dt { flex: 0 0 38px; margin: 0; }
#kbd-help dd { margin: 0; font-size: 12.5px; color: var(--ink-2); }
#kbd-help kbd {
  display: inline-block;
  min-width: 20px;
  padding: 2px 6px;
  border: 1px solid var(--hairline);
  border-bottom-width: 2px;
  border-radius: 5px;
  background: var(--surface-2);
  font-family: var(--mono);
  font-size: 11px;
  color: var(--ink);
  text-align: center;
}

/* Visually-hidden helper for screen-reader-only text (e.g. "Liebe" next to ♥) */
.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; }

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* ===== Global search bar ===== */
#global-search {
  position: relative;
  flex: 0 0 auto;   /* collapsed: just the icon button */
}

/* Toggle button — lives inside #kpi-strip so it stretches to the same
   height as the other tiles automatically. aspect-ratio:1 makes it square. */
.gsearch-toggle {
  flex: 0 0 auto;
  min-width: 0;   /* JS sets explicit width = height after layout */
  padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  color: var(--muted);
  cursor: pointer;
  transition: border-color .15s, color .15s, background .15s;
  position: relative;
}
.gsearch-toggle svg { width: 16px; height: 16px; }
.gsearch-toggle:hover {
  border-color: var(--brand-line);
  color: var(--brand-2);
  background: var(--brand-soft);
}
.gsearch-toggle:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: var(--radius); }

/* Search-active: hide only the KPI strip (brand + status pill stay visible).
   #global-search expands to fill the space between them. */
#topbar.search-active #kpi-strip {
  display: none;
}
#topbar.search-active #global-search {
  flex: 1 1 auto;
}
#topbar.search-active .gsearch-field {
  display: flex;
}

/* Close button — inside the field, rightmost element */
.gsearch-close-btn {
  background: transparent;
  border: 0;
  padding: 0 2px;
  cursor: pointer;
  color: var(--muted);
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px;
  border-radius: 8px;
  flex-shrink: 0;
  transition: background .12s, color .12s;
}
.gsearch-close-btn:hover { background: var(--hl); color: var(--ink); }
.gsearch-close-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 8px; }
.gsearch-close-btn svg { width: 14px; height: 14px; }
.gsearch-field {
  display: none;   /* shown only in search-active state */
  align-items: center;
  gap: 0;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: 0 10px;
  height: 36px;
  flex: 1 1 auto;
  transition: border-color .15s, box-shadow .15s;
  overflow: hidden;
}
.gsearch-field:focus-within {
  border-color: var(--brand-line);
  box-shadow: var(--shadow-sm), 0 0 0 3px rgba(0,180,240,0.12);
}
.gsearch-icon {
  width: 14px; height: 14px;
  color: var(--muted);
  flex-shrink: 0;
  pointer-events: none;
}
.gsearch-field:focus-within .gsearch-icon { color: var(--brand-2); }
#gsearchInput {
  flex: 1;
  border: 0;
  background: transparent;
  font-family: var(--sans);
  font-size: 13px;
  color: var(--ink);
  padding: 0 7px;
  min-width: 0;
  outline: none;
  -webkit-appearance: none;
}
#gsearchInput::placeholder { color: var(--muted); }
#gsearchInput::-webkit-search-decoration,
#gsearchInput::-webkit-search-cancel-button { -webkit-appearance: none; }
.gsearch-clear {
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  color: var(--muted);
  display: none;
  align-items: center;
  justify-content: center;
  width: 18px; height: 18px;
  border-radius: 50%;
  flex-shrink: 0;
  transition: background .12s, color .12s;
}
.gsearch-clear:hover { background: var(--hl); color: var(--ink); }
.gsearch-clear svg { width: 10px; height: 10px; }
#gsearchInput:not(:placeholder-shown) ~ .gsearch-clear { display: flex; }

/* Dropdown */
.gsearch-dropdown {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  min-width: 300px;
  max-width: 380px;
  max-height: 420px;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  z-index: 850;
  padding: 6px 0;
  list-style: none;
  margin: 0;
  overscroll-behavior: contain;
}
.gsearch-section-header {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--muted);
  padding: 8px 14px 4px;
  pointer-events: none;
  display: flex;
  align-items: center;
  gap: 6px;
}
.gsearch-section-header::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--hairline-2);
}
.gsearch-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 14px;
  cursor: pointer;
  transition: background .1s;
  min-height: 38px;
}
.gsearch-item:hover,
.gsearch-item[aria-selected="true"] { background: var(--brand-soft); }
.gsearch-item-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 26px; height: 26px;
  border-radius: 6px;
  background: var(--surface-2);
  color: var(--muted);
}
.gsearch-item-icon svg { width: 13px; height: 13px; }
.gsearch-item-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 26px;
  height: 22px;
  padding: 0 7px;
  border-radius: 5px;
  font-size: 11px;
  font-weight: 700;
  color: #fff;
  flex-shrink: 0;
  letter-spacing: 0.2px;
}
.gsearch-item-text {
  flex: 1;
  min-width: 0;
}
.gsearch-item-name {
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gsearch-item-name mark {
  background: transparent;
  color: var(--brand-2);
  font-weight: 700;
}
.gsearch-item-sub {
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 1px;
}
.gsearch-item-badge {
  font-size: 10px;
  font-weight: 600;
  padding: 2px 6px;
  border-radius: 999px;
  flex-shrink: 0;
  white-space: nowrap;
}
.gsearch-item-badge.active   { background: var(--good-soft);   color: var(--good); }
.gsearch-item-badge.warn     { background: var(--warn-soft);   color: var(--warn); }
.gsearch-item-badge.late     { background: var(--bad-soft);    color: var(--bad);  }
.gsearch-item-badge.early    { background: var(--info-soft);   color: var(--info); }
.gsearch-item-badge.inactive { background: var(--surface-2);   color: var(--muted); }
.gsearch-empty {
  padding: 14px;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
  font-style: italic;
}

/* On narrow screens the dropdown opens left-aligned with the field
   (since the field is now full-width, no overflow concern) */
@media (max-width: 560px) {
  .gsearch-dropdown { left: 0; right: 0; min-width: 0; max-width: none; }
}

/* ======================================================================
   MOBILE — stats-first layout  (≤ 720 px)
   ----------------------------------------------------------------------
   The desktop build is map-first: a fixed chrome, a slide-down stats
   drawer, and floating controls over a full-bleed map. On phones that
   doesn't fit. Here we invert the model:

     • the page becomes a normal vertical scroll
     • the chrome shrinks to a compact, non-fixed header
     • ALL KPI tiles show as a 2-column grid (desktop hides some)
     • the stats drawer is permanently open and flows inline
     • the map is hidden until the user taps "Live-Karte öffnen",
       at which point it fills the screen as an overlay with the
       map controls reachable as a bottom sheet.

   Two buttons drive the overlay: #map-fab (open) and #map-close
   (return to stats). Both are display:none on desktop; the mobile
   block below reveals them. The open state is the body.map-open class,
   toggled by wireMobile() in app.js (which also calls invalidateSize).
   ====================================================================== */

/* These two controls only exist for the mobile overlay — keep them out of
   the desktop layout entirely. */
#map-fab,
#map-close { display: none; }

@media (max-width: 720px) {

  /* --- Page becomes scrollable instead of a fixed full-screen app ------- */
  html, body { height: auto; min-height: 100%; overflow-y: auto; }
  body { padding-bottom: 84px; }      /* clear room for the floating FAB */

  /* --- Chrome: compact, in-flow header (brand + status, KPIs below) ----- */
  #topbar {
    position: static;
    height: auto;
    flex-wrap: wrap;
    padding: 12px 14px;
    gap: 10px 12px;
  }
  #brand { flex: 1 1 auto; }
  .topbar-actions { margin-left: auto; }

  /* All KPI tiles visible in a 2-column grid (desktop hides several via the
     right-to-left hide cascade — ids out-rank those class selectors). */
  #kpi-strip {
    order: 3;
    width: 100%;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
  }
  #kpi-strip .kpi { display: flex; width: auto; min-width: 0; }
  #kpi-strip .kpi.k-stoer { grid-column: 1 / -1; }   /* wide row for line tags */

  /* Map-only chrome affordances make no sense in the stats view. The global
     search is NOT hidden here any more — on mobile it is surfaced as a
     persistent full-width bar (see the dedicated block further down). */
  #drawer-toggle,
  .gsearch-toggle { display: none !important; }

  /* --- Stats drawer: hidden on the compact home; revealed as a full-screen
         overlay (body.stats-open) by the "Live-Statistik öffnen" button ----- */
  #drawer {
    position: fixed;
    inset: 0;
    z-index: 1000;
    max-height: none !important;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    background: var(--paper);
    box-shadow: none;
    border-bottom: none;
    display: none;                 /* shown only in body.stats-open */
  }
  body.stats-open { overflow: hidden; }
  body.stats-open #drawer {
    display: block;
    animation: bp-fade-in 0.26s ease both;
  }
  /* Top padding clears the fixed close button + notch; bottom clears the
     home indicator. */
  #drawer-inner {
    padding: calc(58px + env(safe-area-inset-top, 0px)) 14px
             calc(24px + env(safe-area-inset-bottom, 0px));
  }
  .drawer-grid { grid-template-columns: 1fr; gap: 12px; }
  .problemstops-list { max-height: none; }

  /* Banner stacks: title row, then DQ + phase, then full-width tabs — avoids
     the title overlapping the Datenqualität donut at phone widths. */
  .drawer-banner { grid-template-columns: 1fr auto; gap: 10px 14px; }
  .banner-left { gap: 14px 16px; flex-wrap: wrap; }
  .drawer-phase { justify-self: end; }
  #drawerTabs { grid-column: 1 / -1; justify-self: stretch; }
  .drawer-tabs { justify-content: flex-start; flex-wrap: wrap; }

  /* Status-reasons hint drops into the flow rather than floating. */
  #reasonsCard {
    position: static;
    width: auto;
    max-width: none;
    margin: 0 14px 12px;
  }

  /* Footer sits at the natural end of the scroll. Tidied for phones: the
     desktop one-liner wraps raggedly at this width (dangling "·" separators,
     orphaned links), so the separators are dropped and the links flow as a
     centered row with the disclaimer on its own line beneath. */
  #site-footer {
    position: static;
    white-space: normal;
    padding: 8px 14px calc(8px + env(safe-area-inset-bottom, 0px));
  }
  #site-footer .sep { display: none; }
  #site-footer .footer-row {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    column-gap: 14px;
    row-gap: 2px;
  }
  #site-footer .muted-note { flex-basis: 100%; margin-top: 2px; }

  /* --- Map: live mini-preview on the compact home ---------------------- */
  /* The single Leaflet instance renders small + non-interactive here (taps
     and drags are disabled in app.js so vertical page scroll passes through);
     tapping it, or the "Live-Karte öffnen" button, promotes it to the
     full-screen overlay below. */
  #map {
    display: block;
    position: static;
    width: auto;
    height: 200px;
    margin: 12px 14px 0;
    border-radius: var(--radius-l);
    border: 1px solid var(--hairline);
    box-shadow: var(--shadow-sm);
    overflow: hidden;
    cursor: pointer;
    touch-action: pan-y;           /* let the page scroll over the preview */
  }

  /* Legacy floating FAB retired in favour of #mobile-actions. */
  #map-fab { display: none !important; }

  /* ====================================================================
     OVERLAY STATE — body.map-open
     ==================================================================== */
  body.map-open { overflow: hidden; }

  /* Full-screen map — reset the preview chrome (margin/border/radius). */
  body.map-open #map {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 1000;
    height: 100%;
    height: 100dvh;
    margin: 0;
    border: 0;
    border-radius: 0;
    cursor: auto;
  }

  /* Hide the open-button while the map is up; surface the close button. */
  body.map-open #map-fab { display: none; }
  body.map-open #map-close {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    position: fixed;
    z-index: 1003;
    top: calc(10px + env(safe-area-inset-top, 0px));
    right: 10px;
    padding: 10px 15px;
    border: 1px solid var(--hairline);
    border-radius: 999px;
    background: rgba(252,254,255,0.95);
    -webkit-backdrop-filter: saturate(180%) blur(12px);
            backdrop-filter: saturate(180%) blur(12px);
    color: var(--ink);
    font: 700 13px/1 var(--sans);
    box-shadow: var(--shadow-md);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  body.map-open #map-close svg { width: 14px; height: 14px; }

  /* Map controls become a bottom sheet over the open map. Hidden otherwise
     so they don't float over the stats scroll view. */
  #controls { display: none; }
  body.map-open #controls {
    display: flex;
    position: fixed;
    z-index: 1002;
    left: 8px;
    right: 8px;
    width: auto;
    top: auto;
    bottom: calc(8px + env(safe-area-inset-bottom, 0px));
    /* Collapsed by default – only the handle is visible.
       Expanded via .mob-open (toggled by the injected handle button). */
    max-height: 52px;
    overflow: hidden;
    transition: max-height .35s cubic-bezier(.2,.7,.2,1);
  }
  body.map-open #controls.mob-open {
    max-height: 56vh;
    overflow-y: auto;
  }

  /* Lift Leaflet's attribution above the collapsed Kartenoptionen sheet
     (~52px tall at bottom:8px) – at bottom:0 the OSM/CARTO credit was hidden
     behind the sheet, which violates the tile licenses. */
  body.map-open .leaflet-bottom {
    bottom: calc(64px + env(safe-area-inset-bottom, 0px)) !important;
  }
}

/* ======================================================================
   MOBILE — touch-first UX pass
   ----------------------------------------------------------------------
   Fixes and improvements on top of the stats-first layout above:

     1. KPI tiles: tighter sizing for the 2-column phone grid
     2. Datenqualität: hover popover → always-visible inline block
     3. Info-icon (:focus trigger already added globally above)
     4. Tap targets: segmented controls, layer toggles, line filter rows,
        collapsible headers — all lifted to ≥ 40 px touch height
     5. Erweiterte Modi hidden (Tempo/Auslastung not needed on the go)
     6. Solo-btn always visible (no hover state on touch)
     7. Drag handle visual on the controls bottom sheet
     8. Map close button: slightly larger tap target
   ====================================================================== */

@media (max-width: 720px) {

  /* ---- 1. KPI tiles: compact ----------------------------------------- */
  #kpi-strip .kpi { padding: 8px 11px 7px; }
  .kpi-label { font-size: 10.5px; }
  .kpi-value { font-size: 22px; }

  /* ---- 2. Datenqualität: inline on mobile ----------------------------- */
  /* The hover-reveal popover is inaccessible on touch. On mobile we unwrap
     it into an always-visible block beneath the label + donut row. */
  .banner-dq {
    flex-wrap: wrap;   /* lets the popover wrap onto its own line */
    cursor: default;
  }
  /* Suppress the hover highlight that was designed for mouse interaction */
  .banner-dq:hover,
  .banner-dq:focus-visible { background: transparent; outline: none; }

  .banner-dq-popover {
    /* Override absolute positioning → in-flow block */
    position: static !important;
    transform: none !important;
    opacity: 1 !important;
    pointer-events: auto;
    /* Stretch to its own line inside .banner-dq's flex row */
    flex-basis: 100%;
    /* Light-background reskin (was dark ink panel) */
    background: transparent;
    color: var(--ink-2);
    box-shadow: none;
    padding: 5px 0 0 0;
    border-radius: 0;
    font-size: 11.5px;
    min-width: 0;
  }
  /* Restyle the detail rows for the paper background */
  .banner-dq-popover .dqd-row { padding: 1px 0; gap: 10px; }
  .banner-dq-popover .dqd-lbl { color: var(--muted); }
  .banner-dq-popover .dqd-v   { color: var(--ink-2); font-size: 12px; }
  .banner-dq-popover .dqd-v.good { color: var(--good); }
  .banner-dq-popover .dqd-v.warn { color: var(--warn); }
  .banner-dq-popover .dqd-v.bad  { color: var(--bad);  }
  .banner-dq-popover .dqd-v.crit { color: var(--crit); }
  /* First row ("Datenqualität — XX%") is redundant: the donut already
     shows the score with its colour coding. */
  .banner-dq-popover .dqd-row:first-child { display: none; }

  /* ---- 5. Erweiterte Modi: remove from mobile ------------------------- */
  /* Tempo and Auslastung colouring modes are power-user features; hiding
     them keeps the bottom-sheet controls scannable on a phone. */
  #ctlExpert { display: none !important; }

  /* ---- 4. Tap targets ------------------------------------------------- */
  /* Segmented control (Linie / Verspätung) */
  .segmented label { padding: 11px 3px; }

  /* Layer toggles (Haltestellen / Liniennetz / Verspätungszonen) */
  .ctl-toggle { min-height: 40px; padding: 8px 6px; }

  /* Collapsible section headers */
  .ctl-header { min-height: 40px; padding: 8px 0; }

  /* Line filter rows */
  .row-line { padding: 9px 6px; min-height: 40px; }

  /* ---- 6. Solo-btn: always visible on touch --------------------------- */
  /* The solo-btn fades in on hover; touch devices have no hover state. */
  .row-line .solo-btn {
    opacity: 1;
    width: 28px; height: 28px;
  }

  /* ---- 7. Controls bottom sheet: drag handle -------------------------- */
  /* A 36 × 4 px pill at the top of the panel signals it can be scrolled. */
  body.map-open #controls::before {
    content: "";
    display: block;
    width: 36px;
    height: 4px;
    background: var(--dim);
    border-radius: 2px;
    margin: 10px auto 4px;
    flex-shrink: 0;
  }

  /* ---- 8. Map close button: comfortably tappable ---------------------- */
  body.map-open #map-close {
    min-height: 44px;
    padding: 11px 18px;
  }
}

/* ======================================================================
   MOBILE — map controls bottom sheet handle + popup width cap
   ====================================================================== */

/* Handle button injected by wireMobile() – hidden on desktop */
#mob-ctl-handle { display: none; }

/* Locate-me button: only on the phone full-screen map overlay */
#locate-btn { display: none; }
@media (max-width: 720px) {
  body.map-open #locate-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    position: fixed;
    z-index: 1002;
    right: 10px;
    bottom: calc(96px + env(safe-area-inset-bottom, 0px));  /* clears sheet + attribution */
    width: 46px; height: 46px;
    padding: 0;
    border: 1px solid var(--hairline);
    border-radius: 50%;
    background: rgba(252, 254, 255, 0.95);
    -webkit-backdrop-filter: saturate(180%) blur(12px);
            backdrop-filter: saturate(180%) blur(12px);
    color: var(--brand-2);
    box-shadow: var(--shadow-md);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    animation: bp-fade-in 0.3s ease 0.08s both;
    transition: color .2s ease;
  }
  body.map-open #locate-btn svg { width: 21px; height: 21px; }
  body.map-open #locate-btn:active { transform: scale(0.94); }
  body.map-open #locate-btn.is-locating svg { animation: locate-pulse 1s ease-in-out infinite; }
  body.map-open #locate-btn.is-error { color: var(--crit); }
  /* Hide while the Kartenoptionen sheet is expanded – the FAB would
     otherwise float over the layer toggles. display (not opacity) because
     the bp-fade-in entrance animation's fill-mode pins opacity at 1.
     (#controls precedes #locate-btn in the DOM, so the sibling selector
     works.) */
  body.map-open #controls.mob-open ~ #locate-btn { display: none; }
}
@keyframes locate-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.35; }
}

/* Locate feedback toast – created lazily by app.js. Sits above the FAB. */
#locate-toast {
  position: fixed;
  z-index: 1004;
  left: 50%;
  bottom: calc(160px + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%) translateY(6px);
  max-width: calc(100vw - 48px);
  background: var(--ink);
  color: #fff;
  font: 500 12.5px/1.4 var(--sans);
  padding: 9px 14px;
  border-radius: 10px;
  box-shadow: var(--shadow-lg);
  text-align: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity .25s ease, transform .25s ease;
}
#locate-toast.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* User-position marker (drawn by the locate handler in app.js) */
.user-loc-dot {
  /* L.circleMarker carries the styling; this class is the hook for the
     subtle entrance fade on the SVG path Leaflet renders. */
  animation: bp-fade-in 0.3s ease both;
}

@media (max-width: 720px) {

  /* --- Controls handle ----------------------------------------------- */
  /* Hides the old drag-handle ::before (the handle button replaces it). */
  body.map-open #controls::before { display: none; }

  /* Hide Linien filtern and the legend (i) button – not needed on mobile */
  body.map-open #ctlFilter,
  body.map-open #ctl-legend-info { display: none !important; }

  /* The handle itself */
  body.map-open #mob-ctl-handle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: 14px 16px;
    background: transparent;
    border: 0;
    font-family: var(--sans);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.6px;
    text-transform: uppercase;
    color: var(--muted);
    cursor: pointer;
    flex-shrink: 0;
    gap: 8px;
    -webkit-tap-highlight-color: transparent;
    transition: color .15s ease;
  }
  body.map-open #mob-ctl-handle:hover,
  body.map-open #mob-ctl-handle:focus-visible {
    color: var(--ink-2);
    outline: none;
  }
  .mob-ctl-label { flex: 1; text-align: left; }
  .mob-ctl-chevron {
    width: 16px; height: 16px;
    flex-shrink: 0;
    transition: transform .3s ease;
    /* Collapsed: chevron points UP – the sheet expands upward. (The raw SVG
       points down, shared with the collapsible sections.) */
    transform: rotate(180deg);
  }
  /* Expanded: chevron points down = "dismiss downward". */
  body.map-open #controls.mob-open #mob-ctl-handle .mob-ctl-chevron {
    transform: rotate(0deg);
  }

  /* --- Popup width cap ----------------------------------------------- */
  /* Leaflet sets maxWidth/minWidth inline; override so hub stop popups
     (minWidth: 440 px) cannot blow past the phone's viewport. */
  .leaflet-popup-content-wrapper {
    max-width: calc(100vw - 24px) !important;
    min-width: 0 !important;
  }
  .leaflet-popup-content {
    width: auto !important;
    min-width: 0 !important;
    box-sizing: border-box;
  }
  /* Ensure popup content itself is fluid */
  .bus-popup,
  .stop-popup {
    min-width: 0;
    width: 100%;
    box-sizing: border-box;
  }
  /* Departure grid: single column on narrow popups */
  .dep-rows-grid {
    grid-template-columns: 1fr;
    grid-auto-flow: row;
  }
}

/* ======================================================================
   MOBILE — refinement pass  (≤ 720 px)   [added 2026-06-02]
   ----------------------------------------------------------------------
   Targeted fixes layered on top of the two mobile blocks above. Every
   rule here is gated to the phone layout (the ≤720px media query or the
   body.map-open overlay state, which only exists on phones), so the
   desktop build is left completely untouched.

     1. Status pill: re-show the verdict word + make the timestamp
        tap-revealable (it was display:none from the ≤820px compression,
        leaving only a cryptic coloured dot on phones).
     2. Notch / home-indicator safe-area insets for the static header,
        the floating FAB and the page bottom (matters in installed-PWA
        standalone mode and on notched devices).
     3. Drawer stat tabs lifted to a ≥40px touch target.
     4. Map overlay: zoom control re-anchored to the top of the
        full-screen map (was offset ~200px down by --drawer-bottom),
        bottom-sheet scroll contained, and a soft entrance transition so
        the overlay no longer pops in abruptly.
   ====================================================================== */
@media (max-width: 720px) {

  /* ---- 1. Status pill: verdict word + tap-to-reveal timestamp --------- */
  /* The ≤820px rule collapses the pill to a dot to save header width on
     narrow *desktop*. On the stats-first phone layout there is room (the
     pill can wrap onto its own line), and the spelled-out verdict is the
     single most useful at-a-glance signal — especially in the all-good
     case, where the reasons card stays hidden. */
  #status-pill .txt {
    display: inline;
    white-space: nowrap;
  }
  /* Restore the timestamp element so the existing :focus-within reveal
     (max-width transition) works when the pill is tapped. #status-pill has
     tabindex="0", so a tap focuses it and slides the timestamp in. */
  #status-pill .last-update-wrap {
    display: inline-flex;
  }

  /* ---- 2. Safe-area insets ------------------------------------------- */
  /* Static header must clear the status bar / notch when launched as an
     installed PWA. In a normal browser tab env() resolves to 0, so this is
     a no-op there. */
  #topbar {
    padding-top: calc(12px + env(safe-area-inset-top, 0px));
  }
  /* Reserve the FAB's height AND the home-indicator strip below the footer. */
  body {
    padding-bottom: calc(84px + env(safe-area-inset-bottom, 0px));
  }

  /* ---- 3. Drawer stat tabs: comfortable touch target ----------------- */
  .dtab {
    padding: 9px 16px;          /* ~36–38px tall vs. the desktop ~24px */
    min-height: 38px;
    display: inline-flex;
    align-items: center;
  }
  /* Tactile press feedback (no hover on touch). */
  .dtab:active { transform: scale(0.97); }

  /* ---- 4a. Overlay: re-anchor the Leaflet zoom control --------------- */
  /* On the phone overlay the map is position:fixed inset:0, but the zoom
     control's CSS top is driven by --drawer-bottom (the tall static
     header), pushing +/- ~200px down the screen. Pin it back to the top
     instead, clearing any left safe-area inset. */
  body.map-open .leaflet-top.leaflet-left {
    top: calc(12px + env(safe-area-inset-top, 0px)) !important;
    left: calc(10px + env(safe-area-inset-left, 0px));
  }

  /* ---- 4b. Overlay: contain bottom-sheet scroll ---------------------- */
  /* Stops a flick inside the expanded controls sheet from scroll-chaining
     into the map (and the page) behind it. */
  body.map-open #controls {
    overscroll-behavior: contain;
  }

  /* ---- 4c. Overlay: soft entrance ------------------------------------ */
  /* The map and the controls sheet used to snap in with the display flip.
     A short fade / rise makes opening the map feel of-a-piece with the
     desktop's animated chrome. Honoured-but-neutralised under
     prefers-reduced-motion by the global rule that zeroes durations. */
  body.map-open #map {
    animation: bp-map-in 0.3s cubic-bezier(.2,.7,.2,1) both;
  }
  body.map-open #controls {
    animation: bp-sheet-up 0.34s cubic-bezier(.2,.7,.2,1) both;
  }
  body.map-open #map-close {
    animation: bp-fade-in 0.3s ease 0.06s both;
  }

  /* Full-screen takeover: collapse the page chrome entirely (not just leave it
     mounted behind the overlay) so nothing can bleed at the edges / safe areas
     and the map is genuinely the only thing on screen. Restored the instant
     body.map-open is removed. */
  body.map-open #topbar,
  body.map-open #site-footer,
  body.map-open #reasonsCard,
  body.map-open #drawer-toggle { display: none !important; }
}

/* Map opens by expanding to fill the screen rather than snapping in. */
@keyframes bp-map-in   { from { opacity: 0; transform: scale(1.05); } to { opacity: 1; transform: none; } }
@keyframes bp-fade-in  { from { opacity: 0; } to { opacity: 1; } }
@keyframes bp-sheet-up {
  from { transform: translateY(14px); opacity: 0; }
  to   { transform: none;             opacity: 1; }
}

/* ======================================================================
   MOBILE — compact home  (≤ 720 px)   [added 2026-06-02]
   ----------------------------------------------------------------------
   The phone landing view is now a lightweight dashboard: the chrome KPI
   tiles, a live mini-map preview, and two equal entry buttons. The
   detailed statistics and the full map each open as their own full-screen
   overlay (body.stats-open / body.map-open). Everything here is gated to
   the phone layout, so the desktop build is untouched.
   ====================================================================== */

/* Desktop: the mobile-only actions bar and stats-close button never show. */
#mobile-actions,
#stats-close { display: none; }

@media (max-width: 720px) {

  /* No floating FAB any more → the page just needs a little breathing room
     above the home indicator. */
  body { padding-bottom: calc(18px + env(safe-area-inset-bottom, 0px)); }

  /* ---- Two equal entry buttons --------------------------------------- */
  #mobile-actions {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px;
    margin: 12px 14px 0;
  }
  #mobile-actions button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 9px;
    min-height: 52px;
    padding: 13px 12px;
    border: 1px solid var(--hairline);
    border-radius: var(--radius-l);
    background: var(--surface);
    color: var(--ink);
    box-shadow: var(--shadow-sm);
    font: 700 14px/1.1 var(--sans);
    letter-spacing: 0.1px;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: transform .12s ease, box-shadow .15s ease, border-color .15s ease;
  }
  #mobile-actions button svg { width: 20px; height: 20px; flex-shrink: 0; }
  /* Equal weight, distinguished only by an accent-coloured glyph
     (cyan = map, coral = stats — the two brand colours). */
  #btn-open-map   svg { color: var(--brand); }
  #btn-open-stats svg { color: var(--accent); }
  #mobile-actions button:active { transform: scale(0.975); }
  #mobile-actions button:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
  }

  /* Hide the home affordances while an overlay is up (they sit behind the
     opaque overlay; hiding them also removes them from the tab order). */
  body.map-open   #mobile-actions,
  body.stats-open #mobile-actions,
  body.stats-open #map { display: none; }

  /* ---- Leaflet zoom: hidden on the preview, shown in the full map ----- */
  .leaflet-control-zoom { display: none; }
  body.map-open .leaflet-control-zoom { display: block; }

  /* ---- Stats overlay close button (mirrors #map-close) --------------- */
  body.stats-open #stats-close {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    position: fixed;
    z-index: 1003;
    top: calc(10px + env(safe-area-inset-top, 0px));
    right: 10px;
    min-height: 44px;
    padding: 11px 18px;
    border: 1px solid var(--hairline);
    border-radius: 999px;
    background: rgba(252, 254, 255, 0.95);
    -webkit-backdrop-filter: saturate(180%) blur(12px);
            backdrop-filter: saturate(180%) blur(12px);
    color: var(--ink);
    font: 700 13px/1 var(--sans);
    box-shadow: var(--shadow-md);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    animation: bp-fade-in 0.3s ease 0.04s both;
  }
  body.stats-open #stats-close svg { width: 14px; height: 14px; }
}

/* ======================================================================
   MOBILE — lighter visual pass  (≤ 720 px)   [added 2026-06-09]
   ----------------------------------------------------------------------
   The stats-first home read as "chunky": six separate shadowed, bordered
   white tiles stacked over an equally heavy map card and two bulky entry
   buttons — card-on-card weight everywhere. This pass keeps the exact same
   layout and information but lightens the surfaces:

     • the KPI tiles become ONE calm panel with hairline-divided cells
       (single outer border + shadow instead of six floating boxes)
     • the header collapses to a single compact row (smaller mark/wordmark)
     • the map preview + entry buttons lose their drop shadows and use a
       smaller, consistent radius so nothing competes for visual weight
     • the stats-overlay cards flatten to hairline separators

   Everything is gated to ≤720px (or the phone-only overlay states), so the
   desktop build is byte-for-byte unchanged. Reversible by deleting this
   block.
   ====================================================================== */
@media (max-width: 720px) {

  /* ---- Compact single-row header ------------------------------------- */
  #topbar {
    padding: calc(9px + env(safe-area-inset-top, 0px)) 14px 9px;
    gap: 8px 10px;
    align-items: center;
  }
  #brand { gap: 9px; }
  #brand .mark,
  #brand .mark img,
  #brand .mark svg { height: 32px; }
  #brand h1 { font-size: 18px; letter-spacing: 0; }

  /* ---- KPI strip: one unified flat panel ----------------------------- */
  /* The 1px grid gap plus a hairline-coloured panel background show through
     as crisp internal dividers; a single outer border + shadow replaces the
     six per-tile borders and shadows. */
  #kpi-strip {
    margin-top: 2px;
    gap: 1px;
    background: var(--hairline);
    border: 1px solid var(--hairline);
    border-radius: 14px;
    box-shadow: var(--shadow-sm);
    overflow: hidden;
  }
  #kpi-strip .kpi {
    background: var(--surface);
    border: 0;
    border-radius: 0;
    box-shadow: none;
    padding: 9px 12px 8px;
  }
  /* Tighter, calmer type inside the cells */
  .kpi-label { font-size: 10px; letter-spacing: 0.3px; }
  .kpi-value { font-size: 21px; }
  .bus-fill-row { margin: 3px 0 2px; gap: 8px; justify-content: flex-start; }
  .bus-fill-icon { width: 64px; height: 24px; }
  /* Show the percentage next to the bus icon: the desktop strip hides it for
     uniform tile height, but the 2-column phone grid has room — and the bare
     fill level alone reads as "no data" at a glance. */
  .bus-fill-num { display: block; font-size: 16px; }

  /* ---- Map preview: lighter card ------------------------------------- */
  #map {
    height: 178px;
    margin: 12px 14px 0;
    border-radius: 14px;
    box-shadow: none;
  }

  /* Expand affordance on the inert preview: a small badge in the top-right
     corner signals "this opens full-screen". pointer-events:none so the tap
     falls through to the #map click handler that opens the overlay. The badge
     disappears with the preview chrome once body.map-open is set. */
  body:not(.map-open) #map { position: relative; }
  body:not(.map-open) #map::after {
    content: "";
    position: absolute;
    top: 10px; right: 10px;
    width: 32px; height: 32px;
    z-index: 800;                 /* above Leaflet panes (≤700) */
    border-radius: 10px;
    border: 1px solid rgba(20, 40, 64, 0.10);
    background: rgba(255, 255, 255, 0.92)
      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%233a4a5c' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9.5 2.5h4v4M6.5 13.5h-4v-4M13.5 2.5L9 7M2.5 13.5L7 9'/%3E%3C/svg%3E")
      center / 15px no-repeat;
    box-shadow: var(--shadow-sm);
    pointer-events: none;
  }

  /* ---- Entry buttons: flatter, single-line --------------------------- */
  #mobile-actions { gap: 8px; margin: 12px 14px 0; }
  #mobile-actions button {
    min-height: 46px;
    padding: 11px 12px;
    border-radius: 12px;
    box-shadow: none;
    font: 700 13.5px/1.1 var(--sans);
    white-space: nowrap;
  }
  #mobile-actions button svg { width: 18px; height: 18px; }

  /* ---- Status pill: lighter chrome ----------------------------------- */
  #status-pill {
    box-shadow: none;
    background: var(--surface-2);
  }

  /* ---- Stats overlay cards: hairline-flat ---------------------------- */
  /* Inside the full-screen stats overlay, drop the per-card shadow so the
     scroll view reads as one continuous sheet rather than a stack of boxes. */
  body.stats-open .drawer-card {
    box-shadow: none;
    border-radius: 12px;
  }
}

/* ======================================================================
   MOBILE — preview is inert; map interacts only in full-screen  [2026-06-09]
   ----------------------------------------------------------------------
   On the phone home the map is a STATIC preview: panning/zooming is already
   disabled in JS (setMapInteractive(false)), but Leaflet markers still
   intercept taps and would open a popup on the tiny preview. We make every
   layer + control inside the preview pointer-transparent so a tap anywhere —
   including on a bus — falls through to the #map container's click handler,
   which opens the full-screen map. The #map element itself keeps its default
   pointer-events so that click still fires. Interactivity returns the moment
   body.map-open is set (this rule is scoped to :not(.map-open)).
   ====================================================================== */
@media (max-width: 720px) {
  body:not(.map-open) #map .leaflet-pane,
  body:not(.map-open) #map .leaflet-control { pointer-events: none; }

  /* ---- Readable bus popup in the full-screen map --------------------- */
  /* The earlier mobile rule forces .leaflet-popup-content-wrapper and the
     popup bodies to min-width:0 so the wide hub popups can't overflow the
     viewport. That also strips the bus popup's desktop 240px floor, letting
     it collapse into a thin, unreadable column when a bus near the edge is
     tapped. Restore a sensible floor for the bus popup only (stops/hubs keep
     min-width:0), capped so it can never exceed the viewport. */
  .bus-popup {
    min-width: min(260px, calc(100vw - 48px));
    width: auto;
  }
}

/* ======================================================================
   MOBILE — leaner Live-Statistik + collapsed status pill  [2026-06-09]
   ====================================================================== */
@media (max-width: 720px) {

  /* ---- Live-Statistik: drop secondary detail on phones --------------- */
  /* The Datenqualität donut + score already convey quality at a glance, so
     the spelled-out detail rows (Bus Bunching, Leerfahrten) are redundant on
     mobile. "Aktueller Betrieb" is likewise desktop-only chrome here. */
  .banner-dq-popover { display: none !important; }
  .drawer-phase      { display: none !important; }

  /* ---- Status pill: collapsed to a dot, tap to reveal ---------------- */
  /* On the compact home the pill shows only its coloured status dot and sits
     on the same row/height as the BusPuls logo. Tapping it (the pill is
     focusable, tabindex=0) reveals the spelled-out verdict, e.g.
     "Reibungsloser Betrieb". Overrides the earlier ≤720 rule that kept the
     verdict word always visible. */
  #status-pill { cursor: pointer; padding: 6px 9px; gap: 0; }
  #status-pill .txt,
  #status-pill .last-update-wrap { display: none; }
  #status-pill:focus .txt,
  #status-pill:focus-within .txt { display: inline; white-space: nowrap; }
  #status-pill:focus,
  #status-pill:focus-within { padding: 6px 13px 6px 11px; gap: 8px; }

  /* Swap to the abbreviated verdict on mobile (e.g. "Spürbare Verz.") so the
     expanded pill fits beside the logo. */
  #status-pill .txt-full  { display: none; }
  #status-pill .txt-short { display: inline; }

  /* Keep the collapsed pill on the SAME row/height as the logo. A ≤560px rule
     forces #brand to width:100%, which wrapped the pill onto its own line;
     with the pill reduced to a dot there is ample room beside the wordmark. */
  #brand { width: auto; flex: 1 1 auto; min-width: 0; }
  .topbar-actions { margin-left: auto; }
  #topbar { align-items: center; }
}

/* ======================================================================
   MOBILE — sticky footer + preview attribution fix  [2026-06-09]
   ====================================================================== */
@media (max-width: 720px) {

  /* ---- Footer pinned to the bottom of the page ----------------------- */
  /* Lay the compact home out as a column at least one screen tall and let
     the footer absorb the slack, so it rests on the bottom edge instead of
     floating mid-screen with dead space beneath it. The fixed/overlay
     elements (drawer, controls, full-screen map) are position:fixed and stay
     out of this flow. */
  body { display: flex; flex-direction: column; min-height: 100dvh; }
  /* The map preview GROWS to absorb all spare vertical space, so the home has
     no dead gap above the footer — the entry buttons and footer ride at the
     bottom and the live preview gets as big as the screen allows. */
  #map { flex: 1 1 auto; height: auto; min-height: 200px; }
  #mobile-actions, #site-footer { flex: none; }

  /* ---- Leaflet attribution: at the map's own bottom edge ------------- */
  /* The global rule lifts .leaflet-bottom by --footer-h so the credit line
     clears the desktop footer. On the small mobile preview --footer-h is the
     (wrapped, tall) page footer, which pushes the attribution into the middle
     of the map. Pin it back to the map's bottom for the preview AND overlay. */
  .leaflet-bottom { bottom: 0 !important; }

  /* ---- Readable stop / hub popups in the full-screen map ------------- */
  /* The mobile width-cap above sets .leaflet-popup-content to width:auto,
     which lets the content shrink-wrap to its narrowest column. Hub popups
     (many departures) then become an unreadable sliver. Give stop popups a
     readable floor and let HUB popups take almost the full viewport width.
     calc(100vw - 64px) accounts for the wrapper cap (24px) + the content's
     16px side margins, so the popup never overflows the screen. */
  .stop-popup {
    min-width: min(280px, calc(100vw - 64px));
    width: auto;
  }
  .stop-popup.is-hub {
    min-width: calc(100vw - 64px);
  }
}

/* ----------------------------------------------------------------------
   Status pill: expand to the full verdict by default when the header has
   room for it beside the logo (measured: brand + expanded pill fit on one
   row from ~480px up). Below this it stays the tap-to-reveal dot.
   ---------------------------------------------------------------------- */
@media (min-width: 500px) and (max-width: 720px) {
  #status-pill { padding: 6px 13px 6px 11px; gap: 8px; cursor: default; }
  #status-pill .txt { display: inline; white-space: nowrap; }
}

/* ======================================================================
   MOBILE — persistent full-width global search  [2026-06-09]
   ----------------------------------------------------------------------
   On desktop the search hides behind a toggle. On phones we surface it as a
   permanent full-width bar that sits in the chrome, on its own row directly
   above the map preview (it is the last flex child of #topbar, order:4). The
   field is taller for touch, the 16px font stops iOS zoom-on-focus, and the
   results dropdown drops full-width over the preview. Selecting a result is
   wired (in app.js) to open the full-screen map and fly there.
   ====================================================================== */
@media (max-width: 720px) {
  /* Reveal the search (the generic ≤720 rule hides it) and lay it full-width
     on its own row between the KPI panel and the map preview. */
  #global-search {
    display: block;
    order: -1;            /* sit at the very TOP of the chrome, above the logo */
    width: 100%;
    flex: 0 0 auto;
    margin: 0 0 10px;
  }
  /* Lift the chrome above the (now full-height) map preview so the results
     dropdown — anchored inside the topbar — overlays the map instead of being
     hidden behind it. The topbar's backdrop-filter makes it its own stacking
     context, so an explicit z-index is needed here. Overlays (z:1000+) still
     win. */
  #topbar { position: relative; z-index: 20; }

  /* "Aktueller Status" reasons card removed on mobile for now — it floated
     over the search results and isn't needed on the compact home. */
  #reasonsCard { display: none !important; }
  /* Field is always visible on mobile (no open/close toggle). */
  .gsearch-field {
    display: flex;
    height: 46px;
    border-radius: var(--radius-l);
    padding: 0 14px;
  }
  .gsearch-close-btn { display: none; }       /* no toggle to close on mobile */
  #gsearchInput { font-size: 16px; }          /* ≥16px → no iOS focus zoom */
  .gsearch-icon  { width: 16px; height: 16px; }
  .gsearch-clear { width: 22px; height: 22px; }

  /* Dropdown: full-width over the preview, comfortably tall + scrollable. */
  .gsearch-dropdown {
    left: 0; right: 0;
    min-width: 0; max-width: none;
    max-height: 60vh;
    border-radius: var(--radius-l);
  }
  .gsearch-item { padding-top: 9px; padding-bottom: 9px; }   /* roomier rows */
}
