/*
 * FDS status page. Dark by default because race control happens at night, CSS grid for the layout,
 * and state colours as custom properties so a state is defined once. No framework, no build step.
 *
 * Colour is never the only signal: every state also carries its word, for a colour-blind reader and
 * for a greyscale screenshot (design section 9.2).
 */

:root {
  --bg: #0d1117;
  --panel: #161b22;
  --panel-2: #1c222b;
  --line: #2b323c;
  --text: #e6edf3;
  --muted: #8b949e;

  --ok: #2ea043;
  --ok-dim: #1b4b26;
  --warn: #d29922;
  --warn-dim: #4d3b0f;
  --bad: #f85149;
  --bad-dim: #5a1d1a;
  --idle: #4a5361;
  --neutral: #6e7681;

  --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  color-scheme: dark;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.35;
}

body {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px;
}

h2 {
  margin: 0 0 6px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.muted { color: var(--muted); font-weight: 400; }
.hidden { display: none !important; }

.noscript {
  margin: 0 0 8px;
  padding: 10px 12px;
  border: 1px solid var(--warn);
  border-radius: 6px;
  background: var(--warn-dim);
  color: var(--text);
}
.noscript a { color: var(--text); }

/* ---------- top bar ----------
 * One line: the site, the current mode and the three mode buttons. The site name is the biggest thing
 * on the page on purpose - this page gets opened on a phone to answer "which site is this and what
 * mode is it in" - and nothing else competes for the line. A mode change is confirmed by the mode word
 * and the lit button moving, so there is no result line.
 */

.topbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 14px;
  padding: 8px 12px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 8px;
  font-size: 12px;
}

.site { font-size: 18px; font-weight: 700; letter-spacing: 0.01em; }
.mode-label { color: var(--muted); }
.mode-value {
  font-family: var(--mono);
  font-weight: 700;
  color: var(--text);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
/* The current mode is the only one lit: green in live, amber in setup, grey in standby. */
.mode-value[data-state="live"] { color: var(--ok); }
.mode-value[data-state="setup"] { color: var(--warn); }
.mode-value[data-state="standby"] { color: var(--muted); }

.mode-buttons { display: inline-flex; gap: 4px; }
.mode-buttons button { padding: 3px 12px; font-weight: 600; }
/* The active button is the current mode, so it never sends a command - it is a state display that
 * happens to be clickable for the other two. Its colour matches the mode value beside it (standby is
 * deliberately neutral: it is not a fault and not an active state), so the line reads as one thing. */
.mode-buttons button[aria-pressed="true"] {
  border-color: var(--idle);
  background: var(--panel-2);
  color: var(--text);
  cursor: default;
}
.mode-buttons button[aria-pressed="true"][data-mode="live"] {
  border-color: var(--ok);
  background: var(--ok-dim);
  color: #d3f9d8;
}
.mode-buttons button[aria-pressed="true"][data-mode="setup"] {
  border-color: var(--warn);
  background: var(--warn-dim);
  color: #ffe8b3;
}

/* The car selector's own line, between the identity bar and the two status panels. Hidden by default
 * (the `hidden` attribute) and unhidden by the page only once a catalogue arrives, so a site without
 * cars.yaml is completely unchanged. `[hidden]` needs the explicit display rule because `display:
 * flex` would otherwise beat the attribute's default `display: none`. */
.carbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 10px;
  padding: 6px 12px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 8px;
  font-size: 12px;
}
.carbar[hidden] { display: none; }
.car-label {
  color: var(--muted);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
#car-select { min-width: 170px; font-weight: 600; }
/* The driver selector beside it. Same treatment as the car's - it is the same kind of control - with a
 * narrower box because a name is shorter than a name plus a class. */
#driver-select { min-width: 140px; font-weight: 600; }
/* The chosen class, in the same monospace the mode and channel values use. */
.car-class { font-family: var(--mono); color: var(--text); }
/* The car half and the conditions half of the one conditions bar. The car half is hidden (with its
 * separator) when the host has no cars.yaml, leaving the conditions readings alone. */
.car-field, .wind-field, .cond-field { display: inline-flex; align-items: center; gap: 6px; }
.car-field[hidden] { display: none; }
.cond-sep { width: 1px; align-self: stretch; background: var(--line); margin: 0 4px; }
#wind-direction { min-width: 62px; font-weight: 600; text-transform: uppercase; }
.wind-speed, .cond-number { width: 64px; text-align: right; font-variant-numeric: tabular-nums; }
.wind-unit { color: var(--muted); }

button, select, input {
  font: inherit;
  font-size: 12px;
  color: var(--text);
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 3px 8px;
}
button { cursor: pointer; }
button:disabled, select:disabled, input:disabled { opacity: 0.45; cursor: default; }
button[aria-pressed="true"] { border-color: var(--ok); background: var(--ok-dim); }

/* ---------- top status panels ----------
 * Two small cards above the grid answering "is the T-Box talking" and "is the host healthy", so both
 * answers are on screen on a phone without scrolling. Deliberately two-across at every width: each card
 * carries only two metrics, so a 360 px phone still fits both. The card border and the state word carry
 * the verdict, and the word is always present - colour is never the only signal (design section 9.2).
 */

.statusbar {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 8px;
}

/* ---------- port-mode banner ----------
 * A box-wide fault: the T-Box USB port is on a text protocol (THCOM08 / FDS-Timer / Tag Heuer), not
 * FDS-Binary, so no timestamp frame can ever arrive while telemetry keeps answering. It is stated
 * once here rather than only on each tile, because the cause and the fix are the same for all of
 * them. Red, like a fault, but it is a configuration error rather than a lost link - the T-Box card
 * beside it still reads ONLINE, which is exactly the confusion it exists to resolve.
 */
.alert-banner {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 8px 12px;
  border: 1px solid var(--bad);
  border-left: 4px solid var(--bad);
  border-radius: 6px;
  background: var(--bad-dim);
}
.alert-banner[hidden] { display: none; }
.alert-lead {
  flex: none;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--bad);
}
.alert-text { font-size: 13px; }

