:root{
  --bg:#0b0f14; --panel:#131a22; --panel-2:#0f151c; --line:#1f2a35;
  --text:#e6edf3; --muted:#8ba1b4; --ok:#22c55e; --warn:#eab308; --alarm:#ef4444;
  --off:#4b5563; --accent:#38bdf8;
  --font:"Segoe UI",system-ui,-apple-system,Roboto,Helvetica,Arial,sans-serif;
  --mono:"JetBrains Mono","Cascadia Mono",Consolas,"Courier New",monospace;
}
*{box-sizing:border-box}
html,body{height:100%}
body{margin:0;background:var(--bg);color:var(--text);font-family:var(--font);
  display:flex;flex-direction:column;overflow:hidden}

.bar{display:flex;align-items:center;justify-content:space-between;gap:24px;
  padding:8px 18px;background:linear-gradient(180deg,#141c25,#0f151c);
  border-bottom:1px solid var(--line)}
.brand{display:flex;align-items:center;gap:14px;white-space:nowrap}
/* The project mark is the first thing an operator should recognise, so it is
   sized against the header rather than tucked beside the text, and lifted off
   the dark bar with a soft halo. */
.brand .logo{height:58px;width:auto;display:block;
  filter:drop-shadow(0 0 10px rgba(56,189,248,.22)) drop-shadow(0 1px 2px rgba(0,0,0,.6))}
@media (max-width:1200px){.brand .logo{height:46px}}
@media (max-width:900px){.brand .logo{height:38px}}
.brand-rule{width:1px;height:34px;background:var(--line);margin:0 2px}
.brand strong{letter-spacing:.06em}
.brand .sub{color:var(--muted);font-size:12px}
.brand .ver{font-family:var(--mono);font-size:10.5px;color:var(--muted);
  border:1px solid var(--line);border-radius:99px;padding:1px 7px;
  letter-spacing:.02em;margin-left:-4px}
.dot{width:10px;height:10px;border-radius:50%;background:var(--off);
  box-shadow:0 0 0 3px rgba(255,255,255,.05)}
.dot.live{background:var(--ok);animation:pulse 2s infinite}
.dot.down{background:var(--alarm)}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}}

