:root {
  color-scheme: light dark;
  --bg: #ffffff;
  --fg: #1a1a1a;
  --muted: #666;
  --border: #ddd;
  --accent: #1565c0;
  --accent-fg: #ffffff;
  --row-alt: #f7f7f7;
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #181a1b;
    --fg: #e8e8e8;
    --muted: #999;
    --border: #3a3d3f;
    --accent: #4a90d9;
    --accent-fg: #0b1220;
    --row-alt: #202325;
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--fg);
}

.shell { display: flex; flex-direction: column; height: 100vh; }

.nav {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  padding: 0.6rem 1rem;
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
}
.nav .brand { font-weight: 600; }
.nav-links { display: flex; gap: 1rem; flex: 1; flex-wrap: wrap; }
.nav-links a { color: var(--muted); text-decoration: none; padding: 0.3rem 0; }
.nav-links a.active { color: var(--fg); border-bottom: 2px solid var(--accent); }
.nav-user { display: flex; align-items: center; gap: 0.6rem; color: var(--muted); }

main#page { flex: 1; overflow: auto; padding: 1rem 1.25rem; }

h2 { margin-top: 0; }

.map-view { width: 100%; height: 100%; min-height: 400px; }
/* Positioning context for the track panel, which floats over the map. */
.map-wrap { position: relative; width: 100%; height: 100%; min-height: 400px; }
/* Permanent aircraft labels: a small pill whose background is set inline per
   marker to match its dot (map.js applyLabelColor). No arrow — the background
   is the only colour cue, and a CSS-only arrow can't be tinted inline. */
.map-label { border: none; box-shadow: none; color: #fff; font-size: 0.75rem; font-weight: 600; padding: 1px 5px; border-radius: 4px; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.45); }
.map-label:before { display: none; }
/* Source badge, added only where the same airframe is drawn twice (once per
   receiver) so the two markers can be told apart without decoding the shape.
   Deliberately not called `src-chip`: that's the aircraft page's fixed-width
   database chip further down, and sharing the name means sharing its `width`. */
.map-label .map-src { font-weight: 500; font-size: 0.85em; background: rgba(255, 255, 255, 0.28); border-radius: 3px; padding: 0 3px; margin-left: 3px; }
/* The horizontal/vertical disagreement between an airframe's two sources, on
   the line joining them. Neutral grey: it belongs to the pair, not to either
   marker's colour. */
.map-label.pair-delta { background: rgba(40, 40, 40, 0.78); font-weight: 500; font-size: 0.68rem; }

/* A target coasting on its last known position after losing signal (see
   map_lost_target_minutes). The dimming itself is inline — Leaflet's
   setOpacity for the marker and for its label's tooltip, the trail's and the
   leader line's own opacity — so this only adds the cues opacity alone can't
   carry: a dashed outline and an italic label, so a frozen altitude/speed
   readout can't be mistaken for a current one. */
.map-target-stale .aircraft-dot,
.map-target-stale .adsb-diamond { border-style: dashed; }
.map-label-stale { font-style: italic; }

/* ADS-B markers are diamonds (a rotated square) vs circles for Flarm/OGN. */
.adsb-diamond { display: block; width: 8px; height: 8px; margin: 2px auto; transform: rotate(45deg); border: 1px solid rgba(0, 0, 0, 0.35); box-sizing: border-box; }
.aircraft-dot { display: block; width: 12px; height: 12px; border-radius: 50%; border: 1px solid rgba(0, 0, 0, 0.35); box-sizing: border-box; }

/* Mouse-following readout listing every airspace under the pointer. */
.airspace-hover { background: var(--bg); color: var(--fg); border: 1px solid var(--border); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2); font-size: 0.72rem; line-height: 1.4; padding: 3px 7px; white-space: nowrap; }
.airspace-hover:before { display: none; }

.data-table { border-collapse: collapse; width: 100%; margin-bottom: 1rem; }
.data-table th, .data-table td { border-bottom: 1px solid var(--border); padding: 0.4rem 0.6rem; text-align: left; font-size: 0.9rem; }
.data-table th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
.data-table th.sortable:hover { color: var(--accent); }
.data-table th .sort-ind { color: var(--muted); font-size: 0.8em; }
.data-table tbody tr:nth-child(even) { background: var(--row-alt); }
/* Aircraft page row states. The td-level tints override the row striping
   regardless of parity; declaration order decides which wins when a row is
   both, and a data-quality problem outranks a pending decision. */
