/* WYLA customer app — the approved mockup's design system, as real stylesheet.
   Single dark control-room theme, committed deliberately (not an omission).
   No CDN, no webfonts: system faces only, so it renders offline and identically
   on a rural laptop with no internet. */

:root{
  /* --ink-dim was #5C7089, 3.4:1 on a panel: too faint for the small labels, table headings and help text it is used
     for (console review, 7 Nov 2026). #7A8FAA is 5.2:1 and still reads quieter than --ink-mut. --red is the readable
     red for TEXT (an overdue job's title and date; js/schedule.js, js/reminders.js): 6.2:1 where #A3261B was 2.3:1. */
  --bg:#0A111E; --panel:#101B2D; --panel-2:#0D1626; --line:#1D2B42; --line-soft:#16233A;
  --ink:#E9F0F9; --ink-mut:#8CA0BA; --ink-dim:#7A8FAA; --red:#FF6B5E;
  --accent:#41B9DC; --accent-soft:rgba(65,185,220,.12);
  --good:#3FBF7F; --warn:#E9B23C; --fault:#E05656; --offline:#5C7089;
  --good-soft:rgba(63,191,127,.13); --warn-soft:rgba(233,178,60,.14);
  --fault-soft:rgba(224,86,86,.13); --offline-soft:rgba(92,112,137,.15);
  --disp:"Bahnschrift","Barlow Condensed","Arial Narrow",Arial,sans-serif;
  --body:"Segoe UI",system-ui,-apple-system,sans-serif;
  --r:10px;
}
/* The native parts of controls (date pickers, drop-down lists, scroll bars) follow the dark theme too. */
:root{color-scheme:dark}
*{box-sizing:border-box}
html,body{background:var(--bg);margin:0}
body{font-family:var(--body);color:var(--ink);font-size:14px;line-height:1.45}
button,input,select,textarea{font-family:inherit}
/* A visible keyboard focus everywhere: the ring is never taken away, only coloured. */
:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* Every text box, drop-down and note box is dark (before 7 Nov 2026 a textarea or a select with no class of its own
   was a white browser box with typewriter text). :where() keeps this at no weight at all, so every page's own rule
   for its controls still wins. */
:where(input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"])),
:where(select),:where(textarea){background-color:var(--panel-2);color:var(--ink);border:1px solid var(--line);
  border-radius:7px;padding:6px 9px;font-size:13px}
:where(textarea){font-family:var(--body);line-height:1.45}
:where(option){background-color:var(--panel-2);color:var(--ink)}
:where(input,select,textarea):disabled{opacity:.6}
::placeholder{color:var(--ink-dim);opacity:1}

/* ---------------------------------------------------------------- login */
.login{position:fixed;inset:0;z-index:100;display:flex;align-items:center;justify-content:center;
  background:radial-gradient(1200px 800px at 30% 20%,#12233E 0%,#0A111E 60%,#070C16 100%)}
.login[hidden]{display:none}
.loginbox{width:340px;max-width:92vw;background:var(--panel);border:1px solid var(--line);
  border-radius:14px;padding:30px 28px;box-shadow:0 24px 80px rgba(0,0,0,.55)}
.loginbox .logo{display:flex;align-items:center;gap:12px;justify-content:center;margin-bottom:6px}
.drop{width:26px;height:26px;border-radius:50% 50% 50% 0;transform:rotate(-45deg);flex:0 0 auto;
  background:linear-gradient(135deg,var(--accent),#2478A8);box-shadow:0 0 14px rgba(65,185,220,.45)}
.loginbox .drop{width:34px;height:34px}
h1.brandmark{font-family:var(--disp);font-size:19px;letter-spacing:.06em;margin:0;font-weight:600;line-height:1.15}
h1.brandmark span{color:var(--accent)}
.loginbox .sub{text-align:center;font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-dim);margin:6px 0 22px}
.login label{display:block;font-size:11px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink-dim);font-family:var(--disp);margin:12px 0 5px}
.login input,.login select{width:100%;background:#0B1322;border:1px solid var(--line);
  border-radius:8px;color:var(--ink);padding:11px 12px;font-size:13px}
.login .err{margin-top:12px;font-size:12px;color:var(--fault);min-height:1.2em}
.login .note{text-align:center;font-size:10.5px;color:var(--ink-dim);margin-top:16px}
/* The small copyright line (sign-in boxes and the foot of the menu): quiet, never in the way. */
.copyright{font-size:10px;line-height:1.4;color:var(--ink-dim);margin-top:12px;letter-spacing:.01em}
.loginbox .copyright{text-align:center;margin-top:18px}
.np-title{text-align:center;font-family:var(--disp);font-size:16px;font-weight:600;
  color:var(--warn);margin:4px 0 10px}
.np-body{font-size:13px;line-height:1.5;color:var(--ink-mut);text-align:center;margin:0}
.np-body strong{color:var(--ink);word-break:break-all}
/* "Confirm it is you" (#linkreq): a long email address in the sentence wraps instead of pushing the box
   wider, and the "Email me a new code" link reads as waiting while the server will not send another yet. */
#linkreq .np-body{overflow-wrap:anywhere}
.login .linkbtn{font-size:12px}
.login .linkbtn:disabled{opacity:.5;cursor:not-allowed;text-decoration:none}

/* ---- server capacity bars (admin Health tab) ---- */
.capgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px}
.cap-top{display:flex;justify-content:space-between;align-items:baseline;font-size:12px;
  letter-spacing:.02em;margin-bottom:6px}
.cap-top b{font-variant-numeric:tabular-nums;font-size:14px}
.cap-track{height:8px;border-radius:6px;background:var(--line);overflow:hidden}
.cap-fill{height:100%;border-radius:6px;background:var(--good);transition:width .35s ease}
.cap.warning .cap-fill{background:var(--warn)}
.cap.critical .cap-fill{background:var(--fault)}
.cap.info .cap-fill{background:var(--ink-mut)}
.cap.warning .cap-top b{color:var(--warn)}
.cap.critical .cap-top b{color:var(--fault)}
.cap-sub{font-size:10.5px;margin-top:5px}

/* ---------------------------------------------------------------- shell */
.app{display:flex;min-height:100vh}
.app[hidden]{display:none}
.rail{width:224px;flex:0 0 224px;background:var(--panel-2);border-right:1px solid var(--line);
  display:flex;flex-direction:column;padding:20px 0;position:sticky;top:0;height:100vh;height:100dvh;
  /* The menu is taller than a phone screen (customer and site pickers, then every page, then
     Platform Administration last), and it did not scroll: everything below the fold was out of
     reach (29 Sep 2026). 100dvh is the VISIBLE height, not counting the browser's own bars. */
  overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch}
.brand{padding:0 20px 18px;border-bottom:1px solid var(--line-soft)}
.brand .logo{display:flex;align-items:center;gap:10px}
.brand .tag{font-size:10.5px;color:var(--ink-dim);letter-spacing:.14em;text-transform:uppercase;margin-top:6px}
.unit{padding:16px 20px;border-bottom:1px solid var(--line-soft)}
.eyebrow{font-family:var(--disp);font-size:10.5px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--ink-dim);margin-bottom:4px}
.unit .name{font-family:var(--disp);font-size:15px;letter-spacing:.03em}
.unit .own{font-size:12px;color:var(--ink-mut);margin-top:2px}
.unit select{width:100%;margin-top:8px;background:#0B1322;border:1px solid var(--line);
  border-radius:7px;color:var(--ink);padding:7px 9px;font-size:12.5px}
.nav{padding:10px 10px 14px;display:flex;flex-direction:column;gap:2px}
/* The menu's groups (Today, Sites, Records, People and alerts, Setup). The heading is a label, not a link. */
.navgroup{display:flex;flex-direction:column;gap:2px;margin-top:8px}
.navgroup[hidden]{display:none}
.navhead{font-family:var(--disp);font-size:10.5px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--ink-dim);padding:4px 12px 3px}
.nav a{display:flex;align-items:center;flex-wrap:wrap;gap:0 10px;color:var(--ink-mut);padding:8px 12px;
  border-radius:8px;font-size:13.5px;text-decoration:none;letter-spacing:.02em}
