/* ===================================================================
   WorkSight AI · Workforce Intelligence Platform — design system
   Volty family look: data-dense dashboard, navy/indigo + amber accent,
   Space Grotesk / Inter / JetBrains Mono. Light + dark, WCAG AA.
   =================================================================== */
:root{
  /* brand + accent */
  --brand:#4f63f6; --brand-2:#6d84ff; --cyan:#12b5d6; --accent:#e08a1e;
  --violet:#8b6ee8;
  /* semantic */
  --ok:#12a670; --ok-bg:#12a67018; --warn:#d9922a; --warn-bg:#d9922a1a;
  --bad:#e05265; --bad-bg:#e0526518; --info:#4f63f6; --info-bg:#4f63f618;
  /* neutrals (cool slate) */
  --bg:#eef1f7; --panel:#ffffff; --panel-2:#f6f8fc; --panel-3:#eef2f9;
  --ink:#141d34; --ink-2:#3b475f; --muted:#69748c; --faint:#9aa5bb;
  --line:#e2e7f0; --line-2:#d7deea;
  --nav:#eef2f9; --nav-2:#ffffff; --nav-ink:#3b475f; --nav-ink-2:#69748c; --nav-overlay:rgba(20,29,52,.055); --nav-overlay-2:rgba(20,29,52,.10); --nav-border:rgba(20,29,52,.09); --nav-hover-ink:#141d34;
  /* elevation */
  --sh-1:0 1px 2px rgba(20,29,52,.05), 0 1px 3px rgba(20,29,52,.04);
  --sh-2:0 4px 12px rgba(20,29,52,.06), 0 1px 3px rgba(20,29,52,.05);
  --sh-3:0 16px 38px rgba(20,29,52,.11), 0 2px 8px rgba(20,29,52,.06);
  --sh-brand:0 12px 26px rgba(79,99,246,.32);
  /* geometry + motion */
  --r:16px; --r-sm:11px; --r-xs:8px;
  --t:.18s cubic-bezier(.4,0,.2,1); --t-2:.28s cubic-bezier(.4,0,.2,1);
  --grid:#e6ebf4;
}
[data-theme=dark]{
  --brand:#6d84ff; --brand-2:#8398ff; --cyan:#2fc6e6; --accent:#f0a63f;
  --ok:#2ec48a; --ok-bg:#2ec48a1f; --warn:#e6ab4d; --warn-bg:#e6ab4d20;
  --bad:#f06b7e; --bad-bg:#f06b7e1f; --info:#7d92ff; --info-bg:#7d92ff20;
  --bg:#0a0f1d; --panel:#111a2e; --panel-2:#16223a; --panel-3:#1b2942;
  --ink:#e9eef9; --ink-2:#b7c2d8; --muted:#8b98b3; --faint:#5e6b86;
  --line:#243250; --line-2:#2c3c5e;
  --nav:#070c18; --nav-2:#0d1424; --nav-ink:#aeb9d2; --nav-ink-2:#6b7b9c; --nav-overlay:rgba(255,255,255,.06); --nav-overlay-2:rgba(255,255,255,.12); --nav-border:rgba(255,255,255,.07); --nav-hover-ink:#ffffff;
  --sh-1:0 1px 2px rgba(0,0,0,.3); --sh-2:0 6px 16px rgba(0,0,0,.36);
  --sh-3:0 18px 44px rgba(0,0,0,.5); --sh-brand:0 12px 26px rgba(60,80,220,.4);
  --grid:#22314f;
}
*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{margin:0;background:var(--bg);color:var(--ink);
  font:400 14px/1.55 Inter,system-ui,Segoe UI,sans-serif;
  -webkit-font-smoothing:antialiased;letter-spacing:-.005em}