.data-table tbody tr.inactive-row td { background: rgba(21, 101, 192, 0.12); }
.data-table tbody tr.stale-row td { color: var(--muted); }
/* An aircraft flagged for implausible fixes (see fix_sanity.rs). */
.data-table tbody tr.flagged-row td { background: rgba(224, 134, 42, 0.14); }
.flag-badge { cursor: pointer; color: #e0862a; font-weight: 700; }
/* Small inline labels in the aircraft table: an inactive aircraft awaiting a
   decision, a returning one, and the remaining runtime of a temporary entry. */
.new-badge, .known-badge, .temp-badge {
  font-size: 0.75rem; padding: 0 0.3rem; border-radius: 3px; white-space: nowrap;
  border: 1px solid var(--border); color: var(--muted);
}
.new-badge { border-color: var(--accent); color: var(--accent); font-weight: 600; }
.known-badge { border-color: var(--accent); color: var(--accent); }

/* "✕" that hides a detection from the suggestion card until the aircraft is
   next seen (detected_aircraft::dismiss). Deliberately quiet next to the
   add/ignore buttons -- it's a dismissal, not a decision. */
.suggest-dismiss {
  margin-left: 0.35rem; padding: 0 0.45rem; border: 1px solid var(--border);
  background: transparent; color: var(--muted); border-radius: 4px; cursor: pointer;
}
.suggest-dismiss:hover { color: var(--fg); border-color: var(--muted); }

/* Cross-check database coverage per aircraft: O = OGN-DB, F = Flarmnet,
   A = ADS-B register. Green = same registration as ours, red = a different one,
   faint grey = that database doesn't know the id (nothing wrong, just absent,
   so it must not draw the eye like a mismatch does). Filled chips rather than
   coloured letters, so the row reads at a glance without having to tell
   #2e9e4f from #c0392b on thin glyphs. */
/* Right-aligned figures (storage overview): sizes and counts line up on their
   last digit, which is the whole point of putting them in a column. */
.data-table th.num, .data-table td.num { text-align: right; font-variant-numeric: tabular-nums; }

.src-chips { white-space: nowrap; }
.src-chip {
  display: inline-block; width: 1.15rem; text-align: center; margin-right: 2px;
  border-radius: 3px; font-size: 0.72rem; font-weight: 700; line-height: 1.4;
  color: #fff; cursor: help;
}
.src-chip.src-ok { background: #2e9e4f; }
.src-chip.src-bad { background: #c0392b; }
.src-chip.src-none { background: transparent; color: var(--muted); opacity: 0.5; border: 1px solid var(--border); }
/* An aircraft nobody has heard from in a year is greyed out; its chips
   shouldn't shout over the rest of the row. */
.data-table tbody tr.stale-row .src-chip { opacity: 0.55; }

/* One-time login link box on the user-management page. */
.link-box { margin-top: 1rem; padding: 0.75rem; border: 1px solid var(--border); border-radius: 6px; background: var(--row-alt); font-size: 0.9rem; }
.link-box .link-row { display: flex; gap: 0.4rem; margin-top: 0.4rem; }
.link-box input { flex: 1; border: 1px solid var(--border); background: var(--bg); color: var(--fg); padding: 0.35rem 0.5rem; border-radius: 4px; font-family: monospace; font-size: 0.85rem; }
.data-table input[type="text"], .data-table input[type="number"] { width: 100%; border: 1px solid var(--border); background: var(--bg); color: var(--fg); padding: 0.2rem; }

.inline-form { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 1rem; }
.inline-form input, .inline-form select { border: 1px solid var(--border); background: var(--bg); color: var(--fg); padding: 0.4rem; }

/* Roles page: permissions (rows) × roles (columns) matrix. */
.table-scroll { overflow-x: auto; }
.roles-matrix th.role-col { text-align: center; vertical-align: top; white-space: nowrap; }
.roles-matrix .role-col-name { font-weight: 600; }
.roles-matrix .role-col-meta { font-size: 0.75rem; font-weight: normal; margin: 0.15rem 0; }
.roles-matrix .perm-label { white-space: nowrap; }
.roles-matrix .perm-cell { text-align: center; }
.role-badge { display: inline-block; padding: 0 0.3rem; border: 1px solid var(--border); border-radius: 4px; font-size: 0.7rem; color: var(--muted); }

/* Video download page: render-progress bar. */
.video-dl { max-width: 640px; }
.video-dl-bar { height: 22px; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; background: var(--row-alt); margin: 0.75rem 0; }
.video-dl-fill { height: 100%; width: 0; background: var(--accent); transition: width 0.3s ease; }

/* Multi-role checkbox groups (Benutzer add form + inline edit). */
.role-checks { display: inline-flex; flex-wrap: wrap; gap: 0.2rem 0.75rem; align-items: center; }
.role-checks label { font-size: 0.85rem; white-space: nowrap; display: inline-flex; align-items: center; gap: 0.25rem; }

button {
  background: var(--accent);
  color: var(--accent-fg);
  border: none;
  padding: 0.4rem 0.9rem;
  border-radius: 4px;
  cursor: pointer;
}
button:hover { opacity: 0.9; }
button:disabled { opacity: 0.45; cursor: default; }

.settings-section { margin-bottom: 1.5rem; }
.settings-section h3 { margin-bottom: 0.5rem; }

.settings-form { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 0.75rem; margin-bottom: 1rem; align-items: end; }
.settings-form label { display: flex; flex-direction: column; font-size: 0.85rem; color: var(--muted); gap: 0.25rem; }
.settings-form input, .settings-form textarea { border: 1px solid var(--border); background: var(--bg); color: var(--fg); padding: 0.4rem; }
.settings-form input:disabled, .settings-form textarea:disabled { opacity: 0.6; cursor: not-allowed; }

textarea, #raw-json, #area-wkt {
  width: 100%;
  font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 0.85rem;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--fg);
  padding: 0.5rem;
}

.form-actions { display: flex; align-items: center; gap: 0.75rem; margin: 0.5rem 0 1.5rem; }
.error-text { color: #c0392b; }
.error-banner { padding: 1rem; color: #c0392b; }

/* Red exclamation badge for a hard fault on the Status page (currently: no
   ADS-B feeder connected). Kept a filled circle rather than a coloured
   glyph so it stays legible in both themes and doesn't depend on emoji
   rendering. The text colour is fixed white, not var(--accent-fg), since
   the badge background is always the same red in both themes. */
.badge-alert {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  margin-right: 0.4rem;
  border-radius: 50%;
  background: #c0392b;
  color: #ffffff;
  font-weight: 700;
  font-size: 0.85rem;
  line-height: 1;
  vertical-align: text-bottom;
}

.login-screen { display: flex; align-items: center; justify-content: center; height: 100vh; }
.login-form { display: flex; flex-direction: column; gap: 0.75rem; width: 280px; }
.login-form label { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.85rem; color: var(--muted); }
.login-form input { border: 1px solid var(--border); background: var(--bg); color: var(--fg); padding: 0.5rem; }

.status-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1rem; align-items: start; }
.status-card { border: 1px solid var(--border); border-radius: 6px; padding: 0.75rem 1rem; }
.status-card h3 { margin-top: 0; }
.status-card-wide { grid-column: 1 / -1; }

/* Floats over the map's bottom-left corner rather than shrinking it -- the
   map is the point of the page, and the panel is transient. */
.track-panel {
  position: absolute;
  left: 1rem;
  bottom: 1rem;
  z-index: 1000; /* above Leaflet's tile/overlay panes */
  width: min(420px, calc(100% - 2rem));
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.6rem 0.8rem;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
}
.track-panel h3 { margin: 0; font-size: 0.95rem; }
.track-panel #track-close { padding: 0.15rem 0.5rem; line-height: 1; }
/* 30-min / all-day segmented toggle for the altitude widget. */
.track-range { display: flex; gap: 0.25rem; margin: 0.3rem 0 0.4rem; }
.track-range button { padding: 0.15rem 0.55rem; font-size: 0.78rem; background: var(--bg); color: var(--muted); border: 1px solid var(--border); border-radius: 4px; cursor: pointer; }
.track-range button.active { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
.track-panel #track-summary { font-size: 0.8rem; margin-bottom: 0.25rem; }
/* Which feed/receiver each fix of the plotted track came from: a legend with
   one entry per source, and a line naming the source of the fix the altitude
   chart's picker is on. Both share the dot colour used for the track's fixes. */
.track-sources { font-size: 0.78rem; display: flex; flex-wrap: wrap; gap: 0.15rem 0.6rem; align-items: baseline; }
.track-sources:empty { display: none; }
.src-item { white-space: nowrap; }
.src-swatch { display: inline-block; width: 0.55em; height: 0.55em; border-radius: 50%; margin-right: 0.3em; vertical-align: baseline; }
.track-fix-src { font-size: 0.78rem; min-height: 1.1em; color: var(--muted); }

.card-header-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin-bottom: 0.5rem; }
.card-header-row h3 { margin-bottom: 0; }
.date-nav { display: flex; align-items: center; gap: 0.35rem; }
.date-nav button { padding: 0.3rem 0.6rem; font-size: 0.85rem; }
.date-input { border: 1px solid var(--border); background: var(--bg); color: var(--fg); padding: 0.25rem 0.4rem; border-radius: 4px; font-size: 0.85rem; }

.plain-list { list-style: none; margin: 0; padding: 0; }
.plain-list li { padding: 0.15rem 0; }

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

/* Local weather station (EDKL) widget — Status card, map overlay, replay box. */
.weather-panel { display: flex; gap: 0.8rem; align-items: center; }
.weather-panel-compact { gap: 0.5rem; }
.weather-rose { flex: 0 0 auto; }
.weather-values { flex: 1 1 auto; min-width: 0; font-size: 0.85rem; }
.weather-row { display: flex; justify-content: space-between; gap: 0.75rem; padding: 0.1rem 0; }
.weather-row span { color: var(--muted); }
.weather-age { font-size: 0.75rem; margin-top: 0.25rem; }
.wind-rose .wr-ring { stroke: var(--border); stroke-width: 1.5; }
.wind-rose .wr-tick { fill: var(--muted); font-size: 11px; }
.wind-rose .wr-arrow { stroke: var(--accent); stroke-width: 3; }
.wind-rose .wr-arrow-head { fill: var(--accent); }
.wind-rose .wr-calm { fill: var(--muted); }

/* Live weather overlay on the map, mirroring .track-panel but top-right. */
.weather-box {
  position: absolute;
  right: 1rem;
  top: 1rem;
  z-index: 1000; /* above Leaflet's tile/overlay panes */
  width: min(240px, calc(100% - 2rem));
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.5rem 0.7rem;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
}
.weather-box h3 { margin: 0 0 0.35rem; font-size: 0.9rem; }
/* On the replay map the altitude panel already occupies the top-right, so the
   weather box sits bottom-left instead. */
.weather-box-replay { top: auto; right: auto; bottom: 1rem; left: 1rem; }

/* --- Rain radar (see static/js/radar.js) ---------------------------------- */

/* The overlay sits between the tiles and everything drawn on them. Blocky on
   purpose: a cell is a 1 km measurement, and smoothing it would draw a
   precision the composite does not have. */
.radar-tiles { image-rendering: pixelated; }

.radar-toggle {
  padding: 0.2rem 0.55rem;
  font-size: 0.8rem;
  line-height: 1;
  background: var(--bg);
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 4px;
  cursor: pointer;
}
.radar-toggle.on { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }

/* Same furniture as the weather box, bottom-left of the live map; on the replay
   the weather box already has that corner, so this one sits above it. */
.radar-box {
  position: absolute;
  left: 1rem;
  bottom: 1rem;
  z-index: 1000;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.5rem 0.7rem;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
}
.radar-box h3 { margin: 0 0 0.35rem; font-size: 0.9rem; display: flex; gap: 0.4rem; align-items: baseline; }
.radar-box-replay { bottom: auto; top: 1rem; left: 1rem; }

/* The legend carries the numbers: intensity is never colour alone. */
.radar-legend { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem 0.5rem; font-size: 0.75rem; color: var(--muted); }
.radar-step { display: inline-flex; align-items: center; gap: 0.25rem; }
.radar-step i { width: 12px; height: 12px; border-radius: 2px; display: inline-block; }
.radar-unit { margin-left: 0.1rem; }

.metar-chart { width: 100%; height: auto; }
.metar-chart .chart-axis { stroke: var(--border); stroke-width: 1; }
.metar-chart .chart-label { fill: var(--muted); font-size: 9px; }
.metar-chart .chart-grid { stroke: var(--border); stroke-width: 1; stroke-dasharray: 2 3; opacity: 0.6; }
.metar-chart.chart-interactive { cursor: crosshair; }
.metar-chart .chart-picker-line { stroke: var(--fg); stroke-width: 1; stroke-dasharray: 3 3; opacity: 0.7; }
.metar-chart .chart-picker-dot { stroke: var(--bg); stroke-width: 1.5; }
.metar-chart .chart-picker-label { fill: var(--fg); font-size: 10px; font-weight: 600; }
.metar-chart .chart-picker-label-bg { fill: var(--bg); opacity: 0.85; }

.clearance-row { border: 1px solid var(--border); border-radius: 6px; padding: 0.75rem; margin-bottom: 0.75rem; }
.clearance-row-header { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; }
.clearance-name-input { flex: 1; max-width: 200px; border: 1px solid var(--border); background: var(--bg); color: var(--fg); padding: 0.35rem; font-weight: 600; }

/* Chip lists back the ignored-airspaces list and each Freigabe's cleared
   airspaces; the same widget, populated from a JS model. */
.chip-list { display: flex; flex-wrap: wrap; gap: 0.35rem; margin: 0.4rem 0; }
.chip { display: inline-flex; align-items: center; gap: 0.15rem; padding: 0.15rem 0.15rem 0.15rem 0.6rem; border: 1px solid var(--border); border-radius: 999px; background: var(--row-alt); font-size: 0.85rem; }
.chip-remove { background: transparent; color: var(--muted); border: none; padding: 0 0.35rem; font-size: 1.05rem; line-height: 1; cursor: pointer; border-radius: 999px; }
.chip-remove:hover { color: var(--fg); opacity: 1; }
.chip-empty { font-size: 0.85rem; }
.chip-add-btn { background: transparent; color: var(--accent); border: 1px solid var(--border); }

/* Airspace picker modal: a searchable checkbox list beside an interactive map,
   built on the native <dialog> (backdrop + Esc for free). */
dialog.airspace-picker {
  width: min(960px, 94vw);
  height: min(620px, 88vh);
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--fg);
  box-shadow: 0 6px 30px rgba(0, 0, 0, 0.35);
  overflow: hidden;
}
dialog.airspace-picker::backdrop { background: rgba(0, 0, 0, 0.45); }
.airspace-picker { display: flex; flex-direction: column; }
.picker-header { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; padding: 0.7rem 1rem; border-bottom: 1px solid var(--border); }
.picker-header h3 { margin: 0; font-size: 1rem; }
.picker-controls { display: flex; gap: 0.5rem; }
.picker-search, .picker-class { border: 1px solid var(--border); background: var(--bg); color: var(--fg); padding: 0.35rem 0.5rem; border-radius: 4px; }
.picker-body { flex: 1; display: grid; grid-template-columns: minmax(240px, 1fr) 1.4fr; min-height: 0; }
.picker-list { list-style: none; margin: 0; padding: 0.25rem; overflow-y: auto; border-right: 1px solid var(--border); }
.picker-item label { display: flex; align-items: baseline; gap: 0.5rem; padding: 0.3rem 0.5rem; cursor: pointer; border-radius: 4px; }
.picker-item label:hover { background: var(--row-alt); }
.picker-item-name { font-weight: 500; }
.picker-item-meta { margin-left: auto; color: var(--muted); font-size: 0.78rem; white-space: nowrap; }
.picker-map { height: 100%; min-height: 280px; }
.picker-footer { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 0.6rem 1rem; border-top: 1px solid var(--border); }
.picker-actions { display: flex; gap: 0.5rem; }
.picker-cancel { background: transparent; color: var(--fg); border: 1px solid var(--border); }

@media (max-width: 640px) {
  .picker-body { grid-template-columns: 1fr; grid-template-rows: 1fr 1fr; }
  .picker-list { border-right: none; border-bottom: 1px solid var(--border); }
}

/* Small text link used for the per-day "Details / Replay" link and the replay
   page's "‹ Status" back link. */
.details-link { color: var(--accent); text-decoration: none; font-size: 0.85rem; cursor: pointer; white-space: nowrap; }
.details-link:hover { text-decoration: underline; }

/* --- Day-replay ("Tagesreplay") page --- */
.replay-wrap { display: flex; flex-direction: column; height: 100%; min-height: 500px; gap: 0.6rem; }
.replay-head { display: flex; align-items: baseline; gap: 1rem; flex-wrap: wrap; }
.replay-head h2 { margin: 0; font-size: 1.1rem; }
.replay-date { color: var(--accent); }

/* Prev/next day arrows. The disabled one keeps its space (it's a <span>, not a
   removed node) so the header doesn't jump at the replay window's edges. */
.replay-daynav-group { display: inline-flex; gap: 0.25rem; }
.replay-daynav {
  display: inline-block;
  min-width: 1.4rem;
  text-align: center;
  padding: 0.05rem 0.3rem;
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--accent);
  text-decoration: none;
  font-size: 0.95rem;
  line-height: 1.3;
}
.replay-daynav:hover { background: var(--row-alt); }
.replay-daynav.disabled { color: var(--muted); opacity: 0.45; cursor: not-allowed; }
.replay-daynav.disabled:hover { background: none; }