.nav a:hover{background:var(--panel);color:var(--ink)}
.nav a.on{background:var(--accent-soft);color:var(--accent);font-weight:600}
.nav svg{width:17px;height:17px;flex:0 0 17px;opacity:.9}
/* A part switched off at this site: still listed, greyed, with one line saying which plan has it (C5). */
.nav a.off{color:var(--ink-dim)}
.nav a.off svg{opacity:.45}
.nav a .navnote{flex:1 0 100%;padding-left:27px;font-size:11px;line-height:1.3;color:var(--ink-dim);
  font-weight:400;letter-spacing:0}
.railfoot{margin-top:auto;padding:16px 20px 0;border-top:1px solid var(--line-soft);
  font-size:11.5px;color:var(--ink-dim)}
.railfoot .row{display:flex;justify-content:space-between;margin-top:6px;gap:8px}
.led{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:6px;background:var(--offline)}
.led.on{background:var(--good);box-shadow:0 0 6px var(--good)}
/* Online, but the readings have stopped: not offline, and deliberately not green. */
.led.stale{background:var(--warn);box-shadow:0 0 6px var(--warn)}
.rolechip{display:inline-flex;align-items:center;gap:6px;font-family:var(--disp);font-size:10px;
  letter-spacing:.12em;padding:3px 9px;border-radius:999px;border:1px solid rgba(65,185,220,.4);
  color:var(--accent);background:var(--accent-soft)}
.linkbtn{background:none;border:0;color:var(--ink-dim);font-size:11px;cursor:pointer;
  padding:0;text-decoration:underline}
.linkbtn:hover{color:var(--accent)}

.main{flex:1;min-width:0;display:flex;flex-direction:column}
.topbar{display:flex;align-items:center;gap:14px;padding:14px 26px;border-bottom:1px solid var(--line);
  background:linear-gradient(180deg,rgba(65,185,220,.05),transparent);flex-wrap:wrap}
.topbar h2{font-family:var(--disp);font-size:20px;font-weight:600;letter-spacing:.04em;margin:0}
/* The account name above the page title. Not uppercased: this holds a CUSTOMER's name,
   which is theirs and should read the way they write it — "MacKenzie Water", not
   "MACKENZIE WATER", and certainly not the tenant id it used to show. */
.crumb{font-size:11.5px;color:var(--ink-dim);letter-spacing:.06em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.crumb #crumbSite{color:var(--ink-mut)}
body.scoped .crumb{color:var(--accent)}
.where{min-width:0;flex:0 1 auto}
.topbar h2:focus{outline:none}
.topbar h2:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
/* The pages about the whole account (All sites, Users, Audit log, Admin, Help...) do not show ONE site's status. */
body.sitewide #statusPill,body.sitewide #stackLight{display:none}
/* The pill is a link to the Overview (its title says what it means; round-2 R04-26). */
a.statuspill{text-decoration:none;cursor:pointer}
a.statuspill:hover{filter:brightness(1.15)}
/* The header's "Nobody is told about alarms here" flag (app.js renderToldFlag). */
.toldflag{text-decoration:none;white-space:nowrap;margin-left:8px}
.toldflag[hidden],body.sitewide .toldflag{display:none}
/* The "<n> red alarms waiting" chip beside the status pill (app.js renderAlarmFlag; round-2 R03-21). */
.alarmflag{text-decoration:none;white-space:nowrap;margin-left:8px}
.alarmflag[hidden],body.sitewide .alarmflag{display:none}
/* Who you are, Help and Sign out (moved up from the foot of the menu, 7 Nov 2026). */
.userbox{display:flex;align-items:center;gap:8px;margin-left:6px;flex:0 0 auto}
.topbtn{display:inline-flex;align-items:center;background:none;border:1px solid var(--line);color:var(--ink-mut);
  border-radius:7px;padding:6px 11px;font-size:12px;cursor:pointer;text-decoration:none;font-family:inherit;letter-spacing:.03em}
.topbtn:hover{border-color:var(--accent);color:var(--accent)}
/* "Skip to the page": out of sight until a keyboard reaches it. */
.skip{position:fixed;left:10px;top:-80px;z-index:300;background:var(--panel);color:var(--accent);
  border:1px solid var(--accent);border-radius:8px;padding:10px 14px;font-size:13px;cursor:pointer}
.skip:focus,.skip:focus-visible{top:10px}
.content:focus{outline:none}
.statuspill{display:inline-flex;align-items:center;gap:8px;font-family:var(--disp);letter-spacing:.08em;
  padding:7px 14px;border-radius:999px;font-size:13px;border:1px solid}
.statuspill .dot{width:9px;height:9px;border-radius:50%;background:currentColor;box-shadow:0 0 8px currentColor}
.statuspill[hidden]{display:none}
.statuspill.ok{background:var(--good-soft);border-color:rgba(63,191,127,.4);color:var(--good)}
.statuspill.warning{background:var(--warn-soft);border-color:rgba(233,178,60,.4);color:var(--warn)}
.statuspill.fault{background:var(--fault-soft);border-color:rgba(224,86,86,.45);color:var(--fault)}
.statuspill.offline,.statuspill.unknown{background:var(--offline-soft);border-color:var(--line);color:var(--ink-mut)}
/* An EM300 running standalone (SD card): quiet by design, not offline - its own colour, not grey. */
.statuspill.standalone{background:var(--accent-soft);border-color:rgba(65,185,220,.4);color:var(--accent)}
.spacer{margin-left:auto}
/* The plant light: a picture of the lamp on site with its name beside it, not a button (round-2 R01-17). */
.stackbox{display:flex;align-items:center;gap:7px;cursor:default}
.stacklbl{font-family:var(--disp);font-size:9.5px;line-height:1.15;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-dim)}
.stack{display:flex;flex-direction:column;gap:3px;padding:5px;background:#0B1322;
  border:1px solid var(--line);border-radius:6px}
