:root{--bg:#0f1115;--card:#1a1d24;--ink:#e8eaed;--mut:#9aa0aa;--ok:#2fbf5b;--bad:#e3493f;--warn:#f5a623;--accent:#c64600;--accent-br:#ff7a33;--arm:#f5a623;--bar:#13161c;
  /* Universal kanban metric: one column min-width + one gap drive every tab's
     box layout (the tabview card grid, the in-card item grids and the kanban
     columns) so every tab reads as the same even-width, evenly-spaced board. */
  --kcol:300px;--kgap:12px;
  /* themeable surfaces/borders (dark values = the originals, so dark is unchanged) */
  --ins:#11141a;--ins2:#0e1116;--field:#0c0f14;--line:#23272f;--line2:#2a2f3a;--bd-med:#333;--bd-strong:#3a4150;--bd-soft:#20242c}
/* Light mode - same accent, light surfaces. Toggle in the header (persisted). */
body.light{--bg:#eef1f5;--card:#ffffff;--bar:#f7f8fa;--ink:#1a1d24;--mut:#5b626d;
  --ins:#eef1f5;--ins2:#f3f5f8;--field:#f6f8fa;--line:#e2e6ec;--line2:#d6dbe3;--bd-med:#c8cfd9;--bd-strong:#bcc4d1;--bd-soft:#e8ebf0}
body.light .tab .ti{filter:grayscale(.2)}
body.light #map{background:#dde3ea}
/* --- extra colour themes (cloud only) — each overrides the CSS variables.
   Dark-based: oled, farm, midnight, slate (component tweaks default to dark).
   Light-based: sunny also carries `.light` (set in JS) for the light tweaks. */
body.theme-oled{--bg:#000000;--card:#0a0a0c;--bar:#000000;--ink:#e9ebee;--mut:#8a8f98;
  --ins:#070708;--ins2:#000000;--field:#050506;--line:#191920;--line2:#23232b;--bd-med:#2a2a32;--bd-strong:#3a3a44;--bd-soft:#121218}
body.theme-oled #map,body.theme-oled #gps-map{background:#000}
body.theme-farm{--bg:#14180f;--card:#1e241a;--bar:#171c11;--ink:#eef0e6;--mut:#a6b094;
  --ok:#7bbf3a;--bad:#cc5a3a;--warn:#d8a93a;--accent:#5a8f2a;--accent-br:#7bbf3a;--arm:#c8a24a;
  --ins:#161a10;--ins2:#10130b;--field:#0e120a;--line:#2a3320;--line2:#34402a;--bd-med:#3c4a2e;--bd-strong:#4a5c38;--bd-soft:#222a18}
body.theme-farm #map,body.theme-farm #gps-map{background:#1a2113}
body.theme-midnight{--bg:#0a0e1a;--card:#141a2e;--bar:#0c1120;--ink:#e6ebf7;--mut:#8e98b5;
  --ok:#3fbf8f;--accent:#3a6df0;--accent-br:#5a8aff;--arm:#f5c84a;
  --ins:#0e1322;--ins2:#080c16;--field:#0a0e1c;--line:#1e2740;--line2:#28324f;--bd-med:#32406a;--bd-strong:#42528a;--bd-soft:#161d30}
body.theme-midnight #map,body.theme-midnight #gps-map{background:#0c1426}
body.theme-slate{--bg:#1b1e24;--card:#252a32;--bar:#1f242b;--ink:#e4e8ee;--mut:#9aa3b2;
  --accent:#4a8db5;--accent-br:#6aadd5;
  --ins:#21262d;--ins2:#1a1e24;--field:#1c2128;--line:#313842;--line2:#3c4450;--bd-med:#48515e;--bd-strong:#5a6573;--bd-soft:#272d35}
body.theme-slate #map,body.theme-slate #gps-map{background:#2a313b}
/* Sunny — warm, bright (light-based: `.light` is also set). */
body.theme-sunny{--bg:#fbf6ec;--card:#fffdf8;--bar:#fdf3e2;--ink:#2b2417;--mut:#7a6f56;
  --ok:#3a9a3a;--bad:#d6452f;--warn:#e0a020;--accent:#e2670a;--accent-br:#ff8a2a;--arm:#e0a020;
  --ins:#f6efe0;--ins2:#fbf6ec;--field:#fffaf0;--line:#ecdfc8;--line2:#e0d0b2;--bd-med:#d8c4a0;--bd-strong:#c8b088;--bd-soft:#f0e6d4}
body.theme-sunny #map,body.theme-sunny #gps-map{background:#efe2c8}
/* The theme picker reads as a compact control in the top bar. */
.theme-sel{padding:4px 6px;cursor:pointer;border:1px solid var(--line2);border-radius:8px;background:var(--card);color:var(--ink);font-size:.8rem}
*{box-sizing:border-box}
html,body{margin:0;height:100%;background:var(--bg);color:var(--ink);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Ubuntu,sans-serif;-webkit-text-size-adjust:100%}
body{overflow:hidden}
.hidden{display:none !important}

/* App shell - fixed header + scrolling content + bottom tab bar (native feel).
   No width cap: the content fills the viewport at every size (a half-screen
   laptop window or a tablet no longer shows a narrow centred strip with dead
   margins). The sidebar app layout takes over from 760px up; below that the
   single-column phone layout simply fills the width it's given. */
#app{position:fixed;inset:0;max-width:none;margin:0;display:flex;flex-direction:column;background:var(--bg)}
#bar{flex:0 0 auto;display:flex;align-items:center;gap:8px;padding:12px 14px;background:var(--bar);border-bottom:1px solid var(--line)}
#bar .grow{flex:1}
#title{font-weight:800;font-size:1.15rem;letter-spacing:-.01em}
#title .g{color:var(--accent-br)}
.farmname{color:var(--mut);font-weight:600;font-size:.92rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:34vw}
.hdr-meta{color:var(--mut);font-size:.76rem;font-family:ui-monospace,Menlo,Consolas,monospace;font-variant-numeric:tabular-nums;white-space:nowrap}
#hdr-clock{color:var(--ink)}
.icon-btn{background:transparent;border:1px solid var(--line2);color:var(--mut);font-size:.9rem;line-height:1;padding:4px 8px;border-radius:8px;cursor:pointer}
.icon-btn:hover{color:var(--ink);border-color:var(--bd-strong)}
@media(max-width:600px){#hdr-ip{display:none}}
@media(max-width:430px){.farmname{display:none}}
.chip{font-size:.72rem;background:var(--ins);border:1px solid var(--line2);color:var(--mut);padding:4px 9px;border-radius:99px;white-space:nowrap;max-width:42vw;overflow:hidden;text-overflow:ellipsis}
.chip.ver{background:transparent;font-variant-numeric:tabular-nums}
@media (max-width:560px){.chip.ver{display:none}}
.pill{font-size:.66rem;font-weight:700;letter-spacing:.04em;padding:3px 8px;border-radius:99px;background:var(--bd-med);color:#bbb}
.pill.on{background:var(--ok);color:#06210f}.pill.off{background:var(--bad);color:#2a0a08}

/* the scrolling content region - exactly one .tabview is visible at a time */
.tabview{flex:1 1 auto;overflow-y:auto;-webkit-overflow-scrolling:touch;padding:12px;display:flex;flex-direction:column;gap:10px}

/* bottom tab bar */
#tabs{flex:0 0 auto;display:flex;justify-content:space-around;gap:2px;background:var(--bar);border-top:1px solid var(--line);padding:6px 4px;padding-bottom:max(6px,env(safe-area-inset-bottom))}
.tab{flex:1;display:flex;flex-direction:column;align-items:center;gap:2px;padding:6px 2px;border:0;border-radius:10px;background:transparent;color:var(--mut);font-weight:600;font-size:.66rem;cursor:pointer}
.tab .ti{font-size:1.15rem;line-height:1.1;filter:grayscale(.4) opacity(.8)}
.tab.on{color:var(--accent-br);background:var(--ins)}
.tab.on .ti{filter:none}
/* (The old `.tab.sheet-open` state was folded into `.on` - exactly one nav button
   highlights at a time now; an open group IS the active indicator.) */

/* Mobile bottom bar: Dash is the centred, slightly larger "home" key - sits in
   the middle of the row so it's the easy right-thumb target. */
@media (max-width:759px){
  .tab[data-tab="dash"]{flex:1.3;color:var(--ink)}
  .tab[data-tab="dash"] .ti{font-size:1.5rem;filter:none}
  .tab[data-tab="dash"].on{color:var(--accent-br)}
}

.card{background:var(--card);border-radius:14px;padding:12px}
.card h3{margin:0 0 10px;font-size:.82rem;color:var(--mut);text-transform:uppercase;letter-spacing:.06em;display:flex;align-items:center;justify-content:space-between;gap:8px}
.card h4{margin:14px 0 6px;font-size:.8rem;color:var(--ink)}
.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
.cell{background:var(--ins);border-radius:10px;padding:10px;text-align:center}
.cell label{display:block;font-size:.6rem;color:var(--mut);text-transform:uppercase}
.cell b{font-size:1.1rem}
#map{height:40vh;min-height:220px;border-radius:12px;overflow:hidden;background:#202530;margin-bottom:8px}
.set{display:flex;align-items:center;gap:8px;margin:10px 0}
.set label{flex:1;font-size:.9rem}
.set input,.set select{width:160px;padding:8px;border-radius:8px;border:1px solid var(--bd-med);background:var(--ins);color:var(--ink)}
button{padding:9px 13px;border:0;border-radius:8px;background:var(--accent);color:#fff;font-weight:600;cursor:pointer}
button.mini{padding:4px 9px;font-size:.72rem}
button.ghost{background:var(--line2)}
button.danger{background:var(--bad)}
button.on-edit{background:var(--accent);color:#fff}
#pu-autotune{width:100%;margin-top:8px;background:#6b4ea0}
.row{display:flex;gap:8px;margin-top:8px}
.muted{color:var(--mut);font-size:.78rem;margin:6px 0 0}
.muted code{background:var(--ins);padding:1px 5px;border-radius:5px}
.item{display:flex;justify-content:space-between;align-items:center;background:var(--ins);border-radius:10px;padding:10px;margin-top:8px;gap:8px}
.item .n{font-weight:600}
.item .s{font-size:.8rem;color:var(--mut)}
.item .actions{display:flex;gap:6px;flex-shrink:0}
.fault{background:var(--bad);color:#fff;border-radius:8px;padding:8px;margin-top:8px;font-weight:600;font-size:.85rem}
.dot{width:10px;height:10px;border-radius:99px;background:#555;display:inline-block;margin-left:6px}
.dot.ok{background:var(--ok)}.dot.bad{background:var(--bad)}
.bar{height:8px;border-radius:5px;background:var(--line2);overflow:hidden;flex:1;margin:0 8px}
.bar>i{display:block;height:100%;background:var(--accent)}
/* tank-fill cascade editor */
.casrow{background:var(--ins);border-radius:10px;padding:8px 10px;margin-top:8px}
.casrow .crow{display:flex;align-items:center;gap:8px;margin:4px 0}
.casrow .crow label{font-size:.8rem;color:var(--mut);flex:0 0 auto}
.casrow .crow input{flex:1;min-width:0;padding:6px;border-radius:8px;border:1px solid var(--bd-med);background:var(--field);color:var(--ink)}
.casrow .c-name{font-weight:600}
.casrow .c-relay,.casrow .c-low,.casrow .c-high,.casrow .c-minsrc{flex:0 0 5rem}
.casrow .cx{flex:0 0 auto;background:var(--bad);color:#fff;border:0}
.tag{display:inline-block;font-size:.66rem;background:var(--line2);color:var(--mut);padding:2px 7px;border-radius:99px;margin:2px 3px 0 0}
.tag.role{background:#23364a;color:#9fd0ff}
.tag.off{background:#3a2422;color:#f0a59e}
textarea{width:100%;background:var(--field);color:var(--ink);border:1px solid var(--bd-med);border-radius:8px;padding:8px;font-family:ui-monospace,Menlo,monospace;font-size:.8rem}
hr{border:0;border-top:1px solid var(--line2);margin:14px 0}

/* subscriptions - filter + per-type bulk toggles + a scrollable list (big fleets) */
.subs-tools{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:0 0 8px}
.subs-tools #subs-search{flex:1;min-width:150px;padding:8px;border-radius:8px;border:1px solid var(--bd-med);background:var(--ins);color:var(--ink)}
.subs-bulk{display:flex;gap:4px}
.subs-scroll{max-height:46vh;overflow-y:auto;display:flex;flex-direction:column;gap:6px}
.sub-row{background:var(--ins);border-radius:10px;padding:9px 10px;display:flex;align-items:center;justify-content:space-between;gap:10px}
.sub-row.hide{display:none}
.sub-row .sn{font-weight:600;display:flex;flex-direction:column;min-width:0}
.sub-row .sk{font-size:.64rem;color:var(--mut)}
.evts{display:flex;flex-wrap:wrap;gap:10px}
.evts label{font-size:.78rem;color:var(--ink);display:flex;align-items:center;gap:5px}

/* auth screens */
.auth-screen{position:fixed;inset:0;z-index:1500;display:flex;align-items:center;justify-content:center;padding:20px;background:var(--bg)}
.auth-box{width:100%;max-width:340px;background:var(--card);border-radius:16px;padding:22px;display:flex;flex-direction:column;gap:10px}
.auth-box h1{margin:0 0 6px;font-size:1.4rem;text-align:center}
.auth-box input{padding:11px;border-radius:9px;border:1px solid var(--bd-med);background:var(--ins);color:var(--ink);font-size:1rem}
.auth-box button{padding:11px;font-size:1rem}
.auth-box .oauth{display:block;text-align:center;padding:11px;border-radius:9px;background:var(--line2);color:#fff;text-decoration:none;font-weight:600}
.auth-error{color:var(--bad);font-size:.82rem;min-height:1em;margin:0;text-align:center}
.auth-links{display:flex;justify-content:space-between;gap:10px;margin-top:2px}
.auth-links a,.auth-back{color:var(--accent-br);font-size:.82rem;text-decoration:none}
.auth-back{display:block;text-align:center;margin-top:2px}
.keybox{background:var(--field);border:1px solid var(--bd-med);border-radius:8px;padding:10px;margin:8px 0;overflow-x:auto}
.keybox code{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:.82rem;word-break:break-all;color:var(--ink)}

/* Device popup overlay: a device's native page in an iframe, OVER the current
   view. Full-screen on a phone (a Back button returns); from 760px up it starts
   at the sidebar's right edge so the menu stays visible + clickable behind it
   ("popup over background" AND "menu available" at once). */
#dev-overlay{position:fixed;inset:0;z-index:1300;display:flex;flex-direction:column;background:var(--bg)}
#dev-overlay.hidden{display:none!important}
.dov-bar{flex:0 0 auto;display:flex;align-items:center;gap:10px;padding:8px 12px;background:var(--bar);border-bottom:1px solid var(--line)}
.dov-close{flex:0 0 auto;background:var(--ins);border:1px solid var(--line2);color:var(--ink);font-weight:700;font-size:.85rem;padding:6px 13px;border-radius:8px;cursor:pointer}
.dov-close:hover{border-color:var(--bd-strong)}
.dov-title{flex:1 1 auto;min-width:0;font-weight:700;color:var(--ink);font-size:.95rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dov-pop{flex:0 0 auto;color:var(--mut);text-decoration:none;font-size:1.15rem;line-height:1;padding:4px 9px;border-radius:8px}
.dov-pop:hover{color:var(--ink);background:var(--ins)}
.dov-host{flex:1 1 auto;min-height:0}
.dov-host iframe{width:100%;height:100%;border:0;display:block;background:var(--bg)}
@media (min-width:760px){ #dev-overlay{left:var(--side)} }

/* modal */
.modal{position:fixed;inset:0;background:#000b;display:flex;align-items:center;justify-content:center;padding:16px;z-index:1000}
.modal-box{width:100%;max-width:min(560px,94vw);max-height:88vh;overflow:auto;background:var(--card);border-radius:16px;padding:18px}
.modal-box h3{margin:0 0 12px;font-size:1rem;color:var(--ink);text-transform:none;letter-spacing:0}
.field{margin:10px 0}
.field label{display:block;font-size:.74rem;color:var(--mut);margin-bottom:4px}
.field input,.field select,.field textarea{width:100%;padding:9px;border-radius:8px;border:1px solid var(--bd-med);background:var(--ins);color:var(--ink)}
.field .check{display:flex;align-items:center;gap:8px}
.field .check input{width:auto}
/* device (bowser) config: each tank is a clean card with a responsive field grid
   instead of cramming nine inputs into one flex row */
.devtank{background:var(--ins);border:1px solid var(--line);border-radius:8px;padding:12px;margin-top:10px}
.devtank .sn{font-weight:600;display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:2px}
.dt-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:6px 12px}
.dt-grid .field{margin:6px 0}
/* the IO-channel rows breathe + wrap instead of overflowing the modal */
#modal-body .row{flex-wrap:wrap;align-items:center}
#modal-body .row label{font-size:.82rem;color:var(--mut)}
.checklist{max-height:200px;overflow:auto;border:1px solid var(--line2);border-radius:8px;padding:8px}
.checklist label{display:flex;align-items:center;gap:8px;font-size:.85rem;padding:3px 0}
.checklist input{width:auto}

/* fuel */
.tbl{width:100%;border-collapse:collapse;font-size:.8rem;margin-top:8px}
.tbl th{text-align:left;color:var(--mut);font-weight:600;border-bottom:1px solid var(--line2);padding:6px 6px}
.tbl td{padding:6px 6px;border-bottom:1px solid #181c23}
#screen-img{width:100%;border-radius:10px;background:#000;display:block;image-rendering:auto;cursor:pointer}
#screen-keypad{margin-top:10px}
#screen-keypad .kp-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:6px}
#screen-keypad .kp-nav{display:grid;grid-template-columns:repeat(4,1fr);gap:6px;margin-top:6px}
#screen-keypad .kp-key{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1px;padding:10px 0;border-radius:8px;background:var(--ins);border:1px solid #2a2f37;color:var(--ink);cursor:pointer;line-height:1.05;font-weight:600}
#screen-keypad .kp-key:active{background:var(--accent);color:#fff}
#screen-keypad .kp-d{font-size:1.05rem;font-weight:700}
#screen-keypad .kp-s{font-size:.56rem;letter-spacing:.1em;color:var(--mut);min-height:.7em}
#screen-keypad .kp-ar{flex-direction:row;font-size:.82rem}

.nodewrap .nodehist{margin-top:8px;border-top:1px solid var(--line2);padding-top:8px}
.spark{display:block;background:var(--ins);border-radius:8px;margin:6px 0;padding:4px}
#toast{position:fixed;left:50%;bottom:20px;transform:translateX(-50%);background:#000a;color:#fff;padding:10px 16px;border-radius:10px;opacity:0;transition:opacity .2s;pointer-events:none;z-index:2000}
#toast.show{opacity:1}

/* Active-alert banner - between the header and the tab content, always visible.
   Critical (fault/alarm) rows are red + persistent; warnings are amber + dismissible. */
#alert-banner{flex:0 0 auto;display:flex;flex-direction:column;border-bottom:1px solid var(--line)}
/* one-line summary header - always visible; click to collapse/expand the list */
.al-head{display:flex;align-items:center;color:#fff;font-weight:700}
.al-head.sev-critical{background:var(--bad)}.al-head.sev-high{background:#c2620e}.al-head.sev-low{background:var(--warn);color:#2a1c00}
.al-toggle{flex:1 1 auto;display:flex;align-items:center;gap:8px;min-width:0;background:transparent;border:0;color:inherit;font:inherit;font-weight:700;font-size:.8rem;cursor:pointer;padding:9px 14px;text-align:left}
.al-caret{flex:0 0 auto;opacity:.85}
.al-toggle .al-h-txt{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.al-h-hint{flex:0 0 auto;font-size:.62rem;font-weight:700;opacity:.7;text-transform:uppercase;letter-spacing:.05em}
.al-h-acts{flex:0 0 auto;display:flex;gap:6px;padding:0 10px 0 4px}
.al-bx{background:rgba(0,0,0,.24);color:inherit;border:0;border-radius:8px;padding:4px 10px;font-size:.7rem;font-weight:700;cursor:pointer}
.al-list{display:flex;flex-direction:column;max-height:34vh;overflow-y:auto}
.al-more{border:0;background:rgba(0,0,0,.22);color:#fff;font-weight:600;font-size:.76rem;padding:8px 14px;text-align:left;cursor:pointer}
.al-more:hover{background:rgba(0,0,0,.34)}
.al-row.al-busy{opacity:.45;pointer-events:none}
.al-row.al-gone{display:none}
.al-bulk{display:flex;gap:8px;justify-content:flex-end;margin-top:2px}
/* Alerts-tab quick-filter chips — scoped to .al-quick so the header's
   version/user chips (the base .chip above) keep their own look. */
.al-quick{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 10px}
.al-quick .chip{border:1px solid var(--line2);background:var(--ins);color:var(--ink);border-radius:99px;padding:5px 12px;font-size:.76rem;font-weight:600;cursor:pointer;max-width:none}
.al-quick .chip:hover{border-color:var(--bd-strong)}
.al-quick .chip.on{background:var(--accent);border-color:var(--accent);color:#fff}
.al-row{display:flex;align-items:flex-start;gap:8px;padding:9px 14px;font-size:.82rem;font-weight:600;color:#fff}
.al-row+.al-row{border-top:1px solid rgba(0,0,0,.25)}
.al-row.sev-critical{background:var(--bad)}
.al-row.sev-high{background:#c2620e}
.al-row.sev-low{background:var(--warn);color:#2a1c00}
.al-row.sev-info{background:#2f6fd6}
.al-row.sev-debug{background:var(--bd-strong);color:#cfd6e0}
.al-row.sev-unhealthy{background:var(--bd-strong);color:#cfd6e0}
.al-row.acked{opacity:.6}
/* Alarm text wraps to as many lines as it needs — readability over density,
   no horizontal overflow. Action buttons stay pinned to the top-right. */
.al-row .al-txt{flex:1 1 auto;min-width:0;white-space:normal;overflow-wrap:anywhere;line-height:1.35;text-align:left}
.al-row .al-x,.al-row .al-tag,.al-row select.al-sil{margin-top:1px}
/* a clickable alert label → jumps to the offending device (banner + active list) */
button.al-txt{border:0;background:transparent;color:inherit;font:inherit;font-weight:600;text-align:left;cursor:pointer;padding:0;border-radius:6px}
button.al-txt.al-link:hover{text-decoration:underline}
.al-row .al-go{opacity:.7;font-weight:700}
/* a clickable history row → same jump-to-device */
.al-item.al-link{cursor:pointer}
.al-item.al-link:hover{background:var(--ins2,#222730)}
.al-item.al-link .al-src{text-decoration:underline;text-decoration-style:dotted}
.al-row .al-tag{flex:0 0 auto;font-size:.62rem;background:rgba(0,0,0,.28);padding:2px 7px;border-radius:99px;font-weight:700}
.al-row .al-x{flex:0 0 auto;cursor:pointer;border:0;background:rgba(0,0,0,.22);color:inherit;border-radius:8px;padding:2px 9px;font-weight:700;line-height:1.4}
/* the per-alert Silence-duration picker reads as a button but is a native select */
.al-row select.al-sil{appearance:none;-webkit-appearance:none;padding-right:9px}
.al-row select.al-sil option{color:#1a1d24;background:#fff;font-weight:600}
/* Silenced group (Alerts tab): muted, with an Unsilence affordance */
.al-silenced{margin-top:12px;border-top:1px solid var(--line2);padding-top:8px}
.al-sil-h{font-size:.66rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--mut);padding:0 2px 6px}
.al-silenced .al-row{background:var(--ins);color:var(--ink);border-radius:7px;margin-top:4px}
.al-silenced .al-row .al-unsil{background:var(--bd-strong);color:#fff}

/* Alerts history table (Alerts tab) */
#alerts-table{display:flex;flex-direction:column;gap:2px;margin-top:8px}
.al-item{display:flex;gap:8px;align-items:baseline;padding:7px 8px;border-radius:8px;background:var(--ins)}
.al-item .al-k{font-size:.6rem;font-weight:700;text-transform:uppercase;padding:2px 6px;border-radius:99px;background:var(--bd-med);color:#cbd2dc;flex:0 0 auto}
.al-item .al-k.crit{background:var(--bad);color:#fff}
.al-item .al-when{color:var(--mut);font-size:.68rem;flex:0 0 auto}
.al-item .al-src{font-weight:600;flex:0 0 auto;max-width:32%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.al-item .al-d{color:var(--mut);font-size:.8rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.al-item.crit-row{border-left:3px solid var(--bad);padding-left:6px}

/* dashboard summary tiles - a customisable set; flows to fill the width (more
   tiles on a wide desktop, ~3 across on a phone). */
.bubbles{display:grid;grid-template-columns:repeat(auto-fit,minmax(104px,1fr));gap:8px}
.bubbles-wrap{display:flex;flex-direction:column;gap:8px}
.bubbles-h{display:flex;align-items:center;justify-content:space-between;font-size:.72rem;color:var(--mut);text-transform:uppercase;letter-spacing:.06em;font-weight:700;padding:0 2px}
.dash-editor{display:flex;flex-wrap:wrap;gap:6px;padding:8px;background:var(--ins2);border:1px solid var(--bd-soft);border-radius:8px}
.dash-pick{background:var(--ins);color:var(--mut);border:1px solid var(--line);border-radius:99px;padding:5px 11px;font-size:.76rem;font-weight:600;cursor:pointer}
.dash-pick.on{background:var(--accent);color:#fff;border-color:var(--accent)}
/* quick-start onboarding steps (shown when no equipment is reporting) */
#quickstart-card .qs-step{display:flex;gap:10px;align-items:flex-start;margin:10px 0}
#quickstart-card .qs-n{flex:0 0 auto;width:22px;height:22px;border-radius:50%;background:var(--accent);color:#fff;font-weight:700;font-size:.78rem;display:flex;align-items:center;justify-content:center}
#quickstart-card .qs-step button{margin-top:6px}
.bubble{display:flex;flex-direction:column;align-items:center;gap:2px;padding:12px 6px;border:1px solid var(--line);border-radius:14px;background:var(--card);color:var(--ink);cursor:pointer}
.bubble .bi{font-size:1.4rem;line-height:1.1}
.bubble .bl{font-size:.62rem;color:var(--mut);text-transform:uppercase;letter-spacing:.05em}
.bubble .bv{font-size:1.2rem;font-weight:700}
.bubble .bs{font-size:.64rem;color:var(--mut);text-align:center}
.bubble.warn{border-color:var(--bad)}
.bubble.warn .bv{color:var(--warn)}


/* GPS tab map - its own, slightly taller than the dashboard's. */
#gps-map{height:46vh;min-height:260px;border-radius:12px;overflow:hidden;background:#202530;margin-bottom:8px}

/* Slide-up group sheet (Water / Farm / More). Tap a bottom-bar group → a sheet
   rises with that group's destinations; tap an item or the backdrop to dismiss. */
#sheet-backdrop{position:fixed;inset:0;background:#0009;z-index:1190}
#sheet{position:fixed;left:0;right:0;bottom:0;z-index:1200;max-width:620px;margin:0 auto;background:var(--bar);border-top:1px solid var(--line2);border-radius:18px 18px 0 0;padding:8px 12px max(16px,env(safe-area-inset-bottom));transform:translateY(115%);transition:transform .22s cubic-bezier(.2,.7,.2,1)}
#sheet.up{transform:translateY(0)}
.sheet-grip{width:42px;height:5px;border-radius:99px;background:var(--bd-strong);margin:4px auto 12px}
#sheet-items{display:flex;flex-direction:column;gap:5px}
.sheet-item{display:flex;align-items:center;gap:12px;width:100%;padding:13px 12px;border:0;border-radius:12px;background:var(--ins);color:var(--ink);font-size:.95rem;font-weight:600;text-align:left;cursor:pointer}
.sheet-item:hover{background:#171b22}
.sheet-item .ti{font-size:1.3rem;width:1.7rem;text-align:center;filter:none}
.sheet-item .sl{flex:1}
.sheet-item .tag{font-size:.6rem;margin:0}

/* ─── Polish: motion, focus, hover, scrollbars - small touches, everywhere. ─── */
button{transition:filter .14s ease,transform .04s ease}
button:hover{filter:brightness(1.09)}
button:active{transform:translateY(1px)}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.tab{transition:background .14s ease,color .14s ease}
.item,.sheet-item,.sub-row,.al-item{transition:background .14s ease}
.item:hover{background:#161a21}
.bubble{transition:transform .12s ease,border-color .14s ease,background .14s ease}
.bubble:hover{transform:translateY(-2px);border-color:#33404f;background:#1e222a}
.card{border:1px solid var(--bd-soft)}
html{scroll-behavior:smooth}
*{scrollbar-width:thin;scrollbar-color:var(--line2) transparent}
::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-thumb{background:var(--line2);border-radius:99px;border:2px solid var(--bg)}
::-webkit-scrollbar-thumb:hover{background:var(--bd-strong)}

/* ─── Desktop / tablet (≥760px): a proper app layout - a full-height left
   sidebar for navigation, the header + content in the right column, content that
   flows into as many columns as fit, and the big maps up top. The phone keeps its
   bottom bar; here the very same #tabs buttons reflow into the rail. Engaging from
   760px means a landscape tablet or a half-screen laptop window gets the real app,
   not a narrow phone strip. ──────────── */
@media (min-width:760px){
  :root{--side:226px}
  #app{max-width:none;display:grid;grid-template-columns:var(--side) minmax(0,1fr);grid-template-rows:auto auto minmax(0,1fr)}
  /* nav → left sidebar rail (same buttons, reflowed) */
  #tabs{grid-column:1;grid-row:1/-1;flex-direction:column;justify-content:flex-start;align-items:stretch;gap:3px;padding:12px 10px;border-top:0;border-right:1px solid var(--line);overflow-y:auto}
  /* brand pinned at the very top; Dashboard is the first nav item BELOW it. */
  #tabs::before{content:"FarmSCADA";order:-2;display:block;font-weight:800;font-size:1.05rem;letter-spacing:.01em;color:var(--ink);padding:6px 12px 12px;margin-bottom:4px;border-bottom:1px solid var(--line)}
  /* every row left-aligned; icons share a fixed column so labels line up.
     Idle labels use full --ink (not the dim --mut the phone bar uses) so the
     rail is legible at a glance in both themes. */
  .tab{flex:0 0 auto;flex-direction:row;justify-content:flex-start;gap:11px;padding:10px 12px;font-size:.93rem;border-radius:9px;text-align:left;color:var(--ink)}
  .tab .ti{font-size:1.2rem;filter:none;flex:0 0 auto;width:1.6rem;text-align:center}
  .tab .tl{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .tab:hover{background:#171b22;color:var(--ink)}
  .tab.on{color:#fff;background:var(--accent)}
  .tab.on .ti{filter:none}
  /* Dashboard sits at the TOP of the desktop sidebar (it's the centred home key
     on the phone bar; here "home" reads as the first rail item). */
  .tab[data-tab="dash"]{order:-1}
  /* right column: header (brand lives in the rail now), banner, active view */
  #bar{grid-column:2;grid-row:1}
  #bar #title{display:none}
  #bar .farmname{color:var(--ink);font-size:1.02rem;font-weight:700;max-width:38vw}
  #alert-banner{grid-column:2;grid-row:2}
  #alert-banner .al-list{max-height:26vh}
  /* auto-FIT (not fill): when a row has fewer cards than fit, the empty tracks
     collapse and the cards stretch to fill the width — no dead space beside a
     lone card. In-card item tiles below keep auto-FILL so they stay compact. */
  .tabview{grid-column:2;grid-row:3;padding:18px;display:grid;grid-template-columns:repeat(auto-fit,minmax(var(--kcol),1fr));gap:var(--kgap);align-content:start}
  .tabview>button{grid-column:1/-1;justify-self:start}
  /* the historian (and any .wide card) spans the full content width on desktop -
     a proper big chart, not one narrow grid column the width of a phone */
  .tabview>.card.wide{grid-column:1/-1}
  #tab-history .hsvg{height:360px}
  #tab-dash .map-card,#tab-gps .map-card{grid-column:1/-1}
  /* summary tiles + quick-start span the full content width on desktop */
  #tab-dash .bubbles-wrap,#quickstart-card{grid-column:1/-1}
  /* Data tabs read as a clean vertical stack of full-width sections, each section
     flowing its own items into the universal kanban grid — so a trailing config /
     table card never strands in one narrow column beside dead space. (Config tabs
     -- settings, admin, account, devices -- and alerts keep the multi-column card
     grid, where many small cards tile side by side.) */
  #tab-water>.card,#tab-history>.card,#tab-fuel>.card{grid-column:1/-1}
  #map{height:54vh;min-height:380px}
  #gps-map{height:60vh;min-height:420px}
  .set input,.set select{width:220px}
  .chip{max-width:none}
  /* No flyouts on desktop - groups are inline, expandable sections in the rail,
     so the whole menu is visible at a glance and nothing overlays the content. */
  #sheet,#sheet-backdrop{display:none!important}
  /* a group = an obvious header (tinted, uppercase, a caret) you click to fold,
     with its apps listed inline beneath, indented behind a left rail so it's clear
     they belong to the group. */
  .navsec{display:flex;flex-direction:column}
  .navsec-h{display:flex;align-items:center;gap:11px;width:100%;padding:10px 12px;margin-top:9px;border:0;border-radius:8px;
    background:var(--ins);color:var(--ink);font-weight:800;font-size:.72rem;text-transform:uppercase;letter-spacing:.08em;cursor:pointer;text-align:left}
  .navsec-h:hover{background:var(--card);color:var(--ink)}
  .navsec-h .ti{font-size:1.05rem;filter:none;flex:0 0 auto;width:1.6rem;text-align:center}
  .navsec-h .tl{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .navsec-caret{flex:0 0 auto;font-size:.82rem;opacity:.85;margin-left:2px;transition:transform .18s ease}
  .navsec.col .navsec-caret{transform:rotate(-90deg)}
  .navsec-items{display:flex;flex-direction:column;gap:2px;overflow:hidden;margin:3px 0 2px 23px;padding-left:5px;border-left:1px solid var(--line2)}
  .navsec.col .navsec-items{display:none}
  /* child apps: a touch smaller, icons share the same fixed column */
  #tabs .tab.subtab{padding-left:10px;font-size:.9rem}
  #tabs .tab.subtab .ti{font-size:1.1rem;flex:0 0 auto;width:1.5rem;text-align:center}
  #tabs .tab.subtab .tag{margin-left:auto}
  /* the rail scrolls as one column; the brand stays pinned readable at the top */
  #tabs.sidebar{gap:1px}
  /* List-heavy cards span the full content width and flow their items into as
     many columns as fit - so wide screens fill horizontally instead of leaving a
     single tall column with empty space beside it (e.g. GPS units, fences, gates,
     generators, devices, fuel, users). :has() degrades gracefully if unsupported. */
  .tabview>.card:has(#gps-list),.tabview>.card:has(#fence-list),.tabview>.card:has(#gate-list),
  .tabview>.card:has(#gen-list),.tabview>.card:has(#device-list),.tabview>.card:has(#node-list),
  .tabview>.card:has(#fuel-devices),.tabview>.card:has(#users-list),.tabview>.card:has(#assets-list),
  .tabview>.card:has(#groups-list),.tabview>.card:has(#irr-pivot-list),.tabview>.card:has(#irr-fixed-list),
  .tabview>.card:has(#pa-list),.tabview>.card:has(#driver-list){grid-column:1/-1}
  #gps-list,#fence-list,#gate-list,#gen-list,#device-list,#node-list,#fuel-devices,#users-list,
  #assets-list,#groups-list,#irr-pivot-list,#irr-fixed-list,#pivot-list,#pa-list,#driver-list{
    display:grid;grid-template-columns:repeat(auto-fill,minmax(var(--kcol),1fr));gap:var(--kgap);align-items:start}
  #gps-list>*,#fence-list>*,#gate-list>*,#gen-list>*,#device-list>*,#node-list>*,#fuel-devices>*,
  #users-list>*,#assets-list>*,#groups-list>*,#irr-pivot-list>*,#irr-fixed-list>*,#pivot-list>*,
  #pa-list>*,#driver-list>*{margin-top:0}
}
@media (min-width:1500px){
  /* wider boxes on a big screen — bumping the one column var widens the tabview
     card grid AND every in-card item grid together, so they stay in lockstep. */
  :root{--kcol:340px}
  #tab-dash .map-card,#tab-gps .map-card{grid-column:1/-1}
}

/* fuel device-config channel inputs */
.field .row .chan,.row input.chan{width:64px}

/* inline help marker (native title tooltip) - sits next to its heading title */
.hint{cursor:help;opacity:.6;font-weight:400}
.card h3 .hint{margin-right:auto}
.card h3 .hint:hover{opacity:1}

/* draggable asset-map pins (supervisor placement) */
.asset-pin span{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;border:2px solid #fff;font-size:12px;box-shadow:0 1px 4px rgba(0,0,0,.45);cursor:grab}

/* per-user menu editor (Account → Menu) - drag-and-drop zones */
.nav-ed-hint{margin:0 0 10px}
.navzone{margin:0 0 10px}
.navzone .nz-h{font-size:.66rem;text-transform:uppercase;letter-spacing:.06em;color:var(--mut);margin:0 0 6px;display:flex;align-items:center;justify-content:space-between;gap:8px}
.nz-tog{background:var(--line2);color:var(--mut);border:0;border-radius:6px;padding:3px 10px;font-size:.62rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em;cursor:pointer}
.nz-tog:hover{color:var(--ink)}
.navzone.nz-folded .nz-items{opacity:.45}
.nz-items{display:flex;flex-direction:column;gap:6px;min-height:40px;border:1px dashed var(--line2);border-radius:10px;padding:6px;background:var(--ins2);transition:background .12s,border-color .12s}
.nz-items.over{border-color:var(--accent-br);background:#15191f}
.navzone.nz-hidden .nz-items{background:#15110f;border-color:#3a2a22}
.navchip{display:flex;align-items:center;gap:9px;padding:9px 10px;border-radius:9px;background:#1a1d24;border:1px solid var(--line);cursor:grab;font-size:.9rem;user-select:none}
.navchip:active{cursor:grabbing}
.navchip.dragging{opacity:.4}
.navchip .nc-g{color:var(--mut);letter-spacing:-2px;cursor:grab}
.navchip .nc-i{font-size:1.1rem;width:1.4rem;text-align:center}
.navchip .nc-l{flex:1}
.nz-empty{color:var(--mut);font-size:.74rem;text-align:center;padding:6px;pointer-events:none}
/* delete a group + the "new group" composer column */
.nz-ctl{display:flex;align-items:center;gap:6px}
.nz-del{background:var(--line2);color:var(--mut);border:0;border-radius:6px;padding:3px 8px;font-size:.62rem;font-weight:700;cursor:pointer;line-height:1}
.nz-del:hover{background:#5a2a22;color:#fff}
.navzone.nz-new .nz-h{color:var(--accent-br)}
.nz-newform{display:flex;flex-direction:column;gap:8px;border:1px dashed var(--line2);border-radius:10px;padding:8px;background:var(--ins2)}
.nz-icons{display:grid;grid-template-columns:repeat(auto-fill,minmax(30px,1fr));gap:4px;max-height:160px;overflow-y:auto;padding:2px;border:1px solid var(--line2);border-radius:8px}
.nz-ic{background:var(--line2);border:0;border-radius:7px;width:100%;height:30px;font-size:1rem;cursor:pointer;display:flex;align-items:center;justify-content:center}
.nz-ic.sel{outline:2px solid var(--accent-br)}
.nz-ic:hover{background:var(--accent-br)}
.nz-newrow{display:flex;gap:6px;align-items:center}
.nz-newicon{width:42px;text-align:center;padding:6px;border-radius:8px;border:1px solid var(--bd-med);background:var(--ins);color:var(--ink)}
.nz-newlabel{flex:1 1 auto;min-width:0;padding:6px;border-radius:8px;border:1px solid var(--bd-med);background:var(--ins);color:var(--ink)}

/* ── Kanban: the standard multi-category layout ───────────────────────────────
   Several category lists shown as side-by-side columns (each a vertical stack),
   scrolling sideways if they don't all fit and stacking on a phone. Used by the
   irrigation tab and the menu editor so the layout method reads the same
   everywhere a tab is split into categories. */
.kanban{display:flex;gap:var(--kgap);align-items:flex-start;overflow-x:auto;padding-bottom:6px}
.kanban>*{flex:1 1 0;min-width:var(--kcol);margin:0}
.tabview>.kanban{grid-column:1/-1}                 /* span the desktop grid full-width */
.kanban>.card{align-self:stretch}
/* inside a kanban column the list is a single vertical stack (kanban cards), never
   the wide auto-fill grid those same lists use as a full-width tab card. */
.kanban #irr-pivot-list,.kanban #irr-fixed-list,.kanban #irr-trav-list{display:flex;flex-direction:column;gap:var(--kgap)}
/* the menu editor's drag zones share the universal kanban metric too (even-width
   columns at --kcol), so the board reads the same here as on every other tab. */
@media(max-width:759px){
  .kanban{flex-direction:column;overflow-x:visible}
  .kanban>*{min-width:0;width:100%}
}

/* historian - explorer controls, series chips, the hand-rolled SVG chart */
.hist-bar{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:0 0 10px}
.hist-bar select{padding:7px 9px;border-radius:8px;border:1px solid var(--bd-med);background:var(--ins);color:var(--ink);max-width:46vw}
.hist-bar .grow{flex:1}
.hist-series{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 8px}
.hchip{display:flex;align-items:center;gap:6px;background:var(--ins);border:1px solid var(--line2);border-radius:99px;padding:4px 8px 4px 9px;font-size:.78rem}
.hchip .hdot{width:9px;height:9px;border-radius:50%;flex:0 0 auto}
.hchip .hx{background:transparent;border:0;color:var(--mut);cursor:pointer;font-size:1rem;line-height:1;padding:0 2px}
.hist-chart{width:100%;min-height:110px;background:var(--ins2);border:1px solid var(--bd-soft);border-radius:10px;padding:6px;position:relative}
.hsvg{width:100%;height:300px;display:block;touch-action:none}
/* interactive crosshair + per-series dots + the value/timestamp tooltip */
.hsvg-cross{stroke:var(--bd-strong);stroke-width:1;stroke-dasharray:3,3;opacity:0;pointer-events:none}
.hsvg-dot{opacity:0;pointer-events:none;stroke:var(--ins2);stroke-width:1.5}
.hist-tip{position:absolute;z-index:6;background:#0b0e13;color:#e8eaed;border:1px solid var(--bd-strong);border-radius:7px;padding:6px 9px;font-size:.72rem;pointer-events:none;min-width:120px;box-shadow:0 4px 16px rgba(0,0,0,.4)}
body.light .hist-tip{background:#fff;color:#1a1d24;box-shadow:0 4px 16px rgba(0,0,0,.18)}
.hist-tip .ht-t{color:var(--mut);font-size:.66rem;margin-bottom:3px;font-variant-numeric:tabular-nums}
.hist-tip .ht-r{display:flex;align-items:center;gap:6px;line-height:1.55}
.hist-tip .ht-r b{font-variant-numeric:tabular-nums}
.hist-tip .ht-d{width:10px;height:3px;border-radius:2px;flex:0 0 auto}
.hsvg .hax{fill:var(--mut);font-size:11px}
.hist-legend{display:flex;flex-wrap:wrap;gap:12px;margin-top:8px;padding:0 4px}
.hist-legend .hl{display:flex;align-items:center;gap:6px;font-size:.76rem;color:var(--mut)}
.hist-legend .hdot{width:12px;height:3px;border-radius:2px}

/* ─── Industrial pass: crisp bordered panels, tight utilitarian edges, paneled
   headers with an accent tick, and tabular numerals so live readouts line up like
   an instrument panel. ─── */
.card{border-radius:7px;border:1px solid var(--line)}
.item,.cell,.casrow,.sub-row,.navchip,.al-item{border-radius:5px;border:1px solid var(--line)}
.bubble{border-radius:5px}
button,.tab,.open,input,select,textarea{border-radius:5px}
/* paneled header: a rule + a short accent tick on the left, like a labelled bay */
.card h3{border-bottom:1px solid var(--line);padding-bottom:9px;padding-left:9px;position:relative}
.card h3::before{content:"";position:absolute;left:0;top:1px;bottom:8px;width:3px;border-radius:2px;background:var(--accent)}
.card h3 .hint::before,.card h3 button::before{content:none}
.item .s,.cell b,.cell label,.al-item .al-when,.chip.ver,.hsvg .hax,.bubble .bv,.tbl td,.spark text,.hdr-meta,.n{font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1}
/* status pills + dots get a crisp ring - reads like panel indicators */
.pill{box-shadow:inset 0 0 0 1px rgba(0,0,0,.25)}

/* ─── Light-mode polish: a few surfaces were hard-coded dark and read as dark
   patches on the light theme (the menu highlight + hovers, nav-editor chips, drag
   zones, the GPS map). Re-tint them for light mode, and make the active tab
   highlight actually stand out (the old var(--ins) was near-invisible on light). ─ */
body.light .tab.on{background:#fff3ec;color:var(--accent)}
body.light .tab.on .ti{filter:none}
body.light .tab:hover{background:#eef1f5;color:var(--ink)}
body.light .sheet-item:hover{background:#eef1f5}
body.light .item:hover{background:#f1f4f8}
body.light .bubble:hover{background:#f3f6fa;border-color:var(--bd-strong)}
body.light .navchip{background:#fff}
body.light .nz-items.over{background:#eaf0f7}
body.light .navzone.nz-hidden .nz-items{background:#fbeee8;border-color:#e8c9bb}
body.light #gps-map{background:#dde3ea}

/* Banner note shown when the user has active alerts they haven't subscribed to
   (the banner lists only subscribed alerts; this points them at the Alerts tab). */
.al-note{display:block;width:100%;text-align:left;border:0;cursor:pointer;background:var(--bd-strong);color:#fff;font-weight:700;font-size:.78rem;padding:9px 14px}
body.light .al-note{background:#e6ebf2;color:var(--ink)}

/* Touch-friendly tooltip bubble - the inline ⓘ hints become tappable on mobile
   (native `title` only shows on hover); positioned by app.js. */
.tip-bubble{position:fixed;z-index:3000;background:#0b0e13;color:#e8eaed;border:1px solid var(--bd-strong);border-radius:8px;padding:8px 11px;font-size:.78rem;line-height:1.35;font-weight:500;box-shadow:0 6px 24px rgba(0,0,0,.45);max-width:280px}
body.light .tip-bubble{background:#fff;color:#1a1d24;box-shadow:0 6px 24px rgba(0,0,0,.18)}

/* ─── Bounding-box safety: nothing should overflow its container. Media scales to
   its box (Leaflet protects its own tiles with max-width:none!important, so the
   map is unaffected); live readouts + tooltips wrap or ellipsize, never spill. ─── */
#app img,#app svg,#app canvas{max-width:100%}
#screen-img{height:auto}                 /* the demo/live fuel screen scales to the modal width */
.hist-tip{max-width:60vw;overflow-wrap:anywhere}
.tip-bubble{overflow-wrap:anywhere}
.bubble .bl,.bubble .bv,.bubble .bs{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.al-note{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* ---- Uniform entity config panel (Details · Links · Access) -------------- */
.cog{padding:4px 8px}
.cfg-tabs{display:flex;gap:6px;margin:4px 0 16px;border-bottom:1px solid var(--line, #23272f)}
.cfg-tab{background:transparent;border:0;border-bottom:2px solid transparent;color:var(--muted,#9aa0aa);
  padding:8px 14px;font-weight:700;font-size:.86rem;cursor:pointer;border-radius:0}
.cfg-tab:hover{color:var(--ink,#e8eaed)}
.cfg-tab.sel{color:var(--accent,#ff7a33);border-bottom-color:var(--accent,#ff7a33)}
.cfg-sec{animation:cfgfade .15s ease}
@keyframes cfgfade{from{opacity:0;transform:translateY(3px)}to{opacity:1;transform:none}}
.cfg-lead{color:var(--ink,#e8eaed);font-size:.92rem;line-height:1.45;margin:0 0 14px}
.cfg-lead b{color:var(--accent,#ff7a33)}
.cfg-sec .set{display:flex;align-items:center;gap:10px;margin:11px 0}
.cfg-sec .set>label{flex:0 0 150px;color:var(--muted,#9aa0aa);font-size:.85rem;font-weight:600}
.cfg-sec .set select,.cfg-sec .set input{flex:1;min-width:0;padding:9px 11px;border-radius:9px;
  border:1px solid var(--line,#2c333d);background:var(--field,#0f1115);color:var(--ink,#e8eaed);font-size:.92rem}
.cfg-sec .set input[disabled]{opacity:.6}
.cfg-sec .set select:focus,.cfg-sec .set input:focus{outline:none;border-color:var(--accent,#ff7a33)}
.cfg-u{flex:0 0 auto;color:var(--muted,#9aa0aa);font-weight:700;margin-left:-4px}
.cfg-rev-grp{background:var(--field,#0f1115);border:1px solid var(--line,#23272f);border-radius:11px;padding:12px 14px;margin:10px 0}
.cfg-rev-grp>b{display:block;color:var(--muted,#9aa0aa);font-size:.74rem;text-transform:uppercase;letter-spacing:.04em;margin-bottom:6px}
.cfg-rev{margin:0;padding-left:18px}
.cfg-rev li{padding:2px 0;font-size:.9rem}

.cctv-frame{width:100%;height:72vh;min-height:420px;border:1px solid var(--line,#23272f);border-radius:12px;background:#000}

/* Universal IO picker: a floating searchable dropdown attached to any raw IO
   address field. Fixed-positioned (above modals + cards). */
.io-pickable{background-image:linear-gradient(45deg,transparent 50%,var(--mut) 50%),linear-gradient(135deg,var(--mut) 50%,transparent 50%);background-position:calc(100% - 14px) 1.05em,calc(100% - 9px) 1.05em;background-size:5px 5px,5px 5px;background-repeat:no-repeat}
.io-pop{position:fixed;z-index:4000;width:320px;max-width:92vw;max-height:46vh;overflow-y:auto;background:var(--card);border:1px solid var(--bd-strong);border-radius:10px;box-shadow:0 10px 32px rgba(0,0,0,.5);padding:4px}
.io-opt{display:grid;grid-template-columns:auto 1fr;gap:2px 8px;width:100%;text-align:left;border:0;background:transparent;color:var(--ink);padding:7px 9px;border-radius:7px;cursor:pointer;align-items:center}
.io-opt:hover{background:var(--ins2)}
.io-opt .dot{grid-row:1/3}
.io-opt .io-l{font-size:.84rem;font-weight:600;overflow-wrap:anywhere}
.io-opt .io-l b{font-weight:700;color:var(--accent-br)}
.io-opt .io-t{grid-column:2;font-size:.68rem;color:var(--mut);text-transform:uppercase;letter-spacing:.03em}

/* Cameras (CCTV): live MJPEG tiles when Frigate is proxied same-origin. */
.cctv-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:10px;margin-top:8px}
.cctv-tile{position:relative;display:block;border-radius:12px;overflow:hidden;background:#000;border:1px solid var(--line2);aspect-ratio:16/9}
.cctv-tile img{width:100%;height:100%;object-fit:cover;display:block}
.cctv-tile .cctv-cap{position:absolute;left:0;right:0;bottom:0;padding:6px 10px;background:linear-gradient(transparent,#000c);color:#fff;font-size:.78rem}
.cctv-frame{width:100%;height:70vh;border:0;border-radius:12px;background:#000}