h1,h2,h3,h4,b,.v{font-family:"Space Grotesk",Inter,sans-serif;letter-spacing:-.02em}
.mono{font-family:"JetBrains Mono",ui-monospace,monospace;font-variant-numeric:tabular-nums;letter-spacing:-.02em}
button,input,select{font:inherit;color:inherit}
::selection{background:var(--brand);color:#fff}
:focus-visible{outline:2px solid var(--brand);outline-offset:2px;border-radius:6px}
.muted{color:var(--muted)} .sm{font-size:11px} .spacer{flex:1}
svg{flex-shrink:0}

/* ---------- layout ---------- */
.app{min-height:100dvh;display:flex}
.side{width:246px;position:sticky;top:0;height:100dvh;flex-shrink:0;
  background:linear-gradient(178deg,var(--nav-2),var(--nav));color:var(--nav-ink);
  padding:16px 13px;display:flex;flex-direction:column;
  border-right:1px solid var(--nav-border);z-index:40}
.main{flex:1;min-width:0;display:flex;flex-direction:column}

/* ---------- brand ---------- */
.brand{display:flex;align-items:center;gap:11px;padding:6px 8px 18px}
.mark{width:38px;height:38px;border-radius:12px;display:grid;place-items:center;color:#fff;
  background:linear-gradient(135deg,var(--brand),var(--cyan));box-shadow:var(--sh-brand)}
.brandtext b{display:block;letter-spacing:-.2px;color:var(--nav-hover-ink);font-size:16px;font-weight:700}
.brandtext .ai{color:var(--cyan);margin-left:.26em}
.brandtext small{color:var(--nav-ink-2);font-size:11px}

/* ---------- nav ---------- */
/* Scrolls when the viewport is short, but with NO visible scrollbar (all engines) —
   a soft bottom fade signals more content instead (house pattern, like the other consoles). */
#nav{display:flex;flex-direction:column;gap:2px;overflow-y:auto;flex:1;margin:2px -2px;
  scrollbar-width:none;-ms-overflow-style:none;
  -webkit-mask-image:linear-gradient(to bottom,#000 calc(100% - 26px),transparent);
  mask-image:linear-gradient(to bottom,#000 calc(100% - 26px),transparent)}
#nav::-webkit-scrollbar{display:none;width:0}
.navgroup{margin:8px 0 2px}
.navgroup h5{font-size:9.5px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--nav-ink-2);margin:0 10px 6px;font-weight:600}
.nav{width:100%;border:0;background:none;color:var(--nav-ink);text-align:left;
  padding:7px 11px;border-radius:10px;cursor:pointer;display:flex;gap:11px;
  align-items:center;font-weight:500;font-size:13.5px;transition:background var(--t),color var(--t)}
.nav svg{width:17px;height:17px;opacity:.82}
.nav b{font-weight:500;font-family:Inter,system-ui,sans-serif}
.nav .badge{margin-left:auto;font-size:10px;background:var(--nav-overlay);
  padding:1px 7px;border-radius:99px;font-family:"JetBrains Mono",ui-monospace,monospace;font-weight:600}
.nav:hover{background:var(--nav-overlay);color:var(--nav-hover-ink)}
.nav.on{background:linear-gradient(100deg,var(--brand),#4a86ea);color:#fff;box-shadow:var(--sh-brand)}
.nav.on svg{opacity:1} .nav.on .badge{background:rgba(255,255,255,.2)}
.sidefoot{border-top:1px solid var(--nav-border);padding:13px 8px 2px;font-size:11.5px;color:var(--nav-ink-2)}
.ping{display:flex;align-items:center;gap:7px;color:var(--nav-ink);margin-bottom:7px}
.foothint{margin-top:3px} .foothint b{color:#dbe4f6}

/* ---------- collapse to icon rail (desktop only; mobile keeps the drawer) ----------
   Toggled by #sidemin, persisted in localStorage. Pure class+CSS — works on every
   engine incl. the Capacitor WebView; below 900px these rules never apply. */
.sidemin{margin-left:auto;width:26px;height:26px;border:0;border-radius:8px;cursor:pointer;
  display:grid;place-items:center;background:var(--nav-overlay);color:var(--nav-ink-2);
  transition:background var(--t),color var(--t),transform var(--t-2)}
.sidemin:hover{background:var(--nav-overlay-2);color:var(--nav-hover-ink)}
@media (min-width:900px){
  .side.min{width:72px;padding:16px 10px}
  .side.min .brand{padding:6px 4px 14px;justify-content:center}
  .side.min .brandtext{display:none}
  .side.min .sidemin{position:static;transform:rotate(180deg)}
  .side.min .brand{flex-direction:column;gap:8px}
  .side.min .navgroup h5{display:none}
  .side.min .navgroup{margin:6px 0 2px;border-top:1px solid rgba(255,255,255,.06);padding-top:6px}
  .side.min .navgroup:first-child{border-top:0;padding-top:0}
  .side.min .nav{justify-content:center;padding:9px 0;gap:0}
  .side.min .nav b,.side.min .nav .badge{display:none}
  .side.min .nav svg{width:19px;height:19px}
  .side.min .sidefoot .foothint{display:none}
  .side.min .ping{justify-content:center;margin-bottom:2px;font-size:0}  /* text hidden, dot stays */
}

/* ---------- top bar ---------- */
.top{position:sticky;top:0;z-index:30;display:flex;align-items:center;gap:12px;
  padding:13px 22px;background:color-mix(in srgb,var(--panel) 86%,transparent);
  backdrop-filter:blur(16px) saturate(1.2);border-bottom:1px solid var(--line)}
.titlewrap h1{font-size:19px;margin:0;font-weight:700}
.titlewrap .sub{color:var(--muted);font-size:12px;margin-top:1px}
.live{display:flex;align-items:center;gap:8px;background:var(--panel-2);border:1px solid var(--line);
  padding:6px 11px;border-radius:99px;font-size:11.5px;font-weight:600}
.lbadge{font-size:9px;font-weight:700;letter-spacing:.08em;padding:2px 6px;border-radius:5px;
  background:var(--info-bg);color:var(--brand)}
.rolewrap{display:flex;align-items:center;gap:7px;background:var(--panel-2);border:1px solid var(--line);
  padding:4px 6px 4px 11px;border-radius:99px}
.rolewrap .rl{font-size:11px;font-weight:600;color:var(--ink-2)}
.rolesel{border:1px solid var(--line);background:var(--panel);color:var(--ink);border-radius:99px;
  font:inherit;font-size:11.5px;font-weight:600;padding:4px 8px;cursor:pointer;transition:var(--t)}
.rolesel:hover{border-color:var(--line-2)}
.link.disabled,.btn.disabled{opacity:.4;pointer-events:none}
@media (max-width:900px){.rolewrap .rl{display:none}}
.iconbtn{border:1px solid var(--line);background:var(--panel);color:var(--ink-2);border-radius:10px;
  width:36px;height:36px;display:grid;place-items:center;cursor:pointer;transition:var(--t)}
.iconbtn:hover{color:var(--ink);border-color:var(--line-2);background:var(--panel-2)}
.i-moon{display:none} [data-theme=dark] .i-sun{display:none} [data-theme=dark] .i-moon{display:block}

.only-mobile{display:none}
/* Top-of-site "Enrol person" pill (team 2026-08-12) */
.topbtn{display:inline-flex;align-items:center;gap:7px;height:36px;padding:0 13px;border:1px solid var(--line);
  background:var(--panel);color:var(--ink);border-radius:10px;font:inherit;font-size:13px;font-weight:600;cursor:pointer;
  transition:var(--t);white-space:nowrap}
.topbtn:hover{background:var(--panel-2);border-color:var(--line-2)}
.topbtn svg{width:17px;height:17px}
@media (max-width:820px){.topbtn span{display:none}.topbtn{padding:0 10px}}

/* ---------- dots ---------- */
.dot{width:7px;height:7px;border-radius:50%;background:var(--muted);flex-shrink:0}
.dot.ok{background:var(--ok)} .dot.warn{background:var(--warn)} .dot.bad{background:var(--bad)}
.dot.pulse{box-shadow:0 0 0 0 var(--ok);animation:pulse 2s infinite}
@keyframes pulse{0%{box-shadow:0 0 0 0 color-mix(in srgb,var(--ok) 55%,transparent)}70%{box-shadow:0 0 0 6px transparent}100%{box-shadow:0 0 0 0 transparent}}

/* ---------- content + filters ---------- */
.content{padding:18px 22px 46px;max-width:1560px;width:100%}
.filters{background:var(--panel);border:1px solid var(--line);border-radius:14px;
  padding:10px 12px;display:flex;gap:12px;align-items:flex-end;flex-wrap:wrap;
  margin-bottom:16px;box-shadow:var(--sh-1)}
.fld{display:flex;flex-direction:column;gap:4px}.fld.grow{flex:1;min-width:190px}
.label{font-size:9px;letter-spacing:.13em;text-transform:uppercase;color:var(--faint);font-weight:600}
select,input{border:1px solid var(--line);background:var(--panel-2);color:var(--ink);
  border-radius:9px;padding:8px 11px;transition:var(--t);font-weight:500}
select:hover,input:hover{border-color:var(--line-2)}
select:focus,input:focus{outline:none;border-color:var(--brand);box-shadow:0 0 0 3px var(--info-bg)}
.searchwrap{position:relative;display:flex;align-items:center}
.searchwrap svg{position:absolute;left:11px;color:var(--faint)}
.searchwrap input{padding-left:32px;width:100%}
.btn{display:inline-flex;align-items:center;gap:7px;border:1px solid var(--line);background:var(--panel);
  color:var(--ink);border-radius:10px;padding:8px 13px;font-weight:600;cursor:pointer;transition:var(--t)}
.btn:hover{background:var(--panel-2);border-color:var(--line-2)}
.btn.ghost svg{color:var(--brand)}

/* ---------- kpis ---------- */
.kpis{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin-bottom:14px}
.kpi{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:15px 16px;
  box-shadow:var(--sh-1);position:relative;overflow:hidden;transition:var(--t-2)}
.kpi:hover{box-shadow:var(--sh-2);transform:translateY(-1px)}
.kpi .kt{font-size:11px;color:var(--muted);font-weight:600;display:flex;align-items:center;gap:6px}
.kpi .kt svg{width:14px;height:14px;color:var(--brand);opacity:.9}
.kpi .kv{font-size:27px;font-weight:700;letter-spacing:-.04em;margin-top:8px;line-height:1}
.kpi .kd{font-size:11px;margin-top:6px;font-weight:600;display:flex;align-items:center;gap:4px}
.kd.up{color:var(--ok)}.kd.down{color:var(--bad)}.kd.flat{color:var(--muted)}
.kpi .spark{position:absolute;right:12px;bottom:12px;opacity:.55}

/* ---------- cards + grid ---------- */
.grid{display:grid;grid-template-columns:repeat(12,1fr);gap:14px}
.card{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  padding:17px;box-shadow:var(--sh-2)}
.s12{grid-column:span 12}.s8{grid-column:span 8}.s7{grid-column:span 7}.s6{grid-column:span 6}
.s5{grid-column:span 5}.s4{grid-column:span 4}.s3{grid-column:span 3}
.head{display:flex;gap:10px;align-items:flex-start;margin-bottom:14px}
.head h3{font-size:14.5px;margin:0;font-weight:600}
.head small{color:var(--muted);font-size:11.5px;display:block;margin-top:1px}
.head .right{margin-left:auto}

/* ---------- tags / chips ---------- */
.tag{font-size:9.5px;letter-spacing:.05em;text-transform:uppercase;padding:4px 9px;
  border-radius:99px;font-weight:700;display:inline-flex;align-items:center;gap:5px;white-space:nowrap}
.tag.ok{background:var(--ok-bg);color:var(--ok)} .tag.warn{background:var(--warn-bg);color:var(--warn)}
.tag.bad{background:var(--bad-bg);color:var(--bad)} .tag.info{background:var(--info-bg);color:var(--info)}
.tag .dot{width:6px;height:6px}
.chips{display:flex;gap:6px;flex-wrap:wrap}

/* ---------- hero ring ---------- */
.hero{display:flex;align-items:center;gap:20px}
.ring{width:118px;height:118px;flex-shrink:0}
.ring text{font-family:"Space Grotesk",Inter,sans-serif;font-weight:700}
.minirow{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin-top:15px}
.mini{padding:11px;background:var(--panel-2);border:1px solid var(--line);border-radius:11px}
.mini .ml{font-size:11px;color:var(--muted);font-weight:500}
.mini b{display:block;font-size:18px;margin-top:2px}

/* ---------- progress bars ---------- */
.metric{margin:13px 0}
.metric .mrow{display:flex;align-items:center;margin-bottom:6px;font-size:13px;font-weight:500}
.metric .mrow b{font-family:Inter,system-ui,sans-serif;font-weight:600}
.metric .mrow .mval{margin-left:auto;font-family:"JetBrains Mono",ui-monospace,monospace;font-weight:600;color:var(--ink-2)}
.bar{height:7px;background:var(--panel-3);border-radius:99px;overflow:hidden}
.bar i{display:block;height:100%;border-radius:inherit;
  background:linear-gradient(90deg,var(--brand),var(--cyan));transition:width var(--t-2)}
.bar.a i{background:linear-gradient(90deg,var(--accent),var(--warn))}
.bar.g i{background:linear-gradient(90deg,var(--ok),#34d19b)}

/* ---------- svg charts ---------- */
.chartbox{width:100%}
.chartbox .gl{stroke:var(--grid);stroke-width:1}
.chartbox .axl{fill:var(--faint);font-size:10px;font-family:"JetBrains Mono",ui-monospace,monospace;font-variant-numeric:tabular-nums}
.chartbox .area{fill:url(#areaFill)} .chartbox .ln{fill:none;stroke:var(--brand);stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round}
.chartbox .pt{fill:var(--panel);stroke:var(--brand);stroke-width:2.5}
.chartbox .colr{fill:url(#barFill);rx:5;transition:opacity var(--t)}
.chartbox .colr:hover{opacity:.82}
.chartbox .lbl{fill:var(--ink-2);font-size:10px;font-weight:600;font-family:"JetBrains Mono",ui-monospace,monospace}
.legend{display:flex;gap:16px;flex-wrap:wrap;color:var(--muted);font-size:11.5px;margin-top:10px}
.legend i{display:inline-block;width:9px;height:9px;border-radius:3px;margin-right:6px;vertical-align:-1px}

/* ---------- tables ---------- */
.tablewrap{overflow-x:auto;margin:0 -3px}
table{width:100%;border-collapse:collapse}
th{font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--faint);
  text-align:left;padding:9px 10px;border-bottom:1px solid var(--line);font-weight:700;white-space:nowrap}
td{padding:11px 10px;border-bottom:1px solid var(--line);white-space:nowrap;font-size:13px;vertical-align:middle}
tbody tr{transition:background var(--t)}
tbody tr:hover{background:var(--panel-2)}
tbody tr:last-child td{border-bottom:0}
.person{display:flex;align-items:center;gap:10px}
.av{width:34px;height:34px;border-radius:10px;color:#fff;display:grid;place-items:center;
  font-weight:700;font-size:12px;font-family:"Space Grotesk",Inter,sans-serif;flex-shrink:0}
.person b{font-family:Inter,system-ui,sans-serif;font-weight:600;display:block}
.person .pid{color:var(--muted);font-size:11px;font-family:"JetBrains Mono",ui-monospace,monospace}
.score{font-family:"JetBrains Mono",ui-monospace,monospace;font-weight:700;font-size:14px}
.link{border:1px solid var(--line);background:var(--panel-2);color:var(--brand);border-radius:8px;
  padding:5px 10px;cursor:pointer;font-weight:600;font-size:11.5px;display:inline-flex;align-items:center;gap:5px;transition:var(--t)}
.link:hover{background:var(--panel);border-color:var(--brand);box-shadow:var(--sh-1)}
.link svg{width:13px;height:13px}

/* ---------- heatmap ---------- */
.heat{display:grid;grid-template-columns:110px repeat(8,1fr);gap:5px}
.heat .hc{height:32px;border-radius:8px;display:grid;place-items:center;font-size:10.5px;
  font-family:"JetBrains Mono",ui-monospace,monospace;font-weight:600;cursor:pointer;transition:var(--t)}
.heat .hc.lab{justify-content:start;color:var(--muted);cursor:default;font-family:Inter,system-ui,sans-serif;font-weight:500;padding-left:2px}
.heat .hc.hd{color:var(--faint);height:20px}
.heat .hc.cell:hover{outline:2px solid var(--brand);outline-offset:1px;z-index:1}

/* ---------- timeline ribbons ---------- */
.ribbon{display:grid;grid-template-columns:140px 1fr 42px;gap:12px;align-items:center;margin:11px 0}
.ribbon .rn{font-size:13px;font-weight:500}
.tl{height:15px;background:var(--panel-3);border-radius:6px;overflow:hidden;display:flex}
.tl i{height:100%}

/* ---------- tabs ---------- */
.tabs{display:flex;gap:4px;background:var(--panel-2);padding:4px;border-radius:11px;border:1px solid var(--line)}
.tab{border:0;background:none;color:var(--muted);padding:7px 13px;border-radius:8px;cursor:pointer;font-weight:600;font-size:12.5px;transition:var(--t)}
.tab.on{background:var(--panel);color:var(--ink);box-shadow:var(--sh-1)}
.tab:hover:not(.on){color:var(--ink-2)}

/* ---------- report hero ---------- */
.reporthero{background:linear-gradient(135deg,#182a56,#31509d);color:#fff;border:0}
.reporthero h3,.reporthero .kv{color:#fff} .reporthero .muted,.reporthero .kt{color:#b9c8ea}
.reporthero .kpi{background:rgba(255,255,255,.08);border-color:rgba(255,255,255,.14);box-shadow:none}
.reporthero .kpi:hover{background:rgba(255,255,255,.12)}

/* ---------- roadmap ---------- */
.phase{display:grid;grid-template-columns:180px 1fr;border-bottom:1px solid var(--line)}
.phase:last-child{border-bottom:0}
.phz{padding:16px;font-weight:700;font-size:14px}
.phz small{display:block;color:var(--muted);font-weight:500;font-family:Inter,system-ui,sans-serif;margin-top:3px}
.phb{padding:16px}
.phb b{display:block;margin-bottom:7px;font-size:13.5px}
.bullet{margin:5px 0;color:var(--muted);font-size:13px;padding-left:16px;position:relative}
.bullet:before{content:"";position:absolute;left:3px;top:8px;width:5px;height:5px;border-radius:50%;background:var(--brand)}

/* ---------- sliders (productivity) ---------- */
input[type=range]{-webkit-appearance:none;width:100%;height:6px;border-radius:99px;
  background:var(--panel-3);padding:0;margin-top:8px}
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:16px;height:16px;border-radius:50%;
  background:var(--brand);cursor:pointer;box-shadow:var(--sh-1);border:2px solid var(--panel)}

/* ---------- callout ---------- */
.callout{background:var(--panel-2);border:1px solid var(--line);border-radius:12px;padding:13px 15px;margin-top:12px}
.callout b{font-size:13px} .callout p{margin:5px 0 0;font-size:12.5px;line-height:1.5}
.govline{display:flex;align-items:flex-start;gap:9px;padding:9px 0;font-size:13px;border-bottom:1px solid var(--line)}
.govline:last-child{border-bottom:0}
.govline svg{color:var(--ok);margin-top:1px;flex-shrink:0}

/* ---------- modal ---------- */
.modal{position:fixed;inset:0;z-index:100;background:rgba(7,12,24,.62);backdrop-filter:blur(3px);
  display:none;align-items:center;justify-content:center;padding:20px;opacity:0;transition:opacity var(--t-2)}
.modal.open{display:flex;opacity:1}
.modalbox{width:min(940px,96vw);max-height:92dvh;overflow:auto;background:var(--panel);
  border:1px solid var(--line);border-radius:18px;box-shadow:var(--sh-3);
  transform:scale(.97) translateY(6px);transition:transform var(--t-2)}
.modal.open .modalbox{transform:none}
.modalhead{display:flex;align-items:center;gap:12px;padding:15px 18px;border-bottom:1px solid var(--line)}
.evicon{width:38px;height:38px;border-radius:11px;background:var(--info-bg);color:var(--brand);display:grid;place-items:center}
.modalbody{padding:18px}
.crumbs{font-size:11.5px;color:var(--muted);margin-bottom:11px;font-weight:500}
.videobox{aspect-ratio:16/9;background:radial-gradient(120% 120% at 30% 20%,#1a2c52,#0b1730);
  border-radius:13px;position:relative;overflow:hidden;display:grid;place-items:center}
.playbtn{width:64px;height:64px;border-radius:50%;background:rgba(255,255,255,.14);color:#fff;
  display:grid;place-items:center;backdrop-filter:blur(4px);box-shadow:0 8px 26px rgba(0,0,0,.4)}
.voverlay{position:absolute;left:16px;bottom:14px;color:#dce7ff;text-align:left}
.voverlay b{display:block;font-size:14px} .voverlay span{font-size:11px;opacity:.72}
.vscan{position:absolute;inset:0;background:repeating-linear-gradient(0deg,transparent,transparent 3px,rgba(255,255,255,.02) 4px)}
.videometa{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-top:12px}
.vm{background:var(--panel-2);border:1px solid var(--line);border-radius:11px;padding:10px 12px}
.vm .label{margin-bottom:4px} .vm b{font-size:13px}

/* ---------- live view camera wall ---------- */
.cambar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:14px}
.camgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px}
.camtile{background:var(--panel);border:1px solid var(--line);border-radius:14px;overflow:hidden}
.camhead{display:flex;align-items:baseline;justify-content:space-between;gap:8px;padding:11px 13px}
.camhead b{font-size:13.5px}
.camvid{position:relative;aspect-ratio:16/9;background:radial-gradient(120% 120% at 30% 20%,#1a2c52,#0b1730);overflow:hidden}
.camvid video{width:100%;height:100%;object-fit:cover;display:block;background:#0b1730}
.camoverlay{position:absolute;inset:0;display:grid;place-items:center;gap:10px;grid-auto-flow:row;background:rgba(6,14,32,.28);z-index:2}
.camplay{width:56px;height:56px;border-radius:50%;border:0;background:rgba(255,255,255,.16);color:#fff;font-size:18px;cursor:pointer;backdrop-filter:blur(4px);box-shadow:0 8px 26px rgba(0,0,0,.4);transition:var(--t)}
.camplay:hover{background:rgba(255,255,255,.28);transform:scale(1.05)}
.camstate{position:absolute;left:12px;bottom:10px;font-size:11px;color:#dce7ff;opacity:.85}
.camtimer{position:absolute;top:9px;right:10px;font:600 11px/1 var(--mono,monospace);color:#fff;background:rgba(6,14,32,.6);border:1px solid rgba(255,255,255,.18);padding:3px 7px;border-radius:99px}
.camtimer:empty{display:none}
.camstop{position:absolute;top:8px;left:10px;width:26px;height:26px;border-radius:7px;border:1px solid rgba(255,255,255,.2);background:rgba(6,14,32,.6);color:#fff;cursor:pointer;font-size:11px;line-height:1}

/* ---------- responsive ---------- */
@media(max-width:1180px){.kpis{grid-template-columns:repeat(3,1fr)}
  .s8,.s7,.s6,.s5,.s4,.s3{grid-column:span 12}.s6{grid-column:span 6}}
@media(max-width:900px){
  .side{position:fixed;left:0;transform:translateX(-100%);transition:transform var(--t-2);box-shadow:var(--sh-3)}
  .side.open{transform:none}
  .only-mobile{display:grid}
  .s6{grid-column:span 12}
  .videometa{grid-template-columns:repeat(2,1fr)}
  .phase{grid-template-columns:1fr}.phz{border-bottom:0;padding-bottom:2px}
}
@media(max-width:640px){.kpis{grid-template-columns:repeat(2,1fr)}
  .content{padding:14px}.top{padding:10px 12px;gap:8px}.titlewrap .sub{display:none}.titlewrap h1{font-size:16px}
  .filters{padding:10px}.filters .fld:not(.grow){flex:1 1 44%;min-width:0}.filters select{width:100%}
  .camgrid{grid-template-columns:1fr}.rolesel{max-width:118px}.videometa{grid-template-columns:1fr 1fr}
  .hero{flex-direction:column;align-items:flex-start;gap:14px}
  .head{flex-wrap:wrap}.head .right{margin-left:0;width:100%}
  .tabs{flex-wrap:wrap}.reporthero .kpis{grid-template-columns:1fr 1fr}}
@media(max-width:520px){.live #clock{display:none}.kpis{grid-template-columns:1fr 1fr}
  .rolewrap .rl{display:none}.top{gap:6px}.rolesel{max-width:104px}
  .titlewrap{min-width:0;flex:1}.titlewrap h1{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}}
@media(max-width:440px){.rolewrap{display:none}}
@media(max-width:400px){.kpis{grid-template-columns:1fr}}
@media(prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}
/* heatmap + wide grids scroll instead of overflowing the viewport */
.heatwrap{overflow-x:auto;margin:0 -3px;-webkit-overflow-scrolling:touch}
.heatwrap .heat{min-width:520px}

/* ---------- person detail drawer (right-side panel, anpr.volty.in pattern) ---------- */
.pdrawer{position:fixed;top:0;right:0;width:min(440px,94vw);height:100dvh;z-index:900;
  background:var(--panel);border-left:1px solid var(--line);box-shadow:-18px 0 48px rgba(8,14,30,.18);
  transform:translateX(100%);transition:transform var(--t-2);display:flex;flex-direction:column}
.pdrawer.open{transform:none}
/* SCRIM (2026-09-24): the person drawer is a fixed right panel with NO backdrop. On a WIDE screen the
   page stays fully lit beside it and the panel shade is close to the page shade (both surface tokens),
   so the drawer read as "transparent / theme not uniform" (invisible on narrow screens where the 94vw
   panel covers the page). Fix = a dim scrim element at z-index 899 -- above the page, below the panel
   (z-900) -- separating it in both themes; created + placed after #personDrawer by openPersonDrawer,
   shown via the sibling selector while the drawer is .open, and clicking it closes the drawer. A
   box-shadow scrim was tried first but did not paint reliably here. */
.pdscrim{position:fixed;inset:0;z-index:899;background:rgba(6,10,18,.5);opacity:0;pointer-events:none;transition:opacity var(--t-2)}
.pdscrim.open{opacity:1;pointer-events:auto}
.pdhead{display:flex;align-items:center;gap:10px;padding:15px 16px;border-bottom:1px solid var(--line)}
.pdhead b{font-size:16px}
.pdbody{overflow-y:auto;padding:14px 16px 28px;flex:1;scrollbar-width:thin}
.pdstats{display:grid;grid-template-columns:1fr 1fr;gap:9px}
.pdstat{background:var(--panel-2);border:1px solid var(--line);border-radius:11px;padding:9px 11px;display:flex;flex-direction:column}
.pdstat b{font-size:17px;font-family:"JetBrains Mono",ui-monospace,monospace;margin:1px 0}
.pdsec{margin-top:16px}.pdsec h4{margin:0 0 8px;font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
.zbarw{display:flex;align-items:center;gap:8px;margin:5px 0;font-size:12px}
.zbarw span{width:92px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.zbarw i{height:7px;border-radius:9px;background:linear-gradient(90deg,var(--brand),var(--cyan));display:block}
.zbarw b{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:11px;color:var(--muted);margin-left:auto}
/* One grid, so photo / details / match / actions line up down the whole list. The old flex
   version let the buttons follow the text, so their position moved with each camera name. */
.pdrow{display:grid;grid-template-columns:46px minmax(0,1fr) auto auto;gap:10px;align-items:center;
  padding:8px 0;border-bottom:1px solid var(--line)}
.pdthumb{width:46px;height:46px;border-radius:9px;object-fit:cover;background:var(--panel-3);flex:0 0 auto;cursor:zoom-in}
.pdthumb.noimg{display:flex;align-items:center;justify-content:center}
.pdmeta{min-width:0}
.pdmeta b,.pdmeta>div{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block}
/* Match reads right-aligned in its own column so the eye can scan it down the list. */
.pdmatch{display:flex;align-items:center;gap:6px;justify-content:flex-end;white-space:nowrap;
  font-size:12.5px;color:var(--ink-2)}
.pdtag{font-size:11px;font-weight:700;padding:1px 7px;border-radius:99px}
.pdtag.ok{background:color-mix(in srgb,var(--ok) 18%,transparent);color:var(--ok)}
.pdtag.bad{background:color-mix(in srgb,var(--bad) 18%,transparent);color:var(--bad)}
/* person drawer — tabbed rich profile (anpr.volty.in pattern) */
.pdav .av{width:38px;height:38px;border-radius:12px}
.pdtabs{display:flex;gap:2px;padding:0 10px;border-bottom:1px solid var(--line);overflow-x:auto;scrollbar-width:none}
.pdtabs::-webkit-scrollbar{display:none}
.pdtab{border:0;background:none;color:var(--muted);font-weight:600;font-size:12px;padding:10px 10px;cursor:pointer;
  display:inline-flex;align-items:center;gap:5px;white-space:nowrap;border-bottom:2px solid transparent}
.pdtab svg{width:15px;height:15px;opacity:.8}
.pdtab.on{color:var(--brand);border-bottom-color:var(--brand)}
.pdtab:hover{color:var(--ink)}
.pdloc{display:flex;gap:12px;align-items:center;background:var(--panel-2);border:1px solid var(--line);border-radius:14px;padding:14px}
.pdloc-i{width:46px;height:46px;border-radius:50%;display:grid;place-items:center;flex:0 0 auto;
  background:color-mix(in srgb,var(--brand) 14%,transparent);color:var(--brand)}
.pdloc-i svg{width:22px;height:22px}
.pdloc-zone{font-size:20px;font-weight:700;font-family:"Space Grotesk",Inter,sans-serif}
.pdrows{margin-top:12px;border:1px solid var(--line);border-radius:12px;overflow:hidden}
.pdrows>div{display:flex;justify-content:space-between;align-items:center;padding:9px 12px;border-bottom:1px solid var(--line)}
.pdrows>div:last-child{border-bottom:0}
.pdrows b{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:13px}
.pdcards{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin-top:12px}
.pdcards.two{grid-template-columns:1fr 1fr}
.pdcard{display:flex;gap:10px;align-items:flex-start;background:var(--panel-2);border:1px solid var(--line);border-radius:12px;padding:11px}
.pdcard-i{width:34px;height:34px;border-radius:10px;display:grid;place-items:center;flex:0 0 auto;color:#fff;background:var(--brand)}
.pdcard-i svg{width:17px;height:17px}
.pdcard-i.ok{background:var(--ok)}.pdcard-i.violet{background:var(--violet)}.pdcard-i.accent{background:var(--accent)}.pdcard-i.brand{background:var(--brand)}.pdcard-i.warn{background:var(--warn)}
.pdcard-v{font-size:20px;font-weight:800;font-family:"JetBrains Mono",ui-monospace,monospace;line-height:1.1}
.pdcard-t{min-width:0}
.faint{color:var(--faint)}
.pdarea{display:flex;justify-content:space-between;align-items:center;padding:9px 11px;border:1px solid var(--line);border-radius:10px;margin:6px 0;background:var(--panel-2)}
/* hover-enlarge preview */
.imgzoom{position:fixed;z-index:1200;opacity:0;pointer-events:none;transition:opacity var(--t);
  background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:6px;box-shadow:0 22px 60px rgba(8,14,30,.35)}
.imgzoom.show{opacity:1;pointer-events:auto}
.imgzoom img{display:block;width:300px;height:300px;object-fit:cover;border-radius:10px}
/* ---------- enrolment wizard ---------- */
.enstep{display:flex;gap:9px;align-items:center;padding:7px 9px;border-radius:10px;margin-bottom:5px;border:1px solid var(--line)}
.enstep.on{border-color:var(--brand);background:color-mix(in srgb,var(--brand) 7%,transparent)}
.enstep.done{opacity:.72}
.enstep span{width:22px;height:22px;border-radius:50%;display:grid;place-items:center;font-size:11px;font-weight:700;
  background:var(--panel-3);color:var(--muted);flex:0 0 auto}
.enstep.done span{background:var(--ok);color:#fff}
/* enrol guided sweep — per-axis live gauge (captured ticks + live head-angle caret) */
.enax{margin:7px 0}
.enaxh{display:flex;justify-content:space-between;text-transform:capitalize}
.engmid{position:absolute;left:50%;top:2px;bottom:2px;width:1px;background:var(--line-2)}
.engcur{position:absolute;top:-2px;width:2px;height:20px;background:var(--accent);transform:translateX(-50%);
  box-shadow:0 0 5px var(--accent);transition:left .07s linear;left:50%}
/* enrol verify — per-frame delete (prune a wrong-person capture) */
.efdel{position:absolute;top:5px;right:5px;z-index:2;width:22px;height:22px;border:0;border-radius:50%;padding:0;
  background:rgba(224,82,101,.92);color:#fff;font-size:12px;line-height:22px;cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,.35)}
.efdel:hover{background:var(--bad)}
@media(max-width:760px){.pdrawer{width:100vw}.imgzoom img{width:210px;height:210px}}

/* ---------- dashboard charts (reorganised 2026-08-06) ---------- */
.kpis.k6{grid-template-columns:repeat(6,1fr)}
.kspark{width:100%;height:26px;margin-top:7px;display:block}
.chart{width:100%;height:auto;display:block}
.cax{font:10px "JetBrains Mono";fill:var(--faint)}
.clegend{display:flex;gap:16px;justify-content:center;margin-top:6px;font-size:11.5px;color:var(--muted)}
.clegend span{display:flex;align-items:center;gap:6px}
.clegend i{width:14px;height:3px;border-radius:2px;display:inline-block}
.clegend i.dash{background:repeating-linear-gradient(90deg,var(--faint) 0 4px,transparent 4px 8px)}
.donutwrap{display:flex;flex-direction:column;align-items:center;gap:12px}
.dchart{width:150px;height:150px}
.dnum{font:700 22px "Space Grotesk";fill:var(--ink)} .dsub{font:11px Inter;fill:var(--muted)}
.dlegend{width:100%}
.dlegend div{display:flex;align-items:center;gap:8px;padding:5px 0;border-bottom:1px solid var(--line);font-size:12.5px}
.dlegend div:last-child{border-bottom:0}
.dlegend i{width:10px;height:10px;border-radius:3px;flex:0 0 auto}
.dlegend b{margin-left:auto}
.ranks{display:flex;flex-direction:column;gap:9px}
.rank{display:flex;gap:9px;align-items:center}
.rn{width:20px;height:20px;border-radius:6px;display:grid;place-items:center;font:600 10.5px "JetBrains Mono";
  background:var(--panel-3);color:var(--muted);flex:0 0 auto}
.rbody{flex:1;min-width:0}
.rtop{display:flex;justify-content:space-between;gap:8px;margin-bottom:3px}
.rtrack{height:7px;background:var(--panel-3);border-radius:9px;overflow:hidden}
.rtrack i{display:block;height:100%;border-radius:9px;background:linear-gradient(90deg,var(--brand),var(--cyan))}
.sysrows{display:flex;flex-direction:column}
.sysrow{display:flex;align-items:center;gap:9px;padding:8px 0;border-bottom:1px solid var(--line)}
.sysrow:last-child{border-bottom:0} .sysrow b{margin-left:auto}
@media(max-width:1200px){.kpis.k6{grid-template-columns:repeat(3,1fr)}}
@media(max-width:640px){.kpis.k6{grid-template-columns:repeat(2,1fr)}}

/* ---------- richer KPI cards + live-inside thumbnails (2026-08-06) ---------- */
.kpi-rich{position:relative;overflow:hidden;padding-bottom:0}
.krow{display:flex;align-items:center;gap:9px;margin-bottom:6px}
.kicon{width:32px;height:32px;border-radius:10px;display:grid;place-items:center;flex:0 0 auto;
  color:var(--c);background:color-mix(in srgb,var(--c) 13%,transparent)}
.kpi-rich .kt{font-size:11.5px;font-weight:600;color:var(--muted);margin:0}
.kpi-rich .kv{font-size:27px;line-height:1.15;margin:2px 0 3px}
.kdelta{font-size:11px;font-weight:600;display:inline-flex;align-items:center;gap:3px}
.kdelta.up{color:var(--ok)} .kdelta.down{color:var(--bad)}
.kpi-rich .kspark{margin:8px -16px -1px;width:calc(100% + 32px);height:30px}
.ithumb{width:42px;height:42px;border-radius:9px;object-fit:cover;background:var(--panel-3);display:block}
.ithumb.noimg{display:flex;align-items:center;justify-content:center}
tr[data-prow2]:hover{background:var(--panel-2)}
.enrolcta{display:flex;gap:12px;align-items:center;flex-wrap:wrap}
.enrolcta>div:first-child{flex:1 1 220px;min-width:0}
.enrolcta button{flex:0 0 auto;align-self:center}
@media(max-width:640px){.enrolcta{flex-direction:column;align-items:stretch}.enrolcta button{width:100%}}

/* ════════ COMMAND CENTRE — hero strip, zone map, insights, flows, departments ════════
   Operations-first dashboard. Density is deliberate (this is a wall-display surface), but every
   rule below keeps to the existing token set — no new palette, so light and dark stay in step. */
.hero-strip{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;
  flex-wrap:wrap;margin:0 0 14px}
.greet{font-size:22px;font-weight:650;letter-spacing:-.01em;margin:0 0 2px;color:var(--ink)}
.hero-chips{display:flex;gap:8px;flex-wrap:wrap}
.chip{display:inline-flex;align-items:center;gap:6px;padding:6px 11px;border-radius:999px;
  font-size:12px;font-weight:550;border:1px solid var(--line);background:var(--panel-2);
  color:var(--ink);white-space:nowrap}
.chip svg{width:14px;height:14px}
.chip.ok{border-color:color-mix(in srgb,var(--ok) 40%,var(--line));background:var(--ok-bg);color:var(--ok)}
.chip.warn{border-color:color-mix(in srgb,var(--warn) 40%,var(--line));background:var(--warn-bg);color:var(--warn)}
.chip.bad{border-color:color-mix(in srgb,var(--bad) 42%,var(--line));background:var(--bad-bg,rgba(224,82,101,.12));color:var(--bad)}
.chip.live{letter-spacing:.06em;font-size:11px}

/* ── Live zone map: capacity-weighted tiles, colour = utilisation ───────────────────── */
.zmap{display:flex;flex-wrap:wrap;gap:8px;align-content:flex-start}
.ztile{position:relative;display:flex;flex-direction:column;gap:5px;justify-content:space-between;
  min-height:82px;padding:11px 12px;border-radius:12px;border:1px solid var(--line);
  background:var(--panel-2);color:var(--ink);text-align:left;cursor:pointer;
  transition:transform var(--t),box-shadow var(--t),border-color var(--t)}
.ztile:hover{transform:translateY(-2px);box-shadow:var(--sh-2);border-color:var(--brand)}
.ztile:focus-visible{outline:2px solid var(--brand);outline-offset:2px}
.ztile .zn{font-size:12px;font-weight:600;line-height:1.25;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.ztile .zv{font-size:21px;font-weight:700;line-height:1;font-variant-numeric:tabular-nums}
.ztile .zv i{font-style:normal;font-size:12px;font-weight:500;opacity:.6}
.ztile .zbar{display:block;height:4px;border-radius:3px;background:var(--grid);overflow:hidden}
.ztile .zbar i{display:block;height:100%;border-radius:3px;background:currentColor;opacity:.85}
.ztile .zcfg{font-size:10px;opacity:.75}
/* Sequential single-hue density ramp (never a rainbow); c4 switches to the danger hue because
   over-capacity is a STATE, not just "more". Colour is paired with the number + bar, never alone. */
.ztile.c0{color:var(--muted)}
.ztile.c1{color:var(--ok)}
.ztile.c2{color:var(--cyan)}
.ztile.c3{color:var(--warn);border-color:color-mix(in srgb,var(--warn) 34%,var(--line))}
.ztile.c4{color:var(--bad);border-color:color-mix(in srgb,var(--bad) 46%,var(--line));
  background:color-mix(in srgb,var(--bad) 9%,var(--panel-2))}
.ztile.nc{color:var(--muted);border-style:dashed}
.zlegend{display:flex;align-items:center;gap:7px;margin-top:11px;flex-wrap:wrap}
.zlegend .sw{width:16px;height:8px;border-radius:3px;background:currentColor}
.zlegend .sw.c0{color:var(--muted)}.zlegend .sw.c1{color:var(--ok)}.zlegend .sw.c2{color:var(--cyan)}
.zlegend .sw.c3{color:var(--warn)}.zlegend .sw.c4{color:var(--bad)}

/* ── Needs-attention list ───────────────────────────────────────────────────────────── */
.ins-wrap{display:flex;flex-direction:column;gap:2px}
.ins{display:flex;gap:11px;align-items:flex-start;padding:9px 2px;border-bottom:1px solid var(--line)}
.ins:last-child{border-bottom:0}
.ins-i{flex:0 0 28px;width:28px;height:28px;border-radius:9px;display:flex;align-items:center;
  justify-content:center;background:var(--panel-2)}
.ins-i svg{width:15px;height:15px}
.ins-i.ok{color:var(--ok);background:var(--ok-bg)}
.ins-i.warn{color:var(--warn);background:var(--warn-bg)}
.ins-i.bad{color:var(--bad);background:color-mix(in srgb,var(--bad) 14%,transparent)}
.ins-foot{margin-top:10px;padding-top:9px;border-top:1px dashed var(--line)}
/* ---------- top-bar notifications (bell) — replaces the "Needs attention" card ---------- */
.notifwrap{position:relative;display:flex}
.notifdot{position:absolute;top:2px;right:2px;min-width:16px;height:16px;padding:0 4px;border-radius:99px;
  background:var(--bad);color:#fff;font-size:10px;font-weight:700;line-height:16px;text-align:center;
  font-family:"JetBrains Mono",ui-monospace,monospace;box-shadow:0 0 0 2px var(--panel);pointer-events:none}
.notifpanel{position:absolute;top:calc(100% + 9px);right:0;width:340px;max-width:88vw;background:var(--panel);
  border:1px solid var(--line);border-radius:14px;box-shadow:var(--sh-3);z-index:70;overflow:hidden;animation:notifIn .16s ease}
@keyframes notifIn{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}
.notifhead{display:flex;align-items:center;justify-content:space-between;padding:11px 14px;border-bottom:1px solid var(--line)}
.notifhead b{font-size:13px;font-family:"Space Grotesk",Inter,sans-serif}
.notifx{border:0;background:none;color:var(--muted);font-size:20px;line-height:1;cursor:pointer;padding:0 2px}
.notifx:hover{color:var(--ink)}
.notifbody{max-height:min(60vh,420px);overflow-y:auto;padding:4px 10px}
.notiffoot{display:block;width:100%;border:0;border-top:1px solid var(--line);background:var(--panel-2);
  color:var(--brand);font-weight:600;font-size:12.5px;padding:11px;cursor:pointer;text-align:center}
.notiffoot:hover{background:var(--panel-3)}

/* ── Movement between areas ─────────────────────────────────────────────────────────── */
.flows{display:flex;flex-direction:column;gap:9px}
.flow{display:grid;grid-template-columns:minmax(52px,1fr) 2.1fr minmax(52px,1fr) auto;
  gap:8px;align-items:center}
.flow .fl-s,.flow .fl-d{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.flow .fl-d{text-align:right;color:var(--muted)}
.flow .fl-b{height:7px;border-radius:4px;background:var(--grid);overflow:hidden}
.flow .fl-b i{display:block;height:100%;border-radius:4px}

/* ── Presence by department — 3D "pixar" pillars (present vs enrolled, meaningful heights) ─ */
.dep3d{width:100%;overflow:visible}
.dep3d svg{width:100%;height:auto;display:block;overflow:visible}
.dep3d .d3d-bob{animation:d3dBob 4s ease-in-out infinite;animation-delay:calc(var(--i,0)*.45s)}
.dep3d .d3d-shadow{animation:d3dShadow 4s ease-in-out infinite;animation-delay:calc(var(--i,0)*.45s);transform-box:fill-box;transform-origin:center}
.dep3d.d3d-enter .d3d-grow{animation:d3dGrow 1s cubic-bezier(.34,1.56,.64,1) both;animation-delay:calc(var(--i,0)*.08s)}
.dep3d .d3d-sheen{animation:d3dSheen 7s linear infinite;mix-blend-mode:overlay;pointer-events:none}
@keyframes d3dGrow{0%{transform:scaleY(0);opacity:0}60%{transform:scaleY(1.05);opacity:1}100%{transform:scaleY(1);opacity:1}}
@keyframes d3dBob{0%,100%{transform:translateY(0)}50%{transform:translateY(-5px)}}
@keyframes d3dShadow{0%,100%{transform:scale(1);opacity:.7}50%{transform:scale(.85);opacity:.45}}
@keyframes d3dSheen{0%{transform:translateX(-560px) skewX(-25deg);opacity:0}15%{opacity:.7}35%{opacity:0}100%{transform:translateX(620px) skewX(-25deg);opacity:0}}
/* Reduce-motion: the global `*{animation:none}` rule above freezes the pillars ("stable bars" when the OS
   reduce-motion flag is on). Keep their GENTLE ambient float/shadow/sheen (≤5px — not vestibular) since the
   user wants them moving; only the bigger grow-in ENTRANCE respects reduce-motion. Higher specificity +
   !important overrides the global rule. */
@media(prefers-reduced-motion:reduce){
  .dep3d .d3d-grow{animation:none!important}
  .dep3d .d3d-bob{animation:d3dBob 4s ease-in-out infinite!important;animation-delay:calc(var(--i,0)*.45s)!important}
  .dep3d .d3d-shadow{animation:d3dShadow 4s ease-in-out infinite!important;animation-delay:calc(var(--i,0)*.45s)!important}
  .dep3d .d3d-sheen{animation:d3dSheen 7s linear infinite!important}
}

/* ── Identity split — 3D "pixar" glossy donut (torus + rotating sheen + float) ───────── */
.id3d{display:flex;flex-direction:column;gap:10px;width:100%}
.id3d svg{width:100%;height:auto;display:block;overflow:visible}
.id3d .id3d-float{transform-origin:160px 115px;animation:id3dFloat 4.2s ease-in-out infinite}
.id3d .id3d-shadow{transform-origin:160px 188px;animation:id3dShadowP 4.2s ease-in-out infinite}
.id3d .id3d-shine{animation:id3dShine 4.2s ease-in-out infinite}
.id3d .id3d-seg{transition:opacity var(--t),filter var(--t)}
.id3d .id3d-seg.go{cursor:pointer}
.id3d .id3d-ring:hover .id3d-seg{opacity:.4}
.id3d .id3d-ring:hover .id3d-seg:hover{opacity:1;filter:brightness(1.12)}
.id3d.id3d-enter .id3d-ring{transform-origin:160px 115px;animation:id3dPop .9s cubic-bezier(.34,1.56,.64,1) both}
@keyframes id3dPop{0%{transform:scale(.55);opacity:0}70%{transform:scale(1.04);opacity:1}100%{transform:scale(1)}}
@keyframes id3dFloat{0%,100%{transform:translateY(0)}50%{transform:translateY(-6px)}}
@keyframes id3dShadowP{0%,100%{transform:scale(1);opacity:.6}50%{transform:scale(.9);opacity:.4}}
@keyframes id3dShine{0%,100%{opacity:.25}50%{opacity:.6}}
/* Reduce-motion: keep the identity donut's gentle float/shadow/shine (like the dept pillars); only the
   pop-in ENTRANCE respects the flag. Higher specificity + !important beats the global `*` reduce rule. */
@media(prefers-reduced-motion:reduce){
  .id3d.id3d-enter .id3d-ring{animation:none!important}
  .id3d .id3d-float{animation:id3dFloat 4.2s ease-in-out infinite!important}
  .id3d .id3d-shadow{animation:id3dShadowP 4.2s ease-in-out infinite!important}
  .id3d .id3d-shine{animation:id3dShine 4.2s ease-in-out infinite!important}
}

/* Movement-flow: the liquid light-band is the animated `url(#ribflow)` gradient (SVG SMIL, so it runs
   regardless of the OS reduce-motion flag). Screen-blend so it reads as a soft glow riding each pipe. */
.sankey .rib-flow{mix-blend-mode:screen}

/* ── 3D glossy icons (agy-sculpted): subtle, professional motion — hover-lift everywhere, gentle idle
   float on the logo + KPI feature icons only (dense nav stays calm). Kept alive under reduce-motion. ── */
.ic3d{transition:transform .2s cubic-bezier(.34,1.45,.6,1), filter .2s ease; transform-origin:center; overflow:visible}
.nav:hover .ic3d,.btn:hover .ic3d,.iconbtn:hover .ic3d,.kpi:hover .ic3d,.kpi-rich:hover .ic3d,[data-go]:hover .ic3d,[data-p]:hover .ic3d,[data-prow2]:hover .ic3d{
  transform:translateY(-1.5px) scale(1.09);filter:drop-shadow(0 3px 5px rgba(20,30,60,.30)) brightness(1.05)}
.nav.on .ic3d{filter:drop-shadow(0 2px 3px rgba(79,99,246,.4))}
.logo3d,.kpi-rich .ic3d,.kpi .ic3d{animation:ic3dFloat 5s ease-in-out infinite}
@keyframes ic3dFloat{0%,100%{transform:translateY(0)}50%{transform:translateY(-2px)}}
@media(prefers-reduced-motion:reduce){.logo3d,.kpi-rich .ic3d,.kpi .ic3d{animation:none!important}}

/* ── Occupancy 'liquid ridge' — glossy area grows in, a soft sheen sweeps, the peak orb gently bobs ── */
.occ .occ-grow{animation:occGrow 1.1s cubic-bezier(.34,1.3,.55,1) both}
@keyframes occGrow{0%{transform:scaleY(0);opacity:0}70%{opacity:1}100%{transform:scaleY(1);opacity:1}}
.occ .occ-sheen{animation:occSheen 5.5s linear infinite;pointer-events:none}
@keyframes occSheen{0%{transform:translateX(-260px) skewX(-18deg);opacity:0}12%{opacity:.7}30%{opacity:0}100%{transform:translateX(680px) skewX(-18deg);opacity:0}}
.occ .occ-peak{animation:occBob 4.5s ease-in-out infinite}
@keyframes occBob{0%,100%{transform:translateY(0)}50%{transform:translateY(-2.5px)}}
@media(prefers-reduced-motion:reduce){.occ .occ-grow{animation:none!important}.occ .occ-sheen{animation:occSheen 5.5s linear infinite!important}.occ .occ-peak{animation:occBob 4.5s ease-in-out infinite!important}}

/* ── Fit the whole board in one 16:9 viewport on a desktop display ──────────────────── */
@media(min-width:1280px) and (min-height:720px){
  .grid.dash .card{padding:14px 15px}
  .grid.dash .card .head{margin-bottom:9px}
  .grid.dash .tablewrap{max-height:236px;overflow:auto}
  .kpis.k6 .kpi{padding:11px 12px}
}
@media(max-width:900px){
  .hero-strip{align-items:flex-start}
  .flow{grid-template-columns:minmax(46px,1fr) 1.5fr minmax(46px,1fr) auto}
}

/* ── Occupancy trend: axis chart, peak callout, toggleable legend ─────────────────── */
.chart.occ{width:100%;height:auto;display:block}
.chart .cax{font-size:10px;fill:var(--muted);font-family:var(--mono,inherit)}
.chart .cpk{font-size:12px;font-weight:700;fill:var(--ink)}
.chart .hitcol{cursor:crosshair}
.chart .hitcol:hover{fill:var(--ink);fill-opacity:.04}
.clegend.occleg{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-top:8px}
.occleg .lgi{display:inline-flex;align-items:center;gap:6px;padding:4px 9px;border-radius:999px;
  border:1px solid var(--line);background:var(--panel-2);color:var(--ink);font-size:11px;
  cursor:pointer;transition:opacity var(--t),border-color var(--t)}
.occleg .lgi:hover{border-color:var(--brand)}
.occleg .lgi.off{opacity:.42}
.occleg .lgi.off .ln{opacity:.5}
.occleg .lgi.static{cursor:default;background:transparent;border-color:transparent}
.occleg .ln{width:16px;height:3px;border-radius:2px;display:inline-block}
/* Line STYLE is carried in the legend too, so series identity never rests on colour alone. */
.occleg .ln.dash{background-image:repeating-linear-gradient(90deg,currentColor 0 5px,transparent 5px 9px)}
.occleg .ln.dot{background-image:repeating-linear-gradient(90deg,currentColor 0 2px,transparent 2px 6px)}
.occleg .bandsw{width:16px;height:9px;border-radius:3px;display:inline-block;
  background:var(--ok);opacity:.22;border:1px solid color-mix(in srgb,var(--ok) 40%,transparent)}

/* ── Movement flow (Sankey) ────────────────────────────────────────────────────────── */
.chart.sankey{width:100%;height:auto;display:block;overflow:visible}
.sankey .snm{font-size:11px;font-weight:600;fill:var(--ink)}
.sankey .sct{font-size:11px;fill:var(--muted);font-variant-numeric:tabular-nums}
.sankey .spc{fill:var(--muted);opacity:.8}
.sankey .rib{transition:opacity var(--t)}
.sankey:hover .rib{opacity:.16}
.sankey .rib:hover{opacity:.72}
.skfoot{margin-top:9px;padding-top:8px;border-top:1px dashed var(--line)}

/* ── Volty house cards: accent dash KPIs, rich donut, delegated tooltip ─────────────
   Tokens/patterns from reference_volty-design-tokens.md (candemo-olectra house style):
   3px coloured accent down each KPI, mono uppercase letter-spaced labels, subtle glow. */
.kpi-rich{position:relative;overflow:hidden;border-left:3px solid var(--kc,var(--brand));
  transition:transform var(--t),box-shadow var(--t),border-color var(--t)}
.kpi-rich:hover{transform:translateY(-2px);box-shadow:var(--sh-2)}
.kpi-rich::after{content:'';position:absolute;inset:0;pointer-events:none;opacity:0;
  background:radial-gradient(120% 90% at 0% 0%,var(--kc) 0%,transparent 62%);
  transition:opacity var(--t)}
.kpi-rich:hover::after{opacity:.07}
.kpi-rich .kicon{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;
  border-radius:8px;background:color-mix(in srgb,var(--kc) 16%,transparent);color:var(--kc);flex:0 0 26px}
.kpi-rich .kicon svg{width:15px;height:15px}
.kpi-rich .kt{font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
.kpi-rich .kv{font-size:27px;font-weight:700;line-height:1.1;letter-spacing:-.02em;
  font-variant-numeric:tabular-nums;margin:5px 0 2px}

/* Rich donut */
.donutwrap{display:flex;gap:16px;align-items:center;flex-wrap:wrap}
.dchart{width:150px;height:150px;flex:0 0 150px}
.dchart .seg{transition:opacity var(--t),stroke-width var(--t);
  animation:segIn .5s cubic-bezier(.4,0,.2,1) both}
@keyframes segIn{from{opacity:0;stroke-width:8}to{opacity:1;stroke-width:18}}
.dchart:hover .seg{opacity:.42}
.dchart .seg:hover{opacity:1;stroke-width:21}
.dchart .seg.go{cursor:pointer}
/* RECORDED GOTCHA: without this the centre text eats hover on the arcs behind it. */
.dchart .dcentre{pointer-events:none}
.dchart .dnum{font-size:24px;font-weight:700;fill:var(--ink);font-variant-numeric:tabular-nums}
.dchart .dsub{font-size:10px;fill:var(--muted);letter-spacing:.08em;text-transform:uppercase}
.dlegend{flex:1 1 190px;display:flex;flex-direction:column;gap:9px;min-width:180px}
.dlegend .dlg{display:grid;grid-template-columns:10px 1fr auto auto;gap:8px;align-items:center;
  padding:5px 6px;border-radius:9px;transition:background var(--t)}
.dlegend .dlg:hover{background:var(--panel-2)}
.dlegend .dlg.go{cursor:pointer}
.dlegend .dlg i{width:10px;height:10px;border-radius:3px}
.dlegend .dll{font-size:12px;color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dlegend .dlv{font-size:13px;font-weight:700}
.dlegend .dlp{min-width:34px;text-align:right}
.dlegend .dlbar{grid-column:1/-1;height:4px;border-radius:3px;background:var(--grid);overflow:hidden}
.dlegend .dlbar i{display:block;height:100%;border-radius:3px;opacity:.9}

/* Delegated tooltip */
.vtip{position:fixed;z-index:1000;pointer-events:none;opacity:0;transform:translateY(3px);
  transition:opacity .13s ease,transform .13s ease;max-width:260px;
  background:var(--nav);color:#e9eef9;border:1px solid var(--line);border-radius:9px;
  padding:7px 10px;font-size:11.5px;line-height:1.45;box-shadow:var(--sh-3)}
.vtip.on{opacity:1;transform:translateY(0)}
@media(prefers-reduced-motion:reduce){
  .dchart .seg{animation:none}
  .kpi-rich:hover{transform:none}
  .vtip{transition:none}
}

/* ── House-style card titles + data tables (candemo pattern, theme-safe) ────────────
   candemo renders panel titles in uppercase letter-spaced mono accent. Its accent (#00d4ff)
   is dark-theme only, so we use --brand, which is defined per theme (#4f63f6 / #6d84ff) and
   stays legible on both surfaces. */
.head h3{font-family:"JetBrains Mono","Space Mono",ui-monospace,monospace;
  font-size:11.5px;font-weight:700;letter-spacing:.11em;text-transform:uppercase;
  color:var(--brand);display:flex;align-items:center;gap:7px}
.head h3::before{content:'';width:3px;height:12px;border-radius:2px;background:currentColor;
  opacity:.85;flex:0 0 3px}
.head small{margin-top:3px}

/* Tables: sticky header so the label stays put inside the capped-height dashboard panels,
   and tabular figures so digits don't jitter between refreshes. */
.tablewrap{position:relative}
.tablewrap thead th{position:sticky;top:0;z-index:2;background:var(--panel);
  font-size:10px;color:var(--muted);
  box-shadow:inset 0 -1px 0 var(--line)}
.tablewrap table{border-collapse:separate;border-spacing:0}
td.mono,.score,.kv,td .mono{font-variant-numeric:tabular-nums}
tbody tr:hover td{background:var(--panel-2)}
tbody tr{cursor:default}
tbody tr[data-prow],tbody tr[data-prow2]{cursor:pointer}
/* Row affordance: a brand edge appears on hover for rows that actually open something. */
tbody tr[data-prow] td:first-child,tbody tr[data-prow2] td:first-child{
  box-shadow:inset 3px 0 0 transparent;transition:box-shadow var(--t)}
tbody tr[data-prow]:hover td:first-child,tbody tr[data-prow2]:hover td:first-child{
  box-shadow:inset 3px 0 0 var(--brand)}
.tablewrap::after{content:'';position:sticky;bottom:0;left:0;display:block;height:14px;
  background:linear-gradient(to top,var(--panel),transparent);pointer-events:none;margin-top:-14px}
@media(prefers-reduced-motion:reduce){
  tbody tr[data-prow] td:first-child,tbody tr[data-prow2] td:first-child{transition:none}
}

/* ── Legacy charts brought up to house standard ────────────────────────────────────── */
.chartbox .colr{fill:url(#barFill);transition:opacity var(--t),filter var(--t)}
.chartbox svg:hover .colr{opacity:.42}
.chartbox .colr:hover{opacity:1;filter:brightness(1.12)}
.chartbox .colr.pk{filter:drop-shadow(0 0 6px color-mix(in srgb,var(--brand) 55%,transparent))}
.chartbox .pklab{font-size:11px;font-weight:700;fill:var(--ink);font-variant-numeric:tabular-nums}
.chartbox .gl{stroke:var(--grid);stroke-width:1}
.chartbox .axl{font-size:10px;fill:var(--muted);font-variant-numeric:tabular-nums}
.ring .ringv{transition:stroke-dashoffset .8s cubic-bezier(.4,0,.2,1)}
.ring .ringt{font-size:26px;font-weight:700;fill:var(--ink);font-variant-numeric:tabular-nums}
.ranks .rank{padding:5px 6px;border-radius:9px;transition:background var(--t)}
.ranks .rank:hover{background:var(--panel-2)}

/* ── Live View camera tiles ────────────────────────────────────────────────────────── */
.camtile{transition:transform var(--t),box-shadow var(--t),border-color var(--t)}
.camtile:hover{transform:translateY(-2px);box-shadow:var(--sh-2);border-color:var(--brand)}
.camtile.nostream{opacity:.72}
.camtile.nostream:hover{transform:none;border-color:var(--line)}
.camhead{display:flex;align-items:center;gap:7px}
.camdot{width:7px;height:7px;border-radius:50%;flex:0 0 7px;background:var(--muted)}
.camdot.idle{background:var(--ok);box-shadow:0 0 0 3px color-mix(in srgb,var(--ok) 22%,transparent)}
.camdot.off{background:var(--faint)}
.camdot.live{background:var(--bad);animation:camPulse 1.6s ease-in-out infinite}
@keyframes camPulse{0%,100%{box-shadow:0 0 0 0 color-mix(in srgb,var(--bad) 45%,transparent)}
  50%{box-shadow:0 0 0 5px transparent}}
.camrole{font-size:9px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  padding:2px 6px;border-radius:5px;border:1px solid var(--line);color:var(--muted)}
.camrole.exit{color:var(--bad);border-color:color-mix(in srgb,var(--bad) 40%,var(--line))}
.camrole.entry{color:var(--ok);border-color:color-mix(in srgb,var(--ok) 40%,var(--line))}
@media(prefers-reduced-motion:reduce){
  .camtile:hover{transform:none}
  .camdot.live{animation:none}
  .ring .ringv{transition:none}
}

/* ══ Shared stat primitives — lifts Attendance, Exceptions, People, Productivity,
      Compliance, Reports and Floor in one place, so the six pages can't drift apart. ══ */

/* Stat chips (.mini): mono uppercase label, tabular value, accent edge on hover. */
.mini{position:relative;overflow:hidden;transition:transform var(--t),box-shadow var(--t),border-color var(--t)}
.mini:hover{transform:translateY(-1px);box-shadow:var(--sh-1);border-color:color-mix(in srgb,var(--brand) 38%,var(--line))}
.mini .ml{font-family:"JetBrains Mono","Space Mono",ui-monospace,monospace;font-size:9.5px;
  font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}
.mini b{font-size:19px;font-weight:700;letter-spacing:-.01em;font-variant-numeric:tabular-nums;
  line-height:1.15}
.mini::before{content:'';position:absolute;left:0;top:0;bottom:0;width:2px;background:var(--brand);
  opacity:0;transition:opacity var(--t)}
.mini:hover::before{opacity:.9}
.mini.ok::before{background:var(--ok)} .mini.warn::before{background:var(--warn)}
.mini.bad::before{background:var(--bad)}

/* Labelled progress rows (.metric): the value carries the weight, the bar supports it. */
.metric{margin:12px 0}
.metric .mrow{font-size:12.5px;gap:8px}
.metric .mrow .mval{font-variant-numeric:tabular-nums;font-size:13px;color:var(--ink)}
.bar{height:8px;background:var(--grid);box-shadow:inset 0 1px 2px rgba(0,0,0,.10)}
.bar i{background:linear-gradient(90deg,var(--brand),var(--cyan));
  transition:width .6s cubic-bezier(.4,0,.2,1)}
.metric:hover .bar i{filter:brightness(1.1)}

/* Drawer / summary stat tiles */
.pdstat{transition:border-color var(--t),background var(--t)}
.pdstat:hover{border-color:color-mix(in srgb,var(--brand) 34%,var(--line));background:var(--panel-3)}
.pdstat span:first-child{font-family:"JetBrains Mono","Space Mono",ui-monospace,monospace;
  font-size:9.5px;font-weight:700;letter-spacing:.1em;text-transform:uppercase}
.pdstat b{font-variant-numeric:tabular-nums}

/* Hero block (Floor / Productivity score) */
.hero{padding:4px 0}
.hero .ring{filter:drop-shadow(0 6px 18px color-mix(in srgb,var(--brand) 26%,transparent))}

/* Empty states: give them room and a calmer voice — they appear a lot in an honest UI. */
.empty{border:1px dashed var(--line);border-radius:12px;background:var(--panel-2);
  line-height:1.6}

/* Ranked lists */
.ranks .rn{font-variant-numeric:tabular-nums;font-weight:700}
.ranks .rtrack{height:7px;border-radius:99px;background:var(--grid);overflow:hidden}
.ranks .rtrack i{transition:width .6s cubic-bezier(.4,0,.2,1)}

@media(prefers-reduced-motion:reduce){
  .mini:hover{transform:none}
  .bar i,.ranks .rtrack i{transition:none}
}

/* ══ Card body fills its card — ported from the redesign prototype, 2026-08-08 ═════════
   Grid rows stretch every card to the tallest in the row (right: row-mates should align),
   but card BODIES stayed at their natural height, leaving large voids under the chart cards.
   Only the DATA SURFACE may grow; anything that annotates it keeps its natural height.
   Learned the hard way: a blanket `.head ~ *{flex:1}` gives the legend an equal share and
   squashes the plot to half the card. */
.grid.dash > .card{ display:flex; flex-direction:column; }
.grid.dash > .card > .head{ flex:0 0 auto; }
.grid.dash > .card > svg.chart,
.grid.dash > .card > .chartbox,
.grid.dash > .card > .donutwrap,
.grid.dash > .card > .tablewrap,
.grid.dash > .card > .ins-wrap,
.grid.dash > .card > .deps,
.grid.dash > .card > .flows{ flex:1 1 auto; min-height:0; }
.grid.dash > .card > svg.chart{ width:100%; height:100%; max-height:none; }
.grid.dash .chartbox svg,
.grid.dash .sankey{ width:100%; height:100%; }
/* annotations stay at natural height */
.grid.dash > .card > .clegend,
.grid.dash > .card > .occleg,
.grid.dash > .card > .skfoot,
.grid.dash > .card > .ins-foot,
.grid.dash > .card > .zlegend{ flex:0 0 auto; }
/* tables read from the top; row lists distribute down the card instead of bunching */
.grid.dash > .card > .tablewrap{ display:block; overflow:auto; }
.grid.dash > .card > .deps,
.grid.dash > .card > .ins-wrap,
.grid.dash > .card > .flows{ display:flex; flex-direction:column;
  justify-content:space-between; gap:10px; overflow:auto; }
.grid.dash .dep{ align-items:center; }
.grid.dash .dep .dbar{ height:10px; }

/* ── Live floor map (ported from the prototype, 2026-08-08) ───────────────────────────
   Picker overlays the plan so it costs no map height; the plan letterboxes into a bounded
   box so the card height-matches its row-mate instead of towering over it. */
.fm-wrap{ position:relative; flex:1; min-height:0; display:flex; flex-direction:column; }
.fm-view{ flex:1; min-height:0; display:flex; gap:10px; align-items:stretch; justify-content:center; }
.fm-view.dual .fm-pane{ flex:1 1 50%; }
.fm-pane{ min-width:0; min-height:0; display:flex; align-items:center; justify-content:center; }
.fm-pane svg{ width:100%; height:100%; max-height:100%; display:block; }
/* the plan's own title band duplicates the card header */
.fm-pane svg .title, .fm-pane svg .subtitle{ display:none; }
.fm-count{ font:700 12px "JetBrains Mono",monospace; fill:var(--ink); }
.fm-sel{ position:absolute; top:6px; left:6px; z-index:4; appearance:none; -webkit-appearance:none;
  background:color-mix(in srgb,var(--panel) 92%,transparent);
  backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
  color:var(--ink); border:1px solid var(--line); border-radius:8px;
  padding:3px 22px 3px 9px; font-size:11.5px; font-weight:600; cursor:pointer; box-shadow:var(--sh-1);
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='7'><path d='M1 1l4 4 4-4' fill='none' stroke='%2393a2c4' stroke-width='1.6' stroke-linecap='round'/></svg>");
  background-repeat:no-repeat; background-position:right 7px center; }
.fm-sel:hover{ border-color:var(--brand); }
.fm-sel:focus-visible{ outline:2px solid var(--brand); outline-offset:1px; }
[data-zoneclick]{ transition:opacity var(--t); }
[data-zoneclick]:hover{ opacity:.75; }
@media(max-width:1100px){ .fm-view.dual{ flex-direction:column; } }

/* ── Office map (demo component). Namespaced under #officeMap so nothing leaks. ── */
.ommount{ position:relative; width:100%; }
.ommount .office-map-card{ width:100%; max-width:100%; height:auto; aspect-ratio:872/455; }
#officeMap .live-badge.is-sample{ background:var(--warn-bg); color:var(--warn);
  border-color:color-mix(in srgb,var(--warn) 45%,var(--line)); letter-spacing:.08em; }

#officeMap *, #officeMap *::before, #officeMap *::after{
    box-sizing: border-box;
    margin: 0;
    padding: 0;
  }
#officeMap body{
    background-color: #05070d;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    color: #f8fafc;
    user-select: none;
    -webkit-user-select: none;
    overflow: hidden;
  }
#officeMap /* Component Container - Exact 872x455 */
  .office-map-card{
    position: relative;
    width: 872px;
    height: 455px;
    background: #090d16;
    border-radius: 14px;
    overflow: hidden;
    box-shadow: 0 25px 60px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(255, 255, 255, 0.08);
    background-image: 
      radial-gradient(circle at 50% 35%, rgba(30, 41, 59, 0.38) 0%, rgba(9, 13, 22, 0.95) 75%),
      radial-gradient(circle at 50% 50%, transparent 60%, rgba(5, 7, 13, 0.85) 100%);
  }
#officeMap .vignette-overlay{
    position: absolute;
    inset: 0;
    pointer-events: none;
    box-shadow: inset 0 0 90px rgba(0, 0, 0, 0.75);
    border-radius: 14px;
    z-index: 10;
  }
#officeMap /* Header Top Bar */
  .header-bar{
    position: absolute;
    top: 16px;
    left: 22px;
    right: 22px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    z-index: 20;
    pointer-events: none;
  }
#officeMap .title-group{
    display: flex;
    align-items: center;
    gap: 10px;
    pointer-events: auto;
  }
#officeMap .map-title{
    font-size: 18px;
    font-weight: 800;
    letter-spacing: -0.3px;
    color: #f8fafc;
    text-shadow: 0 2px 10px rgba(0, 0, 0, 0.6);
  }
#officeMap .live-badge{
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px;
    background: rgba(16, 185, 129, 0.14);
    border: 1px solid rgba(16, 185, 129, 0.38);
    border-radius: 20px;
    font-size: 10px;
    font-weight: 800;
    letter-spacing: 1px;
    color: #34d399;
    box-shadow: 0 0 12px rgba(16, 185, 129, 0.18);
  }
#officeMap .live-dot{
    width: 6px;
    height: 6px;
    background-color: #10b981;
    border-radius: 50%;
    box-shadow: 0 0 8px #10b981;
    animation: pulse-dot 1.8s infinite ease-in-out;
  }
  @keyframes pulse-dot {
    0%, 100% { transform: scale(1); opacity: 1; }
50%{ transform: scale(1.4); opacity: 0.5; }
  }
#officeMap .dismiss-btn{
    pointer-events: auto;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: rgba(15, 23, 42, 0.65);
    border: 1px solid rgba(255, 255, 255, 0.12);
    color: #94a3b8;
    font-size: 16px;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: all 0.2s ease;
    backdrop-filter: blur(8px);
  }
#officeMap .dismiss-btn:hover{
    background: rgba(30, 41, 59, 0.9);
    color: #f8fafc;
    border-color: rgba(255, 255, 255, 0.25);
    transform: scale(1.05);
  }
#officeMap /* Left Edge Zoom Cluster */
  .zoom-cluster{
    position: absolute;
    left: 20px;
    top: 140px;
    display: flex;
    flex-direction: column;
    background: rgba(15, 23, 42, 0.82);
    border: 1px solid rgba(255, 255, 255, 0.14);
    border-radius: 9px;
    overflow: hidden;
    backdrop-filter: blur(12px);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
    z-index: 20;
  }
#officeMap .zoom-btn{
    width: 32px;
    height: 32px;
    background: transparent;
    border: none;
    color: #cbd5e1;
    font-size: 15px;
    font-weight: 600;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
  }
#officeMap .zoom-btn:hover{
    background: rgba(255, 255, 255, 0.12);
    color: #ffffff;
  }
#officeMap .zoom-btn:active{
    background: rgba(255, 255, 255, 0.2);
  }
#officeMap .zoom-divider{
    height: 1px;
    background: rgba(255, 255, 255, 0.08);
  }
#officeMap /* Bottom Bar */
  .bottom-bar{
    position: absolute;
    bottom: 16px;
    left: 22px;
    right: 22px;
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    z-index: 20;
    pointer-events: none;
  }
#officeMap .legend-container{
    pointer-events: auto;
    display: flex;
    flex-direction: column;
    gap: 5px;
  }
#officeMap .legend-bar-row{
    display: flex;
    align-items: center;
    gap: 8px;
  }
#officeMap .legend-text{
    font-size: 11px;
    font-weight: 600;
    color: #94a3b8;
  }
#officeMap .gradient-bar{
    width: 110px;
    height: 7px;
    border-radius: 4px;
    background: linear-gradient(90deg, #10b981 0%, #f59e0b 50%, #ef4444 100%);
    box-shadow: 0 0 8px rgba(0, 0, 0, 0.5);
  }
#officeMap .legend-label{
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.5px;
    color: #64748b;
  }
#officeMap .toggle-3d-btn{
    pointer-events: auto;
    height: 30px;
    padding: 0 14px;
    background: rgba(15, 23, 42, 0.85);
    border: 1px solid rgba(255, 255, 255, 0.16);
    border-radius: 8px;
    color: #38bdf8;
    font-size: 12px;
    font-weight: 800;
    letter-spacing: 0.5px;
    display: flex;
    align-items: center;
    gap: 6px;
    cursor: pointer;
    backdrop-filter: blur(10px);
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.4);
    transition: all 0.2s ease;
  }
#officeMap .toggle-3d-btn:hover{
    background: rgba(30, 41, 59, 0.95);
    border-color: rgba(56, 189, 248, 0.4);
    transform: translateY(-1px);
  }
#officeMap .toggle-3d-btn.active-2d{
    color: #94a3b8;
    border-color: rgba(255, 255, 255, 0.12);
  }
#officeMap /* SVG Surface */
  .svg-canvas{
    width: 100%;
    height: 100%;
    display: block;
    cursor: grab;
  }
#officeMap .svg-canvas:active{
    cursor: grabbing;
  }
#officeMap /* Room Group Hover Effects */
  .room-node{
    cursor: pointer;
    transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), filter 0.25s ease;
  }
#officeMap .room-node:hover .floor-mesh{
    filter: brightness(1.25);
  }
#officeMap .room-node:hover .room-glow-fill{
    opacity: 0.95;
  }
#officeMap /* Floating Pill Cards */
  .card-node{
    cursor: pointer;
    pointer-events: auto;
    transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
  }
/* NOTE: a CSS `transform` here OVERRIDES the SVG positioning `transform` attribute on the <g>,
   teleporting the card to the origin → a hover enter/leave blink+move loop. Use a non-geometric
   cue (brightness); the floating-card bg/border/shadow below is the visible hover state. */
#officeMap .card-node:hover{
    filter: brightness(1.08);
  }
#officeMap .floating-card{
    background: rgba(15, 23, 42, 0.88);
    border: 1px solid rgba(255, 255, 255, 0.16);
    border-radius: 18px;
    padding: 4px 12px;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(0, 0, 0, 0.25);
    backdrop-filter: blur(8px);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    white-space: nowrap;
    transition: all 0.2s ease;
  }
#officeMap .card-node:hover .floating-card{
    background: rgba(30, 41, 59, 0.96);
    border-color: rgba(255, 255, 255, 0.32);
    box-shadow: 0 12px 28px rgba(0, 0, 0, 0.7);
  }
#officeMap .card-name{
    font-size: 11px;
    font-weight: 700;
    color: #f8fafc;
    line-height: 1.2;
  }