/* The day's event count in the info line, doubling as the list-modal trigger. */
.replay-events-open {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: var(--accent);
  text-decoration: underline dotted;
  cursor: pointer;
}
.replay-events-open:hover { text-decoration: underline; }

/* Takeoff/landing list modal. */
dialog.replay-events {
  width: min(560px, 94vw);
  max-height: min(70vh, 720px);
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--fg);
  box-shadow: 0 6px 30px rgba(0, 0, 0, 0.35);
  overflow: hidden;
}
dialog.replay-events::backdrop { background: rgba(0, 0, 0, 0.45); }
.replay-events-body { max-height: calc(70vh - 4rem); overflow-y: auto; padding: 0.5rem 1rem 1rem; }
.replay-events-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.replay-events-table th { text-align: left; font-weight: 600; padding: 0.3rem 0.4rem; border-bottom: 1px solid var(--border); position: sticky; top: 0; background: var(--bg); }
.replay-events-table td { padding: 0.25rem 0.4rem; border-bottom: 1px solid var(--border); }
.replay-events-jump {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
  cursor: pointer;
}
.replay-events-jump:hover { text-decoration: underline; }
/* Start/Landung are separate columns, tinted so a glance down either one reads
   as "when did it go up / come down" without a per-row kind word. */
.replay-events-table th.takeoff, .replay-events-kind.takeoff .replay-events-jump { color: #2e9e4f; }
.replay-events-table th.landing, .replay-events-kind.landing .replay-events-jump { color: #c0392b; }
/* Amber, and hollow wherever it is drawn: a pass the detector could not decide,
   not a third kind of landing. */
.replay-events-table th.possible, .replay-events-kind.possible .replay-events-jump { color: #b7791f; }
.replay-events-kind { width: 6.5rem; }
.replay-events-table td:first-child { white-space: nowrap; }

/* The live map's own controls — "Tagesreplay ›" and the m/ft toggle — floating
   over its top-left corner. */
.map-tools {
  position: absolute;
  top: 0.5rem;
  left: 3.4rem; /* clear of Leaflet's zoom control */
  z-index: 500;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
}
.map-tools .details-link {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0.2rem 0.5rem;
}
/* Which unit every altitude on the map is read out in. A segmented pair rather
   than a checkbox: both units are named, so it says what it does without
   having to be toggled to find out. */
.unit-toggle { display: inline-flex; background: var(--bg); border: 1px solid var(--border); border-radius: 4px; overflow: hidden; }
.unit-toggle button { padding: 0.2rem 0.5rem; font-size: 0.8rem; line-height: 1; background: var(--bg); color: var(--muted); border: none; cursor: pointer; }
.unit-toggle button + button { border-left: 1px solid var(--border); }
.unit-toggle button.active { background: var(--accent); color: var(--accent-fg); }

/* The map fills the body; the altitude panel floats over it (top-right). */
.replay-body { position: relative; flex: 1; min-height: 320px; }
.replay-map { width: 100%; height: 100%; border: 1px solid var(--border); border-radius: 6px; }
.replay-panel {
  position: absolute;
  top: 0.6rem;
  right: 0.6rem;
  z-index: 1000; /* above Leaflet's panes */
  width: min(400px, calc(100% - 1.2rem));
  max-height: calc(100% - 1.2rem);
  overflow: auto;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
  padding: 0.6rem 0.8rem;
}
.replay-panel h3 { margin: 0; font-size: 0.95rem; }
/* radio_id / ICAO address shown after the callsign, de-emphasised. Shared by
   the live map's track panel and the replay panel. */
.track-panel h3 .track-id,
.replay-panel h3 .track-id { font-weight: normal; color: var(--muted); }
.replay-panel #replay-panel-close { padding: 0.15rem 0.5rem; line-height: 1; }
.replay-panel #replay-panel-summary { font-size: 0.8rem; margin-bottom: 0.35rem; }
.replay-panel-igc { margin-bottom: 0.35rem; }
/* IGC download link — replay altitude panel + Starts & Landungen table. */
.igc-link { color: var(--accent); text-decoration: none; font-size: 0.8rem; white-space: nowrap; }
.igc-link:hover { text-decoration: underline; }
/* Radar link on takeoff/landing rows: opens the replay at that moment. */
.radar-link { text-decoration: none; opacity: 0.7; }
.radar-link:hover { opacity: 1; }

.replay-transport { display: flex; flex-direction: column; gap: 0.5rem; padding: 0.5rem 0.25rem 0.25rem; }
.replay-controls { display: flex; align-items: center; gap: 0.9rem; flex-wrap: wrap; }
#replay-playpause { min-width: 2.4rem; font-size: 1rem; padding: 0.25rem 0.6rem; }
/* The clock doubles as a jump-to-time field: type HH:MM:SS + Enter. */
.replay-clock { font-family: monospace; font-size: 1rem; font-variant-numeric: tabular-nums; width: 6.5rem; text-align: center; border: 1px solid var(--border); background: var(--bg); color: var(--fg); padding: 0.2rem 0.3rem; border-radius: 4px; }
.replay-clock:disabled { color: var(--muted); }
.replay-clock:focus { outline: none; border-color: var(--accent); }
.replay-speed, .replay-filter { display: flex; align-items: center; gap: 0.4rem; font-size: 0.85rem; color: var(--muted); }
.replay-speed select, .replay-filter input { border: 1px solid var(--border); background: var(--bg); color: var(--fg); padding: 0.2rem 0.4rem; border-radius: 4px; }
.replay-filter input { width: 8rem; }

/* The timeline: the native range slider is the playhead; ticks + the (future)
   radio lane are positioned over the same 0–100% track. */
.replay-timeline { position: relative; padding-bottom: 0.75rem; isolation: isolate; }
#replay-seek { width: 100%; margin: 0; cursor: pointer; }
.replay-ticks, .replay-radio-lane { position: absolute; left: 0; right: 0; height: 12px; pointer-events: none; }
.replay-ticks { top: 1.15rem; }
.replay-radio-lane { top: 1.9rem; height: 6px; } /* radio-call ticks (funkradio) */
/* Each event tick is a small clickable triangle sitting under the slider. */
.replay-tick { position: absolute; transform: translateX(-50%); width: 0; height: 0; padding: 0; border: none; background: transparent; cursor: pointer; pointer-events: auto; border-left: 5px solid transparent; border-right: 5px solid transparent; }
.replay-tick.takeoff { border-bottom: 8px solid #2e9e4f; }
.replay-tick.landing { border-top: 8px solid #c0392b; }
.replay-tick.possible { border-top: 8px solid #b7791f; opacity: 0.75; }
.replay-tick.dimmed { opacity: 0.18; } /* filtered out by the callsign filter */
/* Radio-call ticks: small bars in the lane below the event triangles. RX blue,
   TX orange; the one currently playing grows and gets a ring. */
/* Width is set inline to the call's duration in the current window; min-width
   keeps a short call visible at the original minimal size. */
.replay-radio-tick { position: absolute; top: 0; min-width: 4px; height: 6px; padding: 0; border: none; border-radius: 1px; background: #2b6cb0; cursor: pointer; pointer-events: auto; box-sizing: border-box; }
.replay-radio-tick.tx { background: #dd6b20; }
.replay-radio-tick:hover { background: #1a4d7a; }
.replay-radio-tick.tx:hover { background: #b45309; }
.replay-radio-tick.playing { height: 11px; top: -3px; box-shadow: 0 0 0 1px #fff, 0 0 4px rgba(0, 0, 0, 0.5); z-index: 1; }

/* Radio export (radio_export.js): the [Anfang]…[Ende] range highlight and a
   downward-pointing playhead that tracks the clock across the timeline. */
.replay-range { position: absolute; left: 0; right: 0; top: 0.75rem; height: 1.7rem; pointer-events: none; }
.replay-range-band { position: absolute; top: 0; bottom: 0; background: rgba(21, 101, 192, 0.13); border-left: 1px solid rgba(21, 101, 192, 0.55); border-right: 1px solid rgba(21, 101, 192, 0.55); }
.replay-range-bracket { position: absolute; top: 50%; transform: translate(-50%, -50%); z-index: 5; font: 700 1.15rem/1 monospace; color: var(--accent); text-shadow: 0 0 2px var(--bg), 0 0 2px var(--bg); }
.replay-playhead { position: absolute; top: 0.7rem; height: 1.8rem; width: 2px; margin-left: -1px; background: var(--accent); pointer-events: none; z-index: 6; }
.replay-playhead::before { content: ""; position: absolute; top: -6px; left: 50%; transform: translateX(-50%); width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 7px solid var(--accent); }

/* Overview strip: the whole day at a glance. Wheel to zoom, drag the window box
   to pan, its edges to zoom, click to recentre, double-click to reset. */
.replay-overview { position: relative; height: 16px; margin: 0 0 0.35rem; border: 1px solid var(--border); border-radius: 4px; background: var(--panel, rgba(127, 127, 127, 0.1)); cursor: pointer; user-select: none; touch-action: none; }
.replay-overview-ticks { position: absolute; inset: 0; pointer-events: none; }
.replay-overview-tick { position: absolute; top: 2px; bottom: 2px; width: 1px; transform: translateX(-0.5px); }
.replay-overview-tick.takeoff { background: #2e9e4f; }
.replay-overview-tick.landing { background: #c0392b; }
/* The draggable/zoomable window rectangle. */
.replay-viewport { position: absolute; top: -1px; bottom: -1px; box-sizing: border-box; min-width: 4px; background: rgba(21, 101, 192, 0.18); border: 1px solid var(--accent); border-radius: 3px; cursor: grab; }
.replay-viewport:active { cursor: grabbing; }
.replay-vp-handle { position: absolute; top: 0; bottom: 0; width: 7px; cursor: ew-resize; }
.replay-vp-handle.lo { left: -4px; }
.replay-vp-handle.hi { right: -4px; }
/* Time preview that follows the cursor over the detail bar. */
.replay-hover-time { position: absolute; top: -0.55rem; transform: translateX(-50%); padding: 0 0.25rem; font: 0.72rem/1.4 monospace; font-variant-numeric: tabular-nums; color: var(--fg); background: var(--bg); border: 1px solid var(--border); border-radius: 3px; pointer-events: none; white-space: nowrap; z-index: 7; }

/* Funk-Export controls row. */
.replay-export { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; font-size: 0.9rem; }
.replay-export-label { color: var(--muted); }
.replay-export button { cursor: pointer; }
.replay-export button:disabled { opacity: 0.5; cursor: default; }
/* The download control is an <a download> (native download for a slow server
   build); style it to sit next to the buttons. */
.replay-export-btn {
  cursor: pointer;
  text-decoration: none;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0.2rem 0.55rem;
  font: inherit;
}
.replay-export-btn:hover { border-color: var(--accent); }
#replay-range-info { font-variant-numeric: tabular-nums; }

/* Video-Export controls row + the on-map framing box (video_export.js). */
.replay-video-export { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; font-size: 0.9rem; }
.replay-video-export button { cursor: pointer; }
.replay-video-export button:disabled { opacity: 0.5; cursor: default; }
#replay-video-box[aria-pressed="true"] { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
#replay-video-rec.recording { background: #c0392b; color: #fff; border-color: #c0392b; animation: radio-tx-blink 1s step-start infinite; }
/* The 16:9 capture frame overlaid on the map; the box-shadow dims outside it. */
.replay-video-box {
  position: absolute;
  z-index: 500; /* above tiles/overlays, below the altitude panel (1000) */
  border: 2px solid var(--accent);
  border-radius: 2px;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.45);
  pointer-events: none;
}
.replay-video-box.recording { border-color: #c0392b; }

/* Site-wide live-listen ("Mithören") toggle in the navbar (radio_live.js). */
.radio-live-nav {
  font-size: 1.05rem;
  line-height: 1;
  padding: 0.25rem 0.45rem;
  background: none;
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--muted);
  cursor: pointer;
}
.radio-live-nav:disabled { opacity: 0.4; cursor: default; }
.radio-live-nav.active {
  color: var(--accent);
  border-color: var(--accent);
  animation: radio-live-pulse 1.6s ease-in-out infinite;
}
@keyframes radio-live-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
/* Global radio volume slider in the navbar, next to the Mithören toggle. */
.radio-volume { width: 5rem; vertical-align: middle; cursor: pointer; accent-color: var(--accent); }

/* Flugfunk box (status page): PTT transmitter (radio_tx.js). */
.radio-tx { display: flex; align-items: center; gap: 0.75rem; }
.radio-tx-btn { font-size: 1rem; padding: 0.5rem 1rem; cursor: pointer; touch-action: none; user-select: none; }
.radio-tx-btn:disabled { opacity: 0.5; cursor: default; }
.radio-tx-btn.keyed { background: #c0392b; color: #fff; border-color: #c0392b; }
.radio-tx-onair { color: #c0392b; font-weight: 700; letter-spacing: 0.03em; animation: radio-tx-blink 1s step-start infinite; }
@keyframes radio-tx-blink { 50% { opacity: 0.35; } }

/* Playback playhead on the altitude graph (chart.js). */
.metar-chart .chart-playhead { stroke: var(--accent); stroke-width: 1.5; opacity: 0.9; }

/* Takeoff/landing markers on the map itself (CSS triangles in a divIcon). */
.replay-event-marker { display: block; width: 0; height: 0; border-left: 6px solid transparent; border-right: 6px solid transparent; }
.replay-event-marker.takeoff { border-bottom: 10px solid #2e9e4f; }
.replay-event-marker.landing { border-top: 10px solid #c0392b; border-bottom: none; }
.replay-event-marker.possible { border-top: 10px solid #b7791f; border-bottom: none; opacity: 0.75; }

/* --- Small screens: the live map --------------------------------------------
   The track panel and the weather box float over the map, which works on a
   desktop and not at all on a phone: together they cover most of a 360 px-wide
   map, and the map underneath can't be panned where they sit. So the wrap
   becomes a plain column — a map of fixed height, then the same two boxes as
   ordinary blocks below it. `main#page` already scrolls, so nothing else has
   to change; the m/ft and Tagesreplay controls are small enough to stay over
   the map. */
@media (max-width: 780px) {
  main#page { padding: 0.6rem 0.7rem; }
  /* The navbar wraps to three rows at phone widths and eats the map's height,
     so it gets tighter spacing rather than a burger menu — there are only a
     handful of links and they stay visible. */
  .nav { gap: 0.4rem 0.9rem; padding: 0.45rem 0.7rem; }
  .nav-links { gap: 0.85rem; }
  .nav-user { gap: 0.4rem; font-size: 0.85rem; }
  .nav-user button { padding: 0.25rem 0.5rem; font-size: 0.8rem; }
  .map-wrap { height: auto; min-height: 0; display: flex; flex-direction: column; gap: 0.6rem; }
  /* Enough map to see the circuit, with the panels reachable by a short scroll.
     Not 100%: the wrap is taller than the viewport once they're in the flow. */
  .map-view { height: 62vh; min-height: 240px; }
  .track-panel,
  .weather-box,
  /* Auf dem Telefon ist die Karte klein genug, dass jede schwebende Kachel
     einen nennenswerten Teil davon verdeckt -- die Legende gehört dann unter
     die Karte, nicht darauf. Gilt für beide Karten: die Klasse sitzt auf der
     Live- wie auf der Replay-Kachel, und deren `top`/`left` sind ohne
     Positionierung wirkungslos. */
  .radar-box {
    position: static;
    width: auto;
    max-width: none;
    box-shadow: none;
  }
  /* The day-replay page has the same overlay problem, so it gets the same
     treatment: map first, then the altitude panel and the weather box as
     blocks. Its map is shorter than the live one because the transport,
     timeline and export rows below it are part of using the page. */
  .replay-wrap { height: auto; min-height: 0; }
  .replay-body { display: flex; flex-direction: column; gap: 0.6rem; min-height: 0; }
  .replay-map { height: 50vh; min-height: 220px; }
  .replay-panel {
    position: static;
    width: auto;
    max-width: none;
    max-height: none;
    box-shadow: none;
  }
  .replay-head { gap: 0.5rem 0.7rem; }
  .replay-head h2 { font-size: 1rem; }
  .replay-controls { gap: 0.5rem; }
  .replay-transport { padding: 0.4rem 0 0.25rem; }
  /* Leaflet's own controls are laid out for a mouse; a fingertip needs more. */
  .leaflet-touch .leaflet-bar a { width: 34px; height: 34px; line-height: 34px; }
}