.stack i{width:22px;height:11px;border-radius:3px;background:#1A2436}
.stack i.r.lit{background:var(--fault);box-shadow:0 0 10px var(--fault)}
.stack i.y.lit{background:var(--warn);box-shadow:0 0 10px var(--warn)}
.stack i.g.lit{background:var(--good);box-shadow:0 0 10px var(--good)}

.content{padding:22px 26px 48px;display:flex;flex-direction:column;gap:18px}

/* ---------------------------------------------------------------- panels */
.panel{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:16px 18px;min-width:0}
.panel h3{font-family:var(--disp);font-size:12px;font-weight:600;letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink-mut);margin:0 0 14px;display:flex;align-items:center;gap:8px}
.panel h3 .right{margin-left:auto;font-size:11px;letter-spacing:.06em;color:var(--ink-dim);text-transform:none}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:18px}
.grid23{display:grid;grid-template-columns:2fr 1fr;gap:18px}
.grid2>*,.grid23>*,.kpis>*{min-width:0}
@media(max-width:1100px){.grid2,.grid23{grid-template-columns:1fr}}

.kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:14px}
.kpi{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:14px 16px}
.kpi .lbl{font-family:var(--disp);font-size:10.5px;letter-spacing:.15em;text-transform:uppercase;color:var(--ink-dim)}
.kpi .num{font-family:var(--disp);font-size:26px;font-weight:600;margin-top:6px;font-variant-numeric:tabular-nums}
.kpi .num small{font-size:13px;color:var(--ink-mut);font-weight:400;margin-left:3px}
.kpi .sub{font-size:11.5px;color:var(--ink-mut);margin-top:4px}
.kpi.ok{box-shadow:inset 3px 0 0 var(--good)}
.kpi.warning{box-shadow:inset 3px 0 0 var(--warn)}
.kpi.fault{box-shadow:inset 3px 0 0 var(--fault)}
.kpi.offline{box-shadow:inset 3px 0 0 var(--offline)}
.kpi.info{box-shadow:inset 3px 0 0 var(--accent)}
.suspect{color:var(--warn)}

/* channel table */
.chan{display:grid;grid-template-columns:auto 1fr auto auto;gap:6px 12px;align-items:center;font-size:12.5px}
.chan .tag{font-family:var(--disp);font-size:11px;color:var(--ink-dim);letter-spacing:.1em}
.chan .v{font-variant-numeric:tabular-nums;font-weight:600;text-align:right}
.chan .u{color:var(--ink-dim);font-size:11px}
.chan .qd{width:8px;height:8px;border-radius:50%;background:var(--good)}
.chan .qd.bad{background:var(--warn)}
.chan .qd.calc{background:var(--accent)}
.chan .qd.off{background:transparent;border:1px solid var(--ink-mut)}
/* No reading at all (C6): a grey ring, never the warning amber of a suspect reading (round-2 N01-004, R09-19). */
.chan .qd.nodata,.qd.nodata{background:transparent;border:1px dashed var(--ink-dim)}
.chan .v.off{color:var(--ink-mut);font-weight:400}

/* chips, alerts, tables, buttons */
.chip{font-family:var(--disp);font-size:10.5px;letter-spacing:.12em;padding:4px 10px;
  border-radius:999px;border:1px solid;white-space:nowrap}
.chip.ok{color:var(--good);border-color:rgba(63,191,127,.4);background:var(--good-soft)}
.chip.warning,.chip.yellow{color:var(--warn);border-color:rgba(233,178,60,.4);background:var(--warn-soft)}
.chip.fault,.chip.red{color:var(--fault);border-color:rgba(224,86,86,.4);background:var(--fault-soft)}
.chip.offline,.chip.info{color:var(--ink-mut);border-color:var(--line);background:var(--offline-soft)}
.chip.standalone{color:var(--accent);border-color:rgba(65,185,220,.4);background:var(--accent-soft)}
/* A chip in a table cell wraps instead of setting the width of its whole column (round-2 N09-055, N09-058: one
   "done 2026-11-08 by dev@example.test - results positive" crushed the Job column and pushed every Do it off a tablet,
   and stuck out of its card on a phone). */
td .chip{white-space:normal;overflow-wrap:anywhere;display:inline-block;max-width:16em;line-height:1.35}
.alert{display:flex;gap:12px;padding:12px 14px;border-radius:8px;border:1px solid;align-items:flex-start;margin-bottom:10px}
.alert.yellow{background:var(--warn-soft);border-color:rgba(233,178,60,.35)}
.alert.red{background:var(--fault-soft);border-color:rgba(224,86,86,.35)}
.alert.info{background:var(--offline-soft);border-color:var(--line)}
.alert .sev{font-family:var(--disp);font-size:10px;letter-spacing:.14em;padding:3px 8px;
  border-radius:4px;flex:0 0 auto;margin-top:2px}