#officeMap .card-count-line{
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: 11px;
    line-height: 1.2;
    margin-top: 1px;
  }
#officeMap .person-icon{
    width: 10px;
    height: 10px;
    fill: currentColor;
  }
#officeMap .count-bold{
    font-weight: 800;
  }
#officeMap .capacity-muted{
    font-weight: 500;
    color: #94a3b8;
  }
#officeMap /* Tooltip */
  .room-tooltip{
    position: absolute;
    pointer-events: none;
    z-index: 50;
    opacity: 0;
    transform: translate(-50%, -100%) scale(0.92);
    transition: opacity 0.18s ease, transform 0.18s ease;
    background: rgba(15, 23, 42, 0.95);
    border: 1px solid rgba(255, 255, 255, 0.18);
    border-radius: 10px;
    padding: 10px 14px;
    box-shadow: 0 12px 28px rgba(0, 0, 0, 0.65), 0 0 0 1px rgba(0, 0, 0, 0.2);
    backdrop-filter: blur(12px);
    min-width: 165px;
  }
#officeMap .room-tooltip.visible{
    opacity: 1;
    transform: translate(-50%, -115%) scale(1);
  }
#officeMap .tt-title{
    font-size: 12px;
    font-weight: 700;
    color: #f8fafc;
    margin-bottom: 6px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
  }
#officeMap .tt-badge{
    font-size: 9px;
    font-weight: 800;
    padding: 2px 6px;
    border-radius: 4px;
    text-transform: uppercase;
  }
#officeMap .tt-badge.low{ background: rgba(16, 185, 129, 0.2); color: #34d399; }
#officeMap .tt-badge.med{ background: rgba(245, 158, 11, 0.2); color: #fbbf24; }
#officeMap .tt-badge.high{ background: rgba(239, 68, 68, 0.25); color: #f87171; }
#officeMap .tt-badge.neutral{ background: rgba(148, 163, 184, 0.2); color: #cbd5e1; }
#officeMap .tt-progress-bg{
    width: 100%;
    height: 5px;
    background: rgba(255, 255, 255, 0.1);
    border-radius: 3px;
    overflow: hidden;
    margin-bottom: 8px;
  }
#officeMap .tt-progress-fill{
    height: 100%;
    border-radius: 3px;
  }
#officeMap .tt-metrics{
    display: flex;
    justify-content: space-between;
    font-size: 11px;
    color: #94a3b8;
  }
#officeMap .tt-metrics strong{
    color: #f1f5f9;
  }

  @media (prefers-reduced-motion: reduce) {
    *, .live-dot, .room-tooltip, .toggle-3d-btn, .card-node, .room-node {
      animation: none !important;
      transition: none !important;
    }
  }

/* ===================== Occupancy trend (ref-aligned premium chart, our tokens) ===================== */
.occhd{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:8px}
.occrange{display:flex;gap:6px;flex-wrap:wrap}
.occpill{border:1px solid var(--line);background:var(--panel-2);color:var(--ink-2);border-radius:999px;padding:6px 13px;font:inherit;font-size:12.5px;font-weight:700;cursor:pointer;transition:var(--t)}
.occpill:hover{border-color:var(--line-2);color:var(--ink)}
.occpill.on{background:linear-gradient(135deg,var(--brand),var(--cyan));border-color:transparent;color:#fff;box-shadow:var(--sh-brand)}
.occstats{display:flex;gap:8px}
.occstat{padding:5px 11px;border-radius:12px;background:var(--panel-2);border:1px solid var(--line);text-align:center;min-width:62px}
.occstat .ol{display:block;font-size:9.5px;font-weight:700;letter-spacing:.06em;color:var(--muted);text-transform:uppercase}
.occstat b{font-family:"JetBrains Mono",monospace;font-size:19px;font-weight:800;color:var(--ink)}
.occstat.pk{background:var(--info-bg);border-color:transparent}.occstat.pk b{color:var(--brand)}
.occplot{position:relative;height:clamp(200px,24vw,290px);margin-top:4px}
.occsvg{width:100%;height:100%;display:block}
.occpt{cursor:pointer}
.occtip{position:absolute;transform:translate(-50%,-118%);pointer-events:none;opacity:0;background:var(--panel);border:1px solid var(--brand);color:var(--ink);border-radius:12px;padding:8px 11px;white-space:nowrap;box-shadow:var(--sh-3);transition:opacity .12s ease;z-index:5}
.occtip-meta{font-size:11px;color:var(--muted);margin-bottom:3px}
.occtip-main{font-size:14px;font-weight:800}
.occtip-sub{font-size:11px;color:var(--muted);margin-top:3px}

/* ===================== Analytics sub-tabs + Alerts & Review ===================== */
.antabs{display:flex;gap:4px;margin:2px 0 16px;border-bottom:1px solid var(--line);flex-wrap:wrap}
.antab{display:inline-flex;align-items:center;gap:7px;border:0;background:transparent;color:var(--muted);
  font:inherit;font-size:13.5px;font-weight:600;padding:9px 15px;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;transition:var(--t)}
.antab svg{width:16px;height:16px}
.antab:hover{color:var(--ink)}
.antab.on{color:var(--brand);border-bottom-color:var(--brand)}
.alrow{display:flex;align-items:center;gap:10px;padding:9px 2px;border-bottom:1px solid var(--line)}
.alrow:last-child{border-bottom:0}
.aldot{width:8px;height:8px;border-radius:50%;flex:0 0 auto;background:var(--muted)}
.aldot.ok{background:var(--ok)}.aldot.warn{background:var(--warn)}.aldot.bad{background:var(--bad)}.aldot.info{background:var(--brand)}
.allbl{flex:1;font-size:13px;color:var(--ink-2)}
.alcnt{font-family:"JetBrains Mono",monospace;font-size:14px;font-weight:700;color:var(--muted)}
.alcnt.warn{color:var(--warn)}.alcnt.bad{color:var(--bad)}.alcnt.info{color:var(--brand)}

/* ===================== Skeleton "artist" loaders ===================== */
.sk{display:inline-block;height:12px;border-radius:6px;vertical-align:middle;
  background:linear-gradient(90deg,var(--panel-2) 25%,var(--panel-3) 37%,var(--panel-2) 63%);
  background-size:400% 100%;animation:skwave 1.4s ease infinite}
@keyframes skwave{0%{background-position:100% 50%}100%{background-position:0 50%}}
@media (prefers-reduced-motion:reduce){.sk{animation:none}}

/* ===================== Dashboard date filter + bigger People-on-site table ===================== */
.dashdate{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--line);background:var(--panel);
  border-radius:99px;padding:3px 6px 3px 11px;font-size:12.5px;font-weight:600;color:var(--ink-2)}