.status-card {
  min-width: 0;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 6px 10px 8px;
}
.status-card[data-state="ok"] { border-color: var(--ok-dim); }
.status-card[data-state="bad"] { border-color: var(--bad); }

.status-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 4px;
}
.status-title {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.status-word {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}
.status-word[data-state="ok"] { color: var(--ok); }
.status-word[data-state="warn"] { color: var(--warn); }
.status-word[data-state="bad"] { color: var(--bad); }

.status-metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
  gap: 2px 12px;
  font-size: 12px;
}
.status-metric { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; min-width: 0; }
.status-key { color: var(--muted); white-space: nowrap; }
.status-value { font-family: var(--mono); white-space: nowrap; }
.status-value[data-state="ok"] { color: var(--ok); }
.status-value[data-state="warn"] { color: var(--warn); }
.status-value[data-state="bad"] { color: var(--bad); }
.status-battery { display: inline-flex; align-items: center; gap: 6px; }
.status-meter { display: inline-flex; }
/* The tile battery meter, reused: a little smaller than on a tile. */
.status-meter .meter { width: 24px; height: 13px; }

/* ---------- grid ---------- */

.grid {
  display: grid;
  gap: 8px;
  grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  align-items: start;
}

.panel {
  min-width: 0;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 8px 10px 10px;
}

/* Explicit placement rather than auto-flow, so the reading order and the visual order cannot drift
 * apart: the tiles, the passes summary, the folded Clock & Health panel and then the log each span the
 * page. Section 9.3 is the panel list this has to stay in step with. */
