:root{
  /* Deep Dark defaults -- kept byte-for-byte identical to what
     applyCustomVars() computes for the app's actual default theme
     (bg/comp_bg #000000, accent #ff3b30) so the very first paint already
     matches what JS applies a moment later once /api/settings resolves.
     A mismatch here is exactly what a theme-color "flicker" on load is --
     the page briefly painting these static values before JS overwrites
     them with the real (usually identical) ones. */
  --bg:#000000; --bg1:#080808; --surface:#000000; --surface2:#000000;
  --line:#1a1a1a; --line2:#292929;
  --txt:#e6edf6; --muted:#8a98b0; --muted2:#5d6b82;
  --accent:#ff3b30; --accent2:#c0392b; --accent-soft:rgba(255,59,48,.14);
  --grn:#3ddc97; --amber:#ffb454; --red:#ff6b6b; --cyan:#39d0ff;
  --radius:12px; --shadow:0 1px 2px rgba(0,0,0,.28),0 6px 20px rgba(0,0,0,.22);
  --font:'Inter','Segoe UI',system-ui,-apple-system,sans-serif,'Segoe UI Emoji','Apple Color Emoji','Noto Color Emoji';
  --mono:ui-monospace,'Cascadia Mono','Consolas',Menlo,monospace;
  /* native scrollbars, form controls and date pickers follow the device
     too, now that the rest of the page does */
  color-scheme:light dark;
}

/* First paint, before app.js has read the settings. The values above are
   the dark defaults; on a device set to light these are what stops the
   page flashing dark for the moment it takes JS to work out the real
   palette. The engine overwrites both sets inline a tick later. */
