/* istrain dashboard — frame. Color scheme carried over from the radio/marine dashboard (the reusable radio engine). Signature look: each panel wears a colored left stripe. */ :root{ --bg:#0b1620; --panel:#13212e; --ink:#cfe3f0; --muted:#7d97a8; --line:#243a4d; --accent:#37b3a4; --good:#2c9c6b; --bad:#e0413f; /* per-panel left-stripe colors */ --c-dispatch:#e0b341; --c-detect:#37b3a4; --c-yard:#7fb3ff; --c-future:#5a7088; --c-flow:#b48ce0; --c-stack:#57b6c9; --c-captures:#e0884d; --c-activity:#b6c94d; --c-detectors:#e06b8a; --c-feeds:#8c9ee0; } *{box-sizing:border-box} body{margin:0;font:14px/1.45 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;background:var(--bg);color:var(--ink)} header{position:sticky;top:0;background:#0a131c;border-bottom:1px solid var(--line);padding:12px 16px;z-index:10; display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap} header h1{margin:0;font-size:20px;font-weight:700;letter-spacing:.02em} header h1 .sub{font-size:12px;font-weight:400;color:var(--muted);margin-left:10px} /* hits counter — header stat block */ .hdr-right{display:flex;align-items:center;gap:16px} .controls{display:flex;gap:8px} .btn{border:1px solid var(--line);background:#1b2c3b;color:var(--ink);padding:7px 12px;border-radius:6px;font:600 12px/1 system-ui;cursor:pointer} .btn:hover{filter:brightness(1.18)} .btn:active{transform:translateY(1px)} .btn.reset{background:#3a1414;border-color:#7a2a28;color:#ff9a98} .hits{display:flex;gap:18px;align-items:center} .hits .stat{text-align:right} .hits .num{font:700 20px/1 ui-monospace,monospace;color:var(--accent);font-variant-numeric:tabular-nums} .hits .lab{font-size:10px;letter-spacing:.10em;text-transform:uppercase;color:var(--muted);margin-top:3px} main{display:grid;gap:14px;padding:16px;max-width:1000px;margin:0 auto} .panel{background:var(--panel);border:1px solid var(--line);border-left:4px solid var(--c-future); border-radius:8px;padding:14px;min-width:0} /* min-width:0 lets a grid item shrink so a wide
scrolls inside it instead of widening the whole page (mobile) */
.panel h2{margin:0 0 10px;font-size:14px;color:var(--accent);letter-spacing:.04em;text-transform:uppercase}
.panel h2 .tag{font-size:11px;color:var(--muted);font-weight:400;letter-spacing:.02em;text-transform:none;margin-left:8px}
/* left-stripe colors per panel role */
.panel.dispatch{border-left-color:var(--c-dispatch)}
.panel.dispatch h2{color:var(--c-dispatch)}
.panel.detect{border-left-color:var(--c-detect)}
.panel.yard{border-left-color:var(--c-yard)}
.panel.yard h2{color:var(--c-yard)}
.panel.flow{border-left-color:var(--c-flow)}
.panel.flow h2{color:var(--c-flow)}
.panel.stack{border-left-color:var(--c-stack)}
.panel.stack h2{color:var(--c-stack)}
/* per-PANEL distinct colors — id overrides the reused role-class so each panel reads as its own thing */
.panel#captures{border-left-color:var(--c-captures)} .panel#captures h2{color:var(--c-captures)}
.panel#activity{border-left-color:var(--c-activity)} .panel#activity h2{color:var(--c-activity)}
.panel#detectors{border-left-color:var(--c-detectors)} .panel#detectors h2{color:var(--c-detectors)}
.panel#feeds{border-left-color:var(--c-feeds)} .panel#feeds h2{color:var(--c-feeds)}
/* signal-flow ASCII map — hand-maintained ; scrolls sideways if it can't fit */
.flow-chart{margin:0;overflow-x:auto;-webkit-overflow-scrolling:touch;
background:#0a131c;border:1px solid var(--line);border-radius:6px;padding:14px;
color:#c4dcea;font:11px/1.42 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
.placeholder{color:var(--muted);background:#0a131c;border:1px dashed var(--line);border-radius:6px;
padding:22px;text-align:center;line-height:1.7;font-size:13px}
.placeholder b{color:var(--ink)}
/* radios panel — per-dongle liveness table + status dots */
table.radios{width:100%;border-collapse:collapse;font-size:13px}
table.radios td{padding:6px 8px;border-bottom:1px solid var(--line);vertical-align:middle}
table.radios tr:last-child td{border-bottom:none}
table.radios .sn{font-family:ui-monospace,monospace;color:var(--accent);font-weight:700;width:64px}
table.radios .role{color:var(--ink);text-transform:capitalize;width:80px}
table.radios .nm{color:var(--muted);font-size:12px}
table.radios .st{font-size:12px;text-align:right;font-variant-numeric:tabular-nums}
.dot{display:inline-block;width:10px;height:10px;border-radius:50%;vertical-align:middle}
.dot.ready{background:#6f9bd9;box-shadow:0 0 7px #6f9bd9}
.dot.listening{background:var(--good);box-shadow:0 0 7px var(--good)}
.dot.blocked{background:#e0b341;box-shadow:0 0 7px #e0b341}
.dot.offline{background:var(--bad);box-shadow:0 0 7px var(--bad)}
/* the missing middle states (06-25 truth-by-data-flow): held-but-no-samples is the dangerous lie */
.dot.stalled{background:#e06a2e;box-shadow:0 0 8px #e06a2e}
.dot.held{background:#8a93a6;box-shadow:0 0 6px #8a93a6}
.st.ready{color:#6f9bd9} .st.listening{color:var(--good)} .st.blocked{color:#e0b341} .st.offline{color:var(--bad)}
.st.stalled{color:#e06a2e} .st.held{color:#8a93a6}
.sv-detail.stalled{color:#e06a2e;opacity:1;font-weight:600}
/* errors card — live issues + recent events (below WITH THANKS) */
.err-row{font-family:ui-monospace,monospace;font-size:12px;padding:4px 6px;border-bottom:1px solid var(--line);line-height:1.45}
.err-row:last-child{border-bottom:none}
.err-row.err-live{color:#e06a2e;font-weight:600}
.err-row.err-err{color:#d9705e}
.err-row.err-event{color:var(--muted)}
/* grid panel order (the owner's layout, 2026-06-23; supervisor moved OUT of to the ops corner
below THANKS 2026-07-06): correlate → bot → mid-train → eot → voice activity → captures →
detectors → feeds → signal flow → system map. The signal panels run FRONT-to-BACK of a train
(bot=head · mid=DPU · eot=rear). Thanks + supervisor + errors + checklist follow after . */
#correlate{order:-1} /* overview panel — pinned to the top, under the banner, before the train cards */
#live{order:0} #bot{order:3} #midtrain{order:4} #eot{order:5} #activity{order:6}
#captures{order:7} #detectors{order:8} #feeds{order:9} #flow{order:10} #stack{order:11}
#dispatch{order:12} /* Dispatch/Yard whisper placeholder — parked at the bottom; may use it later */
table.supervisor{width:100%;border-collapse:collapse;font-size:12.5px}
table.supervisor td{padding:4px 6px;border-bottom:1px solid rgba(255,255,255,.05);vertical-align:middle}
table.supervisor tr:last-child td{border-bottom:0}
.sv-grp td{font-size:10.5px;text-transform:uppercase;letter-spacing:.1em;color:var(--muted);padding:8px 6px 2px;border-bottom:0}
.sv-name{font-weight:600}
.sv-detail{font-size:11.5px;opacity:.85}
.sv-since{font-size:11px;color:var(--muted);white-space:nowrap;text-align:right}
.sv-actions{text-align:right;white-space:nowrap}
.sv-ctl{background:none;border:1px solid rgba(255,255,255,.2);color:inherit;opacity:.55;cursor:pointer;font-size:11px;line-height:1;padding:2px 6px;border-radius:3px;margin-left:3px}
.sv-ctl:hover{opacity:1;border-color:var(--accent);color:var(--accent)}
/* NOAA per-radio sanity toggle — idle = quiet outline; on = live green (audio playing, hijacked) */
.noaa-btn{border:1px solid var(--line);background:#1b2c3b;color:var(--ink);
border-radius:6px;font:600 11px/1 system-ui;text-transform:none;letter-spacing:0;
cursor:pointer;white-space:nowrap}
.noaa-btn:hover{filter:brightness(1.18)}
.noaa-btn:active{transform:translateY(1px)}
.noaa-btn:disabled{opacity:.55;cursor:wait}
.noaa-btn.on{background:var(--good);border-color:var(--good);color:#eafff4;box-shadow:0 0 8px var(--good)}
.noaa-btn.mini{padding:4px 9px}
table.radios .noaa-cell{text-align:right;width:1%;white-space:nowrap}
.noaa-na{color:var(--muted);opacity:.5}
/* build checklist — rendered from checklist.md; below thanks. centered like thanks since it
sits outside . checked items dim + strike; group headers rule off the sections. */
.panel.checklist{border-left-color:var(--good)}
/* every section OUTSIDE (thanks / supervisor / errors / checklist) gets the SAME effective
width as the in-main panels (main = 1000px cap minus 16px side padding → 968px interior), so
the whole page reads one column — no full-window-wrapping strays. */
.thanks, .panel.checklist, #supervisor, #errors{
max-width:968px;margin:14px auto;width:calc(100% - 32px);box-sizing:border-box}
.panel.checklist h2{color:var(--good)}
.cl-intro{color:var(--muted);font-size:12px;line-height:1.5;margin-bottom:6px}
.cl-group{color:var(--ink);font-weight:700;font-size:12px;letter-spacing:.04em;text-transform:uppercase;
margin:12px 0 6px;padding-bottom:3px;border-bottom:1px solid var(--line)}
.cl-item{display:flex;gap:8px;align-items:flex-start;font-size:13px;padding:2px 0;color:var(--ink)}
.cl-item.cl-sub{margin-left:22px;font-size:12px;color:var(--muted)}
.cl-item.done{color:var(--muted)}
.cl-item.done .cl-text{text-decoration:line-through;opacity:.7}
.cl-box{flex-shrink:0;color:var(--muted);line-height:1.5}
.cl-item.done .cl-box{color:var(--good)}
.cl-text code,.cl-intro code{background:#0a131c;border:1px solid var(--line);border-radius:3px;
padding:0 4px;font-size:11px;color:var(--accent);word-break:break-word}
/* with thanks — carried from the first istrain, extended for the rebuild */
.thanks{padding:14px 16px;background:#0f1a24;border:1px solid var(--line);
border-left:4px solid var(--good);border-radius:8px;font:12px/1.65 ui-monospace,monospace;color:var(--muted)}
/* width/margin come from the shared outside-main rule above */
.thanks .th-h{color:var(--good);letter-spacing:.12em;font-weight:700;margin-bottom:8px}
.thanks p{margin:8px 0}
.thanks .th-grp b{color:var(--ink);display:inline-block;margin-bottom:2px}
.thanks a{color:#7ec8e3;text-decoration:none}
.thanks a:hover{text-decoration:underline}
.thanks strong{color:var(--ink)}
.thanks em{color:var(--ink);font-style:italic}
.thanks .dim{color:#5a7088}
footer{color:var(--muted);text-align:center;padding:16px;font-size:12px}
footer a{color:var(--accent);text-decoration:none}
/* ---- narrow / portrait (phone) ---- istrain will mostly be viewed on mobile.
Stack the header instead of fighting for one row, trim padding, and let the
radios table wrap instead of overflowing a ~360px screen. */
@media (max-width:560px){
body{font:13px/1.45 system-ui,-apple-system,Segoe UI,Roboto,sans-serif}
/* header: title on its own line, sub beneath it, controls+hits on a full-width row */
header{padding:10px 12px;gap:10px}
header h1{font-size:17px;display:flex;flex-direction:column;gap:2px}
header h1 .sub{margin-left:0;font-size:11px}
.hdr-right{width:100%;justify-content:space-between;gap:10px}
.hits{gap:14px}
.hits .num{font-size:17px}
.btn{padding:7px 10px}
/* body: tighter gutters so panels get the width back */
main{padding:12px;gap:12px}
.panel{padding:12px}
.placeholder{padding:16px 12px;font-size:12.5px;line-height:1.6}
/* radios table: drop fixed cell widths, allow long product names to wrap */
table.radios{font-size:12.5px}
table.radios td{padding:6px 6px}
table.radios .sn,table.radios .role{width:auto}
table.radios .nm{word-break:break-word}
.thanks{padding:12px;font-size:11.5px}
.flow-chart{font-size:10px;padding:12px 10px}
footer{padding:14px 12px}
}
/* captures panel — rail-voice clip list (light: native