.panel-channels { grid-column: 1 / -1; grid-row: 1; }
.panel-passes { grid-column: 1 / -1; grid-row: 2; }
.panel-system { grid-column: 1 / -1; grid-row: 3; }
.panel-log { grid-column: 1 / -1; grid-row: 4; }

/* ---------- the folded Clock & Health panel ----------
 * One panel holding both, closed on every load: the two top cards already say whether the system is
 * working, so this detail is for investigating rather than watching. The heading is the button, and the
 * marker points right when closed and down when open - the state is also on `aria-expanded`.
 */

.panel-system h2 { margin-bottom: 0; }

.fold-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
}
.fold-toggle:hover, .fold-toggle:focus-visible { color: var(--text); }
.fold-mark::before { content: "▸"; font-size: 10px; }
.fold-toggle[aria-expanded="true"] .fold-mark::before { content: "▾"; }

.system-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 8px 16px;
  margin-top: 6px;
}
/* The class sets `display: grid`, so the `hidden` attribute needs its own rule to win. */
.system-body[hidden] { display: none; }
.system-group { min-width: 0; }
.system-group h3 {
  margin: 0 0 4px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ---------- channel tiles ---------- */

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 8px;
}

.tile {
  position: relative;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-left: 4px solid var(--idle);
  border-radius: 6px;
  background: var(--panel-2);
  min-height: 92px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.tile[data-state="never"] { border-style: dashed; opacity: 0.75; }
.tile[data-state="live"] { border-left-color: var(--ok); }
.tile[data-state="retrying"] { border-left-color: var(--warn); }
.tile[data-state="blocked"] { border-left-color: var(--bad); border-color: var(--bad); }
.tile[data-state="offline"] { border-left-color: var(--bad); border-color: var(--bad); }
/* A box-wide fault, so every tile carries it - the same red as BLOCKED/OFFLINE, with the detail line
   coloured too because it is an error rather than a "nothing yet". */
.tile[data-state="wrongMode"] { border-left-color: var(--bad); border-color: var(--bad); }
.tile[data-state="wrongMode"] .tile-last { color: var(--bad); }

.tile.flash { animation: flash 0.9s ease-out; }
@keyframes flash {
  0% { background: #1f6f36; }
  100% { background: var(--panel-2); }
}

.tile-head { display: flex; align-items: baseline; gap: 6px; }
.tile-channel { font-family: var(--mono); font-weight: 700; }
.tile-beam { color: var(--text); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tile-state {
  margin-left: auto;
  flex: none;
  white-space: nowrap;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}
.tile[data-state="live"] .tile-state { color: var(--ok); }
.tile[data-state="retrying"] .tile-state { color: var(--warn); }
.tile[data-state="blocked"] .tile-state, .tile[data-state="offline"] .tile-state { color: var(--bad); }
.tile[data-state="wrongMode"] .tile-state { color: var(--bad); }

.tile-last { font-family: var(--mono); font-size: 13px; }
.tile-last:empty { display: none; }

/* ---------- tile telemetry: battery and signal meters ----------
 * Both meters are inline SVG (the `meter-template` in index.html, cloned by ui.mjs): a 5-cell battery
 * with a cap, and a 5-bar signal. The lit bars are the level - a bar past the count is dimmed, not
 * removed, so the shape of a meter never changes - and `data-state` is what colours them: green at
 * 4-5, amber at 2-3, red at 0-1, for both. Bars and a number together, so colour is never the only
 * signal. A meter is only present for a radio in contact; see ui.mjs.
 */

/* Two rows in a fixed order, bottom-aligned: the meters, and the retry line above the signal. The
 * retry row is `hidden` unless there is something to say, and hidden elements take no space - so a
 * clean channel shows nothing, and a channel with retries shows the count just above the signal bars
 * at the tile's right edge. The meters stay on the bottom line either way, and the tile grows upward
 * from the fixed-height telemetry block, so the first two lines never move. */
.tile-telemetry {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 3px;
  margin-top: auto;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--muted);
}
.tile-telemetry-line { display: flex; align-items: center; gap: 8px; }
.tile-side { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
.tile-side-right { margin-left: auto; }
.tile-side[hidden] { display: none; }
.tile-retry {
  /* Right-aligned over the signal, never truncated or wrapped: a retry count is the one thing on the
   * tile an operator must not misread. */
  display: flex;
  justify-content: flex-end;
  align-items: baseline;
  gap: 6px;
  min-height: 13px;
  color: var(--warn);
  white-space: nowrap;
}
.tile-retry[hidden] { display: none; }

.tile-battery .shell { fill: none; stroke: var(--neutral); stroke-width: 1; }
.tile-battery .cap { fill: var(--neutral); }
/*
 * Every bar starts dim and the lit ones are switched back on. The selector is `rect.bar:nth-of-type`
 * on purpose: these are inline SVG rects, and a class-only `:nth-of-type` would count the battery's
 * shell and cap too, which is why the bars are the first five rects in the markup.
 */
.tile-battery .bar, .tile-signal .bar { fill: var(--neutral); opacity: 0.22; }
.tile-battery[data-level="1-5"] rect.bar:nth-of-type(-n+1),
.tile-signal[data-level="1-5"] rect.bar:nth-of-type(-n+1) { opacity: 1; }
.tile-battery[data-level="2-5"] rect.bar:nth-of-type(-n+2),
.tile-signal[data-level="2-5"] rect.bar:nth-of-type(-n+2) { opacity: 1; }
.tile-battery[data-level="3-5"] rect.bar:nth-of-type(-n+3),
.tile-signal[data-level="3-5"] rect.bar:nth-of-type(-n+3) { opacity: 1; }
.tile-battery[data-level="4-5"] rect.bar:nth-of-type(-n+4),
.tile-signal[data-level="4-5"] rect.bar:nth-of-type(-n+4) { opacity: 1; }
.tile-battery[data-level="5-5"] rect.bar:nth-of-type(-n+5),
.tile-signal[data-level="5-5"] rect.bar:nth-of-type(-n+5) { opacity: 1; }
.tile-battery[data-state="ok"] .bar, .tile-signal[data-state="ok"] .bar { fill: var(--ok); }
.tile-battery[data-state="warn"] .bar, .tile-signal[data-state="warn"] .bar { fill: var(--warn); }
.tile-battery[data-state="bad"] .bar, .tile-signal[data-state="bad"] .bar { fill: var(--bad); }

/* ---------- key/value panels ---------- */

.kv { display: grid; grid-template-columns: max-content 1fr; gap: 2px 10px; font-size: 12px; }
.kv dt { color: var(--muted); }
.kv dd { margin: 0; font-family: var(--mono); overflow-wrap: anywhere; }
.kv dd.ok { color: var(--ok); }
.kv dd.warn { color: var(--warn); }
.kv dd.bad { color: var(--bad); }

/* ---------- log ---------- */

.log-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  margin-bottom: 6px;
  font-size: 12px;
  color: var(--muted);
}
.chips { display: inline-flex; gap: 4px; flex-wrap: wrap; }
.chip {
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
}
.chip[aria-pressed="true"] { border-color: var(--ok); background: var(--ok-dim); }

.log {
  height: 210px;
  overflow-y: auto;
  background: #0a0d12;
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 4px 6px;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.4;
}
.log-line { white-space: pre-wrap; overflow-wrap: anywhere; }
.log-line[data-level="error"] { color: #ff9d96; }
.log-line[data-level="warn"] { color: #ffd479; }
.log-line[data-level="clock"] { color: #79c0ff; }
.log-line[data-level="radio"] { color: #d2a8ff; }
.log-line[data-level="blocked"], .log-line[data-level="cleared"] { color: #ffd479; }
.log-line[data-level="lifecycle"] { color: #7ee787; }
.log-line[data-level="summary"] { color: #a5d6ff; }
.log-line[data-level="debug"] { color: var(--muted); }

/* The passes summary: one line per completed pass, newest first. Same monospace box as the log, but
 * shorter, because a line is a whole pass rather than a console line. */
.passes {
  max-height: 150px;
  overflow-y: auto;
  background: #0a0d12;
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 4px 6px;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.5;
}
.passes:empty::before { content: 'no passes yet'; color: var(--muted); }
/* One pass is one flex row: the summary text takes the width, and the record receipts sit at the
 * right edge as two small links. The text keeps its own wrapping so a long car name cannot push the
 * icons off the line. The links are deliberately high-contrast - near-white on a raised panel with a
 * visible border - because they sit in a dim monospace box and a muted grey icon read as disabled. */
.pass-line { display: flex; align-items: baseline; gap: 8px; }
.pass-text { flex: 1 1 auto; min-width: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
.pass-downloads { flex: 0 0 auto; display: flex; gap: 5px; }
.pass-download {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 7px;
  border: 1px solid var(--muted);
  border-radius: 4px;
  background: var(--panel-2);
  color: var(--text);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.03em;
  line-height: 1.4;
  text-decoration: none;
}
.pass-download:hover, .pass-download:focus-visible {
  color: #ffffff;
  border-color: var(--text);
  background: #2a323d;
}
.pass-download-icon { display: block; }

/* The spoken-result button: a large, high-contrast target, because the operator pressing it is reading
 * the page in daylight. It is a local speech action, not a command, so it stays live on a read-only
 * page - only "no pass yet" disables it. The spoken-status button sits beside it and is never disabled:
 * it is the same kind of local action, and a page that has not heard from a beam yet still has a state
 * to report ("unknown"). */
.passes-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 6px; }
.say-result { flex: 1 1 auto; min-width: 0; padding: 9px 12px; font-size: 14px; font-weight: 700; letter-spacing: 0.02em; }
.say-result:not(:disabled) { border-color: var(--ok); background: var(--ok-dim); }
.say-status { flex: 0 0 auto; padding: 9px 12px; font-size: 14px; font-weight: 700; letter-spacing: 0.02em; }
.say-status:not(:disabled) { border-color: var(--idle); }

/* ---------- footer and overlay ---------- */

.footer {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  padding: 4px 12px;
  font-size: 11px;
  color: var(--muted);
  border-top: 1px solid var(--line);
}

.overlay {
  position: fixed;
  inset: 0;
  background: rgba(5, 8, 12, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10;
}
.overlay-card {
  max-width: 420px;
  padding: 16px 20px;
  background: var(--panel);
  border: 1px solid var(--bad);
  border-radius: 8px;
  text-align: center;
}
.overlay-card p { color: var(--muted); margin: 6px 0 0; }

/* ---------- phone ----------
 * The page is reachable over a tailnet and gets opened on a phone, usually just to answer "is it
 * working". Below 700 px the grid collapses to one column ordered connection → channels → log, and
 * the rest follows (design section 9).
 */

@media (max-width: 700px) {
  body { padding: 6px; gap: 6px; }
  /* The mode row wraps under the site name rather than squashing: on a phone this line still answers
   * "which site, what mode" at a glance, and the three buttons stay thumb-sized. */
  .topbar { gap: 6px 10px; }
  .mode-buttons { width: 100%; }
  .mode-buttons button { flex: 1; }
  .grid { grid-template-columns: minmax(0, 1fr); }
  .panel-channels, .panel-system, .panel-passes, .panel-log { grid-column: 1; grid-row: auto; }
  .panel-channels { order: 1; }
  .panel-passes { order: 2; }
  .panel-system { order: 3; }
  .panel-log { order: 4; }
  .system-body { grid-template-columns: minmax(0, 1fr); }
  .tiles { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
  .log { height: 160px; }
  .passes { max-height: 120px; }
}