@media (prefers-color-scheme: light){
  :root{
    --bg:#f4f6fa; --bg1:#eef1f7; --surface:#ffffff; --surface2:#f7f9fc;
    --line:#dde3ec; --line2:#c8d2e0;
    --txt:#16202e; --muted:#5d6b82; --muted2:#8595ad;
    --shadow:0 1px 2px rgba(16,32,64,.05),0 6px 18px rgba(16,32,64,.07);
  }
}
body.light{
  /* applyCustomVars() computes bg/surface/line/txt/muted from the chosen palette --
     only declare what the engine does NOT set, or it gets overridden here. */
  --accent-soft:rgba(255,59,48,.10);
  --shadow:0 1px 2px rgba(16,32,64,.05),0 6px 18px rgba(16,32,64,.07);
  /* semantic colours darkened so they stay legible on a light surface */
  --grn:#0f9d63; --amber:#b0761a; --red:#d24545; --cyan:#0e8fb3;
}
body.light .prio.Low{background:rgba(15,157,99,.12);color:#0d7a4e}
body.light .prio.Normal{background:rgba(59,158,255,.14);color:#1b60ad}
body.light .prio.High{background:rgba(176,118,26,.14);color:#96620f}
body.light .prio.Urgent{background:rgba(210,69,69,.13);color:#b23434}
body.light .prio.Emergency{background:rgba(210,69,69,.22);color:#94201f}
/* light mode scrollbars + matrix dim tweak only; palette is driven inline by applyCustomVars */
body.light ::-webkit-scrollbar-thumb{ background:#c2ccda; }
body.light canvas#bgCanvas{ opacity:.25; }
*{box-sizing:border-box}
html,body{margin:0;height:100%}
body{background:var(--bg);color:var(--txt);font-family:var(--font);
  overflow:hidden;-webkit-font-smoothing:antialiased}
h2,h3{font-family:var(--font);letter-spacing:-.01em;margin:0;font-weight:650}
a{color:inherit}

/* ---- backdrop: matrix stays BEHIND, dimmed, never bleeds through panels ---- */
canvas#bgCanvas{display:none}
.backdrop-veil{position:fixed;inset:0;z-index:0;pointer-events:none;background:var(--bg)}
.scanlines{display:none}
.vignette{display:none}
canvas#sessBar{position:fixed;bottom:0;left:0;height:3px;width:100%;z-index:60;
  background:var(--accent);opacity:.7}

/* ---- app shell: opaque, locked flex ---- */
#app{position:relative;z-index:2;display:flex;height:100vh;width:100vw;overflow:hidden}
.side{flex:0 0 248px;height:100vh;display:flex;flex-direction:column;padding:16px 12px;
  background:linear-gradient(180deg,var(--surface),var(--bg1));border-right:1px solid var(--line)}
.logo{display:flex;align-items:center;gap:11px;padding:6px 8px 18px;font-family:var(--font);font-weight:800;
  font-size:19px;color:var(--accent);letter-spacing:-.01em}
.logo img{width:34px;height:34px;object-fit:contain;border-radius:8px}
.nav{display:flex;flex-direction:column;gap:2px;margin-top:4px;overflow:auto}
.nav a{display:flex;align-items:center;gap:11px;padding:10px 13px;border-radius:9px;color:var(--muted);
  font-weight:600;font-size:14px;letter-spacing:.3px;cursor:pointer;transition:.15s;border:1px solid transparent}
.nav a:hover{background:var(--accent-soft);color:var(--txt)}
.nav a.active{background:var(--accent-soft);color:var(--accent);border-color:rgba(255,59,48,.35);
  box-shadow:inset 2px 0 0 var(--accent)}
.ni{width:20px;text-align:center;font-size:15px;opacity:.95;flex:0 0 20px;
  font-family:'Segoe UI Emoji','Apple Color Emoji','Noto Color Emoji',sans-serif;
  font-variant-emoji:emoji;line-height:1}

.side-foot{margin-top:auto;display:flex;flex-direction:column;gap:8px;padding-top:12px;border-top:1px solid var(--line)}
.userbox{display:flex;align-items:center;gap:10px;padding:8px;border:1px solid var(--line);border-radius:10px;background:var(--surface2)}
.avatar{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,var(--accent),var(--accent2));
  display:flex;align-items:center;justify-content:center;font-weight:800;color:#fff;flex:0 0 36px}
.userbox #uname{font-weight:600;font-size:13.5px;color:var(--txt);line-height:1.3}
.userbox .mono{font-size:11px;color:var(--muted);letter-spacing:.2px}
.logout{font-size:12px;color:var(--muted);cursor:pointer;text-align:center;padding:6px}
.logout:hover{color:var(--red)}

.main{flex:1;height:100vh;overflow:auto;padding:20px 26px;background:var(--bg);position:relative}
.topbar{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:6px 0 16px;
  border-bottom:1px solid var(--line);margin-bottom:18px}
.crumb{font-family:var(--font);font-size:11.5px;color:var(--muted2);letter-spacing:.3px}
.topbar h2{font-size:21px;color:var(--txt);margin-top:2px}
.spacer{flex:1}

.toolbar{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:14px;align-items:center}
.toolbar input,.toolbar select{background:var(--surface);border:1px solid var(--line);color:var(--txt);
  border-radius:9px;padding:9px 12px;font-family:var(--font);font-size:13px;min-width:160px;height:38px;box-sizing:border-box}
.toolbar input:focus,.toolbar select:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.toolbar .btn.sm{height:38px;display:inline-flex;align-items:center}
.undobar{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:-6px 0 12px;padding:10px 14px;border:1px solid var(--amber);background:rgba(255,176,32,.12);border-radius:9px;color:var(--txt);font-family:var(--font);font-weight:600}
.undobar .ub-msg{flex:1}
.undobar .ub-undo{background:var(--amber);color:#1a1300;border:none;border-radius:7px;padding:6px 16px;font-weight:800;cursor:pointer;font-family:var(--font)}
.undobar .ub-undo:hover{filter:brightness(1.08)}

.btn{background:var(--accent);color:var(--btn-text,#04121f);border:none;border-radius:8px;padding:9px 16px;font-weight:600;
  font-family:var(--font);letter-spacing:0;cursor:pointer;font-size:13px;transition:.15s}
.btn:hover{filter:brightness(1.08)}
/* There was no rule for this, so a disabled button looked exactly like a
   live one -- full accent, pointer cursor, hover brighten and all. Any
   button the app switches off now reads as switched off. */
.btn:disabled,.btn[disabled]{opacity:.45;cursor:not-allowed;filter:none}
.btn:disabled:hover,.btn[disabled]:hover{filter:none}
.btn.mag{background:var(--accent2);color:var(--btn-mag-text,#fff)}
.btn.ghost{background:transparent;border:1px solid var(--line);color:var(--muted)}
.btn.ghost:hover{color:var(--txt);border-color:var(--accent)}
.btn.sm{padding:5px 11px;font-size:12px}
.btn.danger{background:transparent;border:1px solid var(--red);color:var(--red)}
.btn.danger:hover{background:var(--red);color:#fff}

.tablewrap{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);
  overflow:auto;max-height:calc(100vh - 260px);box-shadow:var(--shadow)}
/* top horizontal scrollbar strip */
.hscroll{height:10px;overflow-x:auto;overflow-y:hidden;border:1px solid var(--line);border-bottom:none;
  border-radius:var(--radius) var(--radius) 0 0;background:var(--bg1)}
.hscroll-in{height:1px}
.tablewrap{border-radius:0 0 var(--radius) var(--radius)}
table{width:100%;border-collapse:collapse;font-size:13px}
thead th{position:sticky;top:0;background:var(--bg1);color:var(--muted);text-align:left;padding:11px 14px;
  font-family:var(--font);font-size:11px;font-weight:600;letter-spacing:.4px;text-transform:uppercase;border-bottom:1px solid var(--line2);white-space:nowrap}
tbody td{padding:10px 14px;border-bottom:1px solid var(--line);color:var(--txt);white-space:nowrap}
tbody tr:hover{background:var(--accent-soft)}
#grid tbody tr:nth-child(even):not(.group-head){background:var(--bg1)}
#grid tbody tr:nth-child(even):not(.group-head):hover{background:var(--accent-soft)}
.tk-code{cursor:pointer;border-radius:6px;transition:.15s}
.tk-code:hover{background:var(--accent-soft);color:var(--accent)}
.tk-copy-ico{opacity:0;font-size:11px;margin-left:2px;transition:.15s}
.tk-code:hover .tk-copy-ico{opacity:.8}
.mono{font-family:var(--mono);color:var(--muted)}
/* grouped-rows header band (Group by ...) */
tr.group-head td{background:var(--bg1);border-top:1px solid var(--line2);border-bottom:1px solid var(--line2);
  padding:10px 14px;white-space:nowrap}
tr.group-head:first-child td{border-top:none}
.gh-label{font-weight:650;color:var(--txt);font-size:13px;letter-spacing:-.01em}
.gh-count{color:var(--muted);font-size:12px;margin-left:10px}
.gh-print{margin-left:14px;vertical-align:middle}
tbody tr.group-head:hover td{background:var(--bg1)}

/* selection checkbox column */
.row-chk{width:15px;height:15px;accent-color:var(--accent);cursor:pointer;vertical-align:middle;margin:0}

/* status severity dot (was an empty span with only a colour) */
.sev{display:inline-block;width:7px;height:7px;border-radius:50%;background:currentColor;
  margin-right:7px;vertical-align:middle;flex:0 0 auto}
.status{display:inline-flex;align-items:center}

.row-actions{display:flex;gap:4px;flex-wrap:nowrap;align-items:center;white-space:nowrap;max-width:none}
.row-actions .btn{margin:0;padding:5px 9px;font-size:11px;min-width:64px;text-align:center;box-sizing:border-box}
.row-actions .row-more{min-width:0;width:30px;height:30px;padding:0;font-size:16px;line-height:1;font-weight:700;
  display:inline-flex;align-items:center;justify-content:center;border-radius:8px}
/* Keep the Trash page's two tables (assets / contracts) column-aligned with each
   other -- table-layout:fixed + matching checkbox/action-column widths so
   RESTORE/DEL sit at the same spot in both cards no matter how wide each
   table's own content is (and never push the actions off-screen). */
#trashGrid,#ctTrashGrid{table-layout:fixed}
#trashGrid th:first-child,#trashGrid td:first-child,
#ctTrashGrid th:first-child,#ctTrashGrid td:first-child{width:36px}
#trashGrid th:last-child,#trashGrid td:last-child,
#ctTrashGrid th:last-child,#ctTrashGrid td:last-child{width:180px}
#trashGrid td:not(:first-child):not(:last-child),
#ctTrashGrid td:not(:first-child):not(:last-child){overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.row-menu{position:fixed;z-index:200;background:var(--surface);border:1px solid var(--line);border-radius:10px;
  box-shadow:var(--shadow);padding:5px;min-width:150px;display:flex;flex-direction:column;gap:1px}
.row-menu button{display:block;width:100%;text-align:left;background:transparent;border:none;border-radius:6px;
  padding:8px 10px;font-family:var(--font);font-size:12.5px;font-weight:600;color:var(--txt);cursor:pointer}
.row-menu button:hover{background:var(--accent-soft)}
.row-menu button.danger{color:var(--red)}
.row-menu button.danger:hover{background:rgba(255,56,96,.14)}
.row-menu-sep{height:1px;background:var(--line);margin:4px 2px}
.colpop{position:absolute;right:0;top:100%;margin-top:6px;z-index:50;background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);
  padding:12px;width:340px;max-width:calc(100vw - 40px);display:flex;flex-direction:column;gap:10px}
.colpop .cp-title{font-family:var(--font);font-size:11px;font-weight:600;letter-spacing:.4px;text-transform:uppercase;color:var(--muted);margin:0;padding-bottom:8px;border-bottom:1px solid var(--line)}
.colpop .cp-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:2px 10px;max-height:280px;overflow-y:auto;overflow-x:hidden}
.colpop .cp-item{display:flex;align-items:center;gap:8px;padding:6px 4px;font-size:13px;color:var(--txt);cursor:pointer;border-radius:6px;min-width:0}
.colpop .cp-item span{min-width:0;overflow-wrap:anywhere}
.colpop .cp-item:hover{background:var(--accent-soft)}
.colpop .cp-item input{accent-color:var(--accent);width:15px;height:15px;min-width:15px;min-height:15px;flex:0 0 auto;cursor:pointer;margin:0;padding:0;border-radius:3px}
.colpop .cp-actions{display:flex;gap:6px;padding-top:8px;border-top:1px solid var(--line)}
.colpop .cp-actions .btn{flex:1}
.toolbar{position:relative}
.tb-spacer{flex:1 1 auto;min-width:0}
.sec-hint{color:var(--muted);font-size:12.5px;line-height:1.55;margin:-2px 0 10px;max-width:76ch}
.sec-hint b{color:var(--txt);font-weight:600}
.grid2{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px 18px;align-items:start}
.grid3{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px 18px;align-items:start}
.refbox .ref2{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:6px}
.refbox .ref2 input{width:100%;background:var(--bg1);border:1px solid var(--line);color:var(--txt);border-radius:8px;padding:9px 11px;font-family:var(--font);font-size:13px}
.mlink{float:right;color:var(--accent);font-size:11px;cursor:pointer;font-family:var(--font);text-decoration:none}
.mlink:hover{text-decoration:underline}
.empty{padding:40px;text-align:center;color:var(--muted);font-family:var(--font)}

.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:14px;margin-bottom:18px}
.stat{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);padding:18px 16px;text-align:left;box-shadow:var(--shadow)}
.stat .k{font-size:11.5px;color:var(--muted);letter-spacing:.4px;text-transform:uppercase;font-weight:500;line-height:1.3}
.stat .v{font-family:var(--font);font-size:28px;font-weight:650;letter-spacing:-.02em;line-height:1.15;color:var(--accent);margin-top:6px}
.stat.c2 .v{color:var(--cyan)}
.stat.c3 .v{color:var(--grn)}
.stat.c4 .v{color:var(--amber)}
.stat.c5 .v{color:var(--red)}

/* modal */
.modal{position:fixed;inset:0;z-index:100;background:rgba(5,7,11,.78);display:none;align-items:center;
  justify-content:center;backdrop-filter:blur(4px);padding:clamp(12px,3vw,24px)}
#profileModal{z-index:200}
#usersModal{z-index:210}
#refModal{z-index:220}
.modal.show{display:flex}
.modal-box{background:var(--surface);border:1px solid var(--line2);border-radius:var(--radius);padding:clamp(16px,2.2vw,26px) clamp(16px,2.4vw,28px);
  width:min(640px,94vw);max-height:90vh;display:flex;flex-direction:column;box-shadow:var(--shadow);overflow:hidden}
.modal-box > h3:first-child{flex:0 0 auto;color:var(--accent);font-family:var(--font);font-size:clamp(15px,1.4vw,17px);font-weight:650;letter-spacing:-.01em;margin:0 0 clamp(12px,1.6vw,18px);
  padding-bottom:12px;border-bottom:1px solid var(--line)}
.modal-box > .modal-title-row{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:10px;margin:0 0 clamp(12px,1.6vw,18px);padding-bottom:12px;border-bottom:1px solid var(--line)}
.modal-box > .modal-title-row h3{margin:0;padding:0;border:0;color:var(--accent);font-family:var(--font);font-size:clamp(15px,1.4vw,17px);font-weight:650;letter-spacing:-.01em}
/* One scroll area per modal, and only one. This used to read
   ".modal-box > *:not(h3):not(.m-actions):not(.modal-title-row)", which gave
   EVERY direct child overflow:auto and flex:1 1 auto -- so a modal with a
   header bar, a body and a reply box grew three scrollbars that each clipped
   their own content and fought over the same 90vh. Every modal now wraps its
   body in .modal-scroll, so this applies exactly once. */
.modal-box > .modal-scroll{overflow-y:auto;overflow-x:hidden;flex:1 1 auto;min-height:0;
  display:flex;flex-direction:column;gap:12px;
  /* room for the scrollbar so panels don't run underneath it, and a stable
     gutter so content doesn't shift sideways when it appears */
  padding-right:8px;scrollbar-gutter:stable}
.modal-box > .modal-scroll > *{margin-bottom:0}
/* Anything still sitting directly under .modal-box is a header or a footer:
   it keeps its height and never scrolls. */
.modal-box > *:not(.modal-scroll){flex:0 0 auto}
/* A table inside the one scroll area must not open a second VERTICAL
   scrollbar -- but it still needs the horizontal one, or a table wider than
   the modal is simply clipped with no way to reach the rest of it. Dropping
   the height cap is what removes the vertical scroll: with no max-height the
   wrapper grows to fit its rows, so overflow-y never triggers. */
.modal-box .modal-scroll .tablewrap{max-height:none;overflow-x:auto;overflow-y:visible}
.modal-box .m-actions{flex:0 0 auto;position:sticky;bottom:0;background:var(--surface);display:flex;gap:10px;justify-content:flex-end;margin-top:18px;padding-top:14px;border-top:1px solid var(--line)}
.invbox{background:var(--surface2);border:1px solid var(--line);border-radius:10px;padding:14px;margin-bottom:12px}
.invbox label{display:block;font-family:var(--font);font-weight:700;letter-spacing:.6px;color:var(--muted);
  font-size:11px;margin:9px 0 5px;text-transform:uppercase}
.invbox input,.invbox select,.invbox textarea{width:100%;background:var(--bg1);border:1px solid var(--line);
  color:var(--txt);border-radius:8px;padding:9px 11px;font-family:var(--font);font-size:13px}
.invbox input:focus,.invbox select:focus,.invbox textarea:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.invrow{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.m-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:18px}

/* network scan modal: scan button + deep-scan toggle, prefix input reveals below only when relevant */
.scanrow{display:flex;flex-wrap:wrap;align-items:center;gap:10px}
.scanrow #scanBtn{flex:0 0 auto}
.scanrow .scan-deep{flex:0 0 auto;margin:0;white-space:nowrap}
.scan-prefix-row{margin-top:10px;animation:fade .2s ease}
.scan-prefix-row .scan-prefix{width:100%}
.scan-hint{margin:8px 0 0;font-size:12px}
.scan-status{min-height:16px;margin:10px 0 8px}

/* Product Catalog page */
.catalog-grid{gap:14px}
.cat-count{color:var(--muted);font-size:11px;font-weight:500;text-transform:none;letter-spacing:0;margin-left:4px}
.catrow{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:10px}
.catrow input{flex:1 1 160px;min-width:120px;width:auto}
.catrow select{flex:1 1 180px;min-width:160px}
.catrow button{flex:0 0 auto}
.dtable .col-del{width:1%;white-space:nowrap}

/* compact data tables inside modals (audit, maintenance, scan, backups) */
.modal-box .tablewrap{border-radius:var(--radius);max-height:min(46vh,380px);box-shadow:none;max-width:100%}
.modal-box .tablewrap.noscroll{max-height:none;overflow:visible}
/* backups list: fixed metadata columns that never wrap/reflow, one compact
   kebab-menu button for actions instead of a row of buttons -- a long
   filename plus 3 buttons was forcing the date/size columns to wrap
   mid-word and pushing the buttons off the edge of the modal. */
#backupModal .dtable td,#backupModal .dtable th{white-space:nowrap;padding-left:9px;padding-right:9px}
#backupModal .dtable td:first-child{color:var(--text)}
.muted{color:var(--muted)}
.m-actions .muted,.toolbar .muted,.dash-tools .muted{font-size:12.5px}

.dtable,.tbl{font-size:12.5px}
.dtable thead th,.tbl thead th{padding:9px 12px}
.dtable tbody td,.tbl tbody td{padding:8px 12px;white-space:normal;vertical-align:top;word-break:break-word;line-height:1.45}
.dtable tbody td:first-child,.tbl tbody td:first-child{white-space:nowrap;color:var(--muted)}
.dtable tbody tr:last-child td,.tbl tbody tr:last-child td{border-bottom:none}

/* history */
.histbox{max-height:180px;overflow:auto;background:var(--surface2);border:1px solid var(--line);border-radius:8px;padding:8px;font-family:var(--mono);font-size:12px}
.hist{display:flex;gap:8px;padding:4px 0;border-bottom:1px dashed var(--line)}
.hist .hfield{color:var(--accent);font-weight:700}
.hist .hnew{color:var(--grn)}
.hist .hold{color:var(--muted);text-decoration:line-through}
.hist .hmeta{color:var(--muted);font-size:11px;margin-left:6px}

/* toast */
.toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(30px);background:var(--surface);
  border:1px solid var(--accent);color:var(--accent);padding:11px 18px;border-radius:10px;
  font-family:var(--font);z-index:200;opacity:0;transition:.25s;pointer-events:none;box-shadow:var(--shadow)}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0)}

/* tabs (profile modal) */
.tabs{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:18px;border-bottom:1px solid var(--line);padding-bottom:12px}
.tab{background:transparent;border:1px solid var(--line);color:var(--muted);padding:8px 14px;border-radius:9px;
  cursor:pointer;font-weight:600;font-size:12px;font-family:var(--font);letter-spacing:.3px;transition:.15s}
.tab:hover{color:var(--txt);border-color:var(--accent)}
.tab.active{background:var(--accent-soft);color:var(--accent);border-color:var(--accent)}
.tabpane{display:none}
.tabpane.active{display:block}
.tabpane{padding:4px 0;display:flex;flex-direction:column;gap:14px}
.tabpane .field2{margin-bottom:0}
.chk{display:flex;align-items:center;gap:9px;color:var(--muted);font-size:13px;font-family:var(--font);cursor:pointer}
.chk input{width:16px;height:16px;accent-color:var(--accent);cursor:pointer}
.secthead{color:var(--accent);font-family:var(--font);font-size:12.5px;font-weight:650;letter-spacing:.2px;margin:6px 0 2px;
  padding-top:8px;border-top:1px dashed var(--line)}
.refcard{background:var(--surface2);border:1px solid var(--line);border-radius:10px;padding:14px;display:flex;align-items:center;justify-content:space-between;gap:12px}
.refcard .rc-info{font-size:12px;color:var(--muted);font-family:var(--font)}

.field2{display:flex;flex-direction:column;gap:6px;margin-bottom:0}
.field2 label{font-family:var(--font);font-weight:600;letter-spacing:.5px;color:var(--muted);font-size:11px;text-transform:uppercase}
.field2 input,.field2 select,.field2 textarea{width:100%;background:var(--bg1);border:1px solid var(--line);color:var(--txt);border-radius:8px;padding:8px 11px;font-family:var(--font);font-size:13px;min-height:38px}
.field2 input:focus,.field2 select:focus,.field2 textarea:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.field2 input:disabled,.field2 select:disabled,.field2 textarea:disabled,
.invbox input:disabled,.invbox select:disabled,.invbox textarea:disabled{
  opacity:.62;cursor:not-allowed;background:var(--surface2)}
.brandfoot{margin-top:14px;color:var(--muted);font-size:11px;font-family:var(--font)}
.count{font-family:var(--font);color:var(--muted);font-size:12.5px;white-space:nowrap;margin-right:2px}

/* tickets (osTicket-style) */
.prio{padding:2px 9px;border-radius:20px;font-size:11px;font-weight:700;letter-spacing:.3px}
.prio.Low{background:rgba(61,220,151,.15);color:#5fe0ac}
.prio.Normal{background:rgba(59,158,255,.15);color:#7cc0ff}
.prio.High{background:rgba(255,180,84,.16);color:#ffc879}
.prio.Urgent{background:rgba(255,107,107,.16);color:#ff9b9b}
.prio.Emergency{background:rgba(255,107,107,.25);color:#ff8585}
.status.s_Open{color:var(--accent)}.status.s_InProgress{color:var(--amber)}.status.s_Pending{color:var(--muted2)}.status.s_Resolved{color:var(--grn)}.status.s_Closed{color:var(--muted)}
.tkreplies{display:flex;flex-direction:column;gap:8px;max-height:240px;overflow:auto;margin-bottom:12px}
.rep{background:var(--surface2);border:1px solid var(--line);border-radius:8px;padding:9px 12px;font-size:13px}
.rep.agent{border-left:3px solid var(--accent)}
.rep.requester{border-left:3px solid var(--amber)}
.repmeta{color:var(--muted);font-size:11px;margin-bottom:4px}
.replybox label{display:block;font-family:var(--font);font-weight:600;letter-spacing:.6px;color:var(--muted);margin-bottom:6px}
.replybox textarea{width:100%;background:var(--bg1);color:var(--txt);border:1px solid var(--line);border-radius:6px;padding:9px;font-family:var(--font);resize:vertical}
.rowassign{background:var(--bg1);color:var(--txt);border:1px solid var(--line);border-radius:6px;padding:5px 7px;font-size:13px;max-width:150px}
.loclist{list-style:none;padding:0;max-width:480px}
.loclist li{display:flex;justify-content:space-between;align-items:center;padding:9px 13px;border:1px solid var(--line);border-radius:8px;margin-bottom:6px;background:var(--surface)}

/* =====================================================================
   NAV GROUP LABELS + CUSTOMIZATION / PROFILE / SETTINGS PAGES
   ===================================================================== */
.nav-grp{text-transform:uppercase;font-size:10px;letter-spacing:1px;color:var(--muted2);
  padding:10px 12px 4px;font-family:var(--font);font-weight:700;white-space:nowrap}

/* generic page panel */
.panel{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);
  padding:16px 18px 20px;margin-bottom:16px;box-shadow:var(--shadow);
  display:flex;flex-direction:column;gap:14px}
.panel > .secthead{margin-top:0;padding-top:0;border-top:none}
.panel .spacer{flex:1}

/* preset theme cards */
.preset-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:12px}
.preset-card{display:flex;flex-direction:column;align-items:flex-start;gap:6px;text-align:left;
  background:var(--surface2);border:1px solid var(--line);border-radius:var(--radius);
  padding:12px;cursor:pointer;color:var(--txt);font-family:var(--font);transition:.16s}
.preset-card:hover{border-color:var(--line2);transform:translateY(-1px)}
.preset-card.active{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.preset-card .pc-swatch{display:block;width:100%;height:38px;border-radius:8px;border:1px solid var(--line)}
.preset-card .pc-name{font-weight:700;font-size:14px;letter-spacing:.3px}
.preset-card .pc-sub{font-size:11px;color:var(--muted);font-family:var(--font)}

/* live preview */
.preview-box{border:1px dashed var(--line2);border-radius:var(--radius);padding:16px;background:var(--bg1)}
.preview-box .pv-card{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);
  padding:16px;display:flex;flex-direction:column;gap:10px;box-shadow:var(--shadow)}
.preview-box .pv-title{font-family:var(--font);font-size:14px;font-weight:650;letter-spacing:-.01em;color:var(--accent)}
.preview-box .pv-text{font-size:13px;color:var(--muted);line-height:1.5}
.preview-box .pv-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.preview-box .pv-chip{padding:3px 10px;border-radius:20px;font-size:11px;font-family:var(--font);
  background:var(--accent-soft);color:var(--accent);border:1px solid var(--accent)}

/* profile page */
.avatar-prev{width:64px;height:64px;border-radius:50%;display:flex;align-items:center;justify-content:center;
  background:linear-gradient(135deg,var(--accent),var(--accent2));color:#fff;font-family:var(--font);
  font-size:22px;font-weight:700;overflow:hidden;flex:0 0 auto;border:1px solid var(--line)}
.avatar-prev img{width:100%;height:100%;object-fit:cover;display:block}
.refcard .rc-left{display:flex;align-items:center;gap:12px}
.refcard input[type=file]{color:var(--muted);font-size:12px;font-family:var(--font);max-width:230px}
.refcard input#pApiKey{background:var(--bg1);border:1px solid var(--line);color:var(--txt);
  border-radius:8px;padding:8px 11px;font-size:12px;min-height:38px}
.sesslist{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:6px}
.sesslist li{display:flex;justify-content:space-between;gap:14px;align-items:baseline;
  padding:9px 13px;border:1px solid var(--line);border-radius:8px;background:var(--surface2)}
.sesslist .sl-k{font-family:var(--font);font-weight:700;font-size:11px;letter-spacing:.6px;
  text-transform:uppercase;color:var(--muted)}
.sesslist .sl-v{font-family:var(--mono);font-size:12px;color:var(--txt);
  text-align:right;word-break:break-all}

/* the explanation line under a field: unit, default, computed preview */
.field2 .fhint{font-size:11px;color:var(--muted);line-height:1.4;min-height:15px;margin-top:-1px}

/* range + color inputs inside .field2 */
/* Fixed, theme-independent border -- var(--line) can be near-white on the
   Clean Light preset, which made a white/near-white swatch value (e.g. the
   background-color picker) blend invisibly into the panel behind it. */
.field2 input[type=color]{padding:3px;height:38px;cursor:pointer;border:2px solid #8a93a6;box-shadow:0 0 0 1px rgba(0,0,0,.15) inset}
.field2 input[type=range]{padding:0;min-height:auto;height:26px;accent-color:var(--accent);cursor:pointer;background:transparent;border:none}

/* ---- responsive ---- */
@media(max-width:980px){
  .side{flex:0 0 64px;width:64px;padding:16px 6px}
  .logo{justify-content:center;gap:0;padding:6px 0 16px;font-size:0}
  .logo img{width:30px;height:30px}
  #sideName{display:none}
  .userbox{justify-content:center;padding:6px}
  .userbox > div:not(.avatar){display:none}
  /* font-size:0 was meant to drop the word LOGOUT on the narrow rail, but
     it collapses the power glyph with it -- so the only way out of the
     app became an invisible sliver. Hide the label, keep the icon. */
  .logout{font-size:0;padding:10px 0;min-height:40px}
  .logout::before{content:"⏻";font-size:18px;line-height:1}
  .side-foot{gap:6px}
  /* nav labels are bare text nodes — collapse them, keep the .ni glyph */
  .nav a{justify-content:center;padding:10px 0;font-size:0;gap:0}
  .nav a span.ni{font-size:16px}
  .nav a span:not(.ni){display:none}
  .nav-grp{text-align:center;padding:8px 2px 2px;font-size:8px;letter-spacing:.5px;overflow:hidden}
  .main{padding:16px 14px}
  .preset-grid{grid-template-columns:repeat(auto-fit,minmax(140px,1fr))}
}
@media(max-width:760px){
  .grid2{grid-template-columns:minmax(0,1fr)}
  .grid3{grid-template-columns:minmax(0,1fr)}
  .label-fields-grid{grid-template-columns:minmax(0,1fr)}
  .topbar{flex-wrap:wrap;gap:10px}
  .topbar .spacer{flex-basis:100%;height:0}
  .toolbar{flex-wrap:wrap}
  .panel{padding:13px 13px 16px}
  .refcard{flex-wrap:wrap}
  .refcard input[type=file]{max-width:100%}
  .sesslist li{flex-direction:column;align-items:flex-start;gap:3px}
  .sesslist .sl-v{text-align:left}
}

.dash-tools{display:flex;align-items:center;gap:10px;margin:14px 0 4px}
/* Column count is the user's, defaulting to the two it always was. */
#dashWidgets{display:grid;grid-template-columns:repeat(var(--dashcols,2),minmax(0,1fr));gap:16px;align-items:start}
#dashWidgets .widget{position:relative;min-width:0}
#dashWidgets .widget[data-wkey="feed"]{grid-column:1/-1}
#dashWidgets .widget.wlink{grid-column:auto}
body.edit-layout #dashWidgets .widget{cursor:grab;outline:1px dashed var(--accent);outline-offset:-4px}
body.edit-layout #dashWidgets .widget::before{content:"⠿";position:absolute;top:8px;right:10px;color:var(--accent);font-size:16px;opacity:.7;z-index:2}
body.edit-layout #dashWidgets .widget:hover{outline-color:var(--grn)}
.widget.dragging{opacity:.4;outline:2px solid var(--grn);transform:scale(.98)}
.cfgwrap{display:grid;grid-template-columns:224px minmax(0,1fr);gap:18px;align-items:start}
.cfgnav{display:flex;flex-direction:column;gap:4px;background:var(--surface);border:1px solid var(--line);
  border-radius:var(--radius);padding:10px;position:sticky;top:0}
.cfgitem{text-align:left;background:transparent;border:1px solid transparent;color:var(--muted);padding:10px 12px;
  border-radius:8px;cursor:pointer;font-family:var(--font);font-weight:500;font-size:13.5px;letter-spacing:0;transition:.15s}
.cfgitem:hover{background:var(--accent-soft);color:var(--txt)}
.cfgitem.active{background:var(--accent-soft);color:var(--accent);border-color:rgba(255,59,48,.35);
  font-weight:600;box-shadow:inset 2px 0 0 var(--accent)}
.cfgcontent{min-width:0}
.cfg-sec{animation:fade .2s ease}
.toggle{display:flex;align-items:center;gap:10px;padding:10px 12px;background:var(--surface2);border:1px solid var(--line);border-radius:8px;cursor:pointer;margin-bottom:8px;font-weight:600;font-size:14px}
.toggle:hover{border-color:var(--accent)}
.toggle input{accent-color:var(--accent);width:16px;height:16px;cursor:pointer}
.toggle input:disabled{cursor:not-allowed}
.toggle:has(input:disabled){opacity:.65;cursor:not-allowed}
.label-fields-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin-top:8px}
.label-fields-grid .toggle{margin-bottom:0;font-size:13px;padding:9px 11px}
@keyframes fade{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
/* KPI cards */
.kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(185px,1fr));gap:14px;margin-bottom:16px}
.kpi{background:linear-gradient(160deg,var(--surface),var(--surface2));border:1px solid var(--line);border-radius:var(--radius);
  padding:16px 16px;display:flex;align-items:center;gap:13px;box-shadow:var(--shadow);position:relative;overflow:hidden}
.kpi::after{content:"";position:absolute;right:-18px;top:-18px;width:70px;height:70px;border-radius:50%;
  background:radial-gradient(circle,var(--accent-soft),transparent 70%)}
.kpi-ico{font-size:20px;width:40px;height:40px;display:flex;align-items:center;justify-content:center;border-radius:11px;
  background:var(--bg1);border:1px solid var(--line);flex:0 0 40px}
.kpi-body{min-width:0}
.kpi-v{font-family:var(--font);font-size:26px;font-weight:650;letter-spacing:-.02em;line-height:1.1;color:var(--accent)}
.kpi-k{font-size:11.5px;color:var(--muted);letter-spacing:.4px;text-transform:uppercase;font-weight:500;margin-top:6px}
.kpi.c1 .kpi-v{color:var(--accent)} .kpi.c2 .kpi-v{color:var(--cyan)}
.kpi.c3 .kpi-v{color:var(--grn)} .kpi.c4 .kpi-v{color:var(--amber)} .kpi.c5 .kpi-v{color:var(--red)}
.kpi.c2 .kpi-ico{color:var(--cyan)} .kpi.c3 .kpi-ico{color:var(--grn)} .kpi.c4 .kpi-ico{color:var(--amber)} .kpi.c5 .kpi-ico{color:var(--red)}
/* horizontal bars */
.bars{display:flex;flex-direction:column;gap:11px}
.bar .bar-top{display:flex;justify-content:space-between;font-size:12.5px;margin-bottom:5px;color:var(--muted)}
.bar .bar-top b{color:var(--txt);font-family:var(--font)}
.bar-track{height:8px;background:var(--bg1);border-radius:6px;overflow:hidden;border:1px solid var(--line)}
.bar-fill{height:100%;border-radius:6px;transition:width .6s ease}
/* activity feed */
.feed{display:flex;flex-direction:column;gap:2px;max-height:340px;overflow:auto}
.feed-item{display:flex;gap:11px;padding:10px 4px;border-bottom:1px solid var(--line)}
.fi-dot{width:9px;height:9px;border-radius:50%;background:var(--accent);margin-top:5px;flex:0 0 9px;box-shadow:0 0 0 3px var(--accent-soft)}
.fi-body{min-width:0}
.fi-text{font-size:13px;color:var(--txt)}
.fi-meta{font-size:11px;color:var(--muted2);font-family:var(--font);margin-top:2px}
@media(max-width:820px){ #dashWidgets{grid-template-columns:1fr} }
@media(max-width:900px){
  .cfgwrap{grid-template-columns:minmax(0,1fr)}
  .cfgnav{position:static;flex-direction:row;flex-wrap:wrap}
  .cfgitem{flex:1 1 auto;text-align:center;box-shadow:none}
  .cfgitem.active{box-shadow:none}
}
@media(max-width:980px){
  .kpis{grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}
  .stats{grid-template-columns:repeat(auto-fit,minmax(150px,1fr))}
}

/* Danger Zone -- deliberately loud/over-the-top, this is the one place in
   the app that should NOT look calm and professional */
.cfgitem-danger{color:#ff3b30!important;font-weight:700}
.danger-zone-panel{border:2px solid #ff3b30;background:linear-gradient(135deg,rgba(255,59,48,.12),rgba(0,0,0,.25));
  border-radius:var(--radius);position:relative;overflow:hidden}
.danger-zone-panel::before{content:"";position:absolute;inset:0;pointer-events:none;
  background:repeating-linear-gradient(45deg,rgba(255,59,48,.07) 0 10px,transparent 10px 20px)}
.danger-zone-panel .secthead{letter-spacing:.5px}
.danger-cta{margin-top:16px}
.danger-wipe-btn{background:linear-gradient(135deg,#ff3b30,#c0392b)!important;color:#fff!important;
  font-weight:800;letter-spacing:.6px;text-transform:uppercase;border:none;
  animation:dangerPulse 1.6s ease-in-out infinite}
.danger-wipe-btn:hover{filter:brightness(1.1)}
.danger-wipe-btn:disabled{animation:none;opacity:.35;cursor:not-allowed;filter:none}
@keyframes dangerPulse{0%,100%{box-shadow:0 0 0 0 rgba(255,59,48,.5)}50%{box-shadow:0 0 0 12px rgba(255,59,48,0)}}
.danger-modal{border:2px solid #ff3b30!important}

/* print (labels) */
@media print{
  body{overflow:visible}
  .side,.topbar,.toolbar,.nav,.scanlines,.vignette,.toast{display:none!important}
  @page{size:50mm 19mm;margin:0}
  #labelPrint{width:50mm;height:19mm}
}

/* ---- version & one-click update ---- */
.verbadge{width:42px;height:42px;border-radius:12px;display:grid;place-items:center;
  font-size:19px;background:var(--surface2);border:1px solid var(--line2);
  color:var(--muted);transition:color .25s,border-color .25s}
.verbadge.has-update{color:var(--accent);border-color:var(--accent);
  animation:verPulse 2.2s ease-out infinite}
@keyframes verPulse{
  0%{box-shadow:0 0 0 0 var(--accent-soft)}
  70%{box-shadow:0 0 0 13px transparent}
  100%{box-shadow:0 0 0 0 transparent}
}
.updcard{border:1px solid var(--accent);border-radius:var(--radius);padding:14px;
  background:linear-gradient(180deg,var(--accent-soft),transparent 72%)}
.updhead{display:flex;gap:12px;align-items:center;margin-bottom:13px}
.updring{width:44px;height:44px;flex:0 0 44px;border-radius:50%;display:grid;
  place-items:center;font-size:20px;color:var(--accent);background:var(--surface2);
  border:1px solid var(--accent);animation:verPulse 2.2s ease-out infinite}
.updver{font-size:17px;letter-spacing:.3px;margin-bottom:2px}
.updver b{color:var(--accent)}
.updacts{justify-content:flex-start;flex-wrap:wrap;margin:0 0 9px}
.updhint{color:var(--muted);font-size:12.5px;line-height:1.55}
.updlog{background:var(--surface2);border:1px solid var(--line);border-radius:8px;
  padding:10px;margin:10px 0 0;white-space:pre-wrap;font-size:12px;
  max-height:220px;overflow:auto}
.updwarn{color:var(--amber);border-left:2px solid var(--amber);padding-left:10px;
  line-height:1.5}
.updacts a.btn{display:inline-flex;align-items:center;text-decoration:none}

/* a read-only value that sits in a .field2 where an input would go */
.ro{background:var(--bg1);border:1px solid var(--line);border-radius:8px;
  padding:9px 11px;font-size:13px;color:var(--muted);min-height:38px;
  display:flex;align-items:center;overflow-wrap:anywhere}

/* permission tree: one column per module group, leaves as checkboxes. A role
   is assembled from these rather than from a level per module, so the list
   has to stay scannable at ~40 leaves. */
.permtree{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));
  gap:10px;align-items:start}
.permgrp{background:var(--surface2);border:1px solid var(--line);border-radius:10px;padding:9px 11px 10px}
.permhead{display:flex;align-items:center;justify-content:space-between;gap:8px;
  padding-bottom:6px;margin-bottom:6px;border-bottom:1px solid var(--line)}
.permhead .chk{gap:7px}
.permhead .chk span{font-family:var(--font);font-weight:650;font-size:11px;
  letter-spacing:.4px;text-transform:uppercase;color:var(--txt);line-height:1.25}
.permcount{font-family:var(--font);font-size:10px;color:var(--muted);white-space:nowrap}
.permitems{display:flex;flex-direction:column;gap:3px}
.permitem{gap:7px;padding:1px 0}
.permitem input{width:14px;height:14px}
.permitem span{font-size:12px;line-height:1.3;color:var(--muted)}
.permitem input:checked + span{color:var(--txt)}
.permnote{font-style:normal;font-size:9.5px;letter-spacing:.06em;text-transform:uppercase;
  color:var(--amber);border:1px solid var(--amber);border-radius:4px;padding:0 3px;
  margin-left:4px;opacity:.85}
.permtag{display:inline-block;font-family:var(--font);font-size:10.5px;padding:2px 7px;
  border-radius:20px;background:var(--accent-soft);color:var(--accent);white-space:nowrap}
@media(max-width:560px){.permtree{grid-template-columns:minmax(0,1fr)}}

/* Phones. The 64px icon rail from the 980px breakpoint is fine on a tablet
   but eats a sixth of a 375px screen, which left 283px for the content --
   the tables were containing their own overflow correctly, they just had
   almost nowhere to do it. Narrowing the rail and the gutters gives roughly
   40px back, and tighter cells fit another column before anyone has to
   scroll sideways. */
@media(max-width:560px){
  .side{flex:0 0 48px;width:48px;padding:12px 4px}
  .logo img{width:26px;height:26px}
  .nav a{padding:9px 0}
  .nav a span.ni{font-size:15px}
  .main{padding:12px 9px}
  .topbar h2{font-size:17px}
  .crumb{font-size:9.5px}
  thead th{padding:9px 9px;font-size:10px}
  tbody td{padding:8px 9px}
  table{font-size:12.5px}
  .dtable thead th,.tbl thead th{padding:8px 9px}
  /* one comfortable row of buttons rather than four cramped ones */
  .m-actions{flex-wrap:wrap}
  .m-actions .btn{flex:1 1 auto;min-height:42px}
  .modal-box{padding:14px 13px}
  .invbox{padding:11px}
  .kpis,.stats{grid-template-columns:minmax(0,1fr)}
  /* the count and the search box were sharing a line and squeezing each other */
  .toolbar{gap:8px}
  .toolbar input[type=search],.toolbar input[type=text]{flex:1 1 100%}
}
/* the roles list is short; a scroll frame around three rows is just noise */
#rolesBody td{padding-top:7px;padding-bottom:7px}

/* Heartbeat: monitor status. A dot plus the word, not a
   colour alone -- the state has to survive being read by someone who cannot
   distinguish red from green. */
.hbdot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:7px;
  vertical-align:middle;background:var(--muted2)}
.hbdot.up{background:var(--grn)}
.hbdot.down{background:var(--red);box-shadow:0 0 0 3px rgba(255,107,107,.18)}
.hbdot.pending{background:var(--amber)}
.hbdot.maintenance{background:var(--cyan)}
.hbcounts{display:inline-flex;gap:6px;flex-wrap:wrap;align-items:center}
.hbpill{font-family:var(--font);font-size:10.5px;font-weight:650;letter-spacing:.03em;
  text-transform:uppercase;padding:2px 8px;border-radius:20px;white-space:nowrap;
  background:var(--surface2);color:var(--muted);border:1px solid var(--line)}
.hbpill.up{color:var(--grn);border-color:var(--grn)}
.hbpill.down{color:var(--red);border-color:var(--red)}
.hbpill.pending{color:var(--amber);border-color:var(--amber)}
.hbpill.maintenance{color:var(--cyan);border-color:var(--cyan)}
.hbdot.paused{background:var(--muted2)}
.hbpill.paused{color:var(--muted);border-color:var(--line)}

/* The KPI colours elsewhere are positional (c1..c5). Here the number means a
   state, so red has to mean down and green has to mean up -- a green "1 DOWN"
   reads as reassuring, which is the opposite of true. */
.kpi.hbk-down .kpi-v,.kpi.hbk-down .kpi-ico{color:var(--red)}
.kpi.hbk-pending .kpi-v,.kpi.hbk-pending .kpi-ico{color:var(--amber)}
.kpi.hbk-up .kpi-v,.kpi.hbk-up .kpi-ico{color:var(--grn)}
.kpi.hbk-paused .kpi-v,.kpi.hbk-paused .kpi-ico{color:var(--muted)}
.kpi.hbk-avg .kpi-v,.kpi.hbk-avg .kpi-ico{color:var(--cyan)}

/* The ECG trace. One heartbeat per recorded check, oldest on the left: an up
   check draws a QRS complex, a failed check draws a flatline. The bright
   segment travelling along it is the bedside-monitor idea -- it says the thing
   is being watched right now. The trace itself is history and does not move,
   which matters: a moving trace would imply data that is not there. */
.hbecg{display:block;overflow:visible}
.hbecg path{fill:none;stroke-linecap:round;stroke-linejoin:round;vector-effect:non-scaling-stroke}
.hbecg .hbecg-base{stroke:currentColor;stroke-width:1.5;opacity:.28}
.hbecg .hbecg-live{stroke:currentColor;stroke-width:1.9;
  stroke-dasharray:130 870;stroke-dashoffset:1000;
  filter:drop-shadow(0 0 3px currentColor);
  animation:hbsweep 2.6s linear infinite}
.hbecg.up{color:var(--grn)}
.hbecg.down{color:var(--red)}
.hbecg.pending{color:var(--amber)}
.hbecg.paused{color:var(--muted2)}
/* a paused monitor is not being checked, so nothing should be sweeping */
.hbecg.paused .hbecg-live{animation:none;stroke-dasharray:none;stroke-dashoffset:0;
  opacity:.5;filter:none}
/* down: slower sweep, so a flatline reads as a flatline rather than as activity */
.hbecg.down .hbecg-live{animation-duration:3.6s}
.hbecg.idle .hbecg-live{animation-duration:4.4s;opacity:.7}
@keyframes hbsweep{from{stroke-dashoffset:1000}to{stroke-dashoffset:0}}
.hbecg-cell{width:1%;white-space:nowrap;padding-top:4px;padding-bottom:4px}
.hblink{cursor:pointer}
.hblink:hover b{color:var(--accent)}
.hbtag{display:inline-block;margin-left:6px;padding:1px 6px;border-radius:20px;
  font-size:9.5px;font-weight:650;letter-spacing:.04em;text-transform:uppercase;
  background:var(--surface2);color:var(--muted);border:1px solid var(--line)}
.hbcert-warn{color:var(--amber)}
.hbcert-bad{color:var(--red);font-weight:650}

/* monitor detail */
.hbd-head{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:12px}
.hbd-state{font-weight:650;letter-spacing:.03em}
.hbd-kpis{margin-bottom:14px}
.hbd-kpis .kpi-v{font-size:20px}
/* uptime carries a verdict, so it is coloured like one -- the default accent
   made a healthy 100% render in alarm red */
.hbd-kpis .kpi-v.ok{color:var(--grn)}
.hbd-kpis .kpi-v.warn{color:var(--amber)}
.hbd-kpis .kpi-v.bad{color:var(--red)}
.hbd-kpis .kpi-v.none{color:var(--muted)}
.hbd-ecg .hbecg{width:100%;height:64px}
.hbd-chart{overflow-x:auto}
.hbchart{display:block;width:100%;min-width:420px;height:120px}
.hbchart .hbc-axis{stroke:var(--line);stroke-width:1}
.hbchart .hbc-line{fill:none;stroke:var(--cyan);stroke-width:1.8;stroke-linejoin:round}
.hbchart .hbc-area{fill:var(--cyan);opacity:.12;stroke:none}
.hbchart .hbc-down{fill:var(--red);opacity:.16}
.hbchart .hbc-lbl{fill:var(--muted);font-size:9px;font-family:var(--mono,monospace)}

/* a monitor's alert-channel picker reuses the permission-tree item styling */
.permitem-wrap{display:flex;flex-direction:column;gap:2px}

@media (prefers-reduced-motion: reduce){
  .hbecg .hbecg-live{animation:none;stroke-dasharray:none;stroke-dashoffset:0;filter:none}
}

/* The reason a monitor is down belongs next to its name. Kept to one line:
   a socket error can be a paragraph long, and letting it wrap turned one bad
   row into half the table. The full text is in the title attribute. */
.hberr{font-size:11px;color:var(--red);margin-top:3px;max-width:260px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:400}
.hbmute{margin-left:6px;opacity:.65;font-size:11px}
.hb-down td{background:rgba(255,107,107,.06)}
#page-heartbeat .topbar select,#page-heartbeat .topbar .toggle{flex:0 0 auto}
#page-heartbeat .dtable{min-width:1060px}
#page-heartbeat .dtable td{vertical-align:middle}
#page-heartbeat .dtable td:nth-child(2){min-width:190px}

/* phones: the strip and the actions are what run out of room first */
@media(max-width:560px){
  .hbecg{width:90px}
  .hberr{max-width:160px}
}

/* ticket photos: a grid of thumbnails, each a link to the full-size image.
   Same shape as the portal's picker so a photo looks the same to the person
   who sent it and the person fixing it. */
.shots{display:flex;flex-wrap:wrap;gap:10px}
.shot{position:relative;width:84px;height:84px;border-radius:8px;overflow:hidden;
  border:1px solid var(--line);background:var(--bg1)}
.shot a{display:block;width:100%;height:100%}
.shot img{width:100%;height:100%;object-fit:cover;display:block}
.shot .x{position:absolute;top:3px;right:3px;width:20px;height:20px;padding:0;
  border:0;border-radius:50%;background:rgba(0,0,0,.66);color:#fff;font-size:14px;
  line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center}
.shot .x:hover{background:var(--red)}
.shot .nm{position:absolute;left:0;right:0;bottom:0;background:rgba(0,0,0,.62);
  color:#fff;font-size:9.5px;padding:2px 4px;white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis;pointer-events:none}

/* ticket detail: the modal reuses the asset form's invbox / grid2 / field2
   blocks, so all that is left here is a scroll frame for the change trail --
   it reuses .hist from the asset history and only needs to be stopped from
   stretching the modal when a ticket has been through a lot of hands. */
.tkhist{max-height:210px;min-height:46px;overflow:auto;border-radius:8px;padding:4px 10px;
  background:var(--bg1);border:1px solid var(--line)}
.tkhist .hist:last-child{border-bottom:none}
.tkhist .muted{padding:10px 2px}

/* The update command is the thing people reach for, so it behaves like a
   button: whole block copies on click. */
.updcmd{cursor:pointer;word-break:break-all;white-space:pre-wrap}
.updcmd:hover{border-color:var(--accent);color:var(--txt)}

/* ---------- customisable dashboard ---------- */
.dashcols{display:inline-flex;align-items:center;gap:6px;color:var(--muted);font-size:12px}
.dashcols select{background:var(--surface2);color:var(--txt);border:1px solid var(--line);
  border-radius:6px;padding:4px 6px}
/* Hidden widgets vanish in normal use, and show ghosted while editing so they
   can be dragged and brought back. */
/* Removed is removed -- it returns through + ADD WIDGET, not by hunting for a
   greyed-out panel that is still taking up the grid. */
#dashWidgets .widget.w-hidden{display:none}
.wtools{position:absolute;top:6px;right:32px;z-index:3;display:flex;gap:4px}
.wtool{width:24px;height:24px;line-height:1;border:1px solid var(--line);
  background:var(--surface2);color:var(--muted);border-radius:7px;cursor:pointer;
  font-size:12px;padding:0;display:flex;align-items:center;justify-content:center;
  transition:background .12s,color .12s,border-color .12s}
.wtool:hover{color:var(--btn-text,#04121f);background:var(--accent);border-color:var(--accent)}
.wkill:hover{color:#fff;background:var(--red,#e03b3b);border-color:var(--red,#e03b3b)}
/* A link tile: the whole body is the target, so it is a big easy hit. */
.wlink-body{display:flex;align-items:center;gap:12px;text-decoration:none;color:inherit;
  padding:6px 2px 2px}
.wlink-ico{width:40px;height:40px;flex:0 0 auto;object-fit:contain;border-radius:8px;
  background:var(--surface2);padding:4px}
.wlink-ico-none{display:flex;align-items:center;justify-content:center;font-size:20px}
.wlink-txt{min-width:0}
.wlink-host{font-weight:700;font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.wlink-state{font-size:12px;color:var(--muted);margin-top:3px}
.wlink-body:hover .wlink-host{color:var(--accent)}
.wmlist{display:flex;flex-wrap:wrap;gap:8px}
.wmicon{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.wmicon img{width:32px;height:32px;object-fit:contain;border:1px solid var(--line);
  border-radius:6px;background:var(--surface2);padding:2px}
/* Edit mode reads as a mode: a faint board behind the grid, so it is obvious
   that panels are movable and that the chrome on them is temporary. */
body.edit-layout #dashWidgets{padding:10px;border-radius:12px;
  background:
    linear-gradient(var(--line) 1px, transparent 1px) 0 0/100% 28px,
    var(--surface2);
  background-blend-mode:soft-light;outline:1px dashed var(--line)}
body.edit-layout #dashWidgets .widget{transition:transform .12s,box-shadow .12s}
body.edit-layout #dashWidgets .widget:hover{transform:translateY(-1px);
  box-shadow:0 6px 18px rgba(0,0,0,.28)}
/* Panels in a row should end level. Tables inside them already scroll, so a
   uniform height reads as a grid rather than a ragged staircase. */
#dashWidgets .widget{display:flex;flex-direction:column}
#dashWidgets .widget > .tablewrap{flex:1;min-height:0}

/* A link tile is a target, not a document: it gets the whole panel and a
   little lift, so it looks pressable next to the data panels. */
#dashWidgets .widget.wlink{padding-bottom:12px}
.wlink-body{border-radius:10px;transition:background .12s}
.wlink-body:hover{background:var(--surface2)}

@media(max-width:820px){
  /* One column is right on a phone, but not a column of full-height panels:
     cap the tall ones so the page can be thumbed through instead of scrolled
     past, and let the link tiles pair up two-across since they are short. */
  #dashWidgets{grid-template-columns:1fr!important;gap:12px}
  #dashWidgets .widget > .tablewrap{max-height:240px!important}
  #dashWidgets .widget[data-wkey="feed"] .feed{max-height:260px;overflow:auto}
  .dash-tools{flex-wrap:wrap;gap:8px}
  .wtools{right:8px}
}
@media(max-width:560px){
  #dashWidgets{gap:10px}
  #dashWidgets .widget{padding:12px 12px 14px}
  .wlink-ico{width:34px;height:34px}
  .wlink-host{font-size:13.5px}
}

.dashfilter{background:var(--surface2);color:var(--txt);border:1px solid var(--line);
  border-radius:8px;padding:6px 10px;font-size:13px;min-width:150px;max-width:230px}
.dashfilter:focus{outline:none;border-color:var(--accent)}
/* Filtering hides non-matches outright rather than dimming them: a dashboard
   half full of greyed-out panels is harder to read than a short one. */
#dashWidgets .widget.w-nomatch{display:none}
.wlink-sub{font-size:11.5px;color:var(--muted2,var(--muted));margin-top:1px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* The base .dashfilter rule is defined further down this file than the mobile
   block above, so an override there could never win. On a phone the filter
   takes its own full-width row under the buttons. */
@media(max-width:820px){
  .dashfilter{flex:1 1 100%;max-width:none;order:99}
}

/* ---------- Lost & Found ---------- */
/* Read on a phone, standing next to whoever is asking about the laptop: the
   number has to be tappable and the status has to be changeable without
   hunting for a row. */
.lftools{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.lfsel{background:var(--surface2);color:var(--txt);border:1px solid var(--line);
  border-radius:8px;padding:6px 10px;font-family:var(--font);font-size:13px;min-height:34px}
.lfsel:focus{outline:none;border-color:var(--accent)}
.lf-asset{font-weight:600}
.lf-who{font-weight:600}
.lf-tel{display:inline-block;font-family:var(--mono);font-size:13.5px;color:var(--accent);
  text-decoration:none;margin-top:2px}
.lf-tel:hover{text-decoration:underline}
.lf-note{font-size:12px;color:var(--muted);margin-top:3px;max-width:34ch}
.lf-badge{display:inline-block;font-size:10.5px;font-weight:700;letter-spacing:.7px;
  padding:3px 8px;border-radius:999px;border:1px solid currentColor;white-space:nowrap}
.lf-lost{color:var(--red,#ff5a5f)}
.lf-found{color:var(--amber,#f0a020)}
.lf-returned{color:var(--grn,#3fbf6f)}
.lf-set{display:block;margin-top:6px;background:var(--surface2);color:var(--txt);
  border:1px solid var(--line);border-radius:8px;padding:4px 8px;font-size:12px;
  font-family:var(--font);min-height:30px;max-width:140px}
.lf-set:focus{outline:none;border-color:var(--accent)}

/* ---------- notification types ---------- */
/* Two columns of checkboxes on a desktop; one on a phone. The event key is
   shown because it is what turns up in a webhook payload. */
.ntypes{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:2px 18px}
.ntype{display:flex;align-items:center;gap:8px;min-width:0}
.ntype span{flex:1 1 auto;min-width:0}
.nkey{font-family:var(--mono);font-size:10.5px;color:var(--muted);opacity:.8;
  white-space:nowrap}

@media(max-width:820px){
  .ntypes{grid-template-columns:minmax(0,1fr)}
  .nkey{display:none}
}
@media(max-width:760px){
  /* The table becomes cards: a phone has no room for six columns, and the
     horizontal scroll a .tablewrap would otherwise give hides the status
     control off the right edge -- which is the one thing this page is for. */
  #lfGrid thead{display:none}
  #lfGrid, #lfGrid tbody, #lfGrid tr, #lfGrid td{display:block;width:auto}
  #lfGrid tr{border:1px solid var(--line);border-radius:var(--radius);
    padding:10px 12px;margin-bottom:10px;background:var(--surface2)}
  #lfGrid td{border:none;padding:3px 0}
  #lfGrid td:first-child{font-size:11px;color:var(--muted)}
  .lf-note{max-width:none}
  .lf-set{max-width:none;width:100%;min-height:38px;font-size:14px}
  .lftools{width:100%}
  .lfsel{flex:1 1 auto}
  #page-lostfound .topbar .btn{width:100%}
}

/* Hidden widgets wait here while the layout is being edited, one click from
   coming back. Nothing on this dashboard is created any more, so nothing is
   destroyed either: the cross means "not on my dashboard for now". */
.dashhidden{display:flex;align-items:center;gap:8px;flex-wrap:wrap;flex:1 1 100%;
  margin-top:4px;padding-top:10px;border-top:1px dashed var(--line)}
.dh-label{font-size:10.5px;font-weight:700;letter-spacing:1.2px;text-transform:uppercase;color:var(--muted)}
.dh-chip{display:inline-flex;align-items:center;gap:6px;background:var(--surface2);
  color:var(--txt);border:1px dashed var(--line2);border-radius:999px;padding:5px 12px;
  font-family:var(--font);font-size:12.5px;cursor:pointer;max-width:240px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dh-chip:hover{border-color:var(--accent);border-style:solid}
.dh-eye{color:var(--accent);font-weight:700;font-size:14px;line-height:1}
@media(max-width:560px){
  .dh-chip{max-width:100%;font-size:13px;padding:7px 13px}
}

/* ---------- tag model picker ---------- */
/* Each card is a small drawing of the tag it prints. "Banner" and "Spine"
   mean nothing until you have seen one come off a printer, and a name in a
   dropdown is not a preview. */
.tm-prev{position:relative;display:flex;align-items:center;gap:4px;width:100%;height:40px;
  padding:4px;border-radius:8px;border:1px solid var(--line);background:#fff;box-sizing:border-box;
  overflow:hidden}
.tm-prev i.tm-q{flex:0 0 auto;width:26px;height:26px;border-radius:1px;
  background:repeating-conic-gradient(#111 0 25%,#fff 0 50%) 0 0/6px 6px}
.tm-prev b{font-size:6.5px;font-weight:800;line-height:1.15;color:#111;text-align:left;
  display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
.tm-prev u{font-size:9px;font-weight:800;text-decoration:none;color:#111}
.tm-prev i.tm-rows{flex:1 1 auto;height:20px;border-radius:1px;
  background:repeating-linear-gradient(#9aa4b2 0 2px,transparent 2px 5px)}
/* the coloured part of each model: a band, a panel, a spine */
.tm-prev em{background:var(--accent)}
.tm-p-detail{flex-direction:row-reverse}
.tm-p-plate{flex-direction:column;justify-content:center;gap:1px}
.tm-p-plate i.tm-q{position:absolute;left:5px;top:50%;transform:translateY(-50%)}
.tm-p-plate b,.tm-p-plate u{margin-left:30px}
.tm-p-banner{flex-direction:column;align-items:stretch;gap:2px;padding:0 0 3px}
.tm-p-banner em{display:block;height:10px;border-radius:7px 7px 0 0}
.tm-p-banner i.tm-q{position:absolute;left:5px;bottom:4px;width:20px;height:20px}
.tm-p-banner b{margin-left:28px;padding-right:4px}
.tm-p-side{padding:0}
.tm-p-side em{display:block;width:46%;height:100%;border-radius:7px 0 0 7px}
.tm-p-side i.tm-q{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:22px;height:22px}
.tm-p-side s{position:absolute;right:0;top:0;bottom:0;width:6px;background:var(--accent);
  text-decoration:none;border-radius:0 7px 7px 0}
.tm-p-edge{padding:0}
.tm-p-edge em{display:block;width:9px;height:100%;border-radius:7px 0 0 7px}
.tm-p-edge i.tm-q{position:absolute;left:16px;top:50%;transform:translateY(-50%);width:22px;height:22px}
.tm-p-edge s{position:absolute;right:4px;top:8px;bottom:8px;width:5px;
  background:repeating-linear-gradient(#111 0 3px,transparent 3px 6px);text-decoration:none}
@media(max-width:560px){ .tm-prev{height:36px} }
