/* GB Detect — palette + layout repris de GBAirMon pour rester cohérent. */
:root {
  color-scheme: dark;
  --bg:     #0e1116;
  --panel:  #161b22;
  --fg:     #e6edf3;
  --muted:  #8b949e;
  --accent: #4ea8de;
  --ok:     #56d364;
  --warn:   #d29922;
  --err:    #f85149;
  --unk:    #6e7681;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: Arial, Roboto, sans-serif;
  background: var(--bg);
  color: var(--fg);
  text-shadow: 1px 1px 2px #000;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

.topblock {
  background: #900;
  padding: 0.1em 0.6em 0.3em;
}
.topblock h1 { margin: 0; font-size: 22px; font-weight: bold; color: #fff; }
.topblock h3 { margin: 0; font-size: 12px; font-weight: normal; color: #faa; }

.topnav {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 16px;
  background: #0b0e13;
  border-bottom: 1px solid #30363d;
}
.nav-link {
  padding: 4px 10px;
  font-size: 13px;
  color: var(--muted);
  border-bottom: 2px solid transparent;
}
.nav-link.active { color: var(--fg); border-bottom-color: var(--accent); }
.nav-link:hover  { color: var(--fg); text-decoration: none; }
.nav-spacer { flex: 1; }
.nav-user { font-size: 12px; color: var(--muted); }
.nav-btn  {
  padding: 4px 10px;
  font-size: 12px;
  background: #21262d;
  color: var(--fg);
  border: 1px solid #30363d;
  border-radius: 5px;
  cursor: pointer;
}
.nav-btn:hover { border-color: var(--accent); }

main { padding: 16px 20px; }

/* ---- Generic panels (GBAirMon convention) ---- */
.panel {
  padding: 12px 16px;
  background: var(--panel);
  border: 1px solid #30363d;
  border-radius: 8px;
  min-width: 0;
}
.panel h2 {
  margin: 0 0 8px;
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 8px;
  /* Force every direct child onto the same baseline-equivalent line height
     so pills, h2 titles and muted spans line up cleanly even when their
     intrinsic line-heights differ. */
  line-height: 22px;
}
.panel-head > *,
.panel-head h2,
.panel-head .pill { line-height: 22px; vertical-align: middle; }
.panel-head h2 { margin: 0; display: flex; align-items: center; }

/* ---- Dashboard groups ---- */
.group-section {
  margin-bottom: 12px;
  background: var(--panel);
  border: 1px solid #30363d;
  border-left: 3px solid #30363d;
  border-radius: 8px;
  padding: 8px 12px 12px;
}
.group-section.sev-warn  { border-left-color: var(--warn); }
.group-section.sev-alert { border-left-color: var(--err); }
.group-section.sev-ok    { border-left-color: var(--ok); }
.group-head {
  display: flex;
  align-items: center;
  gap: 12px;
  cursor: pointer;
  user-select: none;
  padding: 4px 0;
  line-height: 20px;
}
.group-head > * { line-height: 20px; vertical-align: middle; }
.group-head::-webkit-details-marker { display: none; }
.group-head::marker                 { display: none; content: ''; }
.group-head .drag-handle {
  cursor: grab;
  color: #555;
  font-size: 14px;
  user-select: none;
  width: 14px;
  text-align: center;
  letter-spacing: -3px;
}
.group-head .drag-handle:hover { color: var(--accent); }
.group-head .drag-handle:active { cursor: grabbing; }
/* SortableJS drag states. */
.group-section.group-drag-ghost   { opacity: 0.3; }
.group-section.group-drag-chosen  { outline: 2px dashed var(--accent); outline-offset: -2px; }
.group-head::before {
  content: '▾';
  font-size: 10px;
  color: var(--muted);
  width: 10px;
}
.group-section:not([open]) .group-head::before { content: '▸'; }
.group-name {
  font-size: 13px;
  font-weight: 700;
  color: var(--fg);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.group-stats { display: flex; gap: 4px; margin-left: 8px; }
.group-stats .pill { padding: 1px 6px; font-size: 10px; min-width: 20px; text-align: center; }
.group-count {
  margin-left: auto;
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.group-section[open] .detectors-grid { margin-top: 8px; }

/* ---- Dashboard tiles (color reflects severity, mirrors the Delphi panels) ---- */
.detectors-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 8px;
}
.det-tile {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  /* Three blocks (name / id+time / quality) anchored to top/middle/bottom
     so the layout breathes evenly inside the fixed tile height instead of
     piling at the top. */
  justify-content: space-between;
  padding: 6px 6px;
  border-radius: 6px;
  cursor: pointer;
  text-align: center;
  font-weight: 700;
  border: 3px solid var(--unk);
  transition: transform 0.1s, box-shadow 0.15s;
  color: #fff;
  text-shadow: 0 1px 1.5px rgba(0, 0, 0, 0.55);
  /* Container query reference so child font sizes can shrink with the
     tile width (no horizontal overflow on long aliases). */
  container-type: inline-size;
  position: relative;          /* anchor the play button */
}
/* Play/stop button: ear-check the monitored stream from the tile. Hidden until
   hover, stays lit while playing. Only rendered for stream-mode detectors. */
.play-btn {
  position: absolute;
  top: 3px;
  right: 3px;
  width: 22px;
  height: 22px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.4);
  color: #fff;
  font-size: 11px;
  line-height: 22px;
  text-align: center;
  cursor: pointer;
  opacity: 0;
  text-shadow: none;
  transition: opacity 0.12s, background 0.12s, transform 0.08s;
  z-index: 3;
}
.det-tile:hover .play-btn { opacity: 0.9; }
.play-btn:hover    { background: var(--accent); opacity: 1; transform: scale(1.1); }
.play-btn.playing  { opacity: 1; background: var(--ok); color: #06210f; }
.det-tile-mid {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: center;
}
.det-tile > .det-tile-id,
.det-tile > .det-tile-time,
.det-tile > .det-tile-q {
  width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* ID line: alias prominent, raw numeric id muted to its right. */
.det-tile-id .alias { font-weight: 700; }
.det-tile-id .rawid { opacity: 0.55; font-weight: 500; margin-left: 5px;
                      font-variant-numeric: tabular-nums; }
/* Time line: HH:MM:SS plus optional "depuis Xm/h/j" if stale. */
.det-tile-time .ts    { font-variant-numeric: tabular-nums; }
.det-tile-time .since { margin-left: 5px; opacity: 0.75; font-style: italic; }
/* Detector name: always reserves space for two lines so single-line
   short names align vertically with multi-line ones across the grid.
   Wraps up to 2 lines, ellipsises beyond, fitTileText shrinks the
   per-line size if the wrapped text overflows. */
.det-tile > .det-tile-name {
  width: 100%;
  white-space: normal;
  word-break: break-word;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: 1.1;
  /* Reserve two lines of room (height computed from font-size × line-height). */
  min-height: calc(2 * 1.1em);
}
.det-tile:hover       { transform: translateY(-1px); outline: 1px solid rgba(255,255,255,0.25); outline-offset: -1px; }
/* Background is a deeply-desaturated tint of the same hue as the border —
   keeps the severity readable at a glance even when the border is thin,
   without competing with the bright accent colour. */
.det-tile.sev-ok      { border-color: var(--ok);   background: #1c4329; }
.det-tile.sev-warn    { border-color: var(--warn); background: #4a3713; }
.det-tile.sev-alert   { border-color: var(--err);  background: #4a1c1c; }
.det-tile.sev-unknown { border-color: var(--unk);  background: #2c3038; }

/* Format chips on a webradio tile (one per mp3/aac stream) */
.fmt-chips { display: flex; gap: 4px; margin-top: 6px; flex-wrap: wrap; }
.fmt-chip {
  font-size: 10px; font-weight: 700; letter-spacing: .3px;
  padding: 1px 7px; border-radius: 4px; cursor: pointer;
  background: var(--bg); border: 1px solid var(--muted); color: var(--muted);
}
.fmt-chip.sev-ok    { color: var(--ok);   border-color: var(--ok); }
.fmt-chip.sev-warn  { color: var(--warn); border-color: var(--warn); }
.fmt-chip.sev-alert { color: var(--err);  border-color: var(--err); }
.fmt-chip:hover { filter: brightness(1.4); }
/* Playable format chip: click to ear-check that format's stream. */
.fmt-chip.playable { cursor: pointer; }
.fmt-chip.playable:hover { background: var(--accent); color: #fff; border-color: var(--accent); }
.fmt-chip.playing {
  background: var(--ok); color: #06210f; border-color: var(--ok);
  filter: none; box-shadow: 0 0 0 2px var(--ok);
}
.fmt-chip.playing::before { content: '⏸'; margin-right: 3px; }

/* Detector detail — "Santé du flux" panel (incidents + uptime, all detectors) */
.health-kinds { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; }
.health-chart { margin: 6px 0 8px; min-height: 0; }
.hk { font-size: 12px; padding: 3px 9px; border-radius: 12px; background: #21262d; border: 1px solid #30363d; }
.hk b { font-variant-numeric: tabular-nums; }
.hk-unavailable, .hk-cut { border-color: var(--err);  color: #ffb3ad; }
.hk-stall, .hk-silence   { border-color: var(--warn); color: #f0cd7a; }
/* Health band: colored incident spans over the selected period. Green base
   = healthy; each span is positioned/sized in % by renderHealthBand(). */
.health-band-wrap { margin: 2px 0 10px; }
.health-band {
  position: relative; height: 20px; border-radius: 5px; overflow: hidden;
  background: linear-gradient(#1c4329, #16351f); border: 1px solid #2b4c37;
}
.health-band .hb-seg { position: absolute; top: 0; bottom: 0; min-width: 2px; }
.hb-cut, .hb-unavailable { background: var(--err); }
.hb-silence              { background: #db6d28; }   /* orange, distinct from stall */
.hb-stall                { background: var(--warn); }
.hb-unavailable          { background: repeating-linear-gradient(45deg, var(--err) 0 5px, #b62f27 5px 10px); }
.hb-legend { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 5px; font-size: 11px; color: var(--muted); }
.hb-leg { display: inline-flex; align-items: center; gap: 4px; }
.hb-swatch { width: 11px; height: 11px; border-radius: 2px; display: inline-block; }
.evt-row { display: flex; gap: 10px; align-items: center; padding: 4px 8px; border-bottom: 1px solid #1e242c; font-size: 12px; }
.evt-row .evt-kind { min-width: 96px; font-weight: 700; }
.evt-row .evt-when { flex: 1; color: var(--muted); font-variant-numeric: tabular-nums; }
.evt-row .evt-dur  { font-variant-numeric: tabular-nums; opacity: 0.85; }
.hk-row-unavailable .evt-kind, .hk-row-cut .evt-kind     { color: var(--err); }
.hk-row-stall .evt-kind,       .hk-row-silence .evt-kind { color: var(--warn); }
.live-badge { color: var(--err); font-weight: 700; }

/* Format tabs on the detector-detail header (switch between formats) */
.fmt-tabs { display: flex; gap: 6px; margin-left: 8px; }
.fmt-tab {
  font-size: 12px; font-weight: 600; text-decoration: none;
  padding: 3px 12px; border-radius: 6px;
  background: var(--bg); border: 1px solid var(--muted); color: var(--muted);
}
.fmt-tab.sev-ok    { color: var(--ok); }
.fmt-tab.sev-warn  { color: var(--warn); }
.fmt-tab.sev-alert { color: var(--err); }
.fmt-tab.sev-unknown { color: var(--muted); }
/* Active tab keeps its status colour (text + border) over a status-tinted
   background, matching the dashboard tiles — selected AND status stay visible. */
.fmt-tab.active             { font-weight: 700; }
.fmt-tab.active.sev-ok      { background: #1c4329; border-color: var(--ok); }
.fmt-tab.active.sev-warn    { background: #4a3713; border-color: var(--warn); }
.fmt-tab.active.sev-alert   { background: #4a1c1c; border-color: var(--err); }
.fmt-tab.active.sev-unknown { background: #2c3038; border-color: var(--muted); }
.det-tile-name    { font-size: 17px; line-height: 1.1; }
.det-tile-id      { font-size: 13px; line-height: 1.1; }
.det-tile-time    { font-size: 11px; line-height: 1.1; opacity: 0.85; margin-top: 1px; }
.det-tile-q       { font-size: 9px;  line-height: 1.1;
                    opacity: 0.85; text-transform: uppercase; letter-spacing: 0.05em; }
.det-tile.disabled { opacity: 0.45; }
.pill { text-shadow: none; }

/* ---- Panel size variants (S/M/L/XL) — applied to the .detectors-grid by JS,
   driven by the dashboard size selector. Persisted in localStorage.
   Calibrated to fit lots of detectors on a 1080p screen at 100% zoom. ---- */
/* Each variant fixes the tile height so all panels in the dashboard
   line up. Heights are content-derived: 2 name lines + id + time + q,
   plus "small space" gaps before id and before q, plus padding. */
.detectors-grid.size-s  { grid-template-columns: repeat(auto-fill, minmax(78px, 1fr)); gap: 4px; }
.detectors-grid.size-s .det-tile           { height: 76px; padding: 3px; border-width: 1px; border-radius: 4px; }
.detectors-grid.size-s .det-tile-name      { font-size: 10px; }
.detectors-grid.size-s .det-tile-id        { font-size: 8px; }
.detectors-grid.size-s .det-tile-time      { font-size: 7px; }
.detectors-grid.size-s .det-tile-q         { font-size: 6px; }

.detectors-grid.size-m  { grid-template-columns: repeat(auto-fill, minmax(115px, 1fr)); gap: 6px; }
.detectors-grid.size-m .det-tile           { height: 92px; padding: 5px; border-radius: 5px; }
.detectors-grid.size-m .det-tile-name      { font-size: 13px; }
.detectors-grid.size-m .det-tile-id        { font-size: 10px; }
.detectors-grid.size-m .det-tile-time      { font-size: 9px; }
.detectors-grid.size-m .det-tile-q         { font-size: 8px; }

.detectors-grid.size-l  { grid-template-columns: repeat(auto-fill, minmax(165px, 1fr)); gap: 8px; }
.detectors-grid.size-l .det-tile           { height: 118px; padding: 7px 6px; }
.detectors-grid.size-l .det-tile-name      { font-size: 17px; }
.detectors-grid.size-l .det-tile-id        { font-size: 13px; }
.detectors-grid.size-l .det-tile-time      { font-size: 11px; }
.detectors-grid.size-l .det-tile-q         { font-size: 9px; }

.detectors-grid.size-xl { grid-template-columns: repeat(auto-fill, minmax(225px, 1fr)); gap: 10px; }
.detectors-grid.size-xl .det-tile          { height: 156px; padding: 12px 8px; }
.detectors-grid.size-xl .det-tile-name     { font-size: 22px; }
.detectors-grid.size-xl .det-tile-id       { font-size: 17px; }
.detectors-grid.size-xl .det-tile-time     { font-size: 14px; }
.detectors-grid.size-xl .det-tile-q        { font-size: 11px; }

/* Radio tabs — sit between the panel head and the dashboard grid. One
   tab per radio the user can see, plus a "Toutes" reset. Visual style
   matches GBAirMon's primary navigation: underline-on-active, colour
   bar follows the radio's accent colour. */
.radio-tabs {
  display: flex;
  gap: 4px;
  margin: 0 0 12px 0;
  border-bottom: 1px solid #30363d;
  flex-wrap: wrap;
}
.radio-tabs button {
  padding: 8px 16px;
  background: transparent;
  color: var(--muted);
  border: none;
  border-bottom: 3px solid transparent;
  cursor: pointer;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  margin-bottom: -1px;
  transition: color .12s, border-color .12s;
}
.radio-tabs button:hover { color: var(--fg); }
.radio-tabs button.active { color: var(--fg); border-bottom-color: var(--accent); }

/* "En écoute" bar in the dashboard header (current stream + pause/resume + close) */
.now-playing {
  display: flex; align-items: center; gap: 6px;
  padding: 2px 8px; border-radius: 6px;
  background: #0b0e13; border: 1px solid var(--ok);
}
.now-playing .np-ico  { font-size: 13px; }
.now-playing .np-name {
  color: var(--ok); font-weight: 700; font-size: 12px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 220px;
}
.now-playing .np-btn {
  padding: 0 7px; height: 20px; line-height: 18px; font-size: 11px;
  background: #21262d; color: var(--fg);
  border: 1px solid #30363d; border-radius: 4px; cursor: pointer;
}
.now-playing .np-btn:hover       { border-color: var(--accent); }
.now-playing .np-close:hover     { border-color: var(--err); color: var(--err); }

/* Size selector in the dashboard header. Mirrors GBAirMon's L/M/S dropdowns. */
.size-selector {
  display: flex;
  gap: 2px;
  padding: 2px;
  background: #0d1117;
  border: 1px solid #30363d;
  border-radius: 5px;
}
.size-selector button {
  padding: 2px 8px;
  font-size: 11px;
  font-weight: 600;
  background: transparent;
  color: var(--muted);
  border: none;
  border-radius: 3px;
  cursor: pointer;
  font-family: inherit;
}
.size-selector button.active { background: var(--accent); color: var(--bg); }
.size-selector button:hover:not(.active) { color: var(--fg); }

/* Expand-all / Collapse-all sit to the left of the size selector — same
   visual language so the dashboard header reads as one toolbar. */
.group-toggle {
  display: flex;
  gap: 2px;
  padding: 2px;
  background: #0d1117;
  border: 1px solid #30363d;
  border-radius: 5px;
  margin-left: auto;
}
.group-toggle button {
  padding: 2px 8px;
  font-size: 11px;
  font-weight: 600;
  background: transparent;
  color: var(--muted);
  border: none;
  border-radius: 3px;
  cursor: pointer;
  font-family: inherit;
}
.group-toggle button:hover { color: var(--fg); }

/* Severity pills (group header counters + detector detail status badge).
   Bumped up so the operator can read them across the room — these are the
   primary "everything is fine / something is broken" signal of the
   dashboard. */
.pill {
  display: inline-block;
  padding: 4px 12px;
  min-width: 28px;
  border-radius: 999px;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-align: center;
  border: 1px solid rgba(0, 0, 0, 0.25);
}
.pill.ok      { background: var(--ok);   color: #06200a; }
.pill.warn    { background: var(--warn); color: #2a1c00; }
.pill.alert   { background: var(--err);  color: #2a0000; }
.pill.unknown { background: var(--unk);  color: #d0d7de; }
/* Hide a counter pill when its count is "0" — keeps the header lean. */
.group-stats .pill.zero { display: none; }

/* ---- Tile attention animations ----
   - Alert (red): franc clignotement, ~0.7 s par cycle, pour qu'on le
     remarque même de loin / coin de l'oeil.
   - Warn  (orange): pulsation douce avec fade in/out, ~2 s par cycle,
     présent mais pas agressif (le warn n'est pas une urgence).
   Les keyframes alternent la luminosité (filter: brightness) plutôt que
   l'opacité ou le background, pour préserver la bague de bordure et le
   stripe d'accent programme/alias. */
@keyframes gbd-blink-alert {
  0%, 49.99%  { filter: none; }
  50%, 100%   { filter: brightness(1.55); }
}
@keyframes gbd-fade-warn {
  0%, 100% { filter: brightness(1); }
  50%      { filter: brightness(1.45); }
}
/* Periods chosen as exact divisors of the 4 s dashboard poll so the CSS
   animation restarts at frame-0 alignment when the tile DOM is rebuilt
   — avoids a visible "snap" every 4 s. */
.det-tile.sev-alert:not(.disabled) { animation: gbd-blink-alert 1s steps(1, end) infinite; }
.det-tile.sev-warn:not(.disabled)  { animation: gbd-fade-warn   2s ease-in-out    infinite; }
@media (prefers-reduced-motion: reduce) {
  .det-tile.sev-alert:not(.disabled),
  .det-tile.sev-warn:not(.disabled)  { animation: none; }
}

/* ---- Detector detail ---- */
.detail-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}
.summary-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 8px;
}
.readout {
  background: #0d1117;
  border: 1px solid #30363d;
  border-radius: 6px;
  padding: 8px 10px;
  min-width: 0;
}
.readout-label { font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; }
.readout-value { font-size: 22px; font-variant-numeric: tabular-nums; margin-top: 2px; }
.readout-text  { font-size: 14px; }
.readout-sub   { font-size: 11px; color: var(--muted); margin-top: 2px; }
.readout-value .unit { font-size: 11px; color: var(--muted); margin-left: 4px; }

.timeline-controls {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 8px;
}
.preset-group { display: flex; gap: 4px; }
.preset-group button {
  padding: 4px 10px;
  font-size: 12px;
  background: #21262d;
  color: var(--muted);
  border: 1px solid #30363d;
  border-radius: 4px;
  cursor: pointer;
}
.preset-group button.active { background: var(--accent); color: var(--bg); border-color: var(--accent); }
.range-inputs label { font-size: 11px; color: var(--muted); margin-right: 8px; }
.range-inputs input[type=datetime-local] {
  background: #0d1117; color: var(--fg);
  border: 1px solid #30363d; border-radius: 4px; padding: 3px 6px;
}
.event-filters { display: flex; gap: 6px; }
.event-filters label {
  font-size: 11px; color: var(--muted);
  display: flex; align-items: center; gap: 4px;
  padding: 3px 8px; border: 1px solid #30363d; border-radius: 4px;
  cursor: pointer;
  user-select: none;
}
.event-filters input { accent-color: var(--accent); }

/* ---- Numeric readouts (ported verbatim from GBAirMon's LEVELS panel)
   Big monospace value with subtle glow, small label above, optional unit.
   Use the .ok / .warning / .alarm / .degraded modifier on .readout-value
   to colour the number per current state. */
.readouts {
  display: grid;
  /* Desktop: every readout on one line (9 of them, equal width).
     `minmax(0, 1fr)` lets the cells shrink below their content's intrinsic
     width so the row never overflows the panel. The fitReadouts() JS pass
     then scales each value's font size to fit the actual cell width. */
  grid-template-columns: repeat(9, minmax(0, 1fr));
  gap: 6px;
}
@media (max-width: 1200px) {
  .readouts { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 8px; }
}
@media (max-width: 700px) {
  .readouts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.readout {
  padding: 10px 12px;
  background: linear-gradient(180deg, #0d1117 0%, #161b22 100%);
  border: 1px solid #30363d;
  border-radius: 10px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
  position: relative;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  container-type: inline-size;
}
.readout-label {
  font-size: clamp(7px, 7cqi, 11px);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}
.readout-value {
  font-family: ui-monospace, "Cascadia Mono", "Consolas", monospace;
  font-size: clamp(14px, 18cqi, 32px);
  font-weight: 600;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  margin-top: 4px;
  line-height: 1.1;
  text-shadow: 0 0 14px rgba(78, 168, 222, 0.25);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
.readout-value .unit {
  font-size: clamp(8px, 8cqi, 14px);
  font-weight: 500;
  color: var(--muted);
  text-shadow: none;
  margin-left: 4px;
}
.readout-sub {
  margin-top: 2px;
  font-size: clamp(8px, 7cqi, 12px);
  font-variant-numeric: tabular-nums;
  color: #555;
}
.readout-value.readout-text {
  white-space: normal;
  word-break: break-word;
  line-height: 1.3;
  font-size: clamp(11px, 12cqi, 18px);
}
/* Two-line timestamp variant: date on top, time below. Used when the
   formatted value is too long to share the cell with the other readouts
   on a single-row layout. */
.readout-value.ts-2line {
  white-space: normal;
  line-height: 1.05;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.readout-value.ts-2line .ts-date,
.readout-value.ts-2line .ts-time {
  display: block;
  font-size: clamp(9px, 11cqi, 16px);
  white-space: nowrap;
}
.readout-value.ts-2line .ts-time { font-size: clamp(10px, 13cqi, 20px); }
.readout-value.ok       { color: #56d364; text-shadow: 0 0 10px rgba(86, 211, 100, 0.25); }
.readout-value.warning  { color: #e3b341; text-shadow: 0 0 10px rgba(227, 179, 65, 0.25); }
.readout-value.alarm    { color: var(--err); text-shadow: 0 0 10px rgba(248, 81, 73, 0.30); }
.readout-value.degraded { color: #555; text-shadow: none; }
.readout-value.info     { color: #4ea8de; text-shadow: 0 0 10px rgba(78, 168, 222, 0.25); }

/* (`.alerts` / `.alert` cards were used for an earlier incarnation of the
   detector top panel — replaced by `.readouts` / `.readout-value`. The
   block was removed because the bare `.alert` selector also matched the
   `.pill.alert` severity pill, applying a 3px green border around it.) */

/* ---- Detector detail charts (GBAirMon / GBMonitor flavour) ----
   The page stacks one wide severity chart on top, then a responsive grid
   of small WM-metric charts (quality, energy, resampling, etc.). Each
   chart card carries its own size selector (S/M/L). */
.charts-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 10px;
  margin-top: 12px;
}
.chart-card {
  background: var(--panel);
  border: 1px solid #30363d;
  border-radius: 8px;
  padding: 8px 10px 10px;
  min-width: 0;
}
.chart-card .chart-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
}
.chart-card .chart-title {
  font-size: 11px;
  font-weight: 700;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  flex: 1;
}
.chart-card .chart-size {
  padding: 1px 4px;
  font-size: 10px;
  background: #0d1117;
  color: var(--muted);
  border: 1px solid #30363d;
  border-radius: 3px;
  cursor: pointer;
}
.chart-card .chart-host { width: 100%; }
.chart-card.size-s { }
.chart-card.size-s .chart-host { height: 110px; }
.chart-card.size-m .chart-host { height: 170px; }
.chart-card.size-l { grid-column: span 2; }
.chart-card.size-l .chart-host { height: 240px; }

.chart-card.wide {
  grid-column: 1 / -1;
}
.chart-card.wide.size-s .chart-host { height: 140px; }
.chart-card.wide.size-m .chart-host { height: 220px; }
.chart-card.wide.size-l .chart-host { height: 320px; }

/* uPlot tweaks to match the dark palette without overriding too much. */
.uplot { font-family: inherit; }
.uplot .u-legend { font-size: 11px; color: var(--muted); }

#chart { width: 100%; height: 300px; }

.pager {
  display: flex;
  gap: 8px;
  align-items: center;
}
.pager button[disabled] { opacity: 0.4; cursor: not-allowed; }

.event-list {
  margin-top: 8px;
  max-height: 50vh; overflow-y: auto;
  font-family: ui-monospace, Consolas, monospace;
  font-size: 12px;
}
.event-row {
  display: grid;
  grid-template-columns: 180px 90px 1fr;
  gap: 8px;
  padding: 4px 8px;
  border-bottom: 1px solid #30363d;
}
.event-row.kind-detection .badge { background: #1f6feb; }
.event-row.kind-quality   .badge { background: #8957e5; }
.event-row.kind-status    .badge { background: var(--accent); }
.event-row.kind-alarm     .badge { background: var(--err); }
.badge {
  display: inline-block; min-width: 60px;
  padding: 1px 6px; border-radius: 3px;
  text-align: center; color: #fff; font-weight: 600;
}

/* ---- Admin / forms ---- */
.tabs { display: flex; gap: 4px; margin-bottom: 12px; }
.tab-btn {
  padding: 6px 14px;
  font-size: 12px;
  background: transparent;
  color: var(--muted);
  border: none;
  border-bottom: 2px solid transparent;
  cursor: pointer;
}
.tab-btn.active { color: var(--fg); border-bottom-color: var(--accent); }

table.crud {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}
table.crud th {
  text-align: left; padding: 6px 10px;
  background: #0d1117; color: var(--muted);
  border-bottom: 1px solid #30363d;
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em;
}
table.crud td {
  padding: 6px 10px;
  border-bottom: 1px solid #30363d;
}
table.crud tr:hover td { background: rgba(78,168,222,0.05); }
/* Inline edit row inserted directly under the row being edited. The
   editor lives inside a single colspan'd cell so the form sits visually
   anchored to the detector instead of appearing at the bottom of the page. */
table.crud tr.inline-editor td {
  background: #0d1117;
  border-bottom: 1px solid #30363d;
  padding: 12px 14px;
}
table.crud tr.inline-editor .form-grid { margin-top: 0; }
table.crud .actions { display: flex; gap: 6px; justify-content: flex-end; }
table.crud .actions button {
  padding: 2px 8px;
  background: #21262d; color: var(--muted);
  border: 1px solid #30363d; border-radius: 3px;
  font-size: 11px; cursor: pointer;
}
table.crud .actions button.danger { color: var(--err); border-color: var(--err); }
table.crud .actions button:hover  { color: var(--fg); }

.form-grid {
  display: grid;
  grid-template-columns: 180px 1fr;
  gap: 8px 12px;
  align-items: center;
  margin-top: 12px;
}
.form-grid label { font-size: 12px; color: var(--muted); }
.color-pick {
  display: flex;
  gap: 8px;
  align-items: center;
}
.color-pick input[type="color"] {
  width: 36px; height: 28px;
  padding: 0; border: 1px solid #30363d; border-radius: 4px;
  background: #0d1117; cursor: pointer;
}
/* Strip the native color-swatch padding/border on Chrome so the swatch
   fills the input cleanly. */
.color-pick input[type="color"]::-webkit-color-swatch-wrapper { padding: 2px; }
.color-pick input[type="color"]::-webkit-color-swatch         { border: 1px solid #30363d; border-radius: 2px; }
.color-pick code {
  font-size: 11px;
  color: var(--muted);
  font-family: ui-monospace, Consolas, monospace;
  min-width: 64px;
}
.color-pick button { padding: 2px 8px; font-size: 11px; }

.form-grid input, .form-grid select, .form-grid textarea {
  background: #0d1117; color: var(--fg);
  border: 1px solid #30363d; border-radius: 4px; padding: 4px 8px;
  font-size: 13px; font-family: inherit;
}
.form-grid textarea { font-family: ui-monospace, Consolas, monospace; min-height: 80px; }
.form-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }
.form-actions button {
  padding: 6px 16px;
  background: var(--accent); color: var(--bg);
  border: none; border-radius: 4px;
  font-weight: 600; cursor: pointer;
}
.form-actions button.secondary { background: #21262d; color: var(--fg); }
.form-actions button.danger    { background: var(--err); color: #fff; }

/* ---- Login dialog ---- */
dialog {
  background: var(--panel);
  color: var(--fg);
  border: 1px solid #30363d;
  border-radius: 8px;
  padding: 16px 20px;
  min-width: 280px;
}
/* Login is driven by body.login-open + a CSS scrim, NOT the native <dialog>
   modal — Chrome can leave the top-layer ::backdrop stuck after close(), dimming
   the whole app behind the rendered panels. A class toggle can't get stuck. */
#loginDialog { display: none; }
body.login-open::before {
  content: ''; position: fixed; inset: 0;
  background: rgba(0, 0, 0, 0.6); z-index: 1000;
}
body.login-open #loginDialog {
  display: block; position: fixed; z-index: 1001;
  top: 50%; left: 50%; transform: translate(-50%, -50%);
}
dialog h3 { margin: 0 0 10px; font-size: 14px; }
dialog input {
  display: block; width: 100%; margin-bottom: 8px;
  padding: 6px 10px; font-size: 13px;
  background: #0d1117; color: var(--fg);
  border: 1px solid #30363d; border-radius: 4px;
}

.err   { color: var(--err);   font-size: 12px; }
.muted { color: var(--muted); }
.flex  { display: flex; gap: 8px; align-items: center; }
.spacer { flex: 1; }

@media (max-width: 700px) {
  main { padding: 12px; }
  .form-grid { grid-template-columns: 1fr; }
  .event-row { grid-template-columns: 140px 70px 1fr; }
}