.dashdate svg{width:14px;height:14px;color:var(--brand)}
.dashdate input[type=date]{border:0;background:transparent;color:var(--ink);font:inherit;font-weight:600;cursor:pointer;padding:3px 2px}
.dashdate input[type=date]::-webkit-calendar-picker-indicator{cursor:pointer;opacity:.7}
.datelatest{border:0;background:var(--brand);color:#fff;border-radius:99px;padding:4px 10px;font-size:11.5px;font-weight:700;cursor:pointer}
.datelatest:hover{background:var(--brand-2)}
/* People-on-site card is full width (s12) — make the table larger + more readable */
.bigtable{max-height:none!important}
.bigtable table{width:100%;font-size:14px}
.bigtable thead th{font-size:11.5px;letter-spacing:.03em;padding:12px 14px}
.bigtable tbody td{padding:12px 14px;vertical-align:middle}
.bigtable tbody tr{transition:background var(--t)}
.bigtable tbody tr:hover{background:var(--info-bg)}
.bigtable .ithumb{width:40px;height:40px;border-radius:9px}
.bigtable .tag{font-size:12px;padding:3px 10px}
/* Standard log tables (Detection log + per-person history + Advanced Search) — PROPORTIONAL % columns,
   wrapping micro-headers, comfortable density. NO horizontal scroll. Reviewed 2026-08-13 (codex + agy):
   fixed pixel widths + fat padding + nowrap headers were starving the text columns and truncating headers. */
.logstd table,.astable{width:100%;min-width:0;table-layout:fixed;border-collapse:collapse}
.logstd th,.logstd td,.astable th,.astable td{box-sizing:border-box}
.logstd thead th,.astable thead th{padding:9px clamp(5px,.5vw,9px);font-size:11px;font-weight:700;line-height:1.15;
  color:var(--muted);text-transform:uppercase;letter-spacing:.02em;white-space:normal;overflow:visible;text-overflow:clip;
  background:var(--panel-2);border-bottom:1px solid var(--line);vertical-align:middle;text-align:left;position:sticky;top:0}
.logstd tbody td,.astable tbody td{padding:9px clamp(5px,.5vw,9px);font-size:12.5px;line-height:1.25;white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis;vertical-align:middle;border-bottom:1px solid var(--line);font-variant-numeric:tabular-nums}
.logstd tbody tr,.astable tbody tr{height:56px;cursor:pointer;transition:background var(--t)}
.logstd tbody tr:hover,.astable tbody tr:hover{background:var(--info-bg)}
.logstd tbody td b,.astable tbody td b{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12.5px}
/* Photos: 40px square, object-fit:cover — never distorted/compressed */
.logstd tbody td .ithumb,.logstd tbody td .noimg,.logstd tbody td .asthumb,.astable td .asthumb,.astable td .asthumb.ph{width:40px;height:40px;min-width:40px;border-radius:8px;object-fit:cover;display:block}
.logstd .astag,.astable .astag{font-size:11px;padding:2px 8px;max-width:100%;overflow:hidden;text-overflow:ellipsis;border-radius:6px}
.logstd .asdot,.astable .asdot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:5px;vertical-align:middle}
.logstd .asdot.ok,.astable .asdot.ok{background:var(--ok)}.logstd .asdot.bad,.astable .asdot.bad{background:var(--bad)}
.logstd .asmatch,.astable .asmatch{color:var(--ok);font-weight:700}
/* activity pill colours (mirror the reference, not one flat grey) */
.astag.act-work{background:var(--ok-bg);color:var(--ok)}
.astag.act-meet{background:var(--info-bg);color:var(--brand)}
.astag.act-stand{background:var(--warn-bg);color:var(--warn)}
.astag.act-walk{background:#8b6ee81f;color:var(--violet)}
.astag.act-other{background:var(--panel-3);color:var(--ink-2)}
/* UNIFIED 10-column widths (sum 100%) + min-width guards — Person Type Status Dept Photo Time Zone
   Dwell Camera Match (serial # removed). Person kept optimum; freed space -> Zone/Camera/Time. */
.logstd :is(th,td):nth-child(1),.astable :is(th,td):nth-child(1){width:13%;min-width:112px}
.logstd :is(th,td):nth-child(2),.astable :is(th,td):nth-child(2){width:8%;min-width:64px}
.logstd :is(th,td):nth-child(3),.astable :is(th,td):nth-child(3){width:8%;min-width:74px}
.logstd :is(th,td):nth-child(4),.astable :is(th,td):nth-child(4){width:9%;min-width:72px}
.logstd :is(th,td):nth-child(5),.astable :is(th,td):nth-child(5){width:6%;min-width:64px;text-align:center}
.logstd :is(th,td):nth-child(6),.astable :is(th,td):nth-child(6){width:14%;min-width:124px}
.logstd :is(th,td):nth-child(7),.astable :is(th,td):nth-child(7){width:15%;min-width:108px}
.logstd :is(th,td):nth-child(8),.astable :is(th,td):nth-child(8){width:8%;min-width:84px}
.logstd :is(th,td):nth-child(9),.astable :is(th,td):nth-child(9){width:12%;min-width:92px}
.logstd :is(th,td):nth-child(10),.astable :is(th,td):nth-child(10){width:8%;min-width:58px}
/* header labels + the Status cell never wrap (funnel sits inline) */
.logstd thead th,.astable thead th{white-space:nowrap}
.logstd :is(th,td):nth-child(3),.astable :is(th,td):nth-child(3){white-space:nowrap}
.ddate{color:var(--muted);font-size:11px}
/* Detection log with the multi-select checkbox column (.logsel) — 11 cols: checkbox + the 10 data cols.
   Higher specificity (two classes) overrides the 10-col .logstd map above so Person shifts to nth-child(2). */
.logsel :is(th,td):nth-child(1){width:34px;min-width:34px;text-align:center}
.logsel :is(th,td):nth-child(2){width:13%;min-width:112px}
.logsel :is(th,td):nth-child(3){width:8%;min-width:64px}
.logsel :is(th,td):nth-child(4){width:8%;min-width:74px;white-space:nowrap}
.logsel :is(th,td):nth-child(5){width:9%;min-width:72px}
.logsel :is(th,td):nth-child(6){width:6%;min-width:64px;text-align:center}
.logsel :is(th,td):nth-child(7){width:13%;min-width:118px}
.logsel :is(th,td):nth-child(8){width:15%;min-width:104px}
.logsel :is(th,td):nth-child(9){width:8%;min-width:84px}
.logsel :is(th,td):nth-child(10){width:11%;min-width:88px}
.logsel :is(th,td):nth-child(11){width:8%;min-width:56px}
.selcol{text-align:center}
.selcol input{cursor:pointer;margin:0}
/* Bulk-action bar (appears when ≥1 row selected) */
.detbulk{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:10px 0 2px;padding:9px 12px;border:1px solid var(--brand);border-radius:10px;background:color-mix(in srgb,var(--brand) 9%,transparent)}
.detbulk-n{font-size:13px;color:var(--ink)}
.detbulk .btn.sm{padding:5px 12px;font-size:12px}
/* Detection-feed per-column header filters (anpr.volty.in Detection Feed pattern). Each filterable
   <th> carries a funnel button that opens a body-appended checkbox/search/range popover. */
.detshow{display:flex;align-items:center;gap:6px;font-size:12.5px;color:var(--muted)}
.detshow select{padding:5px 7px;border:1px solid var(--line);border-radius:8px;background:var(--panel);color:var(--ink);font-size:12.5px}
.btn.danger{background:transparent;border:1px solid var(--bad);color:var(--bad);font-weight:700}
.btn.danger:hover{background:var(--bad);color:#fff}
.logstd thead th .thlbl{vertical-align:middle}
.colf{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;margin-left:5px;padding:0;border:0;border-radius:4px;background:transparent;color:var(--muted);cursor:pointer;vertical-align:middle;opacity:.7}
.colf:hover{background:var(--panel-3);color:var(--ink);opacity:1}
.colf.on{background:var(--brand);color:#fff;opacity:1}
.colmenu{position:fixed;z-index:1200;min-width:196px;max-width:280px;background:var(--panel);border:1px solid var(--line);border-radius:11px;box-shadow:0 12px 34px rgba(0,0,0,.28);padding:10px;font-size:13px}
.colmenu .colmh{font-size:11px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--muted);margin-bottom:8px}
.colmenu .colmlist{max-height:230px;overflow:auto;display:flex;flex-direction:column;gap:2px}
.colmenu .colcb{display:flex;align-items:center;gap:8px;padding:5px 6px;border-radius:7px;cursor:pointer;font-size:12.5px}
.colmenu .colcb:hover{background:var(--panel-3)}
.colmenu .colcb input{cursor:pointer;margin:0}
.colmenu .colmq,.colmenu .colml input{width:100%;box-sizing:border-box;padding:7px 8px;border:1px solid var(--line);border-radius:8px;background:var(--panel-2,var(--panel));color:var(--ink);font-size:12.5px}
.colmenu .colml{display:flex;flex-direction:column;gap:4px;font-size:11px;color:var(--muted);margin-bottom:8px}
.colmenu .colmft{display:flex;justify-content:space-between;gap:8px;margin-top:10px}
.colmenu .btn.sm{padding:5px 12px;font-size:12px}
/* Pagination footer — range label + numbered pager + go-to (page size lives in the header "Show"). */
.detfoot{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;margin-top:12px;padding-top:10px;border-top:1px solid var(--line)}
.detfoot-l,.detfoot-r{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.detgo{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--muted)}
.detgo input{width:56px;text-align:center;padding:4px 6px;border:1px solid var(--line);border-radius:7px;background:var(--panel);color:var(--ink);font-size:12px}
.detfoot .aspg{min-width:30px;height:30px;padding:0 8px;border:1px solid var(--line);border-radius:7px;background:var(--panel);color:var(--ink);font-size:12.5px;cursor:pointer}
.detfoot .aspg.on{background:var(--brand);border-color:var(--brand);color:#fff;font-weight:700}
.detfoot .aspg:disabled{opacity:.4;cursor:default}
.detfoot .asdots{color:var(--muted);padding:0 2px}
/* kebab actions button + body-appended dropdown menu */
.actkebab{min-width:30px!important;min-height:30px!important;padding:4px!important}
.actmenu{position:fixed;z-index:1000;min-width:172px;background:var(--panel);border:1px solid var(--line);
  border-radius:10px;box-shadow:var(--sh-3);padding:5px;display:flex;flex-direction:column;gap:2px}
.actmenu button{display:flex;align-items:center;gap:9px;width:100%;border:0;background:transparent;color:var(--ink);
  font:inherit;font-size:13px;font-weight:500;text-align:left;padding:8px 10px;border-radius:7px;cursor:pointer}
.actmenu button:hover{background:var(--panel-2)}
.actmenu button svg{width:15px;height:15px;color:var(--muted)}
.actmenu button.on{background:var(--info-bg);color:var(--brand)}
.actmenu button.on svg{color:var(--brand)}
.actmenu button.danger{color:var(--bad)}.actmenu button.danger svg{color:var(--bad)}
.actmenu button.danger:hover{background:var(--bad-bg)}

/* ===================== Advanced Person Search ===================== */
.btn.primary{background:var(--brand);border-color:var(--brand);color:#fff;box-shadow:var(--sh-brand)}
.btn.primary:hover{background:var(--brand-2);border-color:var(--brand-2)}
.btn.primary svg{color:#fff}
.btn.sm{padding:6px 10px;font-size:12px;border-radius:8px}
.asf{padding:18px 18px 20px}
.ashd{display:flex;align-items:flex-start;gap:12px;margin-bottom:16px}
.ashd-l{display:flex;align-items:center;gap:12px;flex:1}
.ashd-ic{width:40px;height:40px;border-radius:11px;display:grid;place-items:center;background:var(--info-bg);color:var(--brand);flex:0 0 auto}
.ashd-ic svg{width:20px;height:20px;color:var(--brand)}
.ashd h3{margin:0;font-size:19px}
.ashd small{color:var(--muted)}
.asgrid3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:14px}
.asgrid5{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin-top:14px}
.asfield{display:flex;flex-direction:column;gap:6px}
.asfield>span{font-size:12px;font-weight:600;color:var(--ink-2)}
.asfield input,.asfield select{width:100%;border:1px solid var(--line);background:var(--panel-2);color:var(--ink);
  border-radius:9px;padding:9px 11px;font:inherit;font-size:13px;transition:var(--t);appearance:none}
.asfield input:focus,.asfield select:focus{outline:none;border-color:var(--brand);background:var(--panel);box-shadow:0 0 0 3px var(--info-bg)}
.asfield select{background-image:linear-gradient(45deg,transparent 50%,var(--muted) 50%),linear-gradient(135deg,var(--muted) 50%,transparent 50%);
  background-position:calc(100% - 15px) 16px,calc(100% - 10px) 16px;background-size:5px 5px,5px 5px;background-repeat:no-repeat;padding-right:28px}
.asinput{position:relative;display:flex}
.asinput input{padding-right:38px}
.asico{position:absolute;right:4px;top:4px;bottom:4px;width:30px;border:0;background:transparent;color:var(--muted);cursor:pointer;display:grid;place-items:center;border-radius:7px}
.asico svg{width:15px;height:15px}
.asico:hover{background:var(--panel-3);color:var(--brand)}
.aschiprow{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-top:14px}
.aschl{font-size:12px;font-weight:600;color:var(--muted);margin-right:2px}
.aschip{border:1px solid var(--line);background:var(--panel-2);color:var(--ink-2);border-radius:8px;padding:6px 12px;font-size:12.5px;font-weight:600;cursor:pointer;transition:var(--t)}
.aschip:hover{border-color:var(--line-2);background:var(--panel-3)}
.aschip.on{background:var(--brand);border-color:var(--brand);color:#fff;box-shadow:var(--sh-brand)}
.asbtns{display:flex;justify-content:center;gap:12px;margin-top:20px}
.asbtns .btn{padding:10px 22px}
/* results */
.asres{padding:16px 18px}
.ashead{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px;flex-wrap:wrap}
.ascount{font-size:15px}.ascount b{color:var(--brand);font-size:18px;font-weight:800}
.asexp{display:flex;gap:8px;flex-wrap:wrap}
.astablewrap{overflow-x:auto;border:1px solid var(--line);border-radius:12px}
/* .astable layout/columns are defined in the shared "Standard log tables" block above (proportional %). */
.astable tbody tr{cursor:pointer;transition:background var(--t)}
.astable tbody tr:hover{background:var(--info-bg)}
.astable tbody tr:last-child td{border-bottom:0}
.asthumb{width:34px;height:34px;border-radius:8px;object-fit:cover;background:var(--panel-3);display:block}
span.asthumb.ph{display:grid;place-items:center;color:var(--faint)}
span.asthumb.ph svg{width:16px;height:16px}
.astag{display:inline-flex;align-items:center;padding:2px 9px;border-radius:7px;font-size:11.5px;font-weight:600;background:var(--panel-3);color:var(--ink-2);border:1px solid var(--line)}
.astag.ok{background:var(--info-bg);color:var(--brand);border-color:transparent}
.astag.warn{background:var(--warn-bg);color:var(--warn);border-color:transparent}
.astag.violet{background:#8b6ee81f;color:var(--violet);border-color:transparent}
.asst{color:var(--ink-2)}
.asdot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:6px;vertical-align:middle}
.asdot.ok{background:var(--ok)}.asdot.bad{background:var(--bad)}
.asmatch{color:var(--ok);font-weight:700}
.asfoot{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:14px;flex-wrap:wrap}
.aspager{display:flex;align-items:center;gap:5px;flex-wrap:wrap}
.aspg{min-width:32px;height:32px;padding:0 8px;border:1px solid var(--line);background:var(--panel);color:var(--ink-2);
  border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;transition:var(--t)}
.aspg:hover:not(:disabled):not(.on){background:var(--panel-2);border-color:var(--line-2)}
.aspg.on{background:var(--brand);border-color:var(--brand);color:#fff}
.aspg.nav{color:var(--muted)}
.aspg:disabled{opacity:.4;cursor:not-allowed}
.asdots{color:var(--muted);padding:0 3px}
@media (max-width:1100px){.asgrid5{grid-template-columns:repeat(3,1fr)}}
@media (max-width:820px){.asgrid3{grid-template-columns:1fr}.asgrid5{grid-template-columns:1fr 1fr}.asbtns .btn{flex:1}}

/* Office-hours table (reference representation: Person · Image · Dept · Office Hours · Days) */
.ohtbl table{width:100%;border-collapse:collapse}
.ohtbl th{text-align:left;padding:14px 18px;font-size:12px;letter-spacing:.4px;text-transform:uppercase;color:var(--brand);font-weight:600;border-bottom:1px solid var(--line)}
.ohtbl td{padding:13px 18px;border-bottom:1px solid var(--line);font-size:14px;vertical-align:middle}
.ohtbl tr:last-child td{border-bottom:0}
.ohperson b{font-size:15px;font-weight:700}
.ohgid{color:var(--muted);font-weight:400;margin-top:4px;font-size:12px}
.ohavatar{width:42px;height:42px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-weight:700;font-size:16px;background:var(--panel-3);color:var(--ink);border:2px solid var(--line)}
.ohhours{font-size:18px;font-weight:700;letter-spacing:.3px;font-variant-numeric:tabular-nums;white-space:nowrap}
.ohclock{color:var(--brand);margin-right:8px}
.ohtbl .ohdays{font-weight:600}
.ohfoot{padding:12px 4px 2px}

/* Employee / Department Directory table (People page) — reference: Dept · Person · Image · Shift · Status */
.ddir table{width:100%;border-collapse:collapse}
.ddir th{text-align:left;padding:14px 18px;font-size:12px;letter-spacing:.4px;text-transform:uppercase;color:var(--brand);font-weight:700;border-bottom:1px solid var(--line)}
.ddir td{padding:14px 18px;border-bottom:1px solid var(--line);font-size:14px;vertical-align:middle}
.ddir tr:last-child td{border-bottom:0}
.dddept{display:flex;align-items:center;gap:14px;font-weight:600}
.ddicon{width:40px;height:40px;border-radius:10px;display:grid;place-items:center;font-size:17px;font-weight:800;background:var(--panel-3);color:var(--ink);flex:none}
.ddicon.blue{color:#6d9cff;background:rgba(109,156,255,.13)}
.ddicon.green{color:#31dfa4;background:rgba(49,223,164,.13)}
.ddicon.purple{color:#a675ff;background:rgba(166,117,255,.13)}
.ddicon.orange{color:#ff9e16;background:rgba(255,158,22,.13)}
.ddicon.gold{color:#ffc229;background:rgba(255,194,41,.13)}
.ddicon.cyan{color:#43e1c2;background:rgba(67,225,194,.13)}
.ddicon.red{color:#ff5f69;background:rgba(255,95,105,.13)}
.ddperson b{font-size:15px;font-weight:700}
.ddperson .pid{font-size:12px;margin-top:3px}
.ddshift{display:inline-block;padding:6px 13px;border-radius:8px;font-size:12.5px;font-weight:600;white-space:nowrap}
.ddshift.morning{color:#2fb98a;background:rgba(24,185,137,.14)}
.ddshift.evening{color:#6fa8ff;background:rgba(40,105,220,.14)}
.ddshift.night{color:#a876ff;background:rgba(112,65,220,.14)}
.ddshift.neutral{color:var(--muted);background:var(--panel-3)}
.ddstatus{display:inline-flex;align-items:center;gap:9px;padding:8px 14px;border-radius:8px;font-size:12.5px;font-weight:700;white-space:nowrap}
.ddstatus:before{content:"";width:9px;height:9px;border-radius:50%;background:currentColor}
.ddstatus.active{color:#2fbf8f;background:rgba(18,173,126,.13)}
.ddstatus.leave{color:#e0a11e;background:rgba(192,133,14,.14)}
.ddstatus.inactive{color:#e5555f;background:rgba(177,44,57,.14)}

/* Employee Performance table (Productivity page) — Employee · Dept · Shift · Attendance% · Trend · Score/10 */
.eptbl table{width:100%;border-collapse:collapse}
.eptbl th{text-align:left;padding:13px 16px;font-size:12px;letter-spacing:.5px;text-transform:uppercase;color:var(--brand);font-weight:700;border-bottom:1px solid var(--line)}
.eptbl td{padding:14px 16px;border-bottom:1px solid var(--line);font-size:14px;vertical-align:middle}
.eptbl tr:last-child td{border-bottom:0}
.epemp{display:flex;align-items:center;gap:12px}
.epname{font-size:15px;font-weight:700}
.epid{font-size:12px;margin-top:3px}
.epatt{font-size:17px;font-weight:800;color:#2fbf8f}
.epatt.warning{color:#e0a11e}
.epatt.bad{color:#e5555f}
.eptrend{display:flex;align-items:center;gap:10px}
.eptrend svg{width:70px;height:30px;overflow:visible}
.eptrend.up{color:#29c88f}.eptrend.stable{color:#3f94ff}.eptrend.down{color:#e5555f}
.eptlabel{font-size:12px;font-weight:700}
.epscore{width:56px;height:40px;display:inline-grid;place-items:center;border:1px solid #2ad895;border-radius:9px;font-size:16px;font-weight:800;color:var(--ink);background:var(--panel-3)}
.epscore.blue{border-color:#287bdb}
.epscore.purple{border-color:#6850df}
.epscore.gold{border-color:#d89419;color:#e0a11e}
.epscore.red{border-color:#e33c45;color:#e5555f}

/* Person drawer: photo in Present-Location badge + always-visible close */
.pdloc-photo{overflow:hidden;padding:0;background:var(--panel-3)}
.pdloc-photo img{width:100%;height:100%;object-fit:cover;border-radius:50%;display:block}
.pdclose{color:var(--ink)}
.pdclose:hover{color:var(--bad);border-color:var(--bad)}

/* People-on-site column filters (dashboard) */
.podfbar{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:10px}
.podf{border:1px solid var(--line);background:var(--panel-2);color:var(--ink);border-radius:9px;padding:7px 10px;font-size:13px;min-height:34px}
.podfq{min-width:190px;flex:1 1 190px;max-width:280px}
select.podf{cursor:pointer}
.podf:focus{outline:none;border-color:var(--brand)}

/* Auto-enrolled / Enrolled gallery review (People page) */
.galgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:14px;margin-top:6px}
.galcard{border:1px solid var(--line);border-radius:12px;background:var(--panel-2);padding:12px;display:flex;flex-direction:column;gap:9px}
.galhead{display:flex;justify-content:space-between;align-items:flex-start;gap:8px}
.galwho b{font-size:14px}
.galcrops{display:flex;gap:5px;overflow-x:auto;padding-bottom:3px;min-height:56px}
.galcrops img{width:56px;height:56px;object-fit:cover;border-radius:8px;flex:0 0 auto;background:var(--panel-3);border:1px solid var(--line)}
.galmeta{font-size:12px}
.galacts{display:flex;gap:7px;margin-top:auto}
.galacts .btn{flex:1;justify-content:center}

/* ═══════════════════════════════════════════════════════════════════════════
   MOBILE + TABLET RESPONSIVE LAYER  (append-only — desktop >1024px UNCHANGED)
   Every rule below is inside a max-width media query OR scoped to the NEW
   drawer element (#navBackdrop) / body.nav-open state that JS only sets on
   mobile. No unqualified desktop selector is restated or overridden.
   Core fix: the off-canvas sidebar drawer (the dead #menu button is wired in JS,
   the missing scrim is added here). Breakpoint raised 900→1024 so tablet-portrait
   also gets the hamburger drawer (was: permanent sidebar with a hidden hamburger).
   ═══════════════════════════════════════════════════════════════════════════ */

/* ---- new scrim element (created by app.js). Brand-new selector: cannot touch
        any existing desktop layout. display:none base keeps it off >1024px. ---- */
#navBackdrop{position:fixed;inset:0;z-index:1150;display:none;
  background:rgba(7,12,24,.55);opacity:0;transition:opacity var(--t-2);pointer-events:none}

/* ══════════════ TABLET + MOBILE : ≤1024px — off-canvas sidebar drawer ══════════════ */
@media (max-width:1024px){
  /* reveal the hamburger (base is .only-mobile{display:none}); hide the desktop
     collapse toggle since the drawer always shows full labels */
  .only-mobile{display:grid}
  .sidemin{display:none}

  /* sidebar becomes a fixed off-canvas drawer. `.side.min` is included so a
     desktop-pinned icon-rail (localStorage) can't leave a broken 72px strip. */
  .side,.side.min{position:fixed;top:0;left:0;bottom:0;height:100dvh;
    width:min(284px,82vw);padding:16px 13px;
    transform:translateX(-100%);transition:transform var(--t-2);
    z-index:1200;box-shadow:none}
  /* key visibility on body.nav-open (renderNav() strips .side.open on its render cycle, which
     was closing the drawer mid-open; body.nav-open is only toggled by the drawer JS) */
  body.nav-open .side,body.nav-open .side.min{transform:none;box-shadow:var(--sh-3)}

  /* neutralise the desktop icon-rail collapse INSIDE the drawer (min-width:900
     block still matches 900–1024; these later, equal-specificity rules win) */
  .side.min .brand{flex-direction:row;justify-content:flex-start;padding:6px 8px 18px}
  .side.min .brandtext,
  .side.min .navgroup h5,
  .side.min .nav b,
  .side.min .nav .badge,
  .side.min .sidefoot .foothint,
  .side.min .ping{display:revert}
  .side.min .navgroup{border-top:0;padding-top:0;margin:8px 0 2px}
  .side.min .nav{justify-content:flex-start;padding:7px 11px;gap:11px}
  .side.min .ping{justify-content:flex-start;font-size:11.5px;margin-bottom:7px}

  /* main takes full width automatically (drawer is out of flow); state it plainly */
  .main{width:100%;min-width:0}

  /* scrim visible only while the drawer is open, only on mobile */
  body.nav-open #navBackdrop{display:block;opacity:1;pointer-events:auto}

  /* stop the JS-inline camera-grid columns forcing sideways page scroll */
  .camgrid{grid-template-columns:repeat(2,1fr)!important}

  /* iOS momentum + no horizontal chaining on the scroll wrappers */
  .tablewrap,.astablewrap,.heatwrap{-webkit-overflow-scrolling:touch;overscroll-behavior-x:contain}
  /* belt-and-braces against sideways body scroll from the packed topbar */
  .top{overflow-x:clip}
}

/* ══════════════ MOBILE : ≤768px — stacked layouts, comfortable forms ══════════════ */
@media (max-width:768px){
  /* content + topbar density */
  .content{padding:14px}
  .top{padding:10px 12px;gap:8px}
  .titlewrap{min-width:0;flex:1}
  .titlewrap .sub{display:none}
  .titlewrap h1{font-size:16px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

  /* every multi-column grid collapses to a single column */
  .grid{grid-template-columns:1fr!important}
  .s12,.s8,.s7,.s6,.s5,.s4,.s3{grid-column:auto!important}
  .kpis,.kpis.k6{grid-template-columns:1fr 1fr}
  .videometa{grid-template-columns:1fr 1fr}
  .asgrid3,.asgrid5{grid-template-columns:1fr}

  /* camera wall single column below tablet */
  .camgrid{grid-template-columns:1fr!important}

  /* filter bars stack full width */
  .filters{padding:10px}
  .filters .fld{flex:1 1 100%;min-width:0}
  .filters .fld.grow{min-width:0}
  .filters select,.filters input{width:100%}

  /* forms/inputs full-width & ≥16px so iOS doesn't zoom on focus */
  input:not([type=range]):not([type=checkbox]):not([type=radio]),
  select,textarea{font-size:16px!important}
  .asfield input,.asfield select,.podf,.podfq{width:100%}
  .podfq{max-width:none}

  /* evidence modal → full-screen sheet (beats inline .modalbox width) */
  .modal{padding:0}
  .modalbox{width:100vw!important;max-width:100vw;max-height:100dvh;border-radius:0}
  /* right-side drawers span the viewport */
  .pdrawer{width:100vw}
  .notifpanel{position:fixed;top:56px;left:8px;right:8px;width:auto;max-width:none}

  /* tab strips: single non-wrapping horizontally-scrollable row */
  .antabs,.tabs{flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch}
  .antab,.tab{flex:0 0 auto;white-space:nowrap}

  /* fat desktop table padding trimmed (dense directory/office-hours/perf tables) */
  .ohtbl th,.ohtbl td,.ddir th,.ddir td,.eptbl th,.eptbl td{padding:10px 10px;font-size:13px}
  .ohhours{font-size:15px}
  .logstd tbody td,.astable tbody td,.logstd thead th,.astable thead th{font-size:12px}
  .hero{flex-direction:column;align-items:flex-start;gap:14px}
  .head{flex-wrap:wrap}.head .right{margin-left:0;width:100%}

  /* login gate: never centre-clip on a short/keyboard-shrunk viewport */
  #loginGate{align-items:flex-start!important;overflow-y:auto!important;
    padding:24px 12px!important;-webkit-overflow-scrolling:touch}
  #loginForm input{font-size:16px!important}
}
/* login gate also relaxes on a short landscape viewport */
@media (max-height:560px){
  #loginGate{align-items:flex-start!important;overflow-y:auto!important;padding:20px 12px!important}
}

/* ── floor coverage ribbon: relax the hard 140px name track on small phones ── */
@media (max-width:560px){
  .ribbon{grid-template-columns:minmax(80px,1fr) 1.4fr 34px;gap:8px}
}

/* ══════════════ SMALL PHONE : ≤480px — single-column KPIs, trim secondary UI ══════════════ */
@media (max-width:480px){
  .kpis,.kpis.k6{grid-template-columns:1fr}
  .videometa{grid-template-columns:1fr}
  /* secondary topbar actions (still reachable inside pages) */
  #globalEnroll,#globalSearch{display:none}
  .rolesel{max-width:120px}
  #loginForm{padding:22px 18px!important;border-radius:14px!important;width:94vw!important}
}

/* ── Detection log: mobile card-expand (≤768px). Desktop/tablet keep the .logsel table untouched.
      Compact row (crop+name+status+time) taps open to reveal Type/Dept/Zone/Dwell/Camera/Match. ── */
.detcards{display:none}
@media (max-width:768px){
  .logsel.tablewrap{display:none}
  .detcards{display:block}
  .detcard{border:1px solid var(--line);border-radius:12px;margin-bottom:10px;background:var(--panel2,var(--panel));overflow:hidden}
  .detcard.isw{opacity:.7}
  .detcard-head{display:flex;align-items:center;gap:11px;padding:11px 12px;cursor:pointer}
  .detcard-sel{flex:0 0 auto;display:flex;align-items:center}
  .detcard-thumb{flex:0 0 52px}
  .detcard-main{flex:1;min-width:0}
  .detcard-main>b{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:14px}
  .detcard-sub{margin-top:4px;display:flex;gap:9px;align-items:center;flex-wrap:wrap}
  .detcard-chev{flex:0 0 auto;color:var(--muted);transition:transform .2s}
  .detcard.open .detcard-chev{transform:rotate(180deg)}
  .detcard-detail{padding:2px 13px 13px;border-top:1px solid var(--line)}
  .detcard-kv{display:flex;justify-content:space-between;gap:12px;align-items:center;padding:8px 0;border-bottom:1px solid var(--line);font-size:13px}
  .detcard-kv:last-of-type{border-bottom:0}
  .detcard-kv>span{color:var(--muted);flex:0 0 auto}
  .detcard-open{width:100%;margin-top:10px}
}

/* ── Mobile tap-reliability (2026-08-18): on phones, the continuous .ic3d float animations +
      animated SVG feDropShadow filters saturate weak Android GPUs → main-thread jank → taps drop
      ("tap 5-10 times"). Freeze the decorative animation on mobile (icons stay, just static) and
      declare touch-action:manipulation so Chrome never waits on double-tap-zoom intent. Desktop
      (>768px) untouched. ── */
@media (max-width:768px){
  .ic3d,.logo3d{animation:none!important}
  .pulse{animation:none!important}
  button,a,input,select,textarea,label,.nav,.iconbtn,.topbtn,.btn,.detcard-head{touch-action:manipulation}
}

/* ── THE 922px bug (2026-08-18): grid items default to min-width:auto, so any wide table inside a
      .card forced its grid track to ~908px on a 394px phone → EVERY page panned sideways, tiny UI,
      mis-landing taps. min-width:0 lets cards shrink to the viewport; inner tables scroll inside
      their own wrapper instead of widening the page. Mobile/tablet only. ── */
@media (max-width:1024px){
  .grid>*,.kpis>*,.card{min-width:0;max-width:100%}
  .card{overflow-x:hidden}
  .card .tablewrap,.card .astablewrap,.card .heatwrap{max-width:100%;overflow-x:auto}
  .content{overflow-x:hidden}
}

/* ── Responsive chart SVGs on mobile/tablet (2026-08-18): Office map / Movement flow / Occupancy /
      Identity donut are drawn at fixed pixel widths → clipped "out of frame" in the now-clamped
      cards. All carry viewBox, so scaling to the card width keeps them proportional. Icons are
      unaffected (their attr width ≤24px). ── */
@media (max-width:1024px){
  #view .card svg[viewBox]{max-width:100%;height:auto}
  .ommount svg,.ommount canvas,.ommount>div{max-width:100%!important}
}

/* ── Generic mobile card-expand for ALL log tables (2026-08-18): same UX as the Detections cards,
      auto-generated from any .tablewrap table (attendance, search, people, analytics, alerts…).
      Tables with editable inputs (admin config) keep horizontal scroll. Desktop/tablet unchanged. ── */
.gencards{display:none}
@media (max-width:768px){
  .tablewrap.carded{display:none!important}  /* !important: .grid.dash>.card>.tablewrap{display:block} (4-class) outranks this 2-class rule on the dashboard */
  .gencards{display:block}
  .gencard{border:1px solid var(--line);border-radius:12px;margin-bottom:10px;background:var(--panel2,var(--panel));overflow:hidden}
  .gencard-head{display:flex;align-items:center;gap:11px;padding:11px 12px;cursor:pointer}
  .gencard-img{width:44px;height:44px;border-radius:9px;object-fit:cover;flex:0 0 auto}
  .gencard-main{flex:1;min-width:0;font-size:14px}
  .gencard-main b{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .gencard-chev{flex:0 0 auto;color:var(--muted);transition:transform .2s}
  .gencard.open .gencard-chev{transform:rotate(180deg)}
  .gencard-detail{padding:2px 13px 13px;border-top:1px solid var(--line)}
  .gencard-kv{display:flex;justify-content:space-between;gap:12px;align-items:center;padding:8px 0;border-bottom:1px solid var(--line);font-size:13px}
  .gencard-kv:last-of-type{border-bottom:0}
  .gencard-kv>span{color:var(--muted);flex:0 0 auto;text-transform:capitalize}
  .gencard-kv>div{min-width:0;text-align:right}
  .gencard-open{width:100%;margin-top:10px}
}

/* ═══════════════════════════════════════════════════════════════════════════════════════
   MOBILE SHELL (Phase 1M/2M, folded 2026-08-31 from the standalone-verified patch) —
   structure mirrors gate.volty.in's live production app shell (glass appbar, bottom nav with
   sliding indicator, bottom sheet + scrim, toast), same product-family convention the login
   screen already follows. Palette maps onto shift's own --brand/--ok/--warn/--bad/--panel/--ink
   tokens (both themes), not gate's separate palette. Production version: edge-to-edge, no
   device-mockup bezel (that only existed in the standalone demo file for desktop preview).
   Auto-activated by real viewport width (see boot()/mSyncShellMode() in app.js) — supersedes
   the old cardifyMobile() card conversion for the 5 primary tabs; other pages still fall back
   to the existing desktop-responsive/cardified behavior when visited at narrow width, since
   they don't have a dedicated mobile screen yet (secondary sheet items are placeholders,
   plan §2's explicit allowance for Phase 1M).
   ═══════════════════════════════════════════════════════════════════════════════════════ */
#mobileShellRoot{display:none}
#mobileShellRoot.active{display:flex;flex-direction:column;position:fixed;inset:0;z-index:500;background:var(--bg)}
.mscreen{width:100%;height:100dvh;background:var(--bg);overflow:hidden;position:relative;display:flex;flex-direction:column}
.mappbar{display:flex;align-items:center;justify-content:space-between;padding:max(11px,env(safe-area-inset-top)) 16px 12px;background:color-mix(in srgb,var(--panel) 82%,transparent);backdrop-filter:blur(16px) saturate(1.3);-webkit-backdrop-filter:blur(16px) saturate(1.3);border-bottom:1px solid var(--line);flex:0 0 auto;z-index:6;gap:8px}
.mappbar .mbrand{display:flex;align-items:center;gap:8px;font-weight:800;color:var(--ink);font-size:15px}
.mappbar .mbrand small{display:block;font-weight:600;color:var(--muted);font-size:10.5px}
.mappbar-ic{display:flex;gap:14px;align-items:center;color:var(--ink-2)}
.mappbar-ic .micn{width:20px;height:20px;cursor:pointer;display:grid;place-items:center;border-radius:50%}
.mappbar-ic .micn:active{transform:scale(.88)}
.mbody{flex:1;overflow-y:auto;overflow-x:hidden;padding:14px 14px calc(96px + env(safe-area-inset-bottom));-webkit-overflow-scrolling:touch}
.mbotnav{position:fixed;left:0;right:0;bottom:0;height:calc(64px + env(safe-area-inset-bottom));padding-bottom:env(safe-area-inset-bottom);display:flex;z-index:20;background:color-mix(in srgb,var(--panel) 85%,transparent);backdrop-filter:blur(20px) saturate(1.4);-webkit-backdrop-filter:blur(20px) saturate(1.4);border-top:1px solid var(--line)}
.mnav-ind{position:absolute;top:0;left:0;width:24px;height:3px;border-radius:0 0 4px 4px;background:var(--brand);z-index:1;transition:transform .28s cubic-bezier(.34,1.5,.5,1)}
.mnav{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;color:var(--faint);cursor:pointer;transition:color .2s,transform .12s;z-index:2}
.mnav svg{width:21px;height:21px}
.mnav span{font-size:10px;font-weight:700}
.mnav.on{color:var(--brand)}
.mnav:active{transform:scale(.9)}
.msheetwrap{position:fixed;inset:0;z-index:540}
.mscrim{position:absolute;inset:0;background:rgba(8,12,20,.46);backdrop-filter:blur(2px)}
.msheet{position:absolute;left:0;right:0;bottom:0;background:var(--panel);border-radius:22px 22px 0 0;padding:10px 16px calc(24px + env(safe-area-inset-bottom));box-shadow:0 -16px 44px rgba(8,12,20,.28);max-height:88%;overflow-y:auto}
.msheet .mgrab{width:38px;height:4px;border-radius:3px;background:var(--line-2);margin:6px auto 12px}
.msheet h4{font-size:10.5px;font-weight:800;letter-spacing:.6px;text-transform:uppercase;color:var(--muted);margin:16px 2px 10px}
.msheet h4:first-of-type{margin-top:2px}
.moptg{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.mopt{display:flex;flex-direction:column;align-items:center;gap:6px;padding:14px 8px;border-radius:14px;background:var(--panel-2);cursor:pointer;text-align:center;border:0;font:inherit;color:inherit}
.mopt:active{transform:scale(.96)}
.mopt .moic{width:34px;height:34px;border-radius:50%;background:var(--panel-3);display:grid;place-items:center;color:var(--brand)}
.mopt .mol{font-size:11.5px;font-weight:700;color:var(--ink)}
.mtoast{position:fixed;left:50%;bottom:calc(88px + env(safe-area-inset-bottom));transform:translateX(-50%);background:var(--ink);color:var(--bg);font-size:12px;font-weight:700;padding:10px 18px;border-radius:12px;z-index:550;box-shadow:0 10px 28px rgba(8,12,20,.3);max-width:84%;text-align:center}
.minvinput{width:100%;padding:11px 13px;border-radius:11px;border:1px solid var(--line);background:var(--panel-2);color:var(--ink);font-size:14px;margin-bottom:10px}
.minvchips{display:flex;gap:6px;margin-bottom:14px}
.mchip{flex:1;padding:8px 6px;border-radius:10px;border:1px solid var(--line);background:var(--panel-2);color:var(--ink);font-size:12px;font-weight:700;text-align:center;cursor:pointer}
.mchip.on{background:var(--brand);color:#fff;border-color:var(--brand)}
[data-theme=dark] .mtoast{background:var(--panel-3);color:var(--ink)}
.mkpis{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:14px}
.mkpi{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:12px 14px;display:flex;gap:10px;align-items:flex-start}
.mkpi .mki{width:34px;height:34px;border-radius:50%;background:var(--panel-3);display:grid;place-items:center;flex:0 0 auto;box-shadow:var(--sh-1)}
.mkpi .mki svg{width:19px;height:19px}
.mkpi .mktxt{min-width:0}
.mkpi .mkt{font-size:10.5px;font-weight:700;text-transform:uppercase;color:var(--muted);letter-spacing:.3px}
.mkpi .mkv{font-size:20px;font-weight:800;color:var(--ink);margin-top:1px;line-height:1.15}
.mkpi .mkd{font-size:10.5px;font-weight:600;color:var(--muted);margin-top:2px}
.mcard{background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:14px 16px;margin-bottom:12px}
.mcard h5{display:flex;align-items:center;gap:7px;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.4px;color:var(--muted);margin-bottom:10px}
.mcard h5 svg{width:15px;height:15px}
.mrow-person{display:flex;align-items:center;gap:11px;padding:9px 0;border-bottom:1px solid var(--line-2);cursor:pointer}
.mrow-person:last-child{border-bottom:0}
.mrow-person .mthumb{flex:0 0 auto}
.mrow-person .mmain{flex:1;min-width:0}
.mrow-person .mname{font-weight:700;font-size:13.5px;color:var(--ink)}
.mrow-person .msub{font-size:11px;color:var(--muted);margin-top:1px}
.mtag{display:inline-block;padding:3px 9px;border-radius:999px;font-size:10.5px;font-weight:700}
.mtag.ok{background:var(--ok-bg);color:var(--ok)}
.mtag.warn{background:var(--warn-bg);color:var(--warn)}
.mtag.bad{background:var(--bad-bg);color:var(--bad)}
.mtag.info{background:var(--info-bg);color:var(--info)}
.mempty{text-align:center;color:var(--muted);font-size:12.5px;padding:20px 10px}

/* Phase 4M (2026-08-31): mobile camera grid — reuses the SAME .camtile/.camhead/.camvid/etc
   desktop classes (camTile() output unchanged), just a 2-column wrapper for narrow width. */
.mcamgrid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.mcamgrid .camtile{min-width:0}
.mcamgrid .camhead{flex-wrap:wrap;height:auto;min-height:32px}
.mcamgrid .camhead .muted.sm{display:none}
.mcamgrid .camhead b{font-size:12.5px}

/* ===================================================================================
   GATE-STYLE ENROLMENT — visual system lifted from the Gate Register app.
   Scoped entirely under .gen-* so it cannot reach any existing console surface.
   Tokens are gate's, re-declared locally rather than overriding shift's :root, so
   shift's palette elsewhere is untouched.
   Mobile-first, matching gate's stated priority: mobile -> tablet -> desktop.
   =================================================================================== */
.gen-box{
  --gen-primary:#1E80D8; --gen-green:#159A55; --gen-red:#E5484D;
  --gen-card:#FFFFFF; --gen-ink:#0F1B2D; --gen-ink2:#33425A; --gen-muted:#6B7A90;
  --gen-border:#E4E9F0; --gen-bg:#EDF1F7;
  --gen-shadow:0 1px 2px rgba(15,27,45,.05),0 8px 22px rgba(15,27,45,.07);
  width:min(420px,96vw); background:var(--gen-card); color:var(--gen-ink);
  border-radius:18px; box-shadow:var(--gen-shadow); padding:16px;
  font-family:'Manrope',system-ui,-apple-system,'Segoe UI',sans-serif;
}
.gen-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;margin-bottom:12px}
.gen-head b{font-size:17px;font-weight:800;letter-spacing:-.01em}
.gen-sub{color:var(--gen-muted);font-size:12.5px;margin-top:2px}
.gen-x{background:none;border:0;font-size:26px;line-height:1;color:var(--gen-muted);cursor:pointer;padding:0 4px}

/* Camera stage: square, rounded, with the pose ring gate uses for feedback. */
.gen-stage{position:relative;width:100%;aspect-ratio:1/1;border-radius:16px;overflow:hidden;background:#0F1B2D}
.gen-vid{width:100%;height:100%;object-fit:cover;transform:scaleX(-1)}  /* mirrored = what a user expects */
.gen-ring{
  position:absolute;inset:8px;border-radius:14px;pointer-events:none;
  border:3px solid rgba(255,255,255,.35);transition:border-color .18s ease,box-shadow .18s ease;
}
.gen-ring.ok{border-color:var(--gen-green);box-shadow:0 0 0 3px rgba(21,154,85,.18) inset}
.gen-ring.done{border-color:var(--gen-primary);box-shadow:0 0 0 3px rgba(30,128,216,.18) inset}

/* One instruction at a time — gate's rule. No numbers, no thresholds, no pose pills. */
.gen-prompt{margin-top:14px;text-align:center;font-size:17px;font-weight:800;letter-spacing:-.01em}
.gen-hint{margin-top:4px;text-align:center;font-size:13px;color:var(--gen-muted);min-height:18px}

.gen-bar{margin-top:12px;height:6px;border-radius:99px;background:var(--gen-bg);overflow:hidden}
.gen-bar-fill{height:100%;width:0;border-radius:99px;background:var(--gen-primary);transition:width .28s ease}

.gen-dots{display:flex;gap:8px;justify-content:center;margin-top:12px}
.gen-dot{
  width:44px;height:44px;border-radius:12px;background:var(--gen-bg);
  border:2px solid var(--gen-border);overflow:hidden;display:flex;align-items:center;justify-content:center;
  transition:border-color .18s ease,transform .18s ease;
}
.gen-dot img{width:100%;height:100%;object-fit:cover}
.gen-dot.cur{border-color:var(--gen-primary);transform:scale(1.06)}
.gen-dot.done{border-color:var(--gen-green)}

/* Revealed only once every pose is banked, so there is nothing to fill in too early. */
.gen-form{display:flex;gap:8px;margin-top:14px}
.gen-input{
  flex:1;min-width:0;padding:12px 14px;border-radius:12px;border:1.5px solid var(--gen-border);
  font-size:15px;font-family:inherit;color:var(--gen-ink);background:#fff;
}
.gen-input:focus{outline:none;border-color:var(--gen-primary)}
.gen-submit{
  padding:12px 20px;border-radius:12px;border:0;background:var(--gen-primary);color:#fff;
  font-size:15px;font-weight:800;font-family:inherit;cursor:pointer;white-space:nowrap;
}
.gen-submit:disabled{opacity:.65;cursor:default}

.gen-status{margin-top:10px;text-align:center;font-size:13px;color:var(--gen-muted);min-height:18px}
.gen-status.err{color:var(--gen-red);font-weight:700}
.gen-status.ok{color:var(--gen-green);font-weight:700}

/* Gate hides its FABs while enrolling — same intent here: nothing floats over the camera. */
#enrollModal.open ~ .fab, #enrollModal.open ~ * .fab{display:none !important}

@media (max-width:440px){
  .gen-box{width:100vw;min-height:100dvh;border-radius:0;padding:14px}
  .gen-stage{aspect-ratio:3/4}
  .gen-dot{width:40px;height:40px}
}

/* Above the mobile shell (.active z-500, .msheetwrap z-540) — without this the enrol
   modal renders underneath the phone layout and looks like nothing happened. */
.modal.gen-modal{z-index:600}

/* ===================================================================================
   GATE-STYLE LIVE VIEW — fullscreen/minimise affordance, lifted from gate.volty.in.
   Gate's exact geometry: 34px square, 8px inset, translucent black on a hairline white
   border, so it reads over both a bright and a dark frame.
   =================================================================================== */
.glv-wrap{position:relative}
.glv-fsbtn{
  position:absolute;top:8px;right:8px;z-index:5;
  width:34px;height:34px;border-radius:8px;padding:0;line-height:1;
  border:1px solid rgba(255,255,255,.35);background:rgba(0,0,0,.55);color:#fff;
  display:flex;align-items:center;justify-content:center;cursor:pointer;
  transition:background .15s ease,border-color .15s ease;
}
.glv-fsbtn:hover{background:rgba(0,0,0,.72);border-color:rgba(255,255,255,.55)}
.glv-fsbtn:focus-visible{outline:2px solid #1E80D8;outline-offset:2px}

/* In real fullscreen the wrapper becomes the viewport: letterbox the frame on black
   rather than stretching it, matching gate's object-fit:contain player. */
.glv-wrap:fullscreen{width:100vw;height:100vh;background:#000}
.glv-wrap:fullscreen video{width:100%;height:100%;object-fit:contain;background:#000}
.glv-wrap:-webkit-full-screen{width:100vw;height:100vh;background:#000}
.glv-wrap:-webkit-full-screen video{width:100%;height:100%;object-fit:contain;background:#000}

/* Phone: one camera per page. The tile fills the width and keeps a 16:9 frame, and the
   pager controls stay reachable with a thumb instead of being pushed off-screen. */
@media (max-width:560px){
  #camwall .camtile{width:100%}
  #camwall .camvid{aspect-ratio:16/9;height:auto}
  .glv-fsbtn{width:38px;height:38px}          /* touch target */
}

/* ===================================================================================
   GATE-STYLE MOBILE LIVE VIEW — one camera per page.
   The single tile gets the full card width and a 16:9 frame, and the pager sits under
   it within thumb reach, matching gate's single-camera page.
   =================================================================================== */
.gml-one .camtile{width:100%;margin:0}
.gml-one .camvid{aspect-ratio:16/9;height:auto;width:100%;border-radius:12px;overflow:hidden;background:#000}
.gml-one .camvid video{width:100%;height:100%;object-fit:contain;background:#000}

.gml-pos{
  margin-left:auto;font-size:12px;font-weight:600;opacity:.7;
  font-variant-numeric:tabular-nums;                /* the count must not jitter while paging */
}

.gml-pager{display:flex;align-items:center;justify-content:center;gap:14px;margin-top:12px}
.gml-nav{
  width:44px;height:44px;border-radius:50%;                     /* 44px = comfortable touch target */
  border:1px solid rgba(255,255,255,.18);background:rgba(255,255,255,.06);
  color:inherit;font-size:22px;line-height:1;cursor:pointer;
  display:flex;align-items:center;justify-content:center;
}
.gml-nav:active{background:rgba(255,255,255,.14)}

.gml-dots{display:flex;gap:6px;align-items:center;flex-wrap:wrap;justify-content:center;max-width:180px}
.gml-dot{width:6px;height:6px;border-radius:50%;background:currentColor;opacity:.28;transition:opacity .15s ease,transform .15s ease}
.gml-dot.on{opacity:1;transform:scale(1.5)}

.gml-tip{margin-top:10px;text-align:center;font-size:12px;opacity:.6}

/* ===================================================================================
   COMPACT TABLES + NATURAL SCROLLING
   -----------------------------------------------------------------------------------
   Measured on shift1 at 1440x900: every wide table sat in a .tablewrap whose content
   overflowed horizontally -- #admin by 737px, #movement by 642px, #people 302,
   #attendance 277, #floor 96 -- producing a nested horizontal scrollbar the user has to
   find and drag, plus cells tall enough to read as two lines.

   Two separate problems, two separate fixes:
     1. DENSITY. Tighter padding and a slightly smaller type scale reclaim ~40% of the
        row width, which removes the scrollbar outright on the marginal tables.
     2. UNAVOIDABLE WIDTH. Ten columns cannot fit a 531px column no matter the padding,
        so where scrolling is genuinely required it is made to follow the wheel/touch
        (see the wheel handler in app.js) instead of demanding a drag.

   Cells stay on ONE line and ellipsize, rather than wrapping -- wrapping is what made
   rows look like two lines in the first place. The full value stays available via the
   title attribute the app already sets, and via the row drawer.
   =================================================================================== */

.tablewrap{
  overflow-x:auto;
  overscroll-behavior-x:contain;      /* horizontal scroll must not bounce the whole page */
  -webkit-overflow-scrolling:touch;   /* momentum on iOS */
  scrollbar-width:thin;               /* Firefox: slim rather than absent, so it stays discoverable */
}
/* A slim, low-contrast bar: present when needed, not a visual divider across the card. */
.tablewrap::-webkit-scrollbar{height:6px}
.tablewrap::-webkit-scrollbar-track{background:transparent}
.tablewrap::-webkit-scrollbar-thumb{background:rgba(127,140,165,.34);border-radius:99px}
.tablewrap::-webkit-scrollbar-thumb:hover{background:rgba(127,140,165,.55)}

.tablewrap table{width:100%;border-collapse:collapse}

/* Density: was 9px/7.2px padding at 12.5px type. */
.tablewrap th,
.tablewrap td{
  padding:5px 8px;
  font-size:12px;
  line-height:1.35;
  white-space:nowrap;                 /* one line, always */
  overflow:hidden;
  text-overflow:ellipsis;
  max-width:260px;                    /* a single long value cannot blow out the row */
  vertical-align:middle;
}
.tablewrap th{
  font-size:11px;
  letter-spacing:.02em;
  padding-top:7px;padding-bottom:7px;
}
/* Numeric/mono columns are narrow by nature — let them shrink-wrap. */
.tablewrap td.mono,
.tablewrap td.sm{max-width:none;width:1%}

/* Avatars and tags must not inflate row height once padding is tight. */
.tablewrap .av{width:26px;height:26px;font-size:10px}
.tablewrap .tag{padding:1px 7px;font-size:11px;white-space:nowrap}

/* Phones: tighter still, and the wrap gets the momentum/containment treatment too. */
@media (max-width:560px){
  .tablewrap th,.tablewrap td{padding:5px 6px;font-size:11.5px;max-width:180px}
  .tablewrap th{font-size:10.5px}
}

/* A card promoted because its table is too wide for a half-width slot. */
.card.cardwide{grid-column:1 / -1 !important}

/* ===================================================================================
   NO SIDE SCROLL, EVER  — generalises the .logstd rule (2026-08-13, "PROPORTIONAL %
   columns ... NO horizontal scroll") to every .tablewrap rather than keeping two
   competing table models.

   Card promotion already took total horizontal overflow from 3173px to 96px; fixed
   layout removes the remainder by construction, because columns divide the container
   width instead of summing past it. With the nowrap+ellipsis rule above, a long value
   truncates rather than forcing a scrollbar. overflow-x stays on .tablewrap purely as a
   safety net for a table carrying its own min-width; in normal use it never triggers.
   =================================================================================== */
.tablewrap table{table-layout:fixed;width:100%;min-width:0}
.tablewrap th,.tablewrap td{max-width:none}   /* fixed layout owns the widths now */

/* ===================================================================================
   MOBILE SHELL SCROLLBAR — hidden, matching gate (.body::-webkit-scrollbar{width:0}).
   .mbody is the app-shell scroller (flex:1 + overflow-y:auto) that keeps the appbar and
   bottom nav pinned, so it necessarily scrolls on any screen taller than the viewport
   (Alerts is ~19600px). The bar already takes ZERO layout width -- it is an overlay, so
   it never caused the cramping -- but the emulator paints it as a persistent grey rail
   down the right edge where a real phone would fade it out. Gate hides it outright and
   lets the content itself signal that there is more; same here.
   Scrolling by wheel, trackpad, touch and keyboard is unaffected.
   =================================================================================== */
.mbody::-webkit-scrollbar{width:0;height:0}
.mbody{scrollbar-width:none}          /* Firefox */
.mbody{-ms-overflow-style:none}       /* legacy Edge */

/* ===================================================================================
   SITE HEALTH SCORE — two rows, monitoring and workforce, on the mobile dashboard.
   Kept to two compact rows so it earns its place at the top of the screen without
   pushing the KPIs below the fold.
   =================================================================================== */
.hs-card{--hs-ok:#159A55;--hs-warn:#DC940C;--hs-bad:#E5484D;--hs-na:#94A3B8}

.hs-row{padding:9px 0}
.hs-row + .hs-row{border-top:1px solid rgba(127,140,165,.16)}   /* the two rows stay visually separate */

.hs-top{display:flex;align-items:center;gap:8px}
.hs-ico{display:flex;width:16px;height:16px;opacity:.75;flex:0 0 auto}
.hs-ico svg{width:16px;height:16px}
.hs-label{font-size:13px;font-weight:700;letter-spacing:-.01em}
.hs-val{margin-left:auto;font-size:16px;font-weight:800;font-variant-numeric:tabular-nums}
.hs-val.ok{color:var(--hs-ok)} .hs-val.warn{color:var(--hs-warn)}
.hs-val.bad{color:var(--hs-bad)} .hs-val.na{color:var(--hs-na)}

.hs-partial{
  font-size:10px;font-weight:700;padding:1px 6px;border-radius:99px;
  background:rgba(148,163,184,.18);color:var(--hs-na);white-space:nowrap;
}

.hs-bar{height:6px;border-radius:99px;background:rgba(127,140,165,.18);overflow:hidden;margin-top:7px}
.hs-fill{height:100%;border-radius:99px;transition:width .35s ease}
.hs-fill.ok{background:var(--hs-ok)} .hs-fill.warn{background:var(--hs-warn)}
.hs-fill.bad{background:var(--hs-bad)} .hs-fill.na{background:transparent}

.hs-detail{margin-top:5px;font-size:11.5px;opacity:.68;line-height:1.35}

/* A KPI tile / health row that navigates. Affordance must read on touch, where there
   is no hover: a persistent chevron plus a clear pressed state. */
.mkpi-go{cursor:pointer;position:relative;-webkit-tap-highlight-color:transparent}
.mkpi-go::after{
  content:'';position:absolute;top:50%;right:10px;width:6px;height:6px;
  border-right:2px solid currentColor;border-bottom:2px solid currentColor;
  transform:translateY(-50%) rotate(-45deg);opacity:.30;pointer-events:none;
}
.mkpi-go:active{transform:scale(.985);opacity:.9}
.mkpi-go:focus-visible{outline:2px solid #1E80D8;outline-offset:2px;border-radius:12px}
.hs-row.mkpi-go{padding-right:20px}
@media (prefers-reduced-motion:reduce){ .mkpi-go:active{transform:none} }

/* SITE HEALTH v2 block removed — superseded by v3 (house design system). */

/* SITE HEALTH v3 removed — superseded by the agy-built card below. */

/* ===================================================================================
   SITE HEALTH CARD CSS (mobile, 393px) — WorkSight AI console
   Scoped strictly under .hs-card using only approved design tokens.
   =================================================================================== */

.hs-card {
  box-sizing: border-box;
  width: 100%;
  padding: 10px 12px;
  background: var(--panel);
  color: var(--ink);
  overflow: hidden;
}

.hs-card .hs-heading {
  display: flex;
  align-items: center;
  min-width: 0;
  margin: 0 0 8px;
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--muted);
}

.hs-card .hs-heading-icon {
  display: inline-flex;
  width: 14px;
  height: 14px;
  margin-right: 6px;
  color: var(--brand);
  flex: 0 0 14px;
}

.hs-card .hs-heading-icon svg {
  display: block;
  width: 100%;
  height: 100%;
}

.hs-card .hs-heading-note {
  margin-left: auto;
  font-size: 9px;
  letter-spacing: 0.08em;
  color: var(--faint);
  white-space: nowrap;
}

.hs-card .hs-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
}

.hs-card .hs-panel {
  box-sizing: border-box;
  min-width: 0;
  padding: 8px 8px 7px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  box-shadow: var(--sh-1);
  cursor: pointer;
  user-select: none;
  transition: transform var(--t), box-shadow var(--t);
}

.hs-card .hs-panel:active {
  transform: scale(0.98);
  box-shadow: var(--sh-2);
}

.hs-card .hs-panel:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

.hs-card .hs-panel.is-good {
  --tone: var(--ok);
  --tone-2: var(--cyan);
}

.hs-card .hs-panel.is-warn {
  --tone: var(--warn);
  --tone-2: var(--accent);
}

.hs-card .hs-panel.is-bad {
  --tone: var(--bad);
  --tone-2: var(--violet);
}

.hs-card .hs-panel.is-na {
  --tone: var(--faint);
  --tone-2: var(--faint);
}

.hs-card .hs-summary {
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
}

.hs-card .hs-gauge {
  position: relative;
  flex: 0 0 50px;
  width: 50px;
  height: 50px;
  color: var(--tone);
}

.hs-card .hs-gauge-svg {
  display: block;
  width: 50px;
  height: 50px;
  overflow: visible;
}

.hs-card .hs-track {
  fill: none;
  stroke: var(--panel-3);
  stroke-width: 6;
}

.hs-card .hs-meter {
  fill: none;
  stroke-width: 6;
  stroke-linecap: round;
  stroke-dasharray: 157.0796;
  transition: stroke-dashoffset .8s cubic-bezier(.4, 0, .2, 1);
}

.hs-card .hs-panel.is-na .hs-meter {
  stroke: none;
}

.hs-card .hs-score {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--ink);
}

.hs-card .hs-copy {
  min-width: 0;
  flex: 1 1 auto;
}

.hs-card .hs-name-line {
  display: flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
}

.hs-card .hs-icon {
  display: inline-flex;
  flex: 0 0 13px;
  width: 13px;
  height: 13px;
  color: var(--tone);
}

.hs-card .hs-icon svg {
  display: block;
  width: 100%;
  height: 100%;
}

.hs-card .hs-label {
  font-size: 11.5px;
  font-weight: 700;
  line-height: 14px;
  color: var(--ink-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

.hs-card .hs-inputs {
  display: inline-block;
  margin-top: 3px;
  padding: 1px 5px;
  border-radius: var(--r-sm);
  background: var(--warn-bg);
  border: 1px solid var(--warn);
  color: var(--warn);
  font-size: 9px;
  font-weight: 700;
  line-height: 13px;
  white-space: nowrap;
}

.hs-card .hs-detail {
  margin: 6px 0 0;
  padding-top: 5px;
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 10px;
  line-height: 13px;
  font-variant-numeric: tabular-nums;
  min-height: 26px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

@media (prefers-reduced-motion: reduce) {
  .hs-card .hs-meter {
    transition: none;
  }
  .hs-card .hs-panel {
    transition: none;
  }
  .hs-card .hs-panel:active {
    transform: none;
  }
}

/* ===================================================================================
   MOBILE LIVE VIEW — one header line (customer-facing, not operator-facing).
   The tile's own head duplicated the camera name and exposed the stream transport
   ("Accounts · HLS"); both are hidden here. Name, people count and the pager arrows
   share a single line, and the old bottom prev/dots/next strip is gone.
   =================================================================================== */
.gml-head{
  display:flex;align-items:center;gap:8px;min-width:0;
  margin:0 0 10px;                       /* h5 default margin would indent it */
  text-transform:none;letter-spacing:0;  /* a camera name is a name, not a section label */
}
.gml-title{
  font-size:13px;font-weight:700;color:var(--ink);letter-spacing:-.01em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;
}
.gml-ppl{
  flex:0 0 auto;padding:2px 8px;border-radius:99px;
  background:var(--info-bg);color:var(--info);
  font-size:10.5px;font-weight:700;white-space:nowrap;text-transform:none;letter-spacing:0;
}
.gml-navwrap{display:flex;align-items:center;gap:6px;margin-left:auto;flex:0 0 auto}
.gml-nav{
  width:30px;height:30px;padding:0;line-height:1;font-size:17px;
  display:flex;align-items:center;justify-content:center;border-radius:50%;
}
.gml-pos{
  font-family:"JetBrains Mono",ui-monospace,monospace;font-variant-numeric:tabular-nums;
  font-size:11px;color:var(--muted);white-space:nowrap;text-transform:none;letter-spacing:0;
}

/* The tile repeats the name and shows the transport — operator detail. */
.gml-one .camhead{display:none}
.gml-one .camtile{border:0;background:transparent;padding:0}

/* The .om-* zone-list map was removed — replaced by the real isometric map (.mmap). */

/* ===================================================================================
   MOBILE ISOMETRIC OFFICE MAP PRESENTATION (393px) — WorkSight AI console
   Scoped strictly under .mmap using only approved design tokens.
   =================================================================================== */

/* 1. Container & Aspect Ratio */
.mmap {
  box-sizing: border-box;
  position: relative;
  width: 100%;
  max-width: 100%;
  max-height: 320px;
  overflow: hidden;
  background: var(--panel);
  color: var(--ink);
  border-radius: var(--r);
  touch-action: manipulation;
}

.mmap *,
.mmap *::before,
.mmap *::after {
  box-sizing: border-box;
}

.mmap #officeMap,
.mmap .ommount {
  position: relative;
  width: 100%;
  max-width: 100%;
  overflow: hidden;
  margin: 0;
  padding: 0;
}

.mmap .office-map-card {
  position: relative;
  width: 100% !important;
  max-width: 100% !important;
  height: auto !important;
  aspect-ratio: 872 / 455;
  background: var(--panel);
  background-image: none !important;
  border-radius: var(--r);
  overflow: hidden;
  box-shadow: var(--sh-1);
}

.mmap svg.svg-canvas,
.mmap .office-map-card > svg {
  display: block;
  width: 100% !important;
  height: auto !important;
  max-width: 100%;
  aspect-ratio: 872 / 455;
  overflow: hidden;
}

/* Remove decorative vignette on mobile for a clean, calm console look */
.mmap .vignette-overlay {
  display: none !important;
}

/* 2. In-SVG Floating Label Cards
   Scale compensation: ~45% scale on phone -> font-size 25-26px renders at ~11-12px on-screen. */
.mmap .card-node {
  cursor: pointer;
  pointer-events: auto;
}

.mmap .card-node foreignObject {
  overflow: visible !important;
}

.mmap .floating-card {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: max-content;
  background: var(--panel-2);
  border: 1.5px solid var(--line);
  border-radius: var(--r-sm);
  padding: 4px 10px;
  box-shadow: var(--sh-2);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  white-space: nowrap;
  backdrop-filter: none;
}

.mmap .card-name {
  font-size: 26px;
  font-weight: 700;
  line-height: 1.15;
  color: var(--ink);
  letter-spacing: -0.01em;
}

.mmap .card-count-line {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  font-size: 25px;
  line-height: 1.15;
  margin-top: 1px;
}

.mmap .person-icon {
  width: 22px !important;
  height: 22px !important;
  display: inline-block;
  aspect-ratio: auto !important;
  fill: currentColor;
  flex-shrink: 0;
}

.mmap .count-bold {
  font-weight: 800;
}

.mmap .capacity-muted {
  font-weight: 500;
  color: var(--muted);
}

.mmap .card-node:active .floating-card {
  box-shadow: var(--sh-1);
}

/* 3. Chrome Visibility: Hide Dismiss, Zoom, and Tooltips */
.mmap .dismiss-btn,
.mmap .zoom-cluster,
.mmap .zoom-btn,
.mmap .zoom-divider,
.mmap .room-tooltip,
.mmap [class*="tt-"],
.mmap .tt-title,
.mmap .tt-badge,
.mmap .tt-progress-bg,
.mmap .tt-progress-fill,
.mmap .tt-metrics {
  display: none !important;
  opacity: 0 !important;
  pointer-events: none !important;
  visibility: hidden !important;
}

/* 4. Header Bar & Badges */
.mmap .header-bar {
  position: absolute;
  top: 8px;
  left: 10px;
  right: 10px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  z-index: 20;
  pointer-events: none;
}

.mmap .title-group {
  display: flex;
  align-items: center;
  gap: 8px;
  pointer-events: auto;
}

.mmap .map-title {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-shadow: none;
  line-height: 1.2;
}

.mmap .live-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 7px;
  background: var(--panel-2);
  border: 1px solid var(--ok);
  border-radius: var(--r-sm);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--ok);
  box-shadow: none;
  line-height: 1.2;
}

.mmap .live-badge.is-sample {
  border-color: var(--warn);
  color: var(--warn);
}

.mmap .live-dot {
  width: 6px;
  height: 6px;
  background-color: var(--ok);
  border-radius: 50%;
  box-shadow: none;
  animation: none;
}

.mmap .live-badge.is-sample .live-dot {
  background-color: var(--warn);
}

/* 5. Bottom Bar: Density Legend & 3D Toggle */
.mmap .bottom-bar {
  position: absolute;
  bottom: 8px;
  left: 10px;
  right: 10px;
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  z-index: 20;
  pointer-events: none;
}

.mmap .legend-container {
  pointer-events: auto;
  display: flex;
  flex-direction: column;
  gap: 3px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 4px 8px;
  box-shadow: var(--sh-1);
}

.mmap .legend-bar-row {
  display: flex;
  align-items: center;
  gap: 6px;
}

.mmap .legend-text {
  font-size: 9px;
  font-weight: 600;
  color: var(--muted);
  line-height: 1;
}

.mmap .gradient-bar {
  width: 60px;
  height: 5px;
  border-radius: var(--r-sm);
  background: linear-gradient(90deg, var(--ok) 0%, var(--warn) 50%, var(--bad) 100%);
  box-shadow: none;
}

.mmap .legend-label {
  font-size: 8px;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--faint);
  line-height: 1;
}

/* 3D Toggle Button - Touch friendly (min 40x40) positioned in bottom-right clear space */
.mmap .toggle-3d-btn {
  pointer-events: auto;
  min-width: 40px;
  min-height: 40px;
  height: 40px;
  padding: 0 10px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--brand);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  cursor: pointer;
  box-shadow: var(--sh-1);
  backdrop-filter: none;
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
}

.mmap .toggle-3d-btn svg {
  width: 14px !important;
  height: 14px !important;
  display: inline-block;
  aspect-ratio: auto !important;
  stroke: currentColor;
  flex-shrink: 0;
}

.mmap .toggle-3d-btn:active {
  box-shadow: var(--sh-2);
}

.mmap .toggle-3d-btn.active-2d {
  color: var(--muted);
  border-color: var(--line);
}

/* 6. Reduced Motion */
@media (prefers-reduced-motion: reduce) {
  .mmap,
  .mmap * {
    animation: none !important;
    transition: none !important;
  }
}

/* ===================================================================================
   3D toggle reachability (mobile).
   The toggle lives in .bottom-bar (absolute, bottom:8px) INSIDE the map card. The card
   sits near the end of a long dashboard, so the button rendered at y=710..750 in a 727px
   viewport -- entirely behind the fixed 66px bottom nav, where elementFromPoint resolves
   to nothing and a tap never reaches it. The handler was fine all along; it simply could
   not be pressed. Moved to the map's top-right, clear of the nav and of the density
   legend, and given a real touch target.
   =================================================================================== */
.mmap .bottom-bar{ right:auto; left:10px; }          /* legend keeps the bottom-left */
.mmap .toggle-3d-btn{
  position:absolute; top:10px; right:10px; bottom:auto; left:auto;
  z-index:30;                                        /* above the vignette overlay */
  min-width:44px; min-height:44px;                   /* comfortable touch target */
  display:flex; align-items:center; justify-content:center; gap:5px;
  padding:0 12px; border-radius:var(--r-sm);
  pointer-events:auto;                               /* .bottom-bar sets none */
  background:var(--panel-2); border:1px solid var(--line); color:var(--ink);
  box-shadow:var(--sh-1); font-weight:700;
}
.mmap .toggle-3d-btn svg{width:15px;height:15px;flex:0 0 auto}
.mmap .toggle-3d-btn:active{background:var(--panel-3)}

/* ===================================================================================
   PHASE 2 - person "now" block in the drawer.
   Site presence is the emphasised figure; known zone dwell and the unassigned remainder
   sit under it so the difference is explicit rather than looking like a broken total.
   =================================================================================== */
.p2now{background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r-sm);
  padding:12px 14px;margin:0 0 14px}
.p2head{font-size:9.5px;letter-spacing:.16em;text-transform:uppercase;color:var(--nav-ink-2);
  font-weight:600;margin-bottom:8px}
.p2row{display:flex;align-items:baseline;gap:12px;padding:3px 0}
.p2row + .p2row{border-top:1px solid var(--line)}
.p2lab{font-size:12px;color:var(--muted);flex:1 1 auto;min-width:0}
.p2val{font-size:13px;color:var(--ink);text-align:right;white-space:nowrap}
.p2val.mono{font-family:"JetBrains Mono",ui-monospace,monospace;font-variant-numeric:tabular-nums;
  letter-spacing:-.02em}
.p2val.strong{font-size:15px;font-weight:800}

/* Phase 3: five-up KPI row on the Dashboard. */
.kpis.k5{grid-template-columns:repeat(5,minmax(0,1fr))}
@media(max-width:1180px){.kpis.k5{grid-template-columns:repeat(2,minmax(0,1fr))}}

/* Phase 3b: category chips replace four count-only cards on Alerts & Review. */
.alfilters{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 14px}
.alchip{display:inline-flex;align-items:center;gap:7px;padding:7px 13px;border-radius:99px;
  border:1px solid var(--line);background:var(--panel);color:var(--ink-2);
  font-size:12.5px;font-weight:700;cursor:pointer;transition:border-color var(--t),background var(--t)}
.alchip:hover{border-color:var(--line-2)}
.alchip.on{border-color:var(--brand);background:var(--info-bg);color:var(--brand)}
.alchip-n{font-family:"JetBrains Mono",ui-monospace,monospace;font-variant-numeric:tabular-nums;
  font-size:11px;padding:1px 6px;border-radius:99px;background:var(--panel-3);color:var(--muted)}
.alchip-n.bad{background:var(--bad-bg);color:var(--bad)}
.alchip-n.warn{background:var(--warn-bg);color:var(--warn)}
.alchip:focus-visible{outline:2px solid var(--brand);outline-offset:2px}

/* Phase 5: Analytics section tabs (hidden when a section has no configured source). */
.antabs{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:0 0 14px}
.antab{padding:7px 14px;border-radius:99px;border:1px solid var(--line);background:var(--panel);
  color:var(--ink-2);font-size:12.5px;font-weight:700;cursor:pointer;transition:border-color var(--t)}
.antab:hover{border-color:var(--line-2)}
.antab.on{border-color:var(--brand);background:var(--info-bg);color:var(--brand)}
.antab:focus-visible{outline:2px solid var(--brand);outline-offset:2px}
.antab-note{margin-left:auto;font-size:11px;color:var(--faint)}

/* ===================================================================================
   MAP CARD: OVERLAY -> STACKED BANDS (mobile)
   -----------------------------------------------------------------------------------
   The desktop map overlays all of its chrome on the plan -- title bar absolute at the top,
   legend + 3D toggle absolute at the bottom. That works at 872x455 where there is spare
   margin, and collides at 366px: the title sat on the rooms, the legend sat on the
   bottom-left rooms, the toggle sat on a room, and the plan was clipped by the card edge.

   Fixed by taking those bars OUT of the overlay and letting the card be a flex column:
   header band, plan band, legend+toggle band. Nothing is positioned over the plan, so
   nothing can collide with it, and the plan is never clipped because the card height
   follows its content instead of a fixed aspect box.

   Labels are <foreignObject> INSIDE the svg, so they scale with it (~0.42 at this width).
   Their font-size is set in SVG user units and must be inflated to stay legible on screen.
   =================================================================================== */
.mmap .office-map-card{
  position:relative; display:flex; flex-direction:column;
  width:100%; height:auto; aspect-ratio:auto; overflow:visible; padding:0;
}
.mmap .vignette-overlay{display:none}          /* decorative, and it dimmed the rooms */

/* band 1 — title + LIVE badge */
.mmap .header-bar{
  position:static; order:1; display:flex; align-items:center; gap:8px;
  padding:0 0 8px; inset:auto;
}
.mmap .map-title{font-size:13px; line-height:1.2; margin:0}
.mmap .live-badge{transform:none; font-size:10px}

/* band 2 — the plan itself, complete and unclipped */
.mmap .office-map-card > svg{order:2; display:block; width:100%; height:auto; overflow:visible}

/* band 3 — legend and the 3D toggle share a row UNDER the plan */
.mmap .bottom-bar{
  position:static; order:3; display:flex; align-items:center; justify-content:space-between;
  gap:10px; padding:10px 0 0; inset:auto; pointer-events:auto;
}
.mmap .legend-container{flex:1 1 auto; min-width:0}
.mmap .toggle-3d-btn{
  position:static; flex:0 0 auto; min-width:44px; min-height:44px;   /* touch target */
  display:flex; align-items:center; justify-content:center; gap:6px; padding:0 12px;
  border-radius:var(--r-sm); border:1px solid var(--line); background:var(--panel-2);
  color:var(--ink); box-shadow:var(--sh-1); font-weight:700; pointer-events:auto;
}
.mmap .toggle-3d-btn:active{background:var(--panel-3)}

/* Room labels: in-SVG units, inflated so they survive the ~0.42 scale. */
.mmap .card-name{font-size:30px; line-height:1.1}
.mmap .count-bold{font-size:28px}
.mmap .floating-card{padding:6px 10px}

/* Hover-only chrome is useless on touch. */
.mmap .dismiss-btn,
.mmap .zoom-cluster,
.mmap .zoom-btn,
.mmap .zoom-divider,
.mmap [class^="tt-"],
.mmap [class*=" tt-"]{display:none !important}

@media (prefers-reduced-motion:reduce){ .mmap *{transition:none !important} }

/* #mapCard band override.
   The card carries id="mapCard", so the original chrome rules are ID-specific
   (#mapCard .header-bar { position:absolute } etc.) and out-rank plain .mmap class
   selectors regardless of source order. These repeat the stacked-band layout at ID
   specificity so the bars actually leave the overlay. */
.mmap #mapCard{display:flex;flex-direction:column;height:auto;aspect-ratio:auto;overflow:visible;padding:10px 12px}
.mmap #mapCard .vignette-overlay{display:none}

.mmap #mapCard .header-bar{position:static;order:1;inset:auto;display:flex;align-items:center;gap:8px;padding:0 0 8px;height:auto}
.mmap #mapCard > svg{order:2;display:block;width:100%;height:auto;overflow:visible}
.mmap #mapCard .bottom-bar{position:static;order:3;inset:auto;display:flex;align-items:center;
  justify-content:space-between;gap:10px;padding:10px 0 0;height:auto;pointer-events:auto}
.mmap #mapCard .toggle-3d-btn{position:static;flex:0 0 auto;min-width:44px;min-height:44px;
  display:flex;align-items:center;justify-content:center;gap:6px;padding:0 12px;
  border-radius:var(--r-sm);border:1px solid var(--line);background:var(--panel-2);
  color:var(--ink);box-shadow:var(--sh-1);font-weight:700;pointer-events:auto}
.mmap #mapCard .legend-container{flex:1 1 auto;min-width:0}
.mmap #mapCard .dismiss-btn,
.mmap #mapCard .zoom-cluster,
.mmap #mapCard .room-tooltip{display:none !important}

/* Labels live in <foreignObject> inside the svg and scale with it (~0.42 here). */
.mmap #mapCard .card-name{font-size:30px;line-height:1.1}
.mmap #mapCard .count-bold{font-size:28px}

/* ===== BRAND LOCKUP ===========================================================================
   Two supplied assets, selected by theme on a measured contrast basis (see index.html). Sizes are
   fixed in the markup as width/height attributes as well as here, so the rail never reflows while
   the PNG loads. */
.brand .brandlogo{height:40px;width:auto;display:none;flex:0 0 auto}
.brand .brandicon{height:38px;width:38px;display:block;flex:0 0 auto}
/* LIGHT: the nav is near-white, the lockup is 11:1 on it -- show the real logo and drop the HTML
   wordmark so the brand is not written twice. The descriptor stays as live text because the
   compact derivative deliberately has no baked subtitle. */
[data-theme="light"] .brand{flex-wrap:wrap;position:relative;padding-right:32px}
[data-theme="light"] .brand .sidemin{position:absolute;right:2px;top:9px}
[data-theme="light"] .brand .brandlogo{display:block}
[data-theme="light"] .brand .brandicon{display:none}
[data-theme="light"] .brandtext b{display:none}
[data-theme="light"] .brandtext{flex-basis:100%;margin-top:2px}
[data-theme="light"] .brandtext small{margin-left:2px}
/* COLLAPSED: icon only, in either theme. */
.side.min .brand{justify-content:center}
.side.min .brandlogo{display:none}
.side.min .brandicon{display:block}

/* Mobile app bar mark: sized in CSS and in the tag, so the bar never reflows on load. */
.mappbar .mbrand .mbrandicon{width:26px;height:26px;flex:0 0 auto;display:block}

.fidguide.ok{background:rgba(34,211,238,.18);color:#22d3ee}
.fidguide.adj{background:rgba(255,170,40,.16);color:#fbbf24}
.fidguide.warn{background:rgba(248,113,113,.16);color:#fca5a5}
/* Start control for the enrolment sheet: the capture no longer begins on open. */
.gen-startwrap{display:flex;justify-content:center;margin:10px 0 2px}
.gen-startwrap[hidden]{display:none}
.gen-startwrap .gen-submit{width:auto;min-width:200px}

/* AREA TIME BAR — one bar per area, two segments. The solid part is confirmed sighting time;
   the lighter part is sticky-zone unseen, which the site-time contract credits to the SAME
   area rather than to a separate bucket. Colour is the only thing that separates them. */
.zbarw i.zsplit{position:relative;background:var(--warn);border-radius:6px;overflow:hidden}
.zbarw i.zsplit u{position:absolute;left:0;top:0;bottom:0;display:block;background:var(--brand);border-radius:6px 0 0 6px}
.zlegend2{display:flex;align-items:center;gap:6px;margin-top:8px;flex-wrap:wrap}
.zlegend2 .k{width:10px;height:10px;border-radius:3px;display:inline-block}
.zlegend2 .k.seen{background:var(--brand)}
.zlegend2 .k.unseen{background:var(--warn);margin-left:10px}
.zlegend2 .sp{flex:1 1 auto}
/* ===== Person card (supplied design, adapted) =============================================
   Palette re-mapped onto the console's own theme tokens so the card follows the light/dark
   toggle. The original stylesheet declared these on :root with fixed dark values, which would
   have repainted the whole console -- the names collide with ours. Scoped here instead. */
.ws-person-card{
  --ws-panel:var(--panel-2);
  --ws-panel-2:var(--panel-3,var(--panel-2));
  --ws-line:var(--line);
  --ws-text:var(--ink);
  --ws-muted:var(--ink-2);
  --ws-violet:var(--brand);
  --ws-violet-2:var(--violet,var(--brand));
  --ws-green:var(--ok);
  --ws-amber:var(--warn);
  --ws-coral:var(--bad);
  --ws-blue:#5f84ff;
  --ws-shadow:var(--shadow-card,0 10px 28px rgba(2,8,26,.16));
  color:var(--ws-text);
}

.ws-person-card{width:min(100%,520px)}
.ws-icon-btn.danger{color:#ff684f}
.ws-panel{display:none}.ws-panel.is-active{display:block}
.ws-hero,.ws-details-card,.ws-area-card,.ws-insight,.ws-sightings-summary,.ws-sighting-row{background:linear-gradient(145deg,rgba(18,29,56,.98),rgba(13,22,43,.98));border:1px solid var(--ws-panel-2);box-shadow:var(--ws-shadow)}
.ws-hero{border-radius:22px;padding:28px;position:relative;overflow:hidden}
.ws-location-hero{display:grid;grid-template-columns:auto 1fr auto;gap:18px;align-items:center;min-height:242px}
.ws-location-hero::after{content:"";position:absolute;inset:0 0 0 58%;background:radial-gradient(circle at 55% 45%,rgba(76,75,202,.23),transparent 45%),linear-gradient(115deg,transparent 30%,rgba(92,97,209,.09));pointer-events:none}
.ws-hero-icon{width:68px;height:68px;border-radius:50%;display:grid;place-items:center;background:radial-gradient(circle at 30% 30%,#5533b7,var(--ws-panel-2));border:1px solid #705eff;z-index:1}
.ws-hero-icon svg,.ws-location-mark svg{width:34px;height:34px;stroke:var(--ws-muted);fill:none;stroke-width:1.6}
.ws-location-main{z-index:1}
.ws-location-main h2,.ws-time-hero h2{font-size:31px;margin:0 0 6px;letter-spacing:-.03em}
.ws-subline{color:var(--ws-muted);font-size:17px}
.ws-status{display:inline-flex;align-items:center;gap:8px;margin-top:14px;padding:9px 14px;border-radius:999px;background:rgba(68,179,121,.15);border:1px solid rgba(68,179,121,.18);color:#71d79a;font-size:13px;font-weight:800;text-transform:uppercase;letter-spacing:.06em}
.ws-status span{width:9px;height:9px;border-radius:50%;background:#61d79b}
.ws-location-mark{width:92px;height:92px;display:grid;place-items:center;position:relative;z-index:1}
.ws-location-mark svg{width:40px;height:40px;stroke:#68db9f;fill:#68db9f22}
.ws-radar{position:absolute;border:1px solid rgba(104,219,159,.35);border-radius:50%}.r1{inset:8px}.r2{inset:0}.r3{inset:-10px;opacity:.4}
.ws-hero-footer{grid-column:1/-1;display:flex;gap:10px;align-items:center;padding-top:18px;border-top:1px solid rgba(79,94,136,.25);z-index:1;color:var(--ws-muted);font-size:16px}
.ws-hero-footer svg{width:22px;height:22px;stroke:var(--ws-muted);fill:none;stroke-width:1.7}
.ws-metric-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin:16px 0}
.ws-metric{min-height:134px;border-radius:20px;border:1px solid var(--ws-panel-2);background:linear-gradient(145deg,var(--ws-panel-2),var(--ws-panel-2));display:flex;align-items:center;gap:16px;padding:20px;box-shadow:var(--ws-shadow)}
.ws-metric-icon{width:56px;height:56px;border-radius:17px;display:grid;place-items:center;flex:0 0 auto;border:1px solid currentColor}
.ws-metric-icon svg{width:28px;height:28px;stroke:currentColor;fill:none;stroke-width:1.8}.purple{color:#8674ff;background:var(--ws-panel-2)}.blue{color:#5f84ff;background:#172e66}.amber{color:#efbc56;background:var(--ws-panel-2)}.coral{color:#ff7056;background:var(--ws-panel-2)}
.ws-label{font-size:14px;color:var(--ws-muted);margin-bottom:5px}.ws-value{font-size:28px;font-weight:800;letter-spacing:-.03em}
.ws-details-card{border-radius:22px;overflow:hidden}
.ws-details-card h3,.ws-area-card h3{margin:0;padding:22px 22px 12px;font-size:19px}
.ws-small-icon{display:inline-grid;place-items:center;width:30px;height:30px;border-radius:10px;margin-right:8px;font-size:18px}
.ws-detail-row{display:flex;justify-content:space-between;gap:18px;padding:17px 22px;border-top:1px solid rgba(72,86,126,.25);font-size:15px}
.ws-detail-row span{color:var(--ws-muted)}.ws-detail-row strong{text-align:right;font-size:15px}.ws-confidence{color:#78df9e;background:var(--ws-panel-2);padding:6px 12px;border-radius:999px}
.ws-time-hero{min-height:340px;padding-bottom:0}
.ws-time-copy{max-width:56%;position:relative;z-index:2}
.ws-big-time{font-size:72px;line-height:1;font-weight:850;letter-spacing:-.05em;margin:22px 0 8px}
.ws-clock-art{position:absolute;right:35px;top:42px;width:170px;height:170px;border-radius:50%;border:1px solid rgba(109,103,255,.25);background:radial-gradient(circle,rgba(100,96,255,.09),transparent 65%)}
.ws-clock-ring{position:absolute;inset:28px;border:5px solid #716aff;border-radius:50%;box-shadow:0 0 20px rgba(113,106,255,.35),inset 0 0 15px rgba(113,106,255,.2)}
.ws-clock-hand{position:absolute;left:50%;top:50%;width:4px;border-radius:4px;background:#756fff;transform-origin:50% 100%}.hand-a{height:50px;transform:translate(-50%,-100%) rotate(0)}.hand-b{height:43px;transform:translate(-50%,-100%) rotate(132deg);background:#5d8cff}.ws-clock-dot{position:absolute;width:12px;height:12px;border-radius:50%;background:#7c72ff;left:50%;top:50%;transform:translate(-50%,-50%)}
.ws-time-strip{margin:34px -28px 0;display:grid;grid-template-columns:1fr 1fr 1fr;border-top:1px solid rgba(72,86,126,.25)}
.ws-time-strip>div{padding:20px 22px}.ws-time-strip>div+div{border-left:1px solid rgba(72,86,126,.25)}
.ws-time-strip span{display:block;color:var(--ws-muted);font-size:14px;margin-bottom:7px}.ws-time-strip strong{font-size:24px}
.ws-area-card{margin-top:18px;border-radius:22px;padding-bottom:18px}.ws-section-title{display:flex;align-items:center;gap:8px}.ws-info{display:inline-grid;place-items:center;width:20px;height:20px;border:1px solid var(--ws-muted);border-radius:50%;font-size:12px;color:var(--ws-muted)}
.ws-area-list{padding:0 22px}.ws-area-row{margin:15px 0 22px}.ws-area-head{display:flex;justify-content:space-between;gap:12px;margin-bottom:8px}.ws-area-name{color:var(--ws-muted)}.ws-area-duration{font-weight:800}.ws-bar{height:10px;border-radius:999px;background:var(--ws-panel-2);overflow:hidden}.ws-bar>span{display:block;height:100%;border-radius:999px}
.ws-area-foot{display:flex;justify-content:flex-end;color:var(--ws-muted);font-size:13px;margin-top:5px}
.ws-legend{display:flex;gap:20px;flex-wrap:wrap;padding:6px 22px 0;color:var(--ws-muted);font-size:13px}.legend{display:inline-block;width:15px;height:15px;border-radius:5px;margin-right:7px;vertical-align:-3px}.seen{background:#7c72ff}.estimated{background:#e8b64e}
.ws-insight{margin-top:18px;border-radius:22px;padding:22px;display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:16px;border-color:var(--ws-muted)}.ws-insight-icon{width:54px;height:54px;border-radius:50%;display:grid;place-items:center;background:linear-gradient(145deg,#5832c2,var(--ws-panel-2));color:var(--ws-muted);font-size:24px}.ws-insight strong{display:block;font-size:17px;margin-bottom:4px}.ws-insight span{color:var(--ws-muted);font-size:14px}.ws-chevron{font-size:35px;color:var(--ws-muted)}
.ws-sightings-summary{border-radius:22px;padding:24px;display:grid;grid-template-columns:auto 1fr 130px;gap:18px;align-items:center;position:relative;overflow:hidden}
.ws-target-icon{width:70px;height:70px;border-radius:50%;display:grid;place-items:center;background:linear-gradient(145deg,var(--ws-panel-2),var(--ws-panel-2));border:1px solid #5b50b5}.ws-target-icon svg{width:36px;height:36px;stroke:#a7a1ff;fill:none;stroke-width:1.7}.ws-summary-number{font-size:42px;font-weight:850;line-height:1}.ws-summary-label{font-size:19px;font-weight:700;margin-top:2px}.ws-summary-order{color:var(--ws-muted);margin-top:15px;font-size:14px}.ws-summary-order::first-letter{color:#5fd190}
.ws-sightings-art{height:100px;position:relative}.ws-sightings-art div{position:absolute;border:1px solid rgba(123,114,255,.25);border-radius:50%;inset:10px}.ws-sightings-art div:nth-child(2){inset:25px}.ws-sightings-art div:nth-child(3){inset:40px}.ws-sightings-art span{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);color:#7c72ff}
.ws-list-title{margin:20px 0 12px;font-weight:800;color:var(--ws-muted)}.ws-list-title:first-letter{color:#847aff}.ws-sightings-list{display:grid;gap:9px}.ws-sighting-row{border-radius:18px;padding:12px 14px;display:grid;grid-template-columns:58px 1fr auto auto;gap:12px;align-items:center}.ws-thumb{width:58px;height:58px;border-radius:12px;object-fit:cover;background:linear-gradient(145deg,var(--ws-panel-2),var(--ws-panel-2));border:1px solid #2d3b60}.ws-sighting-name{font-size:16px;font-weight:800}.ws-sighting-meta{color:var(--ws-muted);font-size:13px;margin-top:4px}.ws-sighting-confidence{padding:8px 12px;border-radius:14px;background:var(--ws-panel-2);border:1px solid #24573c;color:#76df9d;font-size:14px;font-weight:800}.ws-row-action{width:42px;height:42px;border-radius:14px;border:1px solid #36457a;background:var(--ws-panel-2);color:#827aff;display:grid;place-items:center;font-size:20px}
.ws-footer svg{width:17px;height:17px;stroke:var(--ws-muted);fill:none;stroke-width:1.6}
@media(max-width:520px){
  .ws-shell{padding:12px 0 28px}.ws-person-card{width:100%}.ws-header{padding:0 14px}.ws-tabs{border-radius:0;border-left:0;border-right:0}.ws-panel{padding:0 12px}.ws-name{font-size:23px}.ws-avatar{width:56px;height:56px}.ws-icon-btn{width:46px;height:46px}
  .ws-location-hero{grid-template-columns:auto 1fr;min-height:220px}.ws-location-mark{display:none}.ws-location-main h2{font-size:27px}.ws-metric{padding:16px;gap:12px;min-height:120px}.ws-metric-icon{width:48px;height:48px}.ws-value{font-size:23px}
  .ws-big-time{font-size:56px}.ws-clock-art{width:135px;height:135px;right:20px;top:48px}.ws-time-copy{max-width:64%}.ws-time-strip strong{font-size:19px}
  .ws-sightings-summary{grid-template-columns:auto 1fr}.ws-sightings-art{display:none}.ws-sighting-row{grid-template-columns:52px 1fr auto auto}.ws-thumb{width:52px;height:52px}
}
@media(max-width:390px){
  .ws-tab{font-size:13px;gap:6px}.ws-metric-grid{grid-template-columns:1fr}.ws-actions{gap:6px}.ws-icon-btn{width:42px;height:42px}.ws-detail-row{font-size:14px}.ws-time-strip{grid-template-columns:1fr}.ws-time-strip>div+div{border-left:0;border-top:1px solid rgba(72,86,126,.25)}.ws-clock-art{opacity:.55}.ws-sighting-row{grid-template-columns:50px 1fr auto}.ws-row-action{display:none}
}

/* Supplied person-card design, adapted: its :root, its html/body page styling and its
   fixed dark palette were removed - those collided with the console's own tokens and
   would have repainted every page. Layout and shapes are the original's. */
.ws-person-card .ws-metric-grid-2{grid-template-columns:1fr 1fr}
.ws-person-card .ws-muted-note{color:var(--ws-muted);font-size:12px;font-weight:600}
.ws-person-card .ws-hero-title{margin:0 0 4px;font-size:20px;font-weight:800;letter-spacing:-.01em}

/* Icon sizing corrected against the reference render (2026-09-05). Three defects: the hero
   glyph was a 68px circle where the reference is a rounded square; the metric tiles ran large
   for a drawer-width card; and .ws-small-icon had NO svg rule, so the SVG inherited the 30px
   box and rendered as a blank filled square. */
.ws-person-card .ws-hero-icon{width:52px;height:52px;border-radius:16px}
.ws-person-card .ws-hero-icon svg{width:24px;height:24px;stroke-width:1.7}
.ws-person-card .ws-metric-icon{width:44px;height:44px;border-radius:14px}
.ws-person-card .ws-metric-icon svg{width:22px;height:22px}
.ws-person-card .ws-small-icon{width:28px;height:28px;border-radius:9px}
.ws-person-card .ws-small-icon svg{width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:1.9;
  stroke-linecap:round;stroke-linejoin:round}
/* Per-row icon in Quick details, as in the reference. */
.ws-person-card .ws-row-icon{width:30px;height:30px;border-radius:9px;display:grid;place-items:center;
  flex:0 0 auto;border:1px solid currentColor}
.ws-person-card .ws-row-icon svg{width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:1.9;
  stroke-linecap:round;stroke-linejoin:round}
.ws-person-card .ws-detail-row{display:flex;align-items:center;gap:10px}
/* The label grows, the icon must not: `.ws-detail-row>span` (0,3,0) outranks
   `.ws-row-icon` (0,2,0), so the icon span stretched to 147px. Pin it here. */
.ws-person-card .ws-detail-row>span{flex:1 1 auto}
.ws-person-card .ws-detail-row>span.ws-row-icon{flex:0 0 30px;width:30px}

/* ===== Card fixes from the codex review, each confirmed by rendering (2026-09-05) ==========
   1. LIGHT THEME WAS BROKEN. The hero and other card surfaces kept the source design's fixed
      near-black gradients, so in light theme dark ink sat on a near-black panel. Measured:
      hero background rgba(18,29,56,.98) against page ink rgb(20,29,52). Surfaces now use the
      theme's own panel tokens.
   2. `.ws-detail-row span{color:muted}` also hit the ROW ICON span and #pdCurDur (a span
      inside strong) -- both measured rgb(59,71,95) instead of their tone / value colour.
      Scoped to the label only.
   3. The tone classes .purple/.blue/.amber/.coral/.green were declared GLOBALLY and would
      collide with any element in the console using those names. Scoped to the card.
   4. The hero-title rule targeted h2 while the markup emits h3, so the design's heading
      treatment never applied. */
.ws-person-card .ws-hero,.ws-person-card .ws-location-hero,.ws-person-card .ws-time-hero{
  background:linear-gradient(145deg,var(--panel-2),var(--panel));border:1px solid var(--line)}
.ws-person-card .ws-details-card,.ws-person-card .ws-area-card,.ws-person-card .ws-metric{
  background:var(--panel);border:1px solid var(--line)}
/* label only -- not the icon span, not the value */
/* The tone must win at the SAME specificity as the icon rule, or `color:inherit` flattens
   every row icon to ink -- which is what my first attempt at this did. */
.ws-person-card .ws-detail-row strong,.ws-person-card .ws-detail-row strong *{color:var(--ink)}
/* Tones, scoped so they cannot leak into the rest of the console. These read the --ws-*
   tokens declared on .ws-person-card so there is one source of truth: an earlier pass
   pointed .blue at the console's --cyan, which rendered Areas Visited teal where the
   reference is a violet-blue. */
.ws-person-card .purple{color:var(--ws-violet)}
.ws-person-card .blue{color:var(--ws-blue)}
.ws-person-card .amber{color:var(--ws-amber)}
.ws-person-card .coral{color:var(--ws-coral)}
.ws-person-card .green{color:var(--ws-green)}
.ws-person-card .ws-hero-title{font-size:22px;font-weight:800;letter-spacing:-.01em;margin:0 0 4px;color:var(--ink)}
.ws-person-card .ws-subline,.ws-person-card .ws-label{color:var(--ink-2)}
.ws-person-card .ws-value,.ws-person-card .ws-big-time{color:var(--ink)}

/* ===== Person card, sized for the drawer it actually lives in ==============================
   The card renders inside .pdrawer -- width:min(440px,94vw) less .pdbody's 16px side padding
   -- so its real width is 408px, NOT the ~520px the supplied design was drawn at. Sizing it
   for the wider box is what made the art read as oversized: at 408px the metric values
   ("9h 55m") and the CURRENTLY PRESENT pill both wrapped onto two lines.

   Targets below are percentages measured off the three reference images by edge detection
   (ref card width 887px), applied to 408px. Reference -> target:
       hero icon      12.0%  ->  44px      metric icon   11.4%  ->  38px
       location mark  13.3%  ->  48px      row icon       7.5%  ->  28px
   The clock is deliberately below its reference share: in the mock it anchors a full-bleed
   mobile hero, but in a 408px drawer beside a 34px headline the same share dominates. It is
   also drawn much lighter -- the reference face is a thin ring with slim hands, not a
   glowing donut (measured ring stroke there is ~4% of face diameter, mine was ~6%). */

/* -- heroes ------------------------------------------------------------------------------ */
.ws-person-card .ws-hero{padding:18px}
.ws-person-card .ws-time-hero{min-height:0;padding-bottom:18px}
.ws-person-card .ws-location-hero{min-height:0;gap:14px}
.ws-person-card .ws-hero-icon{width:44px;height:44px;flex:0 0 44px}
.ws-person-card .ws-hero-icon svg{width:22px;height:22px}
.ws-person-card .ws-hero-title,.ws-person-card .ws-location-main h2,
.ws-person-card .ws-time-hero h2{font-size:20px;margin:0 0 4px}
.ws-person-card .ws-subline{font-size:13px}
/* the pill wrapped to two lines at 408px */
.ws-person-card .ws-status{margin-top:10px;padding:5px 10px;font-size:10.5px;white-space:nowrap}
.ws-person-card .ws-status span{width:7px;height:7px}

/* -- clock: smaller and far lighter than the source ---------------------------------------- */
.ws-person-card .ws-clock-art{width:68px;height:68px;right:18px;top:50%;transform:translateY(-50%)}
.ws-person-card .ws-clock-ring{inset:6px;border-width:2px;box-shadow:none}
.ws-person-card .ws-clock-hand{width:2px}
.ws-person-card .ws-clock-hand.hand-a{height:20px}
.ws-person-card .ws-clock-hand.hand-b{height:15px}
.ws-person-card .ws-clock-dot{width:5px;height:5px}
/* The reference face carries 12 radial tick marks and a graded ring; mine had a flat ring
   and no ticks, which is most of why it read as a solid donut rather than a clock. Ticks
   are drawn with a repeating conic gradient masked to a thin annulus -- no extra markup, so
   app.js keeps emitting the same four child divs. */
.ws-person-card .ws-clock-art::after{content:"";position:absolute;inset:9px;border-radius:50%;
  background:repeating-conic-gradient(from -1deg,currentColor 0deg 2deg,transparent 2deg 30deg);
  color:var(--ws-violet);opacity:.45;pointer-events:none;
  -webkit-mask:radial-gradient(circle,transparent 0 66%,#000 66% 84%,transparent 84%);
          mask:radial-gradient(circle,transparent 0 66%,#000 66% 84%,transparent 84%)}
.ws-person-card .ws-clock-ring{border-color:color-mix(in srgb,var(--ws-violet) 78%,transparent)}
.ws-person-card .ws-clock-hand{border-radius:2px;opacity:.95}
.ws-person-card .ws-big-time{font-size:34px;line-height:1.05;margin:2px 0 4px}
.ws-person-card .ws-time-copy{max-width:calc(100% - 86px)}

/* -- location mark ------------------------------------------------------------------------- */
.ws-person-card .ws-location-mark{width:48px;height:48px;flex:0 0 48px}
.ws-person-card .ws-location-mark svg{width:20px;height:20px}
.ws-person-card .ws-radar.r1{inset:6px}
.ws-person-card .ws-radar.r2{inset:0}
.ws-person-card .ws-radar.r3{inset:-7px}

/* -- metric tiles: the values were wrapping ------------------------------------------------ */
.ws-person-card .ws-metric-grid{gap:11px;margin:11px 0}
.ws-person-card .ws-metric{min-height:0;padding:14px;gap:11px;border-radius:15px}
.ws-person-card .ws-metric-icon{width:38px;height:38px;border-radius:12px}
.ws-person-card .ws-metric-icon svg{width:19px;height:19px}
.ws-person-card .ws-label{font-size:12px;margin-bottom:2px}
.ws-person-card .ws-value{font-size:19px;white-space:nowrap}
/* The reference tints the warning tiles' borders and leaves the neutral ones cool: measured
   stroke is rgb(28,38,63) on Time-on-site / Areas-visited but rgb(73,64,64) on Out-of-view
   and rgb(73,60,67) on Sightings. The tone class sits on the icon, so :has() reads it from
   the tile without app.js having to emit a modifier. */
.ws-person-card .ws-metric:has(.ws-metric-icon.amber){border-color:color-mix(in srgb,var(--warn) 45%,transparent)}
.ws-person-card .ws-metric:has(.ws-metric-icon.coral){border-color:color-mix(in srgb,var(--bad) 45%,transparent)}
/* Badge fills were too dim against the reference: measured interiors there are rgb(59,106,91)
   green and rgb(77,70,153) purple, against rgb(27,89,80) and rgb(46,62,110) here. Tint the
   badge from its own tone instead of painting it flat panel-colour. */
.ws-person-card .ws-metric-icon,.ws-person-card .ws-small-icon,
.ws-person-card .ws-detail-row>span.ws-row-icon{background:color-mix(in srgb,currentColor 26%,var(--ws-panel-2));
  border:1px solid color-mix(in srgb,currentColor 38%,transparent)}
/* unit letters at roughly half height, as the reference sets them */
.ws-person-card .ws-u{font-style:normal;font-size:.58em;font-weight:700;letter-spacing:0;margin-left:1px}
/* the reference heads the time hero with a kicker above the figure */
.ws-person-card .ws-hero-kicker{font-size:13px;font-weight:700;color:var(--ws-text);opacity:.92;margin-bottom:2px}

/* -- details list -------------------------------------------------------------------------- */
.ws-person-card .ws-details-card,.ws-person-card .ws-area-card{border-radius:16px}
.ws-person-card .ws-details-card h3,.ws-person-card .ws-area-card h3{padding:15px 16px 9px;font-size:15px}
.ws-person-card .ws-small-icon{width:26px;height:26px;border-radius:8px;margin-right:7px}
.ws-person-card .ws-small-icon svg{width:15px;height:15px}
.ws-person-card .ws-detail-row{padding:11px 16px;font-size:13.5px;gap:12px}
.ws-person-card .ws-detail-row strong{font-size:13.5px}
.ws-person-card .ws-row-icon{width:28px;height:28px;border-radius:9px}
.ws-person-card .ws-row-icon svg{width:15px;height:15px}
/* tones, at the same specificity as the sizing rules above so neither wins by accident */
.ws-person-card .ws-detail-row>span.ws-row-icon{display:inline-grid;place-items:center;flex:0 0 auto}
.ws-person-card .ws-detail-row>span.ws-row-icon.purple{color:var(--ws-violet)}
.ws-person-card .ws-detail-row>span.ws-row-icon.blue{color:var(--ws-blue)}
.ws-person-card .ws-detail-row>span.ws-row-icon.amber{color:var(--ws-amber)}
.ws-person-card .ws-detail-row>span.ws-row-icon.coral{color:var(--ws-coral)}
.ws-person-card .ws-detail-row>span.ws-row-icon.green{color:var(--ws-green)}

/* ============================ ALERTS & REVIEW (customer surface) ============================
   The workforce/security review page: five category cards, a filter bar, the person incident
   table on the left and the incident review panel on the right.

   Written here rather than in ui-v3.css because that file is GENERATED by pt_make_uiv3.py and any
   hand edit to it is lost on the next regeneration. Everything below reuses the existing design
   tokens, so the page inherits light/dark and spacing from the same source as the rest of the
   console instead of pinning its own colours. */

/* The review panel is an overlay drawer, so the table is never sharing horizontal space with it
   and always gets the full page width. */

.altoolbar{display:flex;flex-wrap:wrap;gap:10px;align-items:center;justify-content:space-between;margin-bottom:14px}
/* .alfbar, not .alfilters -- that name is already taken by another screen's filter strip. */
.alfbar{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
.alfbar .in{min-width:150px}

/* Wide content must scroll inside its own container -- the page body never scrolls sideways. */
/* Deliberately NOT .tablewrap. That wrapper carries a global "one line, always" policy
   (white-space:nowrap + overflow:hidden + text-overflow:ellipsis on every th/td) which suits the
   dense dashboard tables it was written for but truncates this one: "Moved from Prod Line 1 ->
   Stores" became "Moved from Prod ...", hiding the destination, which is the whole point of the
   row. Rather than fight that policy with overrides, this table opts out of it.
   table-layout:fixed is set here because the column shares below depend on it -- without it the
   columns size to content and the header stops lining up with the body. */
.altwrap{overflow-x:hidden}
/* A refresh is in flight: the rows stay readable but visibly stale, so a filter click is
   acknowledged immediately instead of appearing to do nothing until the response lands. */
.allist.busy .altwrap{opacity:.5;transition:opacity var(--motion-fast,.15s) var(--ease,ease)}
.allist.busy .alpager{opacity:.5}
/* No min-width, so the table is ALWAYS exactly its container's width and the wrapper can never
   produce a horizontal scrollbar -- one appeared on narrow windows and scrolling a table sideways
   to read a row is worse than a shortened value. On a narrow window the columns compress and the
   ellipsis (below) takes over; at any normal width every value still fits on its line. */
.altable{width:100%;table-layout:fixed;border-collapse:separate;border-spacing:0}
.altable th:nth-child(1),.altable td:nth-child(1){width:3%}
/* Hidden until the Status header turns selection on. The remaining shares total 96.5% and the
   browser hands the freed 3% back to them proportionally, so nothing needs a second width set. */
.altable:not(.selmode) th:nth-child(1),.altable:not(.selmode) td:nth-child(1){display:none}
.alselhdr{cursor:pointer;user-select:none}
.alselhdr:hover{color:var(--ink)}
.altable.selmode .alselhdr{color:var(--brand)}
.altable th:nth-child(2),.altable td:nth-child(2){width:8%}
.altable th:nth-child(3),.altable td:nth-child(3){width:13.4%}
.altable th:nth-child(4),.altable td:nth-child(4){width:16%}
.altable th:nth-child(5),.altable td:nth-child(5){width:18.7%}
.altable th:nth-child(6),.altable td:nth-child(6){width:10%}
.altable th:nth-child(7),.altable td:nth-child(7){width:15.5%}
/* Status carries a pill, so it needs room for the longest label plus that chrome: an
   "Acknowledged" pill measures 118px, and at a narrower share it spilled over the kebab. */
.altable th:nth-child(8),.altable td:nth-child(8){width:11.2%}
.altable th:nth-child(9),.altable td:nth-child(9){width:3.7%}
/* "DEPARTMENT" is the longest header word; drop its tracking so it sits on one line. */
.altable thead th:nth-child(7){letter-spacing:0}
/* The kebab is a 34px shared icon button; cell padding would push it out of a 5% column. */
.altable td:nth-child(9),.altable th:nth-child(9){padding-left:0;padding-right:0;text-align:center}
.altable thead th{font-size:10px;letter-spacing:.02em;text-transform:uppercase;color:var(--muted);
  font-weight:600;padding:10px 8px;border-bottom:1px solid var(--line);text-align:left;
  position:sticky;top:0;z-index:2;background:var(--panel);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* EVERY cell is a single line. The columns below are sized from the widest real value measured
   across the whole dataset ("Unauthorized Movement" 190px, "Corridor Ground Floor -> Accounts"
   228px, "Research and Development" 185px, an "Acknowledged" pill 134px), so nothing needs to
   wrap. The Person column is the deliberate exception: its name and id are separate lines by
   design. Ellipsis is a safety net for a value longer than anything currently in the data --
   it degrades gracefully instead of spilling into the next column. */
.altable tbody td{padding:9px 8px;border-bottom:1px solid var(--line);vertical-align:middle;
  font-size:13px;line-height:1.35;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.altable tbody tr{cursor:pointer;transition:background var(--motion-fast,.15s) var(--ease,ease)}
.altable tbody tr:hover{background:var(--surface-hover)}
.altable tbody tr.selected{background:var(--surface-selected)}
/* Severity reads as a left edge rather than a coloured row, so the person stays the focus.
   Namespaced: plain .bad/.warn/.info are shared pill classes and must not be put on a row. */
.altable tbody tr.sev-crit td:first-child{box-shadow:inset 3px 0 0 var(--bad)}
.altable tbody tr.sev-warn td:first-child{box-shadow:inset 3px 0 0 var(--warn)}
.altable tbody tr.sev-info td:first-child{box-shadow:inset 3px 0 0 var(--info)}
.altable td.alchk,.altable th.alchk{width:34px;padding-left:10px;padding-right:0}
.altable td.alkebab{width:34px;text-align:right}
.aldet{color:var(--ink-2)}

/* Person cell: a real face crop, the person's name, and the real identifier we hold. */
.alperson{display:flex;align-items:center;gap:9px;min-width:0}
.alperson>div{min-width:0}
.aln,.alid{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.alid{font-size:11px}
.alav{width:34px;height:34px;border-radius:9px;object-fit:cover;flex:0 0 34px;background:var(--panel-2)}
.alav.ph{display:grid;place-items:center;font-size:12px;font-weight:600;color:var(--muted)}
.aln{font-weight:600;line-height:1.25}
.alid{font-size:11.5px;color:var(--muted);font-family:var(--font-mono)}
.altype{display:inline-flex;align-items:center;gap:7px;white-space:nowrap}
.altype svg{width:15px;height:15px;flex:0 0 15px;opacity:.85}

.albulk{display:flex;align-items:center;gap:9px;padding:9px 4px;font-size:13px;color:var(--ink-2)}
.alpager{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:11px 4px 2px;flex-wrap:wrap}
.pgs{display:flex;align-items:center;gap:4px}
.pg{min-width:30px;height:30px;border-radius:8px;border:1px solid var(--line);background:transparent;
  color:var(--ink-2);cursor:pointer;font-size:12.5px}
.pg:hover{background:var(--surface-hover)}
.pg.on{background:var(--brand);border-color:var(--brand);color:#fff;font-weight:600}
.pgdots{color:var(--muted);padding:0 3px}
/* .iconbtn is deliberately NOT restyled here -- it is a shared component already defined in
   styles.css and refined under .v3, and overriding it globally changed every other screen. */

/* ---- row action menu ------------------------------------------------------------------------
   The three review actions live in each row's own menu instead of a side panel or a popup: the row
   already shows the person, the movement, the location and the department, which is what the
   decision is made on.

   Body-appended and fixed-positioned, following the console's existing column-filter popover: the
   table scrolls inside .altwrap, and a menu positioned within a cell would be clipped by that
   scroll container. */
.alrowmenu{position:fixed;z-index:1200;min-width:208px;background:var(--panel);
  border:1px solid var(--line);border-radius:11px;box-shadow:0 12px 34px rgba(8,14,30,.28);
  padding:6px;font-size:13px}
.alrowmenu .almh{font-size:11px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;
  color:var(--muted);padding:6px 8px 8px;border-bottom:1px solid var(--line);margin-bottom:4px;
  display:flex;flex-direction:column;gap:2px}
.alrowmenu .almh span{text-transform:none;letter-spacing:0;font-weight:500;color:var(--ink-2);
  font-size:12px}
.alrowmenu .almi{display:flex;align-items:center;gap:9px;width:100%;padding:8px;border:0;
  background:transparent;color:var(--ink);font:inherit;text-align:left;border-radius:8px;
  cursor:pointer}
.alrowmenu .almi:hover{background:var(--surface-hover)}
.alrowmenu .almi svg{width:15px;height:15px;flex:0 0 15px;opacity:.75}
/* The row whose menu is open stays highlighted, so it is obvious which incident is being acted on. */
.altable tbody tr.menuopen{background:var(--surface-selected)}

/* People on Site uses the Detection Log's standard-log look, but with only EIGHT columns -- the
   selection checkbox, Camera and Match are not rendered. The shared .logstd widths are sized for
   the log's ten, so without these the freed share piled onto Zone (241px) while Area / On site was
   squeezed to 112px. Scoped to .wfstd so the Detection Log's own geometry is untouched. */
/* Shares are PROPORTIONAL TO MEASURED CONTENT, so spare width is spread evenly instead of pooling
   in a few columns. Measured across the rendered rows: Person 189px, Type 80, Status 46, Dept 87,
   Photo 76, Time 178, Zone 160, Area/On site 117 -- against a 1284px table, which left 350px of
   slack sitting mostly in Area (88), Dept (67) and Status (57) while Time and Photo had none.
   Photo is pinned to its 52px crop; every other column gets its measured need PLUS THE SAME
   ~50px, so the gaps are equal rather than proportional -- proportional shares handed the widest
   columns the widest gaps, which is what read as uneven. */
.wfstd :is(th,td):nth-child(1){width:16.4%;min-width:150px}   /* Person */
.wfstd :is(th,td):nth-child(2){width:8.6%;min-width:88px}     /* Type */
.wfstd :is(th,td):nth-child(3){width:6.7%;min-width:70px}     /* Status */
.wfstd :is(th,td):nth-child(4){width:8.1%;min-width:84px}     /* Dept */
.wfstd :is(th,td):nth-child(5){width:7.4%;min-width:76px;text-align:center}  /* Photo */
.wfstd :is(th,td):nth-child(6){width:15.4%;min-width:156px}   /* Time */
.wfstd :is(th,td):nth-child(7){width:13.6%;min-width:126px}   /* Zone */
.wfstd :is(th,td):nth-child(8){width:12%;min-width:122px}   /* Area / On site */
.wfstd thead th{white-space:nowrap}                            /* header labels stay on one line */
.wfstd :is(th,td):nth-child(9){width:12.7%;min-width:116px}   /* Camera */

/* [hidden] is an attribute selector, so it ties with any single class on specificity and loses to
   whichever comes later. .btn sets display:inline-flex, which meant `<button hidden>` -- the
   "Clear all" control, shown only while something is filtering -- was ALWAYS visible, and clicking
   it with nothing to clear did nothing. The HTML spec recommends exactly this !important for that
   reason: nothing should out-rank "this element is not here". */
[hidden]{display:none!important}

/* ==== ATTENDANCE REGISTER =========================================================
   The customer attendance page: one filter bar, one dense register, one pager inside
   the same card. Deliberately no KPI band and no status column -- see attendanceV3().
   Column map is 9 columns; the two fixed ones (row number, kebab) are px so the eight
   readable columns share what is left instead of fighting a percentage each. */
.attfilt{padding:14px 16px;margin-bottom:14px}
.attfilt-row{display:flex;align-items:flex-end;gap:12px;flex-wrap:wrap}
.attfld{display:flex;flex-direction:column;gap:6px;min-width:0}
.attlbl{font-size:11px;font-weight:700;letter-spacing:.02em;color:var(--muted)}
.attfld-q{flex:1 1 320px;max-width:420px}
.attfld-d{flex:0 0 210px}
.attfld-p{flex:0 0 230px}
.attfld .search{display:flex;align-items:center;gap:7px;height:36px;padding:0 10px;
  border:1px solid var(--line);border-radius:9px;background:var(--panel)}
.attfld .search input{flex:1;min-width:0;border:0;background:transparent;color:var(--ink);
  font-size:13px;outline:none}
.attfld .select,.attfld select{height:36px;padding:0 10px;border:1px solid var(--line);
  border-radius:9px;background:var(--panel);color:var(--ink);font-size:13px;width:100%}
/* The native picker is kept (it IS the date picker) but made invisible over a label we can
   format as "Fri, 05 Sep 2026" -- the browser's own dd/mm/yyyy cannot be styled. */
.attdate{position:relative;display:flex;align-items:center;height:36px;padding:0 10px;
  border:1px solid var(--line);border-radius:9px;background:var(--panel)}
.attdate input{position:absolute;inset:0;width:100%;height:100%;opacity:0;cursor:pointer;
  border:0;padding:0;margin:0}
.attdate b{font-size:13px;font-weight:600;color:var(--ink);pointer-events:none;white-space:nowrap;
  flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}
/* Hidden so the ONLY way to open the picker is the click handler in wireAttendanceV3 -- leaving it
   live as well would let one click both open and close the picker. */
.attdate input::-webkit-calendar-picker-indicator{display:none}
.attdate{cursor:pointer;gap:8px}
.attdate svg{width:15px;height:15px;flex:0 0 15px;color:var(--muted);pointer-events:none}
.attbtn{height:36px;padding:0 18px;font-size:13px}

.atthead-r{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.atthead-r b{color:var(--ink);font-weight:700}
.attsep{color:var(--muted);opacity:.5}

.attreg table{min-width:940px}
.attreg tbody tr{height:46px;cursor:default}
.attreg tbody td,.attreg thead th{padding:7px clamp(5px,.5vw,10px)}
.attav{width:30px;height:30px;border-radius:6px;object-fit:cover;display:block}
.attavw{position:relative;display:inline-block;width:30px;height:30px;flex:0 0 30px}
.attavw .attav{position:absolute;inset:0}
.attav.ph{background:var(--info-bg);border:1px solid var(--line)}
.attname{display:flex;align-items:center;gap:9px;min-width:0}
.attname b{font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.attnum{color:var(--muted)}
.attkeb{text-align:right}
.attkeb .iconbtn{opacity:.65;padding:2px 6px;line-height:1}
.attkeb .iconbtn:hover{opacity:1}
.attreg :is(th,td):nth-child(1){width:46px;min-width:46px}          /* # */
.attreg :is(th,td):nth-child(2){width:17%;min-width:161px}          /* Emp Name */
.attreg :is(th,td):nth-child(3){width:7.2%;min-width:70px}           /* Emp ID */
.attreg :is(th,td):nth-child(4){width:20%;min-width:190px}          /* Department */
.attreg :is(th,td):nth-child(5){width:13.5%;min-width:126px}          /* Shift */
.attreg :is(th,td):nth-child(6){width:10.5%;min-width:98px}           /* In Time */
.attreg :is(th,td):nth-child(7){width:10.5%;min-width:98px}           /* Out Time */
.attreg :is(th,td):nth-child(8){width:13.4%;min-width:140px}          /* Work Hours (Today) */
.attreg thead th{white-space:nowrap}                                    /* header labels on one line */
.attreg :is(th,td):nth-child(9){width:50px;min-width:50px;padding-left:0;padding-right:6px}          /* actions */

.attfoot{display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  padding:11px 14px;border-top:1px solid var(--line)}
.attfoot-l{flex:1 1 200px}
.attfoot-c{flex:0 0 auto;display:flex;align-items:center;gap:4px;justify-content:center}
.attfoot-r{flex:1 1 200px;display:flex;justify-content:flex-end}
@media (max-width:820px){
  .attfoot{justify-content:center}
  .attfoot-l,.attfoot-r{flex:1 1 100%;justify-content:center;text-align:center}
}

/* ==== CAMERA TILE: WARM-UP STATE ==================================================
   An on-demand mediamtx source takes 10-20s to produce its first frame. During that
   window the tile must say so: the play control is replaced by a spinner and the state
   line reads "Connecting...", instead of the tile going blank and looking broken. */
.camconnecting .camplay{display:none}
.camconnecting .camstate{display:inline-flex;align-items:center;gap:8px}
.camconnecting .camstate::before{content:"";width:14px;height:14px;flex:0 0 14px;
  border:2px solid currentColor;border-right-color:transparent;border-radius:50%;
  animation:camspin .8s linear infinite;opacity:.85}
@keyframes camspin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){.camconnecting .camstate::before{animation:none}}

/* ==== EMPLOYEE DIRECTORY ==========================================================
   Six columns, no KPI band, no attendance or presence anywhere. The filter bar, footer
   and avatar block are the Attendance Register's own classes on purpose -- two pages
   that do different jobs should still look like one product. */
.empdir table{table-layout:fixed}
.empdir tbody tr{height:46px}
.empdir tbody td,.empdir thead th{padding:7px clamp(5px,.5vw,10px)}
.empdir :is(th,td):nth-child(1){width:25.5%;min-width:184px}   /* Employee */
.empdir :is(th,td):nth-child(2){width:13%;min-width:104px}   /* Employee ID */
.empdir :is(th,td):nth-child(3){width:23%;min-width:170px}   /* Department */
.empdir :is(th,td):nth-child(4){width:13%;min-width:96px}    /* Role */
.empdir :is(th,td):nth-child(5){width:11.5%;min-width:94px}    /* Status */
.empdir :is(th,td):nth-child(6){width:14%;min-width:118px;text-align:right}  /* Actions */
/* Restrained on purpose: this is a record's state, not an alert. */
.empstat{display:inline-block;padding:2px 10px;border-radius:999px;font-size:11px;font-weight:700;
  border:1px solid var(--line);color:var(--muted);background:transparent;white-space:nowrap}
.empstat.on{color:var(--ok);border-color:color-mix(in srgb,var(--ok) 45%,transparent);
  background:color-mix(in srgb,var(--ok) 12%,transparent)}
.empact .btn.sm{padding:4px 12px;font-size:12px}
@media (max-width:900px){ .empdir table{min-width:820px} }
/* ==== SETTINGS (role-based redesign) ==============================================
   Section rail + cards + one area drawer. Reuses the console's existing card/table
   language so Settings reads as the same product as Attendance and the Directory. */
.setnav{display:flex;gap:6px;flex-wrap:wrap;margin:0 0 16px;border-bottom:1px solid var(--line);padding-bottom:10px}
.setnavb{padding:7px 14px;border-radius:9px;border:1px solid transparent;background:transparent;
  color:var(--muted);font-size:13px;font-weight:650;cursor:pointer}
.setnavb:hover{color:var(--ink);background:var(--info-bg)}
.setnavb.on{background:var(--brand);border-color:var(--brand);color:#fff}
.setact{text-align:right}
.setnum{color:var(--muted)}
.setoff{opacity:.55}
/* Edge-sync line. Sits under the section nav on every Settings section: the question it
   answers -- is what I just saved actually live? -- follows every save, not one pane. */
.setsync{display:flex;gap:9px;align-items:flex-start;margin:0 0 14px;padding:10px 13px;
  border:1px solid var(--line);border-radius:10px;background:var(--card);
  font-size:13px;line-height:1.5;color:var(--fg)}
.setsync svg{margin-top:2px;opacity:.8}
.setsync.ok{color:var(--muted);background:transparent;border-color:transparent;
  padding:2px 0 10px;margin-bottom:6px}
.setsync.warn{border-color:color-mix(in srgb,var(--warn,#e0a33e) 45%,transparent);
  background:color-mix(in srgb,var(--warn,#e0a33e) 9%,transparent)}
.setfoot{padding:10px 14px;border-top:1px solid var(--line)}
.setsearch{min-width:230px}
.setgrid{display:flex;gap:26px;flex-wrap:wrap;margin-bottom:14px}
.setstat b{display:block;font-size:24px;font-weight:750;line-height:1.1}
.setstat span{font-size:12px;color:var(--muted)}
.setactions{display:flex;gap:10px;flex-wrap:wrap}
.setkv{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:14px}
.setkv>div{display:flex;flex-direction:column;gap:3px}
.setkv span{font-size:11px;font-weight:700;color:var(--muted);letter-spacing:.02em}
.setkv .setraw b{font-size:12px;color:var(--muted)}
.setnote{display:flex;align-items:center;gap:9px;padding:10px 12px;margin-bottom:12px;border-radius:10px;
  background:color-mix(in srgb,var(--ok) 10%,transparent);border:1px solid color-mix(in srgb,var(--ok) 30%,transparent);font-size:13px}
.setwarn{display:flex;align-items:flex-start;gap:9px;padding:12px 14px;margin-bottom:14px;border-radius:10px;
  background:color-mix(in srgb,var(--bad) 9%,transparent);border:1px solid color-mix(in srgb,var(--bad) 32%,transparent);font-size:13px}
.setdetails{margin-top:12px;font-size:13px}
.setdetails summary{cursor:pointer;color:var(--muted);font-weight:650}
/* status/severity toggle */
.setsw{position:relative;display:inline-flex;align-items:center;cursor:pointer}
.setsw input{position:absolute;opacity:0;width:0;height:0}
.setsw span{width:38px;height:21px;border-radius:999px;background:var(--line);position:relative;transition:background .15s}
.setsw span::after{content:"";position:absolute;top:2px;left:2px;width:17px;height:17px;border-radius:50%;
  background:#fff;transition:transform .15s}
.setsw input:checked+span{background:var(--ok)}
.setsw input:checked+span::after{transform:translateX(17px)}
.setswrow{gap:10px;margin-bottom:8px}
.setswrow b{font-weight:600;font-size:13px}
/* the area drawer */
.setdrawerbg{position:fixed;inset:0;background:rgba(8,12,24,.45);z-index:60}
.setdrawer{position:fixed;top:0;right:0;bottom:0;width:min(440px,94vw);z-index:61;display:flex;flex-direction:column;
  background:var(--panel);border-left:1px solid var(--line);box-shadow:-18px 0 46px rgba(8,12,24,.28)}
.setdhead{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;padding:16px;border-bottom:1px solid var(--line)}
.setdhead h3{margin:0;font-size:16px}
.setdbody{flex:1;overflow:auto;padding:16px}
.setdbody section{margin-bottom:22px}
.setdbody h4{margin:0 0 10px;font-size:12px;font-weight:750;letter-spacing:.03em;color:var(--muted);text-transform:uppercase}
.setradio{display:flex;align-items:center;gap:9px;margin-bottom:7px;font-size:13px;cursor:pointer}
.setdepts{display:grid;grid-template-columns:1fr 1fr;gap:2px 12px;margin:10px 0 0 24px}
.setdepts.off{opacity:.45}
.setfield{display:flex;align-items:center;gap:9px;font-size:13px}
.setfield.off{opacity:.45}
.setfield input{width:88px;padding:6px 9px;border:1px solid var(--line);border-radius:8px;background:var(--panel);color:var(--ink)}
.setdfoot{display:flex;align-items:center;justify-content:flex-end;gap:10px;padding:14px 16px;border-top:1px solid var(--line)}
.setdfoot .muted{margin-right:auto;text-align:left}
/* Column widths MEASURED, not guessed: each number is that column's natural content width at
   1920 with table-layout:auto and nowrap, plus 14px of padding headroom. `.tablewrap` is
   table-layout:fixed, so EVERY column needs a declared share -- .setalerts previously declared
   three of four and the fourth collapsed. Each table also carries a floor equal to the sum of
   its minimums, because `.logstd table` is min-width:0: without one, a narrow viewport shrinks
   the table and ellipsises data instead of letting the wrapper scroll. Re-measure rather than
   nudge if a column's content changes. */
/* RESTORED 2026-09-07. These were deleted by an anchor-slice in 31aaa62 that rewrote the
   table-width block between .setshifts and .camsync and took every other rule in that span
   with it. Without .setmodal the dialog has NO positioning: it falls back to position:static,
   lays out inline below the page content, and sits off-screen behind a fixed backdrop -- so
   Edit appeared to do nothing and Cancel/x/Save could not be clicked. */
.setmodal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:61;
  width:min(520px,94vw);max-height:88vh;display:flex;flex-direction:column;
  background:var(--panel);border:1px solid var(--line);border-radius:14px;
  box-shadow:0 24px 64px rgba(8,12,24,.34)}
.setrow{display:flex;flex-direction:column;gap:6px;margin-bottom:14px}
.setrow>span{font-size:11px;font-weight:700;letter-spacing:.02em;color:var(--muted)}
.setrow input,.setrow select{height:36px;padding:0 10px;border:1px solid var(--line);border-radius:9px;
  background:var(--panel);color:var(--ink);font-size:13px;width:100%}
.setnotewarn{background:color-mix(in srgb,var(--warn,#d08700) 12%,transparent);
  border-color:color-mix(in srgb,var(--warn,#d08700) 34%,transparent)}
.setshifts :is(th,td):nth-child(1){width:5.7%;min-width:38px}
.setshifts :is(th,td):nth-child(2){width:26.6%;min-width:178px}
.setshifts :is(th,td):nth-child(3){width:25.0%;min-width:167px}
.setshifts :is(th,td):nth-child(4){width:16.3%;min-width:109px}
.setshifts :is(th,td):nth-child(5){width:14.2%;min-width:95px}
.setshifts :is(th,td):nth-child(6){width:12.3%;min-width:82px}
.setshifts table{min-width:669px}
.setdir :is(th,td):nth-child(1){width:33.7%;min-width:189px}
.setdir :is(th,td):nth-child(2){width:28.2%;min-width:158px}
.setdir :is(th,td):nth-child(3){width:19.4%;min-width:109px}
.setdir :is(th,td):nth-child(4){width:18.7%;min-width:105px}
.setdir table{min-width:561px}
.setdays :is(th,td):nth-child(1){width:14.8%;min-width:105px}
.setdays :is(th,td):nth-child(2){width:19.7%;min-width:140px}
.setdays :is(th,td):nth-child(3){width:15.0%;min-width:106px}
.setdays :is(th,td):nth-child(4){width:35.4%;min-width:251px}
.setdays :is(th,td):nth-child(5){width:15.1%;min-width:107px}
.setdays table{min-width:709px}
.setareas :is(th,td):nth-child(1){width:18.1%;min-width:155px}
.setareas :is(th,td):nth-child(2){width:11.4%;min-width:97px}
.setareas :is(th,td):nth-child(3){width:13.5%;min-width:115px}
.setareas :is(th,td):nth-child(4){width:22.5%;min-width:192px}
.setareas :is(th,td):nth-child(5){width:13.5%;min-width:115px}
.setareas :is(th,td):nth-child(6){width:11.5%;min-width:98px}
.setareas :is(th,td):nth-child(7){width:9.6%;min-width:82px}
.setareas table{min-width:854px}
.setalerts :is(th,td):nth-child(1){width:26.2%;min-width:129px}
.setalerts :is(th,td):nth-child(2){width:18.9%;min-width:93px}
.setalerts :is(th,td):nth-child(3){width:26.8%;min-width:132px}
.setalerts :is(th,td):nth-child(4){width:28.0%;min-width:138px}
.setalerts table{min-width:492px}
.setcams :is(th,td):nth-child(1){width:15.2%;min-width:135px}
.setcams :is(th,td):nth-child(2){width:20.9%;min-width:186px}
.setcams :is(th,td):nth-child(3){width:14.4%;min-width:128px}
.setcams :is(th,td):nth-child(4){width:13.6%;min-width:121px}
.setcams :is(th,td):nth-child(5){width:12.0%;min-width:107px}
.setcams :is(th,td):nth-child(6){width:14.6%;min-width:130px}
.setcams :is(th,td):nth-child(7){width:9.2%;min-width:82px}
.setcams table{min-width:889px}
/* Employees and Change History: measured the same way as the other settings tables. */
.setemps :is(th,td):nth-child(1){width:16.4%;min-width:110px}
.setemps :is(th,td):nth-child(2){width:19.0%;min-width:127px}
.setemps :is(th,td):nth-child(3){width:28.7%;min-width:192px}
.setemps :is(th,td):nth-child(4){width:23.6%;min-width:158px}
.setemps :is(th,td):nth-child(5){width:12.3%;min-width:82px}
.setemps table{min-width:669px}
.sethist :is(th,td):nth-child(1){width:19.6%;min-width:128px}
.sethist :is(th,td):nth-child(2){width:19.6%;min-width:128px}
.sethist :is(th,td):nth-child(3){width:21.6%;min-width:141px}
.sethist :is(th,td):nth-child(4){width:26.3%;min-width:172px}
.sethist :is(th,td):nth-child(5){width:13.0%;min-width:85px}
.sethist table{min-width:654px}
/* Provenance line: quiet by design -- it answers a question, it does not announce itself. */
/* Advanced Configuration only: the raw editor's tables are generated from arbitrary columns,
   so they size to content and the wrapper scrolls, instead of ellipsising the column names
   that are the only documentation this no-validation screen has. */
.setlegacy .tablewrap table{table-layout:auto;width:auto;min-width:100%}
.setlegacy .tablewrap :is(th,td){white-space:nowrap;overflow:visible;text-overflow:clip;
  width:auto;min-width:0}
/* ico() emits an SVG with no width/height, and `svg{flex-shrink:0}` is global -- so an unsized
   icon in a flex row takes the whole row and shoves the text off the page. That is exactly what
   happened on every Settings section (1148x1148 icons, horizontal page scrollbar). Every
   settings surface that renders an icon sizes it here. */
:is(.setsync,.setnote,.setwarn,.setkv,.setdhead,.setbar) svg{width:16px;height:16px;flex:none}
.setsync span,.setnote span,.setwarn span{min-width:0}
.setprov{margin:12px 0 0;font-size:12px;color:var(--muted)}
.setprov b{color:var(--fg);font-weight:600}
.setwarnval{color:var(--warn,#e0a33e)}
.setbar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin:0 0 12px}
.setbar .input{flex:1 1 240px;min-width:0}
.camsync{font-size:11.5px;letter-spacing:.02em;color:var(--muted)}
.camsync.ok{color:var(--ok,#49b47a)}
.camsync.warn{color:var(--warn,#e0a33e);font-weight:600}

@media (max-width:640px){ .setrow2{grid-template-columns:1fr} }

/* ================================ ADVANCED SEARCH (V3) ================================
   Search carries 13 filters -- far more than any other V3 toolbar -- so they are laid out as
   three labelled groups (identity/time, quick range, attribute selects) rather than crammed into
   the single flex .toolbar the other pages use. The results table is NOT styled here: it renders
   the Detection Log's own ten STD_HEAD columns, so it reuses .logstd's measured geometry and
   .asthumb's crop verbatim instead of carrying a parallel set of widths that could drift. */
.srcard{margin-bottom:16px}
.srtop{display:grid;grid-template-columns:minmax(220px,2fr) minmax(170px,1fr) minmax(170px,1fr);
  gap:12px;padding:14px 16px 0}
/* auto-fill, so the ten selects reflow from five-up down to one-up on their own rather than at
   hand-picked breakpoints that would need re-measuring every time a facet is added. */
.srsel{display:grid;grid-template-columns:repeat(auto-fill,minmax(168px,1fr));gap:12px;padding:14px 16px 0}
.srf{display:flex;flex-direction:column;gap:6px;min-width:0}
.srf>span{font-size:11px;font-weight:700;letter-spacing:.02em;color:var(--muted)}
.srf .in,.srf .select{width:100%;min-width:0}
.srchips{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:14px 16px 0}
.srchl{font-size:11px;font-weight:700;letter-spacing:.02em;color:var(--muted);margin-right:2px}
.srbtns{display:flex;flex-wrap:wrap;gap:10px;padding:16px}
.srres .head .tools{display:flex;flex-wrap:wrap;gap:8px}
@media (max-width:760px){
  .srtop{grid-template-columns:1fr}
}

/* =====================================================================
   WorkSight layout-only beautification reference
   2026-09-08
   HARD SCOPE: no content, label, card, tab, metric, route or JS changes.
   Only alignment, spacing, overlap prevention, icon geometry, photo-slot
   standardisation and responsive reflow.
   ===================================================================== */

/* --- global containment: stop flex/grid children from forcing overlap --- */
#view,
#view > *,
.v3,
.v3 > *,
.grid > *,
.kpis > *,
.card,
.card > *,
.tablewrap,
.tablewrap > *,
.person,
.person > *{
  min-width:0;
}

/* --- sidebar: preserve content and dimensions, improve optical alignment --- */
.nav{
  min-height:38px;
  align-items:center;
}
.nav svg{
  flex:0 0 17px;
}
.nav b{
  line-height:1.2;
}
.nav .badge{
  flex:0 0 auto;
}

/* --- toolbar / header controls: same controls, cleaner alignment --- */
.top,
.top .right,
.toolbar,
.filters,
.v3 .context,
.v3 .card .head{
  align-items:center;
}
.iconbtn,
.topbtn,
.btn{
  flex:0 0 auto;
}
.iconbtn svg,
.topbtn svg,
.btn svg{
  vertical-align:middle;
}

/* --- tabs: preserve labels/count/order; prevent overlap at any width --- */
.tabs,
.v3 .tabs,
.antabs,
.pdtabs{
  max-width:100%;
}
.tabs,
.v3 .tabs,
.antabs{
  overflow-x:auto;
  overflow-y:hidden;
  flex-wrap:nowrap;
  -webkit-overflow-scrolling:touch;
  scrollbar-width:none;
}
.tabs::-webkit-scrollbar,
.v3 .tabs::-webkit-scrollbar,
.antabs::-webkit-scrollbar{
  display:none;
  width:0;
  height:0;
}
.tab,
.v3 .tab,
.antab{
  flex:0 0 auto;
  white-space:nowrap;
}

/* --- cards: keep every existing card, only make internal spacing predictable --- */
.card,
.v3 .card{
  overflow-wrap:anywhere;
}
.card .head,
.v3 .card .head{
  min-width:0;
}
.card .head > div,
.v3 .card .head > div{
  min-width:0;
}
.card .head h3,
.card .head small,
.v3 .card .head h3,
.v3 .card .head small{
  max-width:100%;
}

/* --- standard person image slot: passport portrait proportion everywhere --- */
/* Existing real images remain real images. Missing images become the same-size 👤 fallback. */
.person .av,
.v3 .person .av,
.pdav .av,
.ddav,
.portrait,
.person-photo{
  position:relative;
  flex:0 0 auto;
  overflow:hidden;
  width:34px;
  height:44px;
  aspect-ratio:7/9;
  border-radius:7px;
  display:grid;
  place-items:center;
}

/* Larger existing avatar variants remain larger but keep the same passport geometry. */
.v3 .av.lg{
  width:56px;
  height:72px;
  aspect-ratio:7/9;
  border-radius:9px;
}
.pdav .av{
  width:42px;
  height:54px;
  aspect-ratio:7/9;
  border-radius:8px;
}

/* Real images uniformly fill the passport frame without distortion. */
.person .av img,
.v3 .person .av img,
.pdav .av img,
.ddav img,
.portrait img,
.person-photo img{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  object-fit:cover;
  object-position:center 30%;
  border-radius:inherit;
}

/* No-image fallback: keep the exact same frame and use a human glyph. */
.person .av:not(.hasimg),
.v3 .person .av:not(.hasimg),
.pdav .av:not(.hasimg){
  font-size:0!important;
  color:transparent!important;
  background:var(--panel-3, var(--panel-2))!important;
  border:1px solid var(--line)!important;
}
.person .av:not(.hasimg)::after,
.v3 .person .av:not(.hasimg)::after,
.pdav .av:not(.hasimg)::after{
  content:"👤";
  font-size:19px;
  line-height:1;
  color:initial;
  filter:saturate(.65);
}
.v3 .av.lg:not(.hasimg)::after{
  font-size:30px;
}
.pdav .av:not(.hasimg)::after{
  font-size:23px;
}

/* Person text aligns to the middle of the taller portrait slot. */
.person,
.v3 .person{
  align-items:center;
}
.person .pid,
.v3 .person .meta,
.v3 .person .name{
  min-width:0;
}
.v3 .person .name,
.v3 .person .meta{
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
}

/* --- tables: no content changes; keep rows readable and avoid collisions --- */
.tablewrap,
.v3 .tablewrap{
  max-width:100%;
  overflow-x:auto;
  -webkit-overflow-scrolling:touch;
}
table,
.v3 table{
  max-width:100%;
}
th,
td,
.v3 th,
.v3 td{
  vertical-align:middle;
}
td .btn,
td .iconbtn,
.v3 td .btn,
.v3 td .iconbtn{
  white-space:nowrap;
}

/* --- dashboard grids: reflow instead of overlap --- */
@media (max-width:1280px){
  .v3 .kpis{
    grid-template-columns:repeat(3,minmax(0,1fr));
  }
}
@media (max-width:980px){
  .kpis,
  .kpis.k6,
  .v3 .kpis{
    grid-template-columns:repeat(2,minmax(0,1fr));
  }
  .v3 .grid{
    gap:12px;
  }
}

/* --- phone portrait / narrow landscape: preserve all cards, stack safely --- */
@media (max-width:760px){
  .main,
  #view,
  #mobileShellRoot{
    max-width:100vw;
  }

  .grid,
  .v3 .grid,
  .kpis,
  .kpis.k6,
  .v3 .kpis{
    min-width:0;
  }

  .tabs,
  .v3 .tabs,
  .antabs{
    width:100%;
  }

  .person .av,
  .v3 .person .av{
    width:32px;
    height:41px;
  }

  .v3 .av.lg{
    width:50px;
    height:64px;
  }

  .pdav .av{
    width:40px;
    height:51px;
  }

  .person .av:not(.hasimg)::after,
  .v3 .person .av:not(.hasimg)::after{
    font-size:18px;
  }

  .toolbar,
  .filters{
    row-gap:8px;
  }
}

/* --- very narrow portrait: one-column card flow, same content --- */
@media (max-width:430px){
  .kpis,
  .kpis.k6,
  .v3 .kpis{
    grid-template-columns:1fr;
  }
}

/* --- landscape phones / tablets: use horizontal room instead of oversized stacking --- */
@media (max-height:520px) and (orientation:landscape){
  .kpis,
  .kpis.k6,
  .v3 .kpis{
    grid-template-columns:repeat(2,minmax(0,1fr));
  }

  .tabs,
  .v3 .tabs,
  .antabs{
    overflow-x:auto;
  }

  .person .av,
  .v3 .person .av{
    width:30px;
    height:39px;
  }

  .v3 .av.lg{
    width:46px;
    height:59px;
  }
}

/* --- rotation-safe: don't rely on fixed viewport heights for content cards --- */
@media (orientation:landscape) and (max-width:1000px){
  .grid.dash > .card,
  .v3 .grid > .card{
    min-height:0;
  }
}

/* --- focus / usability preserved --- */
.nav:focus-visible,
.tab:focus-visible,
.v3 .tab:focus-visible,
.antab:focus-visible,
.iconbtn:focus-visible,
.btn:focus-visible{
  outline-offset:2px;
}

/* ============================================================================================
   ONE PERSON-IMAGE SLOT  (2026-09-09)
   Introduced when an audit found 19 live person-image slots using six different fallback
   behaviours: three showed the browser's broken-image icon, one collapsed its table cell, one
   rendered an empty <svg> from a non-existent icon key, one left a blank tinted box, and two fell
   back to coloured initials. `avSlot()` in ui-v3.js now renders all of them through this frame.

   Lives in styles.css, NOT ui-v3.css: ui-v3.css is generated by pt_make_uiv3.py outside this repo
   and any hand edit there is lost on the next regeneration.

   The glyph is drawn by CSS rather than emitted as a literal by JS, so both source files stay
   pure ASCII and no build step can mangle the code point.
   ============================================================================================ */
.wsav{
  position:relative;
  display:inline-block;
  flex:0 0 auto;
  border-radius:6px;
  overflow:hidden;
  background:var(--panel-2);
  vertical-align:middle;
}
/* All three sizes are 7:9 portrait - the same visual family, different scale. */
.wsav--sm{width:28px;height:36px}
.wsav--md{width:34px;height:44px}
.wsav--lg{width:56px;height:72px}

/* Layer 1: the approved fallback, always present underneath. */
.wsav-ph{
  position:absolute;
  inset:0;
  display:flex;
  align-items:center;
  justify-content:center;
  line-height:1;
  opacity:.92;
  color:var(--muted);
  font-size:18px;
}
.wsav-ph::after{content:"\1F464"}          /* person glyph, as a CSS escape */
.wsav--md .wsav-ph{font-size:21px}
.wsav--lg .wsav-ph{font-size:34px}

/* Layer 2: the real crop, revealed only once it decodes (onload sets opacity:1). */
.wsav-img{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  object-fit:cover;                         /* face-oriented crop; never stretch or squash */
  display:block;
  opacity:0;
  transition:opacity .25s;
}

/* The profile header slot keeps its own outer sizing; the frame fills it. */
.ppAv .wsav{width:100%;height:100%;border-radius:inherit}

/* People on Site truncation notice. The ceiling used to be computed and never shown, so the page
   presented a subset as if it were the whole site. Styled as a quiet status line, not an error -
   it is a true statement about scope, not a failure. */
.wfnote{
  margin:10px 2px 2px;
  padding:8px 11px;
  border:1px solid var(--line);
  border-left:3px solid var(--accent);
  border-radius:var(--r-xs,8px);
  background:var(--panel-2);
  color:var(--muted);
}
.wfnote b{color:var(--ink)}

/* ============================================================================================
   NO VISIBLE SCROLLBARS  (user, 2026-09-09)
   Every scrollable surface still scrolls — wheel, trackpad, touch, keyboard, and programmatic
   scrollIntoView all behave exactly as before. Only the painted bar is removed.

   This deliberately reverses the earlier choice at .tablewrap ("slim rather than absent, so it
   stays discoverable", styles.css ~2065). That rule is left in place and simply overridden here,
   so restoring the old behaviour is deleting this block rather than reconstructing it.

   Written last so it wins on order without needing !important, and scoped to the known scroll
   containers rather than `*` — a blanket rule would also strip the bar from anything a future
   page introduces, including surfaces where it is the only affordance.
   ============================================================================================ */
.tablewrap, .astablewrap, .heatwrap, .pdbody, .modalbox, .notifbody,
.colmenu .colmlist, .galcrops, .mbody, .msheet, .tabs, .antabs,
.grid.dash .tablewrap, .grid.dash > .card > .tablewrap, .wfstd, .logstd, .bigtable {
  scrollbar-width: none;          /* Firefox */
  -ms-overflow-style: none;       /* legacy Edge/IE */
}
.tablewrap::-webkit-scrollbar, .astablewrap::-webkit-scrollbar, .heatwrap::-webkit-scrollbar,
.pdbody::-webkit-scrollbar, .modalbox::-webkit-scrollbar, .notifbody::-webkit-scrollbar,
.colmenu .colmlist::-webkit-scrollbar, .galcrops::-webkit-scrollbar, .mbody::-webkit-scrollbar,
.msheet::-webkit-scrollbar, .tabs::-webkit-scrollbar, .antabs::-webkit-scrollbar,
.wfstd::-webkit-scrollbar, .logstd::-webkit-scrollbar, .bigtable::-webkit-scrollbar {
  width: 0;
  height: 0;
  display: none;                  /* Chromium / WebKit */
}

/* ============================================================================================
   ENROLMENT V2  (approved reference, 2026-09-09) — presentation only.
   Scoped entirely to .gen-v2 so nothing here can reach another page. The reference shipped a
   hardcoded dark palette (--bg:#070c17 ...); that is deliberately NOT copied — every colour below
   is a WorkSight token, so the wizard follows light/dark with the rest of the console instead of
   pinning one theme.
   The capture pipeline is untouched: SCRFD + 6DRepNet, five pose zones, burst capture, poseLog and
   POST /api/v1/enroll all behave exactly as before. Only the presentation changed.
   ============================================================================================ */
.gen-modal .gen-v2{
  width:min(1120px,96vw); max-height:92dvh; overflow:auto;
  background:var(--panel); color:var(--ink);
  border:1px solid var(--line); border-radius:var(--r,16px); padding:0;
}
.gen-v2 .genv2-head{
  display:flex; align-items:center; gap:13px; padding:14px 18px;
  border-bottom:1px solid var(--line);
}
.gen-v2 .genv2-logo{
  width:42px;height:42px;border-radius:12px;display:grid;place-items:center;flex:0 0 auto;
  background:linear-gradient(135deg,var(--brand),var(--brand-2));color:#fff;
}
.gen-v2 .genv2-logo svg{width:20px;height:20px}
.gen-v2 .genv2-htxt b{display:block;font-size:18px;letter-spacing:-.01em}
.gen-v2 .genv2-htxt span{display:block;color:var(--muted);font-size:12px;margin-top:2px}
.gen-v2 .genv2-headmeta{margin-left:auto;display:flex;align-items:center;gap:8px}
.gen-v2 .genv2-pill{
  display:inline-flex;align-items:center;gap:7px;border:1px solid var(--line);
  background:var(--panel-2);color:var(--muted);padding:7px 10px;border-radius:999px;
  font-size:11px;font-weight:650;white-space:nowrap;
}
.gen-v2 .genv2-dot{width:7px;height:7px;border-radius:50%;background:var(--faint)}
.gen-v2 .genv2-dot.on{background:var(--ok);box-shadow:0 0 0 4px rgba(47,197,139,.12)}
.gen-v2 .genv2-x{
  width:36px;height:36px;border-radius:11px;border:1px solid var(--line);
  background:var(--panel-2);color:var(--muted);font-size:19px;cursor:pointer;line-height:1;
}
.gen-v2 .genv2-grid{display:grid;grid-template-columns:minmax(0,1.4fr) minmax(300px,.72fr)}
.gen-v2 .genv2-main{padding:18px;min-width:0}
.gen-v2 .genv2-side{padding:18px;border-left:1px solid var(--line);background:var(--panel-2);min-width:0}

.gen-v2 .genv2-progressline{display:flex;gap:8px;align-items:center;margin-bottom:12px}
.gen-v2 .genv2-progressline b{font-size:12px}
.gen-v2 .genv2-progressline span{margin-left:auto;color:var(--muted);font-size:11px;white-space:nowrap}
.gen-v2 .genv2-progressbar{flex:1;height:6px;border-radius:99px;background:var(--panel-3);overflow:hidden}
.gen-v2 .genv2-progressbar i{display:block;width:0;height:100%;border-radius:99px;transition:width .3s;
  background:linear-gradient(90deg,var(--brand),var(--brand-2))}

.gen-v2 .genv2-stage{
  position:relative;aspect-ratio:16/11;border-radius:16px;overflow:hidden;
  border:1px solid var(--line);background:var(--panel-3);
}
.gen-v2 .genv2-vid{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;transform:scaleX(-1)}
.gen-v2 .genv2-statusrow{position:absolute;left:12px;right:12px;top:12px;display:flex;
  justify-content:space-between;gap:8px;z-index:3;pointer-events:none}
.gen-v2 .genv2-badge{
  display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:999px;
  background:rgba(7,13,25,.72);border:1px solid rgba(255,255,255,.12);color:#eff4ff;
  font-size:11px;backdrop-filter:blur(10px);
}
.gen-v2 .genv2-badge.ok{color:#89e6c1;border-color:rgba(47,197,139,.32);background:rgba(47,197,139,.16)}
.gen-v2 .genv2-badge.warn{color:#f6d08a;border-color:rgba(237,169,75,.34);background:rgba(237,169,75,.16)}
.gen-v2 .genv2-faceguide{
  position:absolute;left:50%;top:49%;transform:translate(-50%,-50%);
  /* Sized from the STAGE, not fixed: at phone-landscape the stage is only ~180px tall, and a fixed
     236px guide overflowed it top and bottom -- the oval rendered as two disconnected arcs. The
     cap keeps the desktop size; the percentage keeps the whole oval inside a short stage. */
  height:min(236px,78%);aspect-ratio:184/236;width:auto;border-radius:48% 48% 44% 44%;
  border:2px solid var(--brand);box-shadow:0 0 36px rgba(113,134,255,.18);
  display:grid;place-items:center;z-index:2;
}
.gen-v2 .genv2-faceguide.done{border-color:var(--ok)}
.gen-v2 .genv2-corner{position:absolute;width:26px;height:26px;border-color:var(--brand-2);opacity:.85}
.gen-v2 .c1{left:-5px;top:-5px;border-left:3px solid;border-top:3px solid;border-radius:12px 0 0 0}
.gen-v2 .c2{right:-5px;top:-5px;border-right:3px solid;border-top:3px solid;border-radius:0 12px 0 0}
.gen-v2 .c3{left:-5px;bottom:-5px;border-left:3px solid;border-bottom:3px solid;border-radius:0 0 0 12px}
.gen-v2 .c4{right:-5px;bottom:-5px;border-right:3px solid;border-bottom:3px solid;border-radius:0 0 12px 0}
.gen-v2 .genv2-scanline{position:absolute;left:18%;right:18%;top:50%;height:1px;z-index:1;
  background:linear-gradient(90deg,transparent,var(--brand-2),transparent)}

.gen-v2 .genv2-posecard{
  margin-top:12px;display:grid;grid-template-columns:minmax(0,1fr) auto;gap:14px;align-items:center;
  padding:14px 15px;border:1px solid var(--line);border-radius:14px;background:var(--panel-2);
}
.gen-v2 .genv2-posecard h2{font-size:17px;margin:0 0 3px}
.gen-v2 .genv2-posecard p{margin:0;color:var(--muted);font-size:12px}
.gen-v2 .genv2-count{min-width:76px;text-align:center;padding:9px 10px;border-radius:12px;
  background:var(--panel-3);border:1px solid var(--line)}
.gen-v2 .genv2-count strong{font-size:20px;display:block;line-height:1}
.gen-v2 .genv2-count span{font-size:10px;color:var(--muted)}

.gen-v2 .genv2-metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;margin-top:10px}
.gen-v2 .genv2-metric{padding:9px 10px;border-radius:11px;background:var(--panel-2);border:1px solid var(--line);min-width:0}
.gen-v2 .genv2-metric span{display:block;font-size:9.5px;text-transform:uppercase;letter-spacing:.09em;color:var(--faint)}
.gen-v2 .genv2-metric b{display:block;font-size:13px;margin-top:3px;overflow-wrap:anywhere}
.gen-v2 .genv2-metric.good b{color:var(--ok)}

.gen-v2 .genv2-actions{display:flex;gap:9px;margin-top:14px;flex-wrap:wrap}
.gen-v2 .genv2-btn{border-radius:11px;padding:11px 15px;cursor:pointer;font:inherit;font-weight:700}
.gen-v2 .genv2-btn.primary{border:0;background:linear-gradient(135deg,var(--brand),var(--brand-2));color:#fff}
.gen-v2 .genv2-btn.secondary{border:1px solid var(--line);background:var(--panel-2);color:var(--ink)}
.gen-v2 .genv2-btn.ghost{border:0;background:transparent;color:var(--muted)}
.gen-v2 .genv2-btn:focus-visible{outline:2px solid var(--brand);outline-offset:2px}

.gen-v2 .genv2-sidehead h3{margin:0;font-size:15px}
.gen-v2 .genv2-sidehead p{margin:3px 0 0;color:var(--muted);font-size:11.5px}
.gen-v2 .genv2-coverage{margin-top:14px;display:flex;flex-direction:column;gap:8px}
.gen-v2 .genv2-step{
  display:grid;grid-template-columns:36px minmax(0,1fr) auto;align-items:center;gap:10px;
  padding:9px 10px;border-radius:12px;border:1px solid var(--line);background:var(--panel);
}
.gen-v2 .genv2-step.on{border-color:var(--brand);background:var(--panel-3)}
.gen-v2 .genv2-step.done{border-color:var(--ok)}
.gen-v2 .genv2-n{width:36px;height:36px;border-radius:11px;background:var(--panel-3);
  display:grid;place-items:center;font-weight:800;color:var(--muted)}
.gen-v2 .genv2-step.done .genv2-n{color:var(--ok)}
.gen-v2 .genv2-step b{font-size:12.5px}
.gen-v2 .genv2-step small{display:block;color:var(--muted);font-size:10.5px;margin-top:2px;overflow-wrap:anywhere}
.gen-v2 .genv2-state{font-size:10px;font-weight:750;color:var(--faint)}
.gen-v2 .genv2-step.on .genv2-state{color:var(--brand)}
.gen-v2 .genv2-step.done .genv2-state{color:var(--ok)}

/* Captured previews keep the console-wide 7:9 passport frame. */
.gen-v2 .genv2-strip{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:6px;margin-top:12px}
.gen-v2 .genv2-strip .gen-dot{
  aspect-ratio:7/9;width:auto;height:auto;border-radius:9px;overflow:hidden;
  background:var(--panel-3);border:1px solid var(--line);display:grid;place-items:center;position:relative;
}
.gen-v2 .genv2-strip .gen-dot.cur{border-color:var(--brand)}
.gen-v2 .genv2-strip .gen-dot.done{border-color:var(--ok)}
.gen-v2 .genv2-strip .gen-dot img{width:100%;height:100%;object-fit:cover;display:block}

.gen-v2 .genv2-identity{margin-top:16px;padding-top:15px;border-top:1px solid var(--line)}
.gen-v2 .genv2-sectionTitle{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:10px}
.gen-v2 .genv2-sectionTitle b{font-size:12px}
.gen-v2 .genv2-sectionTitle span{font-size:10px;color:var(--muted)}
.gen-v2 .genv2-identity label{display:block;color:var(--faint);font-size:9.5px;
  text-transform:uppercase;letter-spacing:.1em;margin:0 0 5px}
.gen-v2 .genv2-input{
  width:100%;padding:11px 12px;border-radius:11px;border:1px solid var(--line);
  background:var(--panel-3);color:var(--ink);margin-bottom:9px;outline:none;font:inherit;
}
.gen-v2 .genv2-input:focus{border-color:var(--brand);box-shadow:0 0 0 3px rgba(113,134,255,.12)}
.gen-v2 .genv2-save{width:100%;margin-top:3px}
.gen-v2 .genv2-helper{margin-top:10px;color:var(--muted);font-size:10.5px;padding:10px 11px;
  border:1px dashed var(--line);border-radius:11px}
.gen-v2 .gen-status{margin-top:10px;font-size:11.5px}

@media(max-width:900px){
  .gen-modal .gen-v2{width:100vw;max-height:100dvh;border-radius:0}
  .gen-v2 .genv2-grid{grid-template-columns:1fr}
  .gen-v2 .genv2-side{border-left:0;border-top:1px solid var(--line)}
  .gen-v2 .genv2-stage{aspect-ratio:4/3}
  .gen-v2 .genv2-metrics{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media(max-width:560px){
  .gen-v2 .genv2-head{padding:12px}
  .gen-v2 .genv2-headmeta .genv2-pill{display:none}
  .gen-v2 .genv2-main,.gen-v2 .genv2-side{padding:12px}
  .gen-v2 .genv2-stage{aspect-ratio:3/4}
  .gen-v2 .genv2-faceguide{height:min(198px,78%)}
  .gen-v2 .genv2-posecard{grid-template-columns:1fr}
  .gen-v2 .genv2-count{width:100%}
  .gen-v2 .genv2-actions{display:grid;grid-template-columns:1fr 1fr}
  .gen-v2 .genv2-btn.ghost{grid-column:1/-1}
}
/* Phone landscape: the wizard must never hide Enrol/Cancel behind the fold. */
@media(max-height:460px) and (orientation:landscape){
  .gen-modal .gen-v2{max-height:100dvh}
  .gen-v2 .genv2-stage{aspect-ratio:auto;height:min(46vh,240px)}
  .gen-v2 .genv2-metrics{grid-template-columns:repeat(4,minmax(0,1fr))}
}

/* ---------- touch targets ----------
   The floor / metric chips (Ground floor, Top floor, Occupancy, Dwell, Transitions, Heatmap)
   render 92x23 and 71x23 on a phone. 23px is below any usable touch minimum and they are the
   primary control on the mobile dashboard. Raised only where the pointer is COARSE, so the
   deliberate desktop density is untouched -- a width breakpoint would also catch a narrow
   desktop window, which is a mouse target and does not need the extra height. */
@media (pointer: coarse){
  .chip{min-height:36px;padding-top:8px;padding-bottom:8px}
  .chips{gap:8px}
}

/* ---------- mobile Detection Log ----------
   Compact event cards, not a squeezed desktop table. One card = one detection event, and the only
   time on it is the capture time. */
.mdet-filters{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px}
.mdet-filters #mdetQ{grid-column:1/-1}
.mdet-filters input,.mdet-filters select{width:100%;min-height:38px;padding:8px 10px;border-radius:10px;
  border:1px solid var(--line);background:var(--panel-2);color:var(--ink);font:inherit;font-size:13px}
.mdet-filters .btn{grid-column:1/-1;min-height:38px}
.mdet-note{font-size:11.5px;color:var(--muted);margin:2px 0 10px}
.mdet-note.live::before{content:"";display:inline-block;width:7px;height:7px;border-radius:50%;
  background:var(--ok);margin-right:6px;vertical-align:middle}
.mdet-list{display:flex;flex-direction:column;gap:8px}
.mdet-card{display:grid;grid-template-columns:auto minmax(0,1fr);gap:10px;align-items:center;
  padding:9px 10px;border:1px solid var(--line);border-radius:12px;background:var(--panel-2)}
.mdet-av{width:42px;height:54px;flex:none}                 /* 7:9 portrait, same family as everywhere */
.mdet-av .wsav{width:42px;height:54px}
.mdet-main{min-width:0}
.mdet-name{font-weight:700;font-size:13.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mdet-time{font-size:12px;color:var(--ink);opacity:.9;margin-top:1px}
.mdet-meta{display:flex;gap:8px;margin-top:3px;font-size:11.5px;color:var(--muted);flex-wrap:wrap}
.mdet-meta span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:48%}
.mdet-more{width:100%;margin-top:10px;min-height:40px}

/* ---------- mobile Movement History ----------
   The REAL engine, laid out for a phone: map first and given real height, controls compact under
   it. In landscape the stage takes the height it can get so the replay stays primary. */
.mscreen .mh-app{display:flex;flex-direction:column;gap:8px}
.mscreen .mh-bar{display:flex;flex-wrap:wrap;gap:8px;align-items:center;font-size:12px}
.mscreen .mh-bar .mh-date{min-height:38px;flex:1 1 140px}
.mscreen .mh-bar .mh-back{min-height:38px}
.mscreen .mh-status{flex:1 1 100%;color:var(--muted);font-size:11.5px}
.mscreen .mh-stage{width:100%;height:min(58vh,420px);min-height:260px;border-radius:12px;overflow:hidden}
@media (orientation:landscape) and (max-height:520px){
  .mscreen .mh-stage{height:min(74vh,300px);min-height:180px}
  .mscreen .mh-bar{font-size:11.5px}
}

/* ---------- Movement Tracker: map + zone-entry cards ----------
   Card presentation per the approved reference: image, person, zone word, time, status - in that
   hierarchy - on a light surface using WorkSight tokens. The first pass rendered a dense log-style
   row; this is deliberately not that. */
.mh-split{display:grid;grid-template-columns:minmax(0,1fr) 340px;gap:14px;align-items:stretch}
.mh-split > .mh-stage{min-width:0}
.mh-bar .mh-person{min-height:34px;padding:6px 10px;border-radius:10px;border:1px solid var(--line);
  background:var(--panel-2);color:var(--ink);font:inherit;font-size:13px;max-width:220px}
/* The panel STRETCHES to the map's height; it must never set the row height itself. Measured with
   max-height:660px it was doing exactly that: at 1440x900 the row was 660 while the map was 558,
   and at 1366x768 it forced 159+660=819 into a 768 viewport, which was the 97px of page scroll. */
/* The panel gets the SAME definite height as the map, so neither dictates the other. height:100%
   was wrong: in a content-sized grid row it resolved against nothing, the row grew to fit all 55
   cards, and the page picked up ~3,600px of scroll. A definite height keeps the row at the map's
   height and leaves the list to scroll inside it. */
.mt-panel{border:1px solid var(--line);border-radius:14px;background:var(--panel);
  display:flex;flex-direction:column;height:clamp(360px,calc(100dvh - 190px),900px);
  min-height:0;overflow:hidden}
.mt-head{display:flex;align-items:flex-start;gap:10px;padding:13px 14px;border-bottom:1px solid var(--line)}
.mt-htxt{min-width:0}
.mt-head h3{margin:0;font-size:15px;line-height:1.2}
.mt-hsub{margin:2px 0 0;font-size:11.5px;color:var(--muted)}
.mt-count{margin-left:auto;font-size:11px;color:var(--faint);white-space:nowrap;padding-top:2px}

/* single-person: the face and name are stated once here, not repeated on every row */
.mt-person{display:flex;align-items:center;gap:10px;padding:10px 14px;border-bottom:1px solid var(--line);
  background:var(--panel-2)}
.mt-pmeta{min-width:0;display:flex;flex-direction:column}
.mt-pmeta b{font-size:13.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mt-pmeta span{font-size:11px;color:var(--muted)}
.mt-clear{margin-left:auto;border:1px solid var(--line);background:var(--panel);color:var(--ink);
  border-radius:9px;padding:6px 10px;font:inherit;font-size:11.5px;cursor:pointer;white-space:nowrap}
.mt-clear:hover{background:var(--panel-3)}

.mt-list{overflow-y:auto;min-height:0;padding:10px;display:flex;flex-direction:column;gap:8px}
.mt-card{display:grid;grid-template-columns:auto minmax(0,1fr) auto;gap:11px;align-items:center;
  padding:10px 11px;border:1px solid var(--line);border-radius:12px;background:var(--panel-2);
  cursor:pointer;transition:background .15s,border-color .15s}
.mt-card:hover{background:var(--panel-3);border-color:var(--brand)}
.mt-card.is-current{border-color:color-mix(in srgb,var(--ok) 42%,var(--line));background:var(--ok-bg)}
.mt-card--solo{grid-template-columns:minmax(0,1fr) auto}
.mt-c-main{min-width:0}
.mt-c-name{font-size:13.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mt-c-zone{font-size:12px;color:var(--muted);margin-top:2px;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
.mt-card--solo .mt-c-time{font-size:13.5px;font-weight:700}
.mt-card--solo .mt-c-zone{font-size:12.5px;margin-top:3px}
.mt-c-side{text-align:right;display:flex;flex-direction:column;align-items:flex-end;gap:3px}
.mt-c-time{font-size:12px;color:var(--ink);white-space:nowrap}
.mt-age{font-size:11px;color:var(--muted);white-space:nowrap}
.mt-pill{font-size:10.5px;font-weight:700;color:var(--ok);background:var(--ok-bg);
  border:1px solid color-mix(in srgb,var(--ok) 34%,var(--line));border-radius:999px;padding:2px 8px;
  white-space:nowrap}
/* 1:1 passport square, 46px. The 7:9 portrait standard elsewhere is unchanged. */
.mt-av{position:relative;width:46px;height:46px;flex:none;border-radius:11px;overflow:hidden;
  background:var(--panel-3);display:grid;place-items:center}
.mt-av-ph{font-size:21px;line-height:1;opacity:.72}
.mt-av img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .2s}
.mt-more{font-style:normal;font-size:10.5px;color:var(--faint);margin-left:7px;white-space:nowrap}
.mt-empty{padding:20px 14px;color:var(--muted);font-size:12.5px;text-align:center}

@media(max-width:1100px){
  .mh-split{grid-template-columns:1fr}
  .mt-panel{height:auto;max-height:430px}     /* stacked: the panel sizes itself again */
}
/* The engine restacks its playback bar into several rows below 860px, which took 162px of a phone
   screen and left the map under half the viewport. One scrollable row keeps every control (nothing
   is removed - the row scrolls horizontally) and hands the height back to the map. */
#mhStage .vx-replay__bar{grid-auto-flow:column;
  grid-template-columns:auto auto minmax(0,1fr) auto auto;gap:8px;padding:6px 9px;overflow-x:auto}
#mhStage .vx-replay__seg,#mhStage .vx-replay__toggles{grid-column:auto;justify-self:end}
.mscreen .mh-split{grid-template-columns:1fr;gap:10px}
.mscreen .mt-panel{height:auto;max-height:46vh}
.mscreen .mh-bar .mh-person{max-width:none;flex:1 1 150px}

/* ---------- Movement Tracker: MOBILE LANDSCAPE ----------
   An approved production layout, not a fallback. Portrait stacks; landscape must not, because a
   ~390px-tall viewport stacked vertically leaves the map a strip. Two panes instead: the map keeps
   ~70% and stays the dominant surface, the entry list takes ~30% and scrolls internally, and the
   control row is compressed to a single line so nothing core drops below the fold. Sized with dvh
   so the mobile browser's collapsing toolbars cannot clip the controls. */
@media (orientation:landscape) and (max-height:520px){
  .mscreen .msheet{max-height:100dvh;height:100dvh;border-radius:0;padding-top:4px}
  .mscreen .msheet > h4{font-size:14px;margin:2px 0 4px}
  .mscreen .mgrab{display:none}                      /* the grab handle costs height we need */

  .mscreen .mh-app{gap:6px;height:calc(100dvh - 54px)}
  .mscreen .mh-bar{flex-wrap:nowrap;gap:6px;font-size:11px;overflow-x:auto;padding-bottom:2px}
  .mscreen .mh-bar > *{flex:0 0 auto}
  .mscreen .mh-bar .mh-lbl{display:none}             /* the date input is self-evident */
  .mscreen .mh-bar .mh-date,
  .mscreen .mh-bar .mh-person{min-height:32px;font-size:12px;max-width:150px}
  .mscreen .mh-status{flex:1 1 auto;min-width:0;white-space:nowrap;overflow:hidden;
    text-overflow:ellipsis;font-size:10.5px}

  /* TWO PANES. Explicitly overrides the portrait single-column rule above. */
  .mscreen .mh-split{grid-template-columns:minmax(0,70fr) minmax(0,30fr);gap:8px;
    height:100%;align-items:stretch}
  .mscreen .mh-split > .mh-stage{height:100%;min-height:0}
  .mscreen .mt-panel{max-height:none;height:calc(100dvh - 96px);min-height:0}
  .mscreen .mt-head{padding:7px 9px}
  .mscreen .mt-head h3{font-size:12.5px}
  .mscreen .mt-sub{display:none}                     /* the count is secondary at this width */
  .mscreen .mt-list{padding:5px;gap:4px}
  .mscreen .mt-row{padding:5px 6px;gap:7px}
  .mscreen .mt-av{width:40px;height:40px}            /* stays 1:1 and stays legible */
  /* The engine restacks its playback bar into rows below 860px wide, which on a 390px-tall screen
     consumed 162px and left the map only 160px. Keep it on one scrollable row in landscape. */
  #mhStage .vx-replay__bar{grid-auto-flow:column;grid-template-columns:auto auto minmax(0,1fr) auto auto;
    gap:8px;padding:5px 8px;overflow-x:auto}
  #mhStage .vx-replay__seg,#mhStage .vx-replay__toggles{grid-column:auto;justify-self:end}
  #mhStage .vx-replay__play{width:32px;height:32px}
  .mscreen .mt-l1 b{font-size:12px}
  .mscreen .mt-time,.mscreen .mt-l2{font-size:10.5px}
}

/* ---------- Movement Tracker: ONE contextual side panel ----------
   The replay engine ships its own right-hand column (People list + Rooms/visits). With the Movement
   Tracker's Zone Entries panel added beside it, the page carried THREE side columns showing
   overlapping context - map + People + Rooms + Zone Entries - which is not the approved layout.
   The engine's column is removed here and its two jobs are re-homed rather than dropped:
     person selection      -> the top "All People / person" selector (#mhPerson)
     per-person room history -> the right-hand Zone Entries panel
   Scoped to #mhStage so the engine keeps its own chrome anywhere else it is mounted. The engine's
   grid is `minmax(0,1fr) 232px`; collapsing it to one column hands that 232px + 12px gap back to
   the map, which is the dominant surface. The control bar already spans 1/-1, so it is unaffected. */
#mhStage.vx-replay{grid-template-columns:minmax(0,1fr)}
#mhStage .vx-replay__panel{display:none}