.readouts{display:flex;gap:22px;align-items:center;flex-wrap:wrap}
.ro{display:flex;flex-direction:column;align-items:flex-end;min-width:96px}
.ro-label{font-size:10px;text-transform:uppercase;letter-spacing:.09em;color:var(--muted)}
.ro-value{font-family:var(--mono);font-size:20px;font-variant-numeric:tabular-nums}
.ro-value.small{font-size:12px;color:var(--muted);max-width:260px;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.session{display:flex;flex-direction:column;align-items:flex-end;gap:3px}
.session .who{font-size:10px;text-transform:uppercase;letter-spacing:.09em;
  color:var(--muted)}
.signout{background:transparent;border:1px solid var(--line);border-radius:6px;
  color:var(--muted);font:inherit;font-size:11px;padding:3px 9px;cursor:pointer}
.signout:hover{color:var(--text);border-color:var(--accent)}
.area-chip{font-family:var(--mono);font-size:12px;margin-left:8px}
.area-chip:first-child{margin-left:0}
.site .area{color:var(--accent);opacity:.85}

main{flex:1;display:flex;min-height:0}
.map-pane{position:relative;flex:1;min-width:0}
#map{position:absolute;inset:0;background:#0d1319}
#fallback{position:absolute;inset:0;width:100%;height:100%}
/* Utility class, so it must beat the ID rules it is applied against
   (#map, #fallback, #trend, #psd): an id selector outranks a class, and
   without this a hidden element stays on screen. */
.hidden{display:none !important}
.leaflet-container{background:#0d1319}

.map-controls{position:absolute;z-index:600;top:12px;left:56px;display:flex;gap:10px;
  background:rgba(15,21,28,.88);border:1px solid var(--line);border-radius:8px;
  padding:8px 12px;backdrop-filter:blur(4px)}
.ctl{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--muted)}
.ctl select,.ctl input[type=range]{background:#0b1118;color:var(--text);
  border:1px solid var(--line);border-radius:5px;padding:2px 4px;font-size:12px}
.ctl.checkbox{cursor:pointer}

.legend{position:absolute;z-index:600;left:12px;bottom:16px;width:240px;
  background:rgba(15,21,28,.9);border:1px solid var(--line);border-radius:8px;padding:10px}
.legend-bar{height:10px;border-radius:5px;
  background:linear-gradient(90deg,#22c55e 0%,#22c55e 20%,#eab308 50%,#ef4444 100%)}
.legend-ticks{display:flex;justify-content:space-between;font-size:10px;
  color:var(--muted);margin-top:4px;font-family:var(--mono)}
/* Angle is a signed quantity with a meaningful zero, so it gets a diverging
   scale rather than the one-sided severity ramp: blue lags the reference,
   red leads it, and grey is in step with it. */
.legend-bar.angle{background:linear-gradient(90deg,#2563eb 0%,#60a5fa 25%,
  #94a3b8 50%,#fb923c 75%,#dc2626 100%)}
.legend-note{font-size:10px;color:var(--muted);margin-top:6px;line-height:1.4}

.side{width:340px;border-left:1px solid var(--line);background:var(--panel-2);
  display:flex;flex-direction:column;min-height:0}
.panel{padding:10px 12px;border-bottom:1px solid var(--line);min-height:0;
  display:flex;flex-direction:column}
.panel.grow{flex:1}
.panel h2{margin:0 0 8px;font-size:11px;text-transform:uppercase;
  letter-spacing:.1em;color:var(--muted);font-weight:600}
.site-list{display:flex;flex-direction:column;gap:6px;overflow:auto}
.site{display:grid;grid-template-columns:6px 1fr auto;gap:10px;align-items:center;
  background:var(--panel);border:1px solid var(--line);border-radius:7px;padding:7px 9px}
.site .swatch{width:6px;height:34px;border-radius:3px;background:var(--off)}
.site .name{font-size:13px}
.site .meta{font-size:10.5px;color:var(--muted);font-family:var(--mono)}
.site .val{text-align:right;font-family:var(--mono)}
.site .f{font-size:15px;font-variant-numeric:tabular-nums}
.site .d{font-size:11px;color:var(--muted)}
.site.offline{opacity:.55}
/* Registered but never delivered. Dashed, not crossed out: it is a
   commissioning gap, not a failure, and should not read as an alarm. */
.site.awaited{opacity:.45;border-style:dashed}
.site.awaited .swatch{background:transparent;border:1px dashed var(--off)}

.event-log{overflow:auto;display:flex;flex-direction:column;gap:4px;font-size:12px}
.ev{display:grid;grid-template-columns:58px 1fr;gap:8px;padding:4px 6px;
  border-left:3px solid var(--off);background:var(--panel);border-radius:0 5px 5px 0}
.ev.info{border-color:var(--accent)}
.ev.warn{border-color:var(--warn)}
.ev.alarm{border-color:var(--alarm)}
.ev time{font-family:var(--mono);color:var(--muted);font-size:11px}
.ev .who{color:var(--muted)}

/* A fault the operator can see. Silence is the wrong default when the panel
   it would otherwise leave blank is the whole point of the page. */
.errbar{position:fixed;top:0;left:0;right:0;z-index:2000;
  background:rgba(239,68,68,.94);color:#fff;font-size:12px;
  padding:7px 14px;font-family:var(--mono);
  box-shadow:0 2px 10px rgba(0,0,0,.5)}
.diag{position:absolute;right:12px;top:44px;z-index:800;width:340px;
  max-height:70%;overflow:auto;background:rgba(11,17,24,.95);
  border:1px solid var(--accent);border-radius:8px;padding:9px 11px;
  font-family:var(--mono);font-size:10.5px;line-height:1.55;color:var(--muted)}
.diag b{color:var(--text);font-weight:600;display:inline-block;min-width:92px}
.diag .diag-log{margin-top:7px;padding-top:7px;border-top:1px solid var(--line);
  color:#7a8a99}
.chart-pane{height:262px;border-top:1px solid var(--line);background:var(--panel-2);
  padding:8px 12px 10px;display:flex;flex-direction:column;position:relative;
  flex:0 0 auto}
/* A time-frequency heat map needs vertical room the strip along the bottom
   does not have, so the panel is draggable up over the map. The grip sits on
   the border itself rather than taking a row of its own. */
.chart-resize{position:absolute;top:-4px;left:0;right:0;height:9px;
  cursor:ns-resize;z-index:700}
.chart-resize::after{content:"";position:absolute;left:50%;top:3px;
  width:46px;height:3px;margin-left:-23px;border-radius:2px;
  background:var(--line);transition:background .12s}
.chart-resize:hover::after,.chart-resize.active::after{background:var(--accent)}
body.resizing{cursor:ns-resize;user-select:none}
.expand{background:transparent;border:1px solid var(--line);border-radius:6px;
  color:var(--muted);cursor:pointer;font-size:10px;line-height:1;padding:5px 7px}
.expand:hover{color:var(--text)}
.chart-pane.tall .expand{transform:rotate(180deg)}
.chart-head{display:flex;align-items:center;gap:16px;flex-wrap:wrap;
  margin-bottom:4px}
.chart-head h2{margin:0 0 4px;font-size:11px;text-transform:uppercase;
  letter-spacing:.1em;color:var(--muted);font-weight:600}
.chart-legend{display:flex;gap:12px;font-size:11px;color:var(--muted);flex-wrap:wrap}
.chart-legend i{display:inline-block;width:10px;height:2px;margin-right:4px;
  vertical-align:middle}
#trend,#psd{display:block;flex:0 0 auto;min-height:0}
#trend-plot,#psd-plot{flex:0 0 auto;min-height:0}
/* uPlot ships a light theme; these are the few colours worth overriding so it
   sits in the same control-room palette as everything else. */
.u-legend{font-family:var(--mono);font-size:11px;color:var(--muted)}
.u-legend .u-marker{width:10px;height:2px;border-width:0 0 2px 0}
.u-legend th,.u-legend td{font-weight:400}
.u-legend .u-value{color:var(--text)}
.u-select{background:rgba(56,189,248,.18)}
.u-cursor-x,.u-cursor-y{border-color:rgba(230,237,243,.35)}
.u-axis{color:var(--muted)}
.u-over,.u-under{border-color:var(--line)}

.tabs{display:flex;gap:2px}
.tab{background:transparent;border:1px solid transparent;border-radius:6px;
  color:var(--muted);font:inherit;font-size:11px;text-transform:uppercase;
  letter-spacing:.09em;font-weight:600;padding:4px 10px;cursor:pointer}
.tab:hover{color:var(--text)}
.tab.active{color:var(--text);background:var(--panel);border-color:var(--line)}
.chart-tools{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.grp{display:flex;align-items:center;gap:12px}
/* The hidden attribute's display:none comes from the browser's own stylesheet,
   which any author rule outranks - so an element given display:flex or
   display:block here stays on screen when hidden is set. That is how three
   window selectors ended up on the panel at once. */
[hidden]{display:none !important}
.chart-note{font-size:11px;color:var(--muted);font-family:var(--mono)}

/* Inter-area mode table. One row per DMD mode, with the mode shape drawn
   inline: bar length is how strongly a site takes part, colour is which end of
   the oscillation it sits on. */
.modes{flex:1;overflow:auto;min-height:0;display:flex;flex-direction:column;gap:6px}
.mode{background:var(--panel);border:1px solid var(--line);border-radius:8px;
  padding:8px 10px;display:grid;grid-template-columns:186px 172px 1fr;gap:14px;
  align-items:center;cursor:pointer}
.mode:hover{border-color:#31414f}
@media (max-width:1240px){.mode{grid-template-columns:186px 1fr}
  .mode canvas.compass{display:none}}
.mode canvas.compass{width:168px;height:168px;display:block}
/* A focused mode gets the whole width for its shape. */
.mode.focused{grid-template-columns:186px 300px 1fr;border-color:var(--accent)}
.mode.focused canvas.compass{width:296px;height:296px}
.mode-hint{color:var(--muted);font-size:10.5px;padding:0 2px 6px}
.mode.unconfirmed{opacity:.6;border-style:dashed}
.mode-key{display:flex;flex-direction:column;gap:2px}
.mode-f{font-family:var(--mono);font-size:17px;font-variant-numeric:tabular-nums}
.mode-sub{font-size:10.5px;color:var(--muted);font-family:var(--mono)}
.mode-tag{display:inline-block;font-size:9.5px;letter-spacing:.08em;
  text-transform:uppercase;padding:1px 6px;border-radius:99px;margin-right:5px;
  border:1px solid currentColor}
.tag-ok{color:var(--ok)}
.tag-warn{color:var(--warn)}
.tag-alarm{color:var(--alarm)}
.tag-muted{color:var(--muted)}
.shape{display:flex;flex-direction:column;gap:3px}
.shape-row{display:grid;grid-template-columns:104px 1fr 74px 78px;gap:8px;
  align-items:center;font-size:11px}
.shape-row.is-ref .sname{color:var(--accent)}
.shape-row .sphase{font-family:var(--mono);font-size:10.5px;text-align:right}
.shape-row .sname{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.shape-bar{height:9px;background:#0b1118;border-radius:5px;overflow:hidden;
  position:relative}
.shape-bar i{display:block;height:100%;border-radius:5px}
.shape-row .snum{font-family:var(--mono);color:var(--muted);text-align:right;
  font-size:10.5px}
.modes-empty{color:var(--muted);font-size:12px;padding:10px 2px;line-height:1.6}
.mode-excluded{color:var(--muted);font-size:11px;padding:2px 2px 6px;line-height:1.5}

/* Cross-method agreement. A mode several independent estimators find is
   physical; one a single method reports is arithmetic until corroborated. */
.consensus{display:flex;flex-direction:column;gap:5px;margin-bottom:8px}
.cons-row{display:grid;grid-template-columns:96px 116px 1fr 150px;gap:12px;
  align-items:center;background:var(--panel);border:1px solid var(--line);
  border-radius:7px;padding:6px 10px;font-size:11.5px}
.cons-row .cf{font-family:var(--mono);font-size:15px}
.cons-row .cz{font-family:var(--mono);color:var(--muted)}
.cons-agree{display:flex;gap:3px;align-items:center}
.cons-agree i{width:11px;height:11px;border-radius:3px;background:var(--line);
  display:inline-block}
.cons-agree i.on{background:var(--accent)}
.cons-row .cm{color:var(--muted);font-size:10.5px;text-align:right;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cons-row.weak{opacity:.55;border-style:dashed}
.methods-strip{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px}
.mchip{border:1px solid var(--line);border-radius:6px;padding:3px 8px;
  font-size:10.5px;color:var(--muted);font-family:var(--mono)}
.mchip b{color:var(--text);font-weight:600;font-family:var(--font)}
.mchip.bad{border-color:var(--alarm);color:#fecaca}
.mchip.good{border-color:var(--ok)}
.mode-excluded b{color:var(--text);font-weight:600}

.pmu-label{font-family:var(--mono);font-size:11px;color:#e6edf3;
  text-shadow:0 1px 3px #000,0 0 8px #000;white-space:nowrap;pointer-events:none}
.pmu-label b{font-weight:600}