.alert.yellow .sev{background:var(--warn);color:#1A1405}
.alert.red .sev{background:var(--fault);color:#fff}
.alert.info .sev{background:var(--offline);color:#fff}
.alert .at{min-width:0}
.alert .at b{font-size:13px}
.alert .at div{font-size:12px;color:var(--ink-mut);margin-top:2px}
.alert .when{margin-left:auto;font-size:11px;color:var(--ink-dim);white-space:nowrap;font-variant-numeric:tabular-nums}
.btn{background:none;border:1px solid var(--line);color:var(--ink-mut);border-radius:7px;
  padding:7px 13px;font-size:12px;cursor:pointer;letter-spacing:.03em}
.btn:hover:not(:disabled){border-color:var(--accent);color:var(--accent)}
.btn:disabled{opacity:.45;cursor:not-allowed}
.btn.pri{border-color:rgba(65,185,220,.5);color:var(--accent);background:var(--accent-soft)}
.btnrow{display:flex;gap:10px;flex-wrap:wrap;margin-top:12px}
/* A table wider than its box scrolls INSIDE it. Shadows at the edges show there is more (they appear only where the
   table runs on - the panel-coloured covers scroll with the table and hide them at each end), the first column stays
   in place while the rest scrolls, and js/ui.js markWideTables() adds a "Scroll sideways for more" line beneath. */
.twrap{overflow-x:auto;-webkit-overflow-scrolling:touch;
  background:
    linear-gradient(to right,var(--panel) 30%,rgba(16,27,45,0)) left center/40px 100% no-repeat local,
    linear-gradient(to left,var(--panel) 30%,rgba(16,27,45,0)) right center/40px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%,rgba(0,0,0,.6),rgba(0,0,0,0)) left center/14px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%,rgba(0,0,0,.6),rgba(0,0,0,0)) right center/14px 100% no-repeat scroll}
.twrap.more tr:not(.subrow)>:first-child{position:sticky;left:0;z-index:1;background-color:var(--panel)}
.twrap.more tr.editing>:first-child,.twrap.more tr.delrow>:first-child{background-color:var(--panel-2)}
.twrap.more tr.notsent>:first-child{background:linear-gradient(var(--fault-soft),var(--fault-soft)),var(--panel)}
.twrap.more tr.prog-row:hover>:first-child{background:linear-gradient(rgba(65,185,220,.06),rgba(65,185,220,.06)),var(--panel)}
.twraphint{font-size:11.5px;color:var(--ink-dim);text-align:right;margin:4px 2px 0}
.twrap.atend+.twraphint{visibility:hidden}
table{width:100%;border-collapse:collapse;font-size:12.5px;min-width:560px}
th{font-family:var(--disp);font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-dim);text-align:left;padding:8px 10px;border-bottom:1px solid var(--line)}
td{padding:9px 10px;border-bottom:1px solid var(--line-soft);font-variant-numeric:tabular-nums}
tr:last-child td{border-bottom:0}
.muted{color:var(--ink-mut)}
.dim{color:var(--ink-dim)}
/* Block, not flex: these carry prose with inline <b>/<i>, and flex would promote every
   inline child to a flex item — which silently reflows a sentence into columns. */
.empty{color:var(--ink-mut);font-size:12.5px;padding:10px 0;display:block}
.chart{width:100%;height:auto;display:block}
.legend{display:flex;gap:16px;font-size:11px;color:var(--ink-mut);margin-top:8px;flex-wrap:wrap}
.legend i{display:inline-block;width:10px;height:3px;border-radius:2px;margin-right:5px;vertical-align:middle}
.bar{height:9px;border-radius:5px;background:#0B1322;border:1px solid var(--line);overflow:hidden}
.bar i{display:block;height:100%;border-radius:5px;background:var(--accent)}
.bar i.warning{background:var(--warn)} .bar i.ok{background:var(--good)}
.life{margin-bottom:16px}
.life .lr{display:flex;justify-content:space-between;font-size:12.5px;margin-bottom:6px;gap:10px}
.life .foot{display:flex;justify-content:space-between;font-size:11px;color:var(--ink-dim);margin-top:5px}
.kv{display:flex;justify-content:space-between;border-bottom:1px solid var(--line-soft);
  padding:7px 0;font-size:12.5px;gap:12px}
.kv:last-child{border-bottom:0}
.kv b{font-variant-numeric:tabular-nums}
/* serial equipment rows (overview, Connections) */
.eqrow{padding:10px 0;border-bottom:1px solid var(--line-soft)}
.eqrow:last-child{border-bottom:0}
.eqhead{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-bottom:3px}
.eqplanned{color:var(--ink-dim);font-size:12px;margin-top:6px}
.banner{font-size:12px;border-radius:8px;padding:9px 13px;border:1px solid}
.banner.warn{color:var(--warn);background:var(--warn-soft);border-color:rgba(233,178,60,.3)}
.banner.err{color:var(--fault);background:var(--fault-soft);border-color:rgba(224,86,86,.35)}
.banner.info{color:var(--accent);background:var(--accent-soft);border-color:rgba(65,185,220,.4)}
.toast{position:fixed;right:18px;bottom:18px;z-index:200;display:flex;flex-direction:column;gap:8px}
.toast div{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--accent);
  border-radius:8px;padding:10px 14px;font-size:12.5px;box-shadow:0 10px 30px rgba(0,0,0,.5);max-width:340px}
.toast div.err{border-left-color:var(--fault)}
.toast div.ok{border-left-color:var(--good)}
.toast div.warn{border-left-color:var(--warn)}
/* "Show passwords" (Admin > Fleet): a modal over the page. Below the toasts (z-index 200) so
   "Copied." is seen; the password wraps anywhere, since a 20-character run has no spaces. */
.modal-back{position:fixed;inset:0;z-index:150;background:rgba(4,8,15,.7);display:flex;
  align-items:center;justify-content:center;padding:16px;overflow-y:auto}
.modal{width:100%;max-width:520px;max-height:calc(100vh - 32px);overflow-y:auto;
  box-shadow:0 24px 80px rgba(0,0,0,.55)}
.modal .btnrow{justify-content:flex-end}
.cred-row{border:1px solid var(--line);border-radius:8px;padding:10px 12px;margin:0 0 10px;background:var(--panel-2)}
.cred-what{font-weight:600;margin-bottom:2px}
.cred-pass{display:flex;align-items:center;gap:10px;margin-top:6px}
.cred-row code{font-family:Consolas,"Cascadia Mono","Courier New",monospace;color:var(--ink)}
.cred-pw{flex:1 1 auto;min-width:0;word-break:break-all;font-size:14px;padding:6px 8px;
  border-radius:6px;background:var(--bg);border:1px solid var(--line-soft);user-select:all}
.cred-pending{margin:4px 0 10px}
.cred-pending .banner{margin:0 0 10px}
.skel{background:linear-gradient(90deg,#12203A 25%,#17263F 50%,#12203A 75%);
  background-size:200% 100%;animation:sk 1.2s infinite;border-radius:6px;height:14px}
@keyframes sk{to{background-position:-200% 0}}

/* ---------------------------------------------------------------- site map topology */
/* The diagram has a minimum legible width, so on a phone it scrolls sideways inside
   its own container rather than squashing into unreadability — and the page body
   itself never scrolls horizontally. */
.topo{overflow-x:auto;-webkit-overflow-scrolling:touch;padding-bottom:4px}
.topo svg{min-width:820px;width:100%;height:auto;display:block}

/* ---- conduits. Three stacked strokes make a flat line read as round pipe: a dark
   casing, a coloured core, and the animated flow riding on top. */
.pipe .pcase{fill:none;stroke:#0A121D;stroke-width:7;stroke-linecap:round}
.pipe .pcore{fill:none;stroke:#33445C;stroke-width:3.4;stroke-linecap:round}
.pipe .pflow{fill:none;stroke:none;stroke-width:2;stroke-linecap:round}
.pipe.ok .pcore{stroke:var(--good)}
.pipe.wn .pcore{stroke:var(--warn)}
.pipe.fl .pcore{stroke:var(--fault);stroke-width:4.2}
.pipe.off .pcore{stroke:#2E3F55}
.pipe.ok .pflow,.pipe.wn .pflow{stroke:rgba(255,255,255,.75);stroke-dasharray:5 13;
  animation:tflow 1.15s linear infinite}
@keyframes tflow{to{stroke-dashoffset:-36}}

/* ---- device cards. One light source, top-left, for every face in the diagram. */
.card .cface{fill:url(#faceG);stroke:#2B3E58;stroke-width:1}
.card .cedge{fill:#0B1420}
.card .cspec{stroke:rgba(255,255,255,.14);stroke-width:1.4;fill:none;stroke-linecap:round}
.hub .hface{fill:url(#hubG);stroke:#3C6094;stroke-width:1.4}
.hub .hedge{fill:#08111C}
.hub .vent{fill:#0C1726;opacity:.85}
.card.fl .cface{stroke:rgba(224,86,86,.55)}
.card.wn .cface{stroke:rgba(233,178,60,.45)}

.led.ok{fill:var(--good)} .led.wn{fill:var(--warn)}
.led.fl{fill:var(--fault)} .led.off{fill:#38495F}
.tt1{fill:var(--ink);font-family:var(--disp);font-size:12.5px;letter-spacing:.04em;font-weight:600}
.tt2{fill:var(--accent);font-size:11px;font-variant-numeric:tabular-nums}
.tt3{fill:var(--ink-dim);font-size:8.5px;letter-spacing:.1em}
.maplegend svg{vertical-align:middle}

/* ---------------------------------------------------------------- operational views */
/* Small button for in-table actions (classify, end session). Table rows are dense, so
   the full-size .btn would force the row height up on every line. */
.btn.sm{padding:4px 9px;font-size:11px;border-radius:6px}
.btnrow.tight{margin-top:0;gap:5px}
/* A second, denser KPI band for panels that carry their own summary strip. */
.kpis.tight{grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:10px;margin-bottom:14px}
.kpis.tight .kpi{padding:10px 12px}
.kpis.tight .kpi .num{font-size:20px;margin-top:4px}

/* Form controls on the support tab. */
.formrow{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:10px}
.formrow label,label.wide{display:flex;flex-direction:column;gap:5px;font-size:11px;
  letter-spacing:.1em;text-transform:uppercase;color:var(--ink-dim);
  font-family:var(--disp);min-width:0}
.formrow label{flex:1 1 180px}
label.wide{width:100%}
/* Inputs sitting in a .formrow are skinned by the same rule as the selects. Without the
   input selector a text field inside a form row falls back to the browser default — a
   white box on a dark panel — which is how the Add-a-user and Create-site fields read. */
.formrow select,.formrow input,label.wide input,label.wide select{background:var(--panel-2);
  border:1px solid var(--line);border-radius:7px;color:var(--ink);padding:8px 10px;
  font-size:13px;font-family:inherit;letter-spacing:normal;text-transform:none;width:100%}
.formrow select:focus,.formrow input:focus,label.wide input:focus{
  outline:2px solid var(--accent);outline-offset:-1px}

/* Per-user role picker in the Users table. Same skin as the Add-a-user form so the page
   reads as one set of controls, but sized to sit inside a table cell. A disabled picker
   (your own row, or somebody who outranks you) is dimmed rather than hidden — the role is
   still worth reading. */
.rolesel{background:var(--panel-2);border:1px solid var(--line);border-radius:7px;
  color:var(--ink);padding:5px 8px;font-size:12px;font-family:inherit;max-width:100%}
.rolesel:disabled{opacity:.55;cursor:not-allowed}

/* ---------------------------------------------------------------- provisioning rows */
/* Row actions (Edit · Archive · Delete…) wrap rather than force the table wider — the
   provisioning tables are already at their min-width on a phone. */
.rowacts{white-space:nowrap}
.rowacts>*{margin-right:10px}
.rowacts>*:last-child{margin-right:0}
/* An archived record is still readable — it is the thing you came to restore or delete —
   but it must never be mistaken for a live one at a glance. */
tr.archived td{opacity:.5}
tr.archived td .chip{opacity:1}
/* Call-outs: a text or call that was NOT sent is the line that matters in the log, so the whole
   row carries the fault tint; a step of the escalation list is a block of its own. */
tr.notsent td{background:var(--fault-soft)}
.costep{border-top:1px solid var(--line-soft);padding-top:10px;margin-top:10px}
/* A person's per-level channel grid: level, three tick boxes, what the level does. */
.changrid{display:grid;grid-template-columns:auto repeat(3,auto) 1fr;gap:2px 14px;align-items:center;font-size:12px}
/* A row being edited, and the confirmation panel beneath a row facing deletion, are both
   lifted off the table so it is obvious which record they belong to. `.subrow` is the same
   idea for a full-width panel beneath a row — an alarm recipient's editor or its removal
   confirmation — and deliberately does NOT skin its inputs the way `tr.editing` does, because
   it carries a normal `.formrow` and tick boxes rather than cell-sized fields. */
tr.editing td,tr.delrow td,tr.subrow td{background:var(--panel-2)}
tr.editing input,tr.editing select{background:var(--panel);border:1px solid var(--line);
  border-radius:6px;color:var(--ink);padding:5px 8px;font-size:12.5px;font-family:inherit;
  width:100%;max-width:100%}
tr.editing input:focus,tr.editing select:focus{outline:2px solid var(--accent);outline-offset:-1px}
/* The box a refused value came from (a site's fixed internet address or LAN address): marked
   until the next Save or a change of the Automatic / Fixed choice. */
tr.editing input[aria-invalid="true"]{border-color:var(--fault);outline:2px solid var(--fault);outline-offset:-1px}

/* Tick-box pickers (alarm recipients, notification lists, My alarms: sites, alarm types,
   levels, members). Each box is a bordered chip with a thumb-sized target, and the row wraps
   rather than widening anything - on a phone a long site list becomes more rows, never a
   sideways scroll. Select all / Clear get padding so they are tappable too. */
.picker{margin:0 0 12px;min-width:0}
.pickhead{display:flex;align-items:center;gap:2px 16px;flex-wrap:wrap;margin-bottom:6px}
.pickname{font-family:var(--disp);font-size:11px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink-dim)}
.pickhead .linkbtn{font-size:12px;padding:7px 0}
.pickboxes{display:flex;flex-wrap:wrap;gap:6px 8px}
.pickboxes label,.picktoggle{display:inline-flex;align-items:center;gap:8px;min-height:36px;
  padding:6px 11px;border:1px solid var(--line);border-radius:7px;font-size:12.5px;
  color:var(--ink);cursor:pointer;max-width:100%;overflow-wrap:anywhere}
.pickboxes input,.picktoggle input{width:16px;height:16px;margin:0;flex:0 0 auto;
  accent-color:var(--accent)}
.pickhint{margin:6px 0 0;font-size:12px}
/* The editor and the removal confirmation beneath a recipient or a list span the whole
   table, which on a phone is wider than the screen. Their contents are pinned to the visible
   part of the scrolling box instead, so the fields and tick boxes wrap to the screen and stay
   in view however far the table has been scrolled sideways. Scoped to these two tables:
   inline-size containment stops a box sizing to its content, which other tables may rely on. */
.subwrap{container-type:inline-size}
.subwrap tr.subrow td>*{position:sticky;left:10px;max-width:calc(100cqw - 20px);
  box-sizing:border-box}

/* ---------------------------------------------------------------- customer scope */
/* Two states mean "you are not in your own account", and they must never be mistaken for
   each other, because the difference is whether the Save button writes to a customer.
   Everything here is chosen to read as the OPPOSITE of the impersonation bar below it:

     scoped         accent cyan · solid square leading stripe · steady, no animation
                    · "Working in" · writes allowed · does not expire
     impersonating  warning amber · round pulsing dot · "Viewing as"
                    · read-only · counts down

   Colour alone would not be enough (it is the one channel a colour-blind operator may not
   have), so the shape, the wording and the motion all differ too. */
.scopebar[hidden]{display:none}
.scopebar{display:flex;align-items:center;gap:12px;padding:9px 22px 9px 18px;
  background:var(--accent-soft);border-bottom:1px solid rgba(65,185,220,.35);
  border-left:4px solid var(--accent);flex-wrap:wrap}
.scopebar .scopemark{width:9px;height:9px;background:var(--accent);flex:0 0 9px;border-radius:2px}
.scopebar .scopetext{min-width:0;font-size:12.5px;display:flex;gap:10px;flex-wrap:wrap;align-items:baseline}
.scopebar .scopetext b{color:var(--accent);letter-spacing:.02em}
.scopebar .scopetext .dim{font-size:11.5px}
.scopebar .btn{margin-left:auto;border-color:rgba(65,185,220,.5);color:var(--accent);flex:0 0 auto}
.scopebar .btn:hover:not(:disabled){border-color:var(--accent);background:var(--accent-soft)}
/* The rail's customer block is the control that got you here and the way back out, so it
   carries the state too — visible even when the bar has scrolled off the top. Again a
   different shape from the impersonation treatment: a left edge on one block, not a ring
   around the whole window. */
.unit.customer{border-left:3px solid transparent}
body.scoped .unit.customer{border-left-color:var(--accent);background:rgba(65,185,220,.06)}
body.scoped .unit.customer .name{color:var(--accent)}

/* ---------------------------------------------------------------- impersonation bar */
/* Loud on purpose. The one way "view as customer" causes real harm is somebody
   forgetting they are in it and reading a customer's numbers as their own, so this sits
   above the content at every breakpoint and the way out never scrolls off screen. */
/* A class with `display` beats the [hidden] attribute, so the impersonation banner rendered
   on EVERY page — permanently claiming you were viewing as a customer when you were not.
   The banner's whole purpose is telling you when you are in someone else's account, so one
   that is always on is worse than none. */
.impbar[hidden]{display:none}
.impbar{display:flex;align-items:center;gap:12px;padding:9px 22px;
  background:linear-gradient(90deg,rgba(233,178,60,.18),rgba(233,178,60,.06));
  border-bottom:1px solid rgba(233,178,60,.45);flex-wrap:wrap}
.impbar .impdot{width:9px;height:9px;border-radius:50%;background:var(--warn);flex:0 0 9px;
  box-shadow:0 0 0 4px rgba(233,178,60,.18);animation:imppulse 1.8s ease-in-out infinite}
@keyframes imppulse{50%{box-shadow:0 0 0 8px rgba(233,178,60,.05)}}
.impbar .imptext{min-width:0;font-size:12.5px;display:flex;gap:10px;flex-wrap:wrap;align-items:baseline}
.impbar .imptext b{color:var(--warn)}
.impbar .imptext .dim{font-size:11.5px;font-variant-numeric:tabular-nums}
.impbar .btn{margin-left:auto;border-color:rgba(233,178,60,.5);color:var(--warn);flex:0 0 auto}
.impbar .btn:hover:not(:disabled){border-color:var(--warn);background:var(--warn-soft)}
/* A second, quieter reminder for anyone who has scrolled the banner off the top: the whole
   window is edged in the same warning colour for as long as the customer view is running.
   Set on <body> by applyRoleVisibility(). */
body.impersonating{box-shadow:inset 0 0 0 2px rgba(233,178,60,.55)}

/* ---------------------------------------------------------------- act as customer */
/* The third "not your own account" state (docs/ACT-AS-CUSTOMER-DESIGN.md), told apart from both others by colour,
   shape and words: violet (not the view's amber or the scope's cyan), a square marker with a thick left edge, and
   "Acting as <user> ... Stop acting". Changes made here DO land in the customer's account, under the admin's name,
   which is why it must never be mistaken for the read-only view. The window edge is dashed rather than solid. */
:root{--act:#B79CFF; --act-soft:rgba(183,156,255,.14)}
.impbar.act{background:linear-gradient(90deg,rgba(183,156,255,.22),rgba(183,156,255,.07));
  border-bottom:1px solid rgba(183,156,255,.5);border-left:5px solid var(--act)}
.impbar.act .impdot{border-radius:2px;background:var(--act);box-shadow:none;animation:none}
.impbar.act .imptext b{color:var(--act)}
.impbar.act .btn{border-color:rgba(183,156,255,.6);color:var(--act)}
.impbar.act .btn:hover:not(:disabled){border-color:var(--act);background:var(--act-soft)}
body.acting{outline:2px dashed rgba(183,156,255,.6);outline-offset:-2px}
/* A control that stays with the customer in an act session (ui.js markCustomerOnly): greyed, never hidden. */
[data-customer-only]:disabled{cursor:not-allowed}

/* ---------------------------------------------------------------- feature settings */
/* One row per optional module (js/features.js): name and hint on the left, chips and the
   switch on the right. The switch is a button with role="switch", so it is announced as one. */
.featrows{display:grid;gap:8px}
.featrow{display:grid;grid-template-columns:1fr auto;gap:10px;align-items:center;padding:10px 12px;
  border-radius:8px;background:var(--panel-2);border:1px solid var(--line-soft)}
.featrow.changed{border-color:rgba(65,185,220,.45)}
.featrow .t{font-weight:600}
.featrow .d{color:var(--ink-mut);font-size:12.5px;margin-top:2px}
.featright{display:flex;align-items:center;gap:8px;justify-content:flex-end;flex-wrap:wrap}
.chip.dashed{border-style:dashed}
.sw{position:relative;width:42px;height:24px;border-radius:999px;background:#22314a;border:1px solid var(--line);
  cursor:pointer;flex:none;padding:0}
.sw::after{content:"";position:absolute;top:2px;left:2px;width:18px;height:18px;border-radius:50%;
  background:var(--ink-dim);transition:transform .15s}
.sw[aria-checked="true"]{background:rgba(63,191,127,.35);border-color:var(--good)}
.sw[aria-checked="true"]::after{transform:translateX(18px);background:var(--good)}
.sw:disabled{opacity:.45;cursor:not-allowed}
/* The menu a site's users will see: hidden items struck through, never removed, so the owner
   sees what switching a module off takes away. */
.menuprev{display:grid;gap:2px}
.menuprev span{display:flex;justify-content:space-between;gap:8px;padding:7px 10px;border-radius:6px;font-size:13px}
.menuprev span.hide{color:var(--ink-dim);text-decoration:line-through;opacity:.6}
.menuprev small{font-size:11px;color:var(--ink-dim);text-decoration:none}
@media (prefers-reduced-motion:reduce){.sw::after{transition:none}}
@media(max-width:620px){.featrow{grid-template-columns:1fr}.featright{justify-content:flex-start}}

/* ---------------------------------------------------------------- responsive */
.menubtn{display:none;background:var(--panel-2);border:1px solid var(--line);border-radius:8px;
  color:var(--ink);width:38px;height:38px;align-items:center;justify-content:center;cursor:pointer;flex:0 0 38px}
.menubtn svg{width:20px;height:20px}
.scrim{display:none;position:fixed;inset:0;background:rgba(4,8,15,.6);z-index:40}
.scrim.show{display:block}
.railclose{display:none}
@media(max-width:920px){
  /* The closed menu is out of sight AND out of the keyboard's way (visibility, after the slide; app.js also sets
     `inert`): before 7 Nov 2026 Tab walked thirty invisible stops in it before reaching the page. */
  .rail{position:fixed;left:-248px;top:0;transition:left .22s ease,visibility 0s linear .22s;z-index:50;visibility:hidden}
  .rail.open{left:0;box-shadow:24px 0 60px rgba(0,0,0,.5);visibility:visible;transition:left .22s ease}
  .railclose{display:flex;align-items:center;justify-content:center;position:absolute;top:10px;right:10px;width:44px;height:44px;
    background:var(--panel);border:1px solid var(--line);border-radius:8px;color:var(--ink);cursor:pointer}
  .railclose svg{width:20px;height:20px}
  .menubtn{display:flex}
  .topbar{padding:12px 14px}
  .content{padding:16px 14px 40px}
  .grid2,.grid23{grid-template-columns:1fr}
  .impbar{padding:9px 14px}
  .scopebar{padding:9px 14px 9px 10px}
}
@media(max-width:620px){
  .topbar h2{font-size:17px}
  .kpis{grid-template-columns:repeat(2,minmax(0,1fr))}
  .kpi .num{font-size:21px}
  .kpi .sub{font-size:10.5px}
  .statuspill{font-size:11px;padding:6px 10px}
  .toldflag,.alarmflag{white-space:normal;max-width:11em;line-height:1.25;margin-left:0}
  .content{gap:14px}
  .panel{padding:13px 13px}
  .panel h3{flex-wrap:wrap}
  .panel h3 .right{margin-left:0;flex-basis:100%}
  /* Key/value rows stack rather than crushing a long value against its label. */
  .kv{flex-direction:column;gap:2px}
  .kv b{text-align:left}
  .btnrow{gap:8px}
  .btnrow .btn{flex:1 1 auto;min-width:0}
  .chan{grid-template-columns:auto 1fr auto auto;font-size:12px;gap:5px 8px}
  .alert{flex-wrap:wrap}
  .alert .when{margin-left:0;flex-basis:100%;margin-top:4px}
  .toast{left:12px;right:12px;bottom:12px}
  .toast div{max-width:none}
  /* The exit button drops to its own full-width row rather than being squeezed to a
     few pixels next to the session detail. */
  .impbar .btn{margin-left:0;flex:1 1 100%}
  .impbar .imptext{flex-direction:column;gap:1px}
  .scopebar .btn{margin-left:0;flex:1 1 100%}
  .scopebar .scopetext{flex-direction:column;gap:1px}
  .kpis.tight{grid-template-columns:repeat(2,minmax(0,1fr))}
  .formrow label{flex:1 1 100%}
  /* In-table buttons are thumbed on a phone, not clicked. 26px tall is under the
     comfortable touch target, so give them height here without loosening the desktop
     row rhythm. They do not stretch: .btnrow.tight opts out of the flex:1 rule above. */
  .btn.sm{padding:9px 11px;font-size:11.5px}
  .btnrow.tight .btn{flex:0 0 auto}
  /* A finger, not a mouse pointer: every button, menu entry and button-like link is at least 44 px tall. */
  .btn,.btn.sm,.topbtn,.linkbtn,.nav a,.menubtn,a.chip,button.chip{min-height:44px}
  .btn,a.btn,.linkbtn,a.chip,button.chip{display:inline-flex;align-items:center;justify-content:center}
  .menubtn{width:44px;height:44px;flex:0 0 44px}
  .pickboxes label,.picktoggle{min-height:44px}
  /* 16 px in every field: iPhones zoom the whole page into any field with smaller text. */
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(#linkCodeInput),select,textarea{font-size:16px !important;min-height:44px}
  /* The top bar: where you are on the first line, who you are on the next. The plant light stays on Plant view. */
  .crumb #crumbCustomer,.crumb .crumbsep{display:none}
  .stackbox{display:none}
  .userbox{order:5;flex:1 1 100%;justify-content:flex-end;margin-left:0}
}
/* Tables that become cards on a phone: a page builder adds class="cards" to the table and data-label="Due" (the
   column's name) to each cell; under 700 px each row is a card with the labels above the values, so nothing is
   squeezed to a letter a line or pushed off the edge. */
@media(max-width:920px){
  table.cards{min-width:0}
  table.cards,table.cards tbody,table.cards tr,table.cards td{display:block;width:100%}
  table.cards thead,table.cards tr:has(>th){display:none}
  table.cards tr{border:1px solid var(--line);border-radius:8px;padding:8px 12px;margin:0 0 10px;background:var(--panel-2)}
  table.cards td{border:0;padding:5px 0;position:static !important;background:none !important}
  table.cards td[data-label]::before{content:attr(data-label);display:block;font-family:var(--disp);font-size:10.5px;
    letter-spacing:.12em;text-transform:uppercase;color:var(--ink-dim);margin-bottom:2px}
  table.cards td:empty{display:none}
  .twrap:has(>table.cards){overflow:visible;background:none}
  .twrap:has(>table.cards)+.twraphint{display:none}
  table.cards td{overflow-wrap:anywhere}
  table.cards td .chip{max-width:100%}
  /* The paper sheet's grid (#/backfill): every box as wide as its card (verify-aio-ab item 9). */
  table.cards.bf-grid td input,table.cards.bf-grid td select{width:100%;max-width:100%;min-width:0;box-sizing:border-box}
}
/* A tablet has room for the label BESIDE its value, so a card is half as tall. */
@media(min-width:621px) and (max-width:920px){
  table.cards td[data-label]:not([data-label=""]){position:relative !important;padding-left:10.5em}
  table.cards td[data-label]:not([data-label=""])::before{position:absolute;left:0;top:6px;width:9.5em;margin:0}
  /* Lab samples: each result beside its own unit, two to a row - never "Total coliforms unit" under E. coli
     (round-2 R09-08). */
  .formrow:has(>label>[data-lab-field="ecoliUnit"]){display:grid;grid-template-columns:1fr 1fr}
}
/* Tablets are touch screens too (round-2 R09-14: 25-33 px buttons at 768 px): 44 px targets up to 920 px wide and on
   any touch screen, the getting-started links and the menu foot links included. */
@media(max-width:920px),(pointer:coarse){
  .btn,.btn.sm,.topbtn,.linkbtn,.nav a,.menubtn,a.chip,button.chip{min-height:44px}
  .btn,a.btn,.linkbtn,a.chip,button.chip{display:inline-flex;align-items:center;justify-content:center}
  .btn.sm{padding:9px 11px}
  .btnrow.tight .btn{flex:0 0 auto}
  .tstart a,.railfoot a.footlink{display:inline-flex;align-items:center;min-height:44px}
  .pickboxes label,.picktoggle{min-height:44px}
}
/* A short laptop screen (1366 x 768): the menu is tighter, so more of it fits without scrolling (round-2 R04-18). */
@media(min-width:921px) and (max-height:900px){
  .rail{padding-top:12px}
  .brand{padding-bottom:10px}
  .brand .tag{display:none}
  .unit{padding:9px 20px}
  .unit select{margin-top:5px}
  .nav{padding-top:2px}
  .navgroup{margin-top:3px;gap:0}
  .nav a{padding:5px 12px}
  .navhead{padding:3px 12px 1px}
}
/* Very narrow phones: one KPI per row beats two unreadable ones. */
@media(max-width:400px){
  .kpis{grid-template-columns:1fr}
  .kpis.tight{grid-template-columns:1fr}
}
@media (prefers-reduced-motion:reduce){
  .skel{animation:none}
  /* Pipes keep their colour, which is what carries the meaning — only the flow stops. */
  .pipe .pflow{animation:none}
  /* The bar stays visually distinct without the pulse — colour and border carry it. */
  .impbar .impdot{animation:none}
}

/* Sites overview: a cell opens its site on a double-click (programme.js data-prog-go) */
tr.prog-row td[data-prog-go] { cursor: pointer; user-select: none; }
tr.prog-row:hover td { background: rgba(65, 185, 220, .06); }

/* ---------------------------------------------------------------- Today (js/today.js) */
.trow{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:9px 0;border-bottom:1px solid var(--line-soft)}
.trow:last-of-type{border-bottom:0}
.trow .tmain{min-width:0}
.trow .tmain b{font-size:13px}
.trow .tmain .dim{font-size:12px;margin-top:2px}
.trow .tside{display:flex;align-items:center;gap:8px;flex:0 0 auto;flex-wrap:wrap;justify-content:flex-end}
.trow .btn{white-space:nowrap}
.tstart{list-style:none;margin:0;padding:0;display:grid;gap:10px}
.tstart li{display:flex;gap:10px;align-items:flex-start}
.tstart .tick{width:20px;height:20px;flex:0 0 20px;border-radius:50%;border:1px solid var(--line);display:inline-flex;
  align-items:center;justify-content:center;font-size:12px;color:var(--good);margin-top:1px}
.tstart li.done .tick{border-color:var(--good);background:var(--good-soft)}
.tstart li.done a{color:var(--ink-mut)}
.tstart a{color:var(--accent)}
@media(max-width:620px){.trow{flex-direction:column;align-items:stretch}.trow .tside{justify-content:flex-start}}

/* ---------------------------------------------------------------- a page you cannot open, or no page at all */
.notice h3{color:var(--ink)}

/* ---------------------------------------------------------------- links, folds and a switch to find (review hooks) */
/* Bare links in a panel are readable on the dark page (HOOKS-WP4 11). */
.panel a:not(.btn){color:var(--accent)}
/* The "Why?" / "Which rules?" / "More details" folds. */
details>summary{cursor:pointer;color:var(--ink-dim)}
details>summary:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* The row a "not switched on" page sends the owner to, on Setup > Features (HOOKS-WP7 12). */
.featrow.focus{outline:2px solid var(--accent);outline-offset:2px;border-radius:6px}
/* The Setup editor's inputs: each label above its box, rows that wrap and never push the page wider than the screen
   (console review, 7 Nov 2026: "the editor layout is broken, worst on a phone"; HOOKS-WP7 9). */
.cfgch{border:1px solid var(--line);border-radius:8px;padding:10px 12px;margin:0 0 10px;min-width:0}
.cfghead{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:0 0 8px}
.cfghead .btn{margin-left:auto}
.cfggrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:8px 12px;margin:0 0 6px}
.cfgf{display:flex;flex-direction:column;gap:3px;font-size:12px;min-width:0}
.cfgf input:not([type=checkbox]),.cfgf select{width:100%;min-width:0;box-sizing:border-box}
@media(max-width:620px){.cfggrid{grid-template-columns:1fr 1fr}}
