/* =====================================================================================
   ui-v3.css — VisionHR redesign design system, SCOPED to `.v3`
   Generated from visionhr_redesign_reference.html by pt_make_uiv3.py. Do not hand-edit:
   regenerate, so the reference stays the single source of truth.

   LOADED AFTER styles.css. Every rule is prefixed with `.v3`, so it applies only inside a
   renderer that has been ported (`<div class="v3">…`). The reference stylesheet is NOT
   namespaced — 63 of its class names and 25 of its custom properties collide with live
   styles.css at different values (.app, .side, .nav, .sm, :focus-visible, --accent, --r).
   Appending it verbatim, as the handoff suggests, would restyle the whole console at once.

   Custom properties are redeclared on `.v3` itself; because they inherit, the reference
   palette applies throughout a ported subtree and nowhere else.

   NOT PORTED: section 3 (APP SHELL). The sidebar and top bar sit outside the page root,
   already carry the agreed 10-destination IA, and the live versions are richer.
   ===================================================================================== */

.v3{
  --brand:#4f63f6; --brand-2:#6d84ff; --accent:#12b5d6;
  --ok:#12a670; --ok-bg:rgba(18,166,112,.12);
  --warn:#d9922a; --warn-bg:rgba(217,146,42,.13);
  --bad:#e05265; --bad-bg:rgba(224,82,101,.12);
  --info:#4f63f6; --info-bg:rgba(79,99,246,.10);

  --bg:#f3f5f9; --panel:#ffffff; --panel-2:#f7f9fc; --panel-3:#eef2f9;
  --ink:#141d34; --ink-2:#3b475f; --muted:#69748c; --faint:#9aa5bb;
  --line:rgba(20,29,52,.08); --line-strong:rgba(20,29,52,.15);
  --surface-hover:rgba(20,29,52,.035); --surface-selected:rgba(79,99,246,.08);
  --nav:#eef2f9; --nav-ink:#3b475f;

  --shadow-card:0 1px 2px rgba(20,29,52,.04);
  --shadow-hover:0 6px 18px rgba(20,29,52,.08),0 1px 3px rgba(20,29,52,.05);
  --shadow-float:0 18px 48px rgba(20,29,52,.16),0 2px 8px rgba(20,29,52,.06);
  --focus-ring:0 0 0 3px rgba(79,99,246,.28);

  --r:14px; --r-sm:10px; --r-xs:8px;
  --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px; --sp-5:20px; --sp-6:24px; --sp-8:32px; --sp-10:40px;
  --motion-fast:140ms; --motion-base:200ms; --motion-slow:280ms;
  --ease:cubic-bezier(.2,.8,.2,1);
  --font:Inter,system-ui,-apple-system,"Segoe UI",sans-serif;
  --font-display:"Space Grotesk",Inter,system-ui,sans-serif;
  --font-mono:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
}[data-theme=dark] .v3{
  --brand:#6d84ff; --brand-2:#8398ff; --accent:#2fc6e6;
  --ok:#2ec48a; --ok-bg:rgba(46,196,138,.14); --warn:#e6ab4d; --warn-bg:rgba(230,171,77,.14);
  --bad:#f06b7e; --bad-bg:rgba(240,107,126,.14); --info:#7d92ff; --info-bg:rgba(125,146,255,.14);
  --bg:#0c1220; --panel:#131b2e; --panel-2:#182238; --panel-3:#1d2a44;
  --ink:#e9eef9; --ink-2:#b7c2d8; --muted:#8b98b3; --faint:#5e6b86;
  --line:rgba(255,255,255,.07); --line-strong:rgba(255,255,255,.14);
  --surface-hover:rgba(255,255,255,.04); --surface-selected:rgba(125,146,255,.12);
  --nav:#080d19; --nav-ink:#aeb9d2;
  --shadow-card:0 1px 2px rgba(0,0,0,.25);
  --shadow-hover:0 8px 22px rgba(0,0,0,.35),0 1px 3px rgba(0,0,0,.3);
  --shadow-float:0 22px 56px rgba(0,0,0,.55),0 2px 8px rgba(0,0,0,.4);
  --focus-ring:0 0 0 3px rgba(125,146,255,.35);
}
@media (prefers-reduced-motion:reduce){ *,*::before,*::after{ animation:none!important; transition-duration:0s!important; } }.v3 html, .v3{height:100%}.v3{margin:0;background:var(--bg);color:var(--ink);font:400 14px/1.5 var(--font);-webkit-font-smoothing:antialiased}.v3 h1, .v3 h2, .v3 h3, .v3 h4{font-family:var(--font-display);letter-spacing:-.015em;margin:0;font-weight:600}.v3 button, .v3 input, .v3 select{font:inherit;color:inherit}.v3 button{cursor:pointer}.v3 :focus-visible{outline:none;box-shadow:var(--focus-ring)}.v3 .mono{font-family:var(--font-mono);font-variant-numeric:tabular-nums}.v3 .muted{color:var(--muted)}.v3 .faint{color:var(--faint)}.v3 .sm{font-size:12px}.v3 .num{font-family:var(--font-display);font-variant-numeric:tabular-nums;font-weight:600}/* Page header */
.v3 .pagehead{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;margin:0 0 20px;flex-wrap:wrap}.v3 .pagehead h2{font-size:22px;line-height:1.15}.v3 .pagehead .sub{color:var(--muted);font-size:13px;margin-top:4px}.v3 .pagehead .tools{display:flex;align-items:center;gap:8px;flex-wrap:wrap}/* Buttons */
.v3 .btn{display:inline-flex;align-items:center;gap:7px;height:34px;padding:0 13px;border-radius:var(--r-xs);border:1px solid var(--line-strong);background:var(--panel);font-weight:500;font-size:13px;
  transition:background var(--motion-fast) var(--ease),border-color var(--motion-fast) var(--ease),transform var(--motion-fast) var(--ease)}.v3 .btn svg{width:15px;height:15px}.v3 .btn:hover{background:var(--surface-hover)}.v3 .btn:active{transform:translateY(1px)}.v3 .btn.primary{background:var(--brand);border-color:var(--brand);color:#fff}.v3 .btn.primary:hover{filter:brightness(1.06)}.v3 .btn.ghost{border-color:transparent;background:transparent}.v3 .btn.danger{color:var(--bad);border-color:transparent;background:transparent}.v3 .btn.danger:hover{background:var(--bad-bg)}.v3 .btn.sm{height:28px;padding:0 10px;font-size:12px}.v3 .iconbtn{width:34px;height:34px;border-radius:var(--r-xs);border:1px solid var(--line);background:var(--panel);display:grid;place-items:center;color:var(--ink-2)}.v3 .iconbtn svg{width:16px;height:16px}.v3 .link{background:none;border:0;color:var(--brand);font-weight:500;font-size:12.5px;padding:4px 6px;border-radius:6px}.v3 .link:hover{background:var(--surface-selected)}/* Status pill  (dot + text, never color alone) */
.v3 .pill{display:inline-flex;align-items:center;gap:6px;padding:3px 10px;border-radius:999px;font-size:12px;font-weight:500;line-height:1.6;background:var(--panel-2);color:var(--ink-2);border:1px solid var(--line);white-space:nowrap}.v3 .pill .dot{width:6px;height:6px;border-radius:50%;background:var(--muted)}.v3 .pill.ok{color:var(--ok);background:var(--ok-bg);border-color:transparent}.v3 .pill.ok .dot{background:var(--ok)}.v3 .pill.warn{color:var(--warn);background:var(--warn-bg);border-color:transparent}.v3 .pill.warn .dot{background:var(--warn)}.v3 .pill.bad{color:var(--bad);background:var(--bad-bg);border-color:transparent}.v3 .pill.bad .dot{background:var(--bad)}.v3 .pill.info{color:var(--brand);background:var(--info-bg);border-color:transparent}.v3 .pill.info .dot{background:var(--brand)}/* Filter chips */
.v3 .chips{display:flex;gap:6px;flex-wrap:wrap}.v3 .chip{border:1px solid var(--line);background:var(--panel);color:var(--ink-2);border-radius:999px;padding:6px 13px;font-size:12.5px;font-weight:500;line-height:1.3;
  transition:background var(--motion-fast) var(--ease),border-color var(--motion-fast) var(--ease),color var(--motion-fast) var(--ease)}.v3 .chip:hover{background:var(--surface-hover);border-color:var(--line-strong)}.v3 .chip.on{background:var(--surface-selected);border-color:rgba(79,99,246,.35);color:var(--brand);font-weight:600}.v3 .chip .n{opacity:.6;margin-left:4px;font-weight:500}/* Search + select */
.v3 .search{display:flex;align-items:center;gap:8px;background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:0 12px;height:36px;min-width:240px;transition:border-color var(--motion-fast) var(--ease),box-shadow var(--motion-fast) var(--ease)}.v3 .search:focus-within{border-color:rgba(79,99,246,.5);box-shadow:var(--focus-ring)}.v3 .search svg{width:15px;height:15px;color:var(--muted)}.v3 .search input{border:0;background:none;outline:none;flex:1;font-size:13px}.v3 .select{height:36px;border:1px solid var(--line);background:var(--panel);border-radius:10px;padding:0 10px;font-size:13px}.v3 .toolbar{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:14px}.v3 .toolbar .spacer{flex:1}/* Cards: hero / primary / support / interactive */
.v3 .card{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow-card);padding:18px 20px;
  transition:border-color var(--motion-fast) var(--ease),box-shadow var(--motion-fast) var(--ease),transform var(--motion-fast) var(--ease)}.v3 .card.support{background:var(--panel-2);border-color:transparent;box-shadow:none}.v3 .card.interactive{cursor:pointer}.v3 .card.interactive:hover{transform:translateY(-2px);box-shadow:var(--shadow-hover);border-color:var(--line-strong)}.v3 .card .head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:14px}.v3 .card .head h3{font-size:14.5px}.v3 .card .head small{display:block;margin-top:2px;font-size:12px;color:var(--muted)}.v3 .grid{display:grid;grid-template-columns:repeat(12,1fr);gap:16px}.v3 .s12{grid-column:span 12}.v3 .s8{grid-column:span 8}.v3 .s7{grid-column:span 7}.v3 .s6{grid-column:span 6}.v3 .s5{grid-column:span 5}.v3 .s4{grid-column:span 4}.v3 .s3{grid-column:span 3}.v3 .span2{grid-row:span 2}.v3 .context{display:flex;align-items:center;gap:22px;flex-wrap:wrap;padding:12px 18px;background:var(--panel-2);border-radius:var(--r-sm);margin-bottom:16px;font-size:13px}.v3 .context .c{display:flex;align-items:center;gap:8px;color:var(--ink-2)}.v3 .context .c span{color:var(--muted)}.v3 .context .c b{font-weight:600}.v3 .context .c svg{width:15px;height:15px;color:var(--brand)}/* KPI */
.v3 .kpis{display:grid;grid-template-columns:repeat(5,1fr);gap:14px;margin-bottom:18px}.v3 .kpi{position:relative;display:flex;flex-direction:column;gap:6px;background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow-card);padding:16px 18px 14px;min-height:110px;text-align:left;
  transition:border-color var(--motion-fast) var(--ease),box-shadow var(--motion-fast) var(--ease),transform var(--motion-fast) var(--ease),background var(--motion-slow) var(--ease)}.v3 .kpi.go{cursor:pointer}.v3 .kpi.go:hover{transform:translateY(-2px);box-shadow:var(--shadow-hover);border-color:var(--line-strong)}.v3 .kpi .k-label{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--muted);font-weight:500}.v3 .kpi .k-label svg{width:15px;height:15px;color:var(--brand)}.v3 .kpi .k-value{font-family:var(--font-display);font-size:30px;font-weight:600;letter-spacing:-.03em;line-height:1.05;display:flex;align-items:baseline;gap:6px}.v3 .kpi .k-value small{font-size:15px;font-weight:500;color:var(--muted);letter-spacing:0}.v3 .kpi .k-sub{font-size:12px;color:var(--muted);line-height:1.35}.v3 .kpi .k-status{position:absolute;top:16px;right:16px}.v3 .kpi.is-attn{border-color:rgba(217,146,42,.45)}.v3 .kpi.is-bad{border-color:rgba(224,82,101,.45)}.v3 .kpi.changed{background:var(--surface-selected)}/* add for 900ms on real value change */
.v3 .kpi .k-value.swap{animation:v3-num var(--motion-base) var(--ease)}
@keyframes v3-num{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}/* Lists (attention, area activity, previews) */
.v3 .list{display:flex;flex-direction:column}.v3 .row{display:flex;align-items:center;gap:12px;padding:10px 6px;border-bottom:1px solid var(--line);cursor:pointer;border-radius:8px;transition:background var(--motion-fast) var(--ease)}.v3 .row:last-child{border-bottom:0}.v3 .row:hover{background:var(--surface-hover)}.v3 .row .grow{flex:1;min-width:0}.v3 .row .t{font-weight:500;font-size:13.5px;line-height:1.3}.v3 .row .d{font-size:12px;color:var(--muted);margin-top:1px}.v3 .row .r{text-align:right;flex-shrink:0}.v3 .chev{width:16px;height:16px;color:var(--faint);flex-shrink:0;transition:transform var(--motion-fast) var(--ease),color var(--motion-fast) var(--ease)}.v3 .row:hover .chev, .v3 tr:hover .chev{color:var(--brand);transform:translateX(2px)}.v3 .sev{width:8px;height:8px;border-radius:50%;flex-shrink:0;background:var(--muted)}.v3 .sev.warn{background:var(--warn)}.v3 .sev.bad{background:var(--bad)}.v3 .sev.ok{background:var(--ok)}.v3 .sev.info{background:var(--brand)}.v3 .bar{height:6px;border-radius:999px;background:var(--panel-3);overflow:hidden;margin-top:6px}.v3 .bar i{display:block;height:100%;border-radius:999px;background:var(--brand);opacity:.85;transition:width var(--motion-slow) var(--ease)}.v3 .bar i.warn{background:var(--warn)}.v3 .bar i.bad{background:var(--bad)}.v3 .bar i.muted{background:var(--faint)}/* Person cell */
.v3 .person{display:flex;align-items:center;gap:12px;min-width:0}.v3 .av{width:36px;height:36px;border-radius:10px;font-size:12px;font-weight:600;display:grid;place-items:center;color:#fff;flex-shrink:0;font-family:var(--font-display)}.v3 .av.lg{width:64px;height:64px;border-radius:16px;font-size:20px}.v3 .av.unreg{background:var(--panel-3);color:var(--muted);border:1px dashed var(--line-strong)}.v3 .person .name{font-weight:600;font-size:13.5px;line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.v3 .person .meta{font-size:12px;color:var(--muted);margin-top:1px}/* Tables */
.v3 .tablewrap{overflow-x:auto}.v3 table{width:100%;border-collapse:separate;border-spacing:0}.v3 thead th{font-size:11px;letter-spacing:.04em;text-transform:uppercase;color:var(--muted);font-weight:600;padding:10px 12px;border-bottom:1px solid var(--line);white-space:nowrap;text-align:left}.v3 tbody td{padding:12px;border-bottom:1px solid var(--line);vertical-align:middle}.v3 tbody tr{transition:background var(--motion-fast) var(--ease)}.v3 tbody tr.click{cursor:pointer}.v3 tbody tr.click:hover{background:var(--surface-hover)}.v3 tbody tr.selected{background:var(--surface-selected)}.v3 th:first-child, .v3 td:first-child{padding-left:4px}.v3 td.chevcell{width:28px;text-align:right;padding-right:14px}.v3 .cell2 .a{font-weight:500}.v3 .cell2 .b{font-size:12px;color:var(--muted);margin-top:2px}
@media (max-width:1280px){.v3 .resp{display:none}}/* Empty / skeleton / error */
.v3 .empty{padding:28px 16px;text-align:center;color:var(--muted);font-size:13px;line-height:1.5}.v3 .empty b{display:block;color:var(--ink);font-weight:600;margin-bottom:3px}.v3 .sk{background:linear-gradient(90deg,var(--panel-3) 25%,var(--panel-2) 50%,var(--panel-3) 75%);background-size:400% 100%;border-radius:6px;animation:v3-sk 1.4s ease infinite}
@keyframes v3-sk{from{background-position:100% 0}to{background-position:0 0}}.v3 .errbox{display:flex;align-items:center;gap:14px;padding:14px 16px;border-radius:var(--r-sm);background:var(--warn-bg);color:var(--ink)}.v3 .errbox b{display:block;font-weight:600}.v3 .errbox .sm{color:var(--muted)}/* Tabs */
.v3 .tabs{display:flex;gap:2px;border-bottom:1px solid var(--line);margin-bottom:16px}.v3 .tab{background:none;border:0;padding:10px 14px;color:var(--muted);font-weight:500;position:relative;border-radius:8px 8px 0 0}.v3 .tab:hover{color:var(--ink);background:var(--surface-hover)}.v3 .tab.on{color:var(--brand);font-weight:600}.v3 .tab.on::after{content:"";position:absolute;left:8px;right:8px;bottom:-1px;height:2px;background:var(--brand);border-radius:2px}/* Drawer / panel */
.v3 .scrim{position:fixed;inset:0;z-index:80;background:rgba(10,15,29,.35);opacity:0;pointer-events:none;transition:opacity var(--motion-base) var(--ease)}.v3 .scrim.open{opacity:1;pointer-events:auto}.v3 .drawer{position:fixed;top:0;right:0;width:min(480px,94vw);height:100dvh;z-index:90;background:var(--panel);border-left:1px solid var(--line);box-shadow:var(--shadow-float);transform:translateX(105%);transition:transform var(--motion-slow) var(--ease);display:flex;flex-direction:column}.v3 .drawer.open{transform:none}.v3 .drawer .dh{display:flex;align-items:flex-start;gap:14px;padding:20px 22px 16px;border-bottom:1px solid var(--line)}.v3 .drawer .dh h3{font-size:18px}.v3 .drawer .dh .sub{color:var(--muted);font-size:12.5px;margin-top:2px}.v3 .drawer .db{padding:18px 22px 28px;overflow:auto;flex:1}.v3 .drawer .x{margin-left:auto}.v3 .section{font-size:11.5px;letter-spacing:.05em;text-transform:uppercase;color:var(--muted);font-weight:600;margin:20px 0 8px}.v3 .section:first-child{margin-top:0}.v3 .stat{display:flex;justify-content:space-between;align-items:baseline;padding:9px 0;border-bottom:1px solid var(--line);font-size:13.5px}.v3 .stat span{color:var(--muted)}.v3 .stat b{font-weight:600}.v3 .acct{margin:6px 0 4px}.v3 .acct .l{display:flex;justify-content:space-between;font-size:13px;margin-top:10px}.v3 .acct .l b{font-weight:600}.v3 .acct .l:first-child{margin-top:0}.v3 .timeline{list-style:none;margin:0;padding:0 0 0 18px;border-left:2px solid var(--line);position:relative}.v3 .timeline li{position:relative;padding:0 0 16px 14px}.v3 .timeline li::before{content:"";position:absolute;left:-24px;top:4px;width:10px;height:10px;border-radius:50%;background:var(--panel);border:2px solid var(--brand)}.v3 .timeline li.current::before{background:var(--brand)}.v3 .timeline .tm{font-family:var(--font-mono);font-size:12px;color:var(--muted)}.v3 .timeline .pl{font-weight:600;font-size:13.5px}.v3 .timeline .du{font-size:12px;color:var(--muted)}.v3 .evidence{display:flex;gap:8px;overflow-x:auto;padding-bottom:4px}.v3 .evidence .ev{flex:0 0 96px;height:72px;border-radius:8px;background:var(--panel-3);position:relative;overflow:hidden}.v3 .evidence .ev span{position:absolute;left:6px;bottom:4px;font-size:10.5px;color:var(--muted);font-family:var(--font-mono)}.v3 details.adv{margin-top:18px;border-top:1px solid var(--line);padding-top:10px}.v3 details.adv summary{cursor:pointer;font-weight:500;color:var(--muted);font-size:13px}.v3 details.adv .kv{display:grid;grid-template-columns:140px 1fr;gap:6px 12px;font-size:12.5px;margin-top:10px}.v3 details.adv .kv span{color:var(--muted)}.v3 details.adv .kv code{font-family:var(--font-mono);font-size:12px}.v3 .actions{display:flex;gap:8px;margin-top:16px;flex-wrap:wrap}/* Alert item */
.v3 .alert{display:grid;grid-template-columns:14px 1.4fr 1fr 1fr 90px auto;gap:14px;align-items:center;padding:12px 8px;border-bottom:1px solid var(--line);border-radius:8px;transition:background var(--motion-fast) var(--ease);cursor:pointer;border-left:3px solid transparent}.v3 .alert:hover{background:var(--surface-hover)}.v3 .alert.bad{border-left-color:var(--bad)}.v3 .alert.warn{border-left-color:var(--warn)}.v3 .alert .what{font-weight:600}.v3 .alert .why{font-size:12px;color:var(--muted)}/* Camera tile */
.v3 .cams{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}.v3 .cam{position:relative;aspect-ratio:16/9;border-radius:12px;overflow:hidden;background:#0b1020;border:1px solid var(--line);transition:border-color var(--motion-fast) var(--ease)}.v3 .cam:hover{border-color:var(--line-strong)}.v3 .cam .ov{position:absolute;left:0;right:0;padding:10px 12px;display:flex;align-items:center;gap:8px;color:#fff;font-size:12.5px;background:linear-gradient(180deg,rgba(0,0,0,.55),transparent);opacity:.85;transition:opacity var(--motion-fast) var(--ease)}.v3 .cam .ov.bot{bottom:0;top:auto;background:linear-gradient(0deg,rgba(0,0,0,.55),transparent);opacity:0;justify-content:flex-end}.v3 .cam:hover .ov.bot, .v3 .cam:focus-within .ov.bot{opacity:1}.v3 .cam .ov b{font-weight:600}.v3 .cam .ov .pill{background:rgba(255,255,255,.14);color:#fff;border-color:transparent}.v3 .cam .state{position:absolute;inset:0;display:grid;place-items:center;color:#8b98b3;font-size:13px;text-align:center;line-height:1.5}.v3 .cam .ov .iconbtn{width:28px;height:28px;background:rgba(255,255,255,.12);border-color:transparent;color:#fff}/* Admin rail */
.v3 .rail{display:grid;grid-template-columns:220px 1fr;gap:20px}.v3 .railnav{display:flex;flex-direction:column;gap:2px}.v3 .railnav button{text-align:left;background:none;border:0;padding:9px 12px;border-radius:8px;color:var(--ink-2);display:flex;justify-content:space-between}.v3 .railnav button.on{background:var(--surface-selected);color:var(--brand);font-weight:600}.v3 .railnav button span{color:var(--faint);font-size:12px}.v3 .tiles{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-bottom:18px}.v3 .tile{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-sm);padding:14px 16px;cursor:pointer;transition:border-color var(--motion-fast) var(--ease),transform var(--motion-fast) var(--ease)}.v3 .tile:hover{transform:translateY(-2px);border-color:var(--line-strong)}.v3 .tile b{font-size:20px;font-family:var(--font-display);display:block}.v3 .tile span{font-size:12px;color:var(--muted)}/* Forms */
.v3 .field{display:flex;flex-direction:column;gap:5px;margin-bottom:14px}.v3 .field label{font-size:12.5px;font-weight:500}.v3 .field .help{font-size:12px;color:var(--muted)}.v3 .field input, .v3 .field select{height:36px;border:1px solid var(--line-strong);border-radius:8px;padding:0 10px;background:var(--panel)}.v3 .switch{width:38px;height:22px;border-radius:999px;background:var(--brand);position:relative;display:inline-block}.v3 .switch::after{content:"";position:absolute;right:3px;top:3px;width:16px;height:16px;border-radius:50%;background:#fff}.v3 .savebar{position:sticky;bottom:0;background:var(--panel);border-top:1px solid var(--line);padding:12px 0 0;display:flex;gap:8px;justify-content:flex-end;margin-top:18px}/* Command palette (Investigate) */
.v3 .palette{position:fixed;inset:0;z-index:100;display:none;place-items:start center;padding-top:12vh;background:rgba(10,15,29,.45)}.v3 .palette.open{display:grid}.v3 .palette .box{width:min(680px,94vw);background:var(--panel);border-radius:16px;box-shadow:var(--shadow-float);border:1px solid var(--line);overflow:hidden}.v3 .palette .search{height:54px;border:0;border-bottom:1px solid var(--line);border-radius:0;padding:0 18px;font-size:16px}.v3 .palette .search input{font-size:16px}.v3 .palette .hint{padding:12px 18px;font-size:12.5px;color:var(--muted)}.v3 .palette .res{padding:6px 10px 12px}/* Map placeholder (the real map component is kept; only its wrapper changes) */
.v3 .map{position:relative;aspect-ratio:16/9.5;border-radius:12px;background:radial-gradient(120% 90% at 30% 20%,#182238,#0d1526);border:1px solid var(--line);overflow:hidden}.v3 .map .zone{position:absolute;border-radius:8px;border:1px solid rgba(125,146,255,.35);background:rgba(125,146,255,.10);padding:8px 10px;color:#e9eef9;font-size:12px;cursor:pointer;transition:background var(--motion-base) var(--ease),border-color var(--motion-base) var(--ease),transform var(--motion-base) var(--ease)}.v3 .map .zone b{display:block;font-family:var(--font-display);font-size:14px}.v3 .map .zone:hover, .v3 .map .zone.sel{background:rgba(125,146,255,.22);border-color:rgba(125,146,255,.7);transform:translateY(-1px)}.v3 .map .zone.warn{border-color:rgba(230,171,77,.6);background:rgba(230,171,77,.14)}.v3 .map .legend{position:absolute;left:12px;bottom:10px;font-size:11.5px;color:#8b98b3}/* Chart */
.v3 .chart{width:100%;height:220px}.v3 .chart .grid line{stroke:var(--line);stroke-width:1}.v3 .chart .axis{font-size:11px;fill:var(--muted)}.v3 .chart .area{fill:url(#ag)}.v3 .chart .lineP{fill:none;stroke:var(--brand);stroke-width:2.2;stroke-linecap:round}.v3 .chart .lineP.reveal{stroke-dasharray:2000;stroke-dashoffset:2000;animation:v3-draw .5s var(--ease) forwards}
@keyframes v3-draw{to{stroke-dashoffset:0}}.v3 .pane{display:none}.v3 .pane.on{display:block}.v3 .heat{display:grid;grid-template-columns:120px repeat(12,1fr);gap:3px;font-size:11px;color:var(--muted);align-items:center}.v3 .heat .lab{padding-right:8px;text-align:right;color:var(--ink-2);font-size:12px}.v3 .heat i{display:block;height:26px;border-radius:4px;background:var(--brand);}.v3 .heat .hr{text-align:center}.v3 .gal{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}.v3 .gcard{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:14px;display:flex;flex-direction:column;gap:8px}.v3 .gcard .face{aspect-ratio:1;border-radius:12px;background:var(--panel-3);display:grid;place-items:center;color:var(--faint)}.v3 .gcard .strip{display:flex;gap:4px}.v3 .gcard .strip i{flex:1;height:26px;border-radius:4px;background:var(--panel-3)}/* Page switching */
.v3 section.page{display:none}.v3 section.page.on{display:block}.v3 .pagein{animation:v3-pin var(--motion-base) var(--ease)}
@keyframes v3-pin{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}/* Components gallery */
.v3 .gallery h4{margin:26px 0 10px;font-size:13px;color:var(--muted);letter-spacing:.04em;text-transform:uppercase}.v3 .swatches{display:flex;gap:10px;flex-wrap:wrap}.v3 .sw{width:110px;border-radius:10px;border:1px solid var(--line);padding:10px;font-size:11.5px}.v3 .sw i{display:block;height:34px;border-radius:6px;margin-bottom:6px}.v3 .demo{display:flex;gap:10px;flex-wrap:wrap;align-items:center}


/* =====================================================================================
   APPENDIX — components the reference does not define. Same scoping rule: all under `.v3`.
   ===================================================================================== */

/* ---------------------------------------------------------------------------------------
   DENSITY: match the production console, not the reference's page scale.
   The reference is a standalone showcase and is drawn ~25% larger than the shipped console:
   card padding 18/20 vs 14/15, KPI padding 16/18/14 with a 110px floor, KPI value 30px vs
   27px, grid gap 16 vs 14, KPI gap 14 vs 12. Copied verbatim, the same dashboard needs a
   third more vertical space and stops matching what was tuned in production. These override
   the copied values; everything else about the reference's look is untouched.
   --------------------------------------------------------------------------------------- */
.v3 .card{padding:14px 15px}
.v3 .card .head{margin-bottom:11px}
.v3 .grid{gap:14px}
.v3 .kpis{gap:12px;margin-bottom:14px}
.v3 .kpi{padding:13px 15px 12px;min-height:0;gap:4px}
.v3 .kpi .k-value{font-size:27px;letter-spacing:-.04em}
.v3 .kpi .k-sub{margin-top:1px}
.v3 .pagehead{margin-bottom:14px}
.v3 .pagehead h2{font-size:20px}
.v3 .row{padding:8px 6px}
.v3 tbody td{padding:9px 12px}
.v3 thead th{padding:8px 12px}

/* GROUND-FLOOR PLAN. Geometry ported from floor-map-bundle.html, redrawn in our tokens:
   that artifact ships its own design language and a mini-framework this console does not
   load, so only the plan itself is kept. The reference's `.map` is a percentage-positioned
   div placeholder; a real plan needs true coordinates, hence SVG and its own rules. */
.v3 .planmap{position:relative;background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r-sm);padding:10px}
/* The map card spans two grid rows, so the plan must fit the height of the two cards beside it,
   not set it. Uncapped it drew ~430px tall and pushed everything below it off the fold. */
.v3 .planmap svg{display:block;width:100%;height:auto;max-height:400px}
/* THREE-QUARTER (3D) SCENE. The source artifact paints in its own Modernist red on paper
   white; every surface here is a token instead, so the scene reads in both themes. Face
   shading is one tint at three levels — top lit, east mid, south dark — which is what makes
   the extrusions legible without any lighting model. */
/* Face colours are their OWN tokens, not --panel/--panel-2/--panel-3. Those three sit within a
   few percent of each other in dark theme, so the extrusions vanished into the slab and the
   scene read flat. A three-quarter view only works if top, east and south are clearly apart. */
/* Light: neutral greys. The previous set carried a blue cast that fought the coloured zone
   tints; a grey shell lets occupancy colour be the only hue in the scene. Slab and furniture
   move with the walls so the whole model reads as one material. */
.v3 .planmap{--slab:#eceef0;--w-t:#ffffff;--w-e:#dcdee2;--w-s:#b8bbc1;
             --fu-t:#f3f4f6;--fu-e:#d2d5d9;--fu-s:#acb0b6;--shadow:rgba(28,30,36,.16)}
/* Dark: same faces lifted toward white so the extrusions stay legible against the panel.
   Top / east / south keep their separation -- a three-quarter view only reads if they do. */
[data-theme=dark] .v3 .planmap{--slab:#171c27;--w-t:#a9b0bd;--w-e:#767d8c;--w-s:#4c5361;
             --fu-t:#b8bfcb;--fu-e:#828a99;--fu-s:#565d6b;--shadow:rgba(0,0,0,.45)}
.v3 .planmap .shadow{fill:var(--shadow)}
.v3 .planmap .slab{fill:var(--slab);stroke:var(--line-strong);stroke-width:1.2}
.v3 .planmap .wall3{stroke:var(--line-strong);stroke-width:.5;stroke-linejoin:round}
.v3 .planmap .wall3.f-t{fill:var(--w-t)}
.v3 .planmap .wall3.f-e{fill:var(--w-e)}
.v3 .planmap .wall3.f-s{fill:var(--w-s)}
.v3 .planmap .furn3{stroke:var(--line);stroke-width:.4;stroke-linejoin:round}
.v3 .planmap .furn3.f-t{fill:var(--fu-t)}
.v3 .planmap .furn3.f-e{fill:var(--fu-e)}
.v3 .planmap .furn3.f-s{fill:var(--fu-s)}
/* Room floors: tint = share of capacity. */
.v3 .planmap .zn{cursor:pointer;fill:color-mix(in srgb,var(--ink) 6%,var(--slab));stroke:var(--line-strong);stroke-width:1;
  transition:fill var(--motion-fast) var(--ease),stroke var(--motion-fast) var(--ease)}
.v3 .planmap .zn.some{fill:color-mix(in srgb,var(--brand) 22%,var(--panel-2))}
.v3 .planmap .zn.near{fill:color-mix(in srgb,var(--warn) 30%,var(--panel-2))}
.v3 .planmap .zn.full{fill:color-mix(in srgb,var(--bad) 32%,var(--panel-2))}
.v3 .planmap .zn.transit{fill:color-mix(in srgb,var(--ink) 2%,var(--slab))}
/* An unmapped room is drawn hollow. "No configured area" and "nobody is in it" are
   different facts and must not look the same. */
.v3 .planmap .zn.unmapped{fill:none;stroke-dasharray:7 6;opacity:.5}
/* In dark theme `fill:none` shows the near-black slab and the room reads as a hole. A solid
   grey lifts it off the floor. `opacity:.5` above dims the WHOLE element, so a translucent
   wash ended up at ~8% and still looked black -- opacity is reset here and the dimming is
   carried by the colours themselves. The dashed outline still says "not configured". */
[data-theme=dark] .v3 .planmap .zn.unmapped{fill:#3b414d;stroke:#6b7282;opacity:1}
[data-theme=dark] .v3 .planmap .zn.unmapped:hover,
[data-theme=dark] .v3 .planmap .zn.unmapped:focus-visible{fill:var(--surface-selected);stroke:var(--brand)}
.v3 .planmap .zn.stale{stroke-dasharray:3 4}
.v3 .planmap .zn:hover,.v3 .planmap .zn:focus-visible{fill:var(--surface-selected);stroke:var(--brand);stroke-width:2}
.v3 .planmap .zt{fill:var(--ink);font:700 17px/1 var(--font-display);text-anchor:middle;paint-order:stroke;
  stroke:var(--slab);stroke-width:4px}
.v3 .planmap .zn-n{fill:var(--ink-2);font:600 14px/1 var(--font-mono);text-anchor:middle;paint-order:stroke;
  stroke:var(--slab);stroke-width:4px}
.v3 .planmap .lbl.dim{opacity:.55}
/* Transit total under the area table — visible, so the time is relabelled rather than lost. */
.v3 .transitnote{margin-top:9px;font-size:11.5px;color:var(--muted);background:var(--panel-2);
  border:1px solid var(--line);border-radius:var(--r-xs);padding:7px 10px}
.v3 .transitnote b{color:var(--ink-2);font-family:var(--font-mono)}
/* Floor + view switches, and the two views the extra data supports. */
.v3 .planmap .mapbar{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;margin-bottom:8px}
.v3 .planmap .mapbar .chips{gap:5px}
.v3 .planmap .mapbar .chip{font-size:11.5px;padding:3px 10px}
/* Tint strength comes from --t (0..1) set per room, so dwell and heatmap read as intensity
   rather than as the capacity bands, which would imply a limit none of them has. */
.v3 .planmap .v-dwell .zn.some,.v3 .planmap .v-heatmap .zn.some{
  fill:color-mix(in srgb,var(--brand) calc(var(--t) * 46%),var(--slab))}
.v3 .planmap .v-movement .zn{fill:var(--slab);opacity:.75}
.v3 .planmap .v-movement .lbl{opacity:.5}
/* Area transitions: weight = people who made that move today. */
.v3 .planmap .flow{fill:none;stroke:var(--brand);stroke-width:var(--w);stroke-linecap:round;opacity:.75}
.v3 .planmap .flown{fill:var(--ink);font:600 13px/1 var(--font-mono);text-anchor:middle;
  paint-order:stroke;stroke:var(--slab);stroke-width:4px}
.v3 .planmap .noflow{fill:var(--faint);font:500 15px/1 var(--font);text-anchor:middle}
.v3 .planmap .lbl.sm .zt{font-size:12px}
.v3 .planmap .lbl.sm .zn-n{font-size:10.5px}
.v3 .planmap .legend{position:absolute;left:12px;right:12px;bottom:7px;font-size:11.5px;color:var(--faint)}
/* Configured areas that are not rooms on this plan (other floors, R&D, Support, Cafeteria).
   Without them the map hides live occupancy and its numbers stop reconciling with the KPI row. */
.v3 .offplan{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:10px}
.v3 .offplan .lab{font-size:11px;color:var(--faint);letter-spacing:.04em;text-transform:uppercase;margin-right:2px}
.v3 .offplan .chip{cursor:pointer;font-size:12px;padding:3px 9px}
.v3 .offplan .chip .n{margin-left:6px;font-family:var(--font-mono);color:var(--muted)}

/* STALE = "not measured", and it must not look like "measured zero". The API forces a zone's
   occupancy to 0 once it stops reporting and flags stale:true; overnight that is every zone. */
.v3 .row.stale{opacity:.62}
.v3 .row.stale .r .t{color:var(--faint)}
.v3 .staleall{font-size:12px;color:var(--warn);background:var(--warn-bg);border-radius:var(--r-xs);padding:7px 10px;margin-bottom:8px}
.v3 .offplan .chip.stale{opacity:.6}
.v3 .planmap .zn.stale rect:first-child{stroke-dasharray:3 4}
.v3 .planmap .lbl.stale .zn-n{fill:var(--faint)}

/* occTrend() is app.js's own component, dropped into a v3 card — give its parts the v3 scale. */
.v3 .occhd{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:8px}
.v3 .occplot{position:relative}
.v3 .occsvg{display:block;width:100%;height:auto;max-height:250px}

/* LEAK-THROUGH RESETS. `.v3 .card .head h3` overrides only the properties the reference
   sets; anything live styles.css sets and the reference does not still applies inside the
   wrapper. Those are the visible ones: the console's card titles are uppercase with an
   accent bar, the reference's are sentence-case and plain. Reset explicitly rather than
   hoping the reference happens to name every property. */
.v3 .card .head h3{text-transform:none;letter-spacing:-.015em;color:var(--ink)}
.v3 .card .head h3::before{display:none;content:none}
.v3 .card .head small{text-transform:none;letter-spacing:0}
/* measured, not guessed: a live rule paints card titles var(--brand) (#6d84ff in dark) and the
   reference never sets a colour there, so it inherited the brand blue on every ported card. */

/* A person's name is the row's identity and must never be clipped. The reference ellipsises
   it, which suits its narrow demo table; with real names and a 9-column register that hides
   who the row is about. Let the cell size to its content — .tablewrap already scrolls. */
.v3 td:first-child{white-space:nowrap}
.v3 .person .name{overflow:visible;text-overflow:clip}

/* ---------------------------------------------------------------------------------------
   CAMERA WALL. The reference defines .cams/.cam/.ov/.state and they are copied above
   untouched; these are only the pieces a REAL tile needs and a mock-up does not — the video
   element itself, the play control, the status dot and the countdown. renderLiveWall() owns
   the grid and writes its column count inline, so the wall keeps its own layout selector.
   --------------------------------------------------------------------------------------- */
.v3 .camgrid{display:grid;gap:14px}
/* The picture is the BACKGROUND of the tile: it fills the frame at z-index 0 and every overlay
   sits above it. Without the explicit stacking the video (absolutely positioned, no z-index)
   competed with the overlays and could paint over the name bar. */
.v3 .cam video{position:absolute;inset:0;z-index:0;width:100%;height:100%;object-fit:cover;background:#0b1020;display:block}
.v3 .cam .ov{z-index:3}
/* .state is display:grid in the reference, where it holds ONE child. Ours holds the play button
   and its caption, and two grid children became two rows — the button floated in the upper half
   and the caption sat in the lower one. Flex column keeps them together over the picture. */
.v3 .cam .state{z-index:2;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px}
/* While a camera is playing the label, countdown and stop control must STAY on screen. They are
   hover-only by design, which is right for an idle wall and wrong the moment there is a picture:
   the overlays faded out with the stream running and left no way to tell which camera it was. */
.v3 .cam.is-playing .ov{opacity:1}
.v3 .cam.is-playing .ov.bot{opacity:1}
.v3 .cam.is-playing .ov{background:linear-gradient(180deg,rgba(0,0,0,.72),transparent)}
.v3 .cam.is-playing .ov.bot{background:linear-gradient(0deg,rgba(0,0,0,.72),transparent)}
.v3 .cam .camzone{color:#c7d2ff;opacity:.9;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.v3 .cam .camdot{width:7px;height:7px;border-radius:50%;flex:none;background:var(--muted)}
.v3 .cam .camdot.idle{background:var(--brand)}
.v3 .cam .camdot.off{background:var(--faint)}
.v3 .cam .camdot.live{background:var(--ok);box-shadow:0 0 0 3px rgba(46,196,138,.25)}
.v3 .cam .camplay{width:52px;height:52px;border-radius:50%;border:1px solid rgba(255,255,255,.3);
  background:rgba(255,255,255,.14);color:#fff;display:grid;place-items:center;cursor:pointer;
  transition:background var(--motion-fast) var(--ease),transform var(--motion-fast) var(--ease)}
.v3 .cam .camplay svg{width:22px;height:22px;margin-left:2px}
.v3 .cam .camplay:hover{background:rgba(255,255,255,.24);transform:scale(1.06)}
.v3 .cam .camstate{color:#8b98b3;font-size:12.5px;text-align:center;line-height:1.5}
.v3 .cam .ov.bot{flex-wrap:nowrap}
/* nowrap + auto margin: at 3x3 the countdown wrapped onto its own line and sat on top of the
   maximise button. It belongs at the left of the control bar, on one line. */
.v3 .cam .camtimer{color:#fff;font:600 11.5px/1 var(--font-mono);margin-right:auto;opacity:.9;white-space:nowrap}
.v3 .cam.nostream .state{color:var(--faint)}

/* --- a camera that is not reporting ------------------------------------------------------------
   Three distinct resting states on the wall, because they need three different responses:
     .camdot.off   no stream URL configured  -> an ADMIN job, nothing is wrong on site
     .camdot.down  configured but not reporting -> a SITE job, go and look at the camera
     .camdot.idle  healthy, simply not playing right now
   Before this, a dead camera was indistinguishable from an idle one and the wall looked fine
   while cam127 had been off the network for hours. */
.v3 .cam .camdot.down{background:var(--warn);box-shadow:0 0 0 3px var(--warn-bg)}
/* Dimmed, not hidden. The tile stays clickable: the stream is served by the NVR and may still
   play even when the edge grabber cannot reach the camera, so this states what is known rather
   than deciding the picture is unavailable. */
.v3 .cam.camoffline{outline:1px solid var(--warn);outline-offset:-1px}
.v3 .cam.camoffline .ov b{color:var(--warn)}
.v3 .cam.camoffline .camstate{color:var(--warn)}
.v3 .cam.camoffline .camstate .sm{color:var(--muted);font-size:11.5px}

/* The page-level banner. The wall can be sixteen tiles deep, so the names are repeated at the
   top where they are read first; each tile is still marked individually. */
.v3 .camdown-bar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  margin:0 0 12px;padding:9px 13px;border-radius:var(--r);
  background:var(--warn-bg);border:1px solid var(--warn);color:var(--text)}
.v3 .camdown-bar svg{width:17px;height:17px;color:var(--warn);flex:none}
.v3 .camdown-bar b{font-size:13px}
.v3 .camdown-bar span{font-size:12.5px;color:var(--muted);min-width:0}


/* Date picker living inside a .btn in the page header */
.v3 .pagehead .btn input[type=date]{border:0;background:none;color:inherit;font:inherit;padding:0;width:114px}

/* The dashboard's top row is styled entirely by agy's cards3d.css, folded in below.
   The hand-written .hs-row rules that used to live here were removed rather than left to
   fight it: two stylesheets owning the same selectors is how a layout drifts. */

/* Two-column list — Area activity. Eleven areas in one narrow column ran far past the chart
   beside it; paired up they finish level with it. `.row` is a flex row, so the columns come
   from the container, and the per-row bottom border is dropped for a gap instead (a border
   would draw a seam down the middle of the pair). Collapses back to one column when the card
   is too narrow to split. */
.v3 .list.cols2{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));column-gap:16px}
.v3 .list.cols2 .row{border-bottom:0;padding:6px 6px}
.v3 .list.cols2 .row + .row{border-top:1px solid var(--line)}
.v3 .list.cols2 .row:nth-child(2){border-top:0}
.v3 .list.cols2 .staleall{grid-column:1/-1}
@media (max-width:1180px){ .v3 .list.cols2{grid-template-columns:minmax(0,1fr)} }

/* Right-hand value column of the dashboard lists */
.v3 .row .r{text-align:right;min-width:80px;flex:none}
.v3 .row .r .t{font-size:13px}
.v3 .row .r .d{font-size:11.5px;color:var(--muted)}
.v3 .row .person{min-width:0}
.v3 .row .grow{min-width:0}

/* The v3 summary block injected into app.js's own person drawer: it must bring its own
   padding, since it is a guest inside a panel this stylesheet does not own. */
.v3.pdv3{padding:14px 16px 6px}
.v3.pdv3 .section:first-child{margin-top:0}

/* Chip filtering hides rows with [hidden]; display:table-row would otherwise win. */
.v3 tbody tr[hidden]{display:none}
.v3 .alert[hidden]{display:none}


/* ===== 3D KPI cards — agy cards3d.css, verified 0 unscoped rules ===== */
/* cards3d.css — WorkSight AI Workforce Operations Console 3D KPI Cards */

.hs-row {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 16px;
  width: 100%;
  box-sizing: border-box;
  perspective: 1200px;
}

@media (max-width: 1460px) {
  .hs-row {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 16px;
  }
}

@media (max-width: 820px) {
  .hs-row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
  }
}

@media (max-width: 520px) {
  .hs-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 12px;
  }
}

.hs-row .hs-panel {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  width: 100%;
  min-width: 0;
  padding: 16px 18px 14px 18px;
  background: linear-gradient(175deg, var(--panel) 0%, var(--panel-2) 100%);
  border: 1px solid var(--line);
  border-radius: var(--r, 16px);
  text-align: left;
  text-decoration: none;
  color: var(--ink);
  font-family: var(--font);
  cursor: pointer;
  outline: none;
  overflow: hidden;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;

  /* 3D Transform physics variables */
  --hs-rx: 0deg;
  --hs-ry: 0deg;
  --hs-mx: 50%;
  --hs-my: 20%;
  --hs-lift: 0px;

  transform-style: preserve-3d;
  transform: perspective(1000px) translateY(var(--hs-lift, 0px)) rotateX(var(--hs-rx, 0deg)) rotateY(var(--hs-ry, 0deg));
  transition: transform 0.35s var(--ease, cubic-bezier(0.16, 1, 0.3, 1)),
              box-shadow 0.35s var(--ease, cubic-bezier(0.16, 1, 0.3, 1)),
              border-color 0.35s var(--ease, cubic-bezier(0.16, 1, 0.3, 1));
  will-change: transform;

  /* Soft dimensional contact shadow and rim highlight */
  box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.08),
              0 8px 20px -4px rgba(0, 0, 0, 0.16),
              inset 0 1px 0 rgba(255, 255, 255, 0.14),
              inset 0 -1px 0 rgba(0, 0, 0, 0.14);

  /* Staggered entrance animation (runs once) */
  animation: hs-panel-enter 0.75s var(--ease, cubic-bezier(0.16, 1, 0.3, 1)) backwards;
}

.hs-row .hs-panel:nth-child(1) { animation-delay: 0.04s; }
.hs-row .hs-panel:nth-child(2) { animation-delay: 0.10s; }
.hs-row .hs-panel:nth-child(3) { animation-delay: 0.16s; }
.hs-row .hs-panel:nth-child(4) { animation-delay: 0.22s; }
.hs-row .hs-panel:nth-child(5) { animation-delay: 0.28s; }
.hs-row .hs-panel:nth-child(6) { animation-delay: 0.34s; }

@keyframes hs-panel-enter {
  0% {
    opacity: 0;
    transform: perspective(1000px) translateY(18px) rotateX(10deg) scale(0.96);
  }
  100% {
    opacity: 1;
    transform: perspective(1000px) translateY(0) rotateX(0deg) scale(1);
  }
}

.hs-row .hs-panel:hover {
  --hs-lift: -4px;
  border-color: var(--line-strong);
  box-shadow: 0 4px 8px -2px rgba(0, 0, 0, 0.12),
              0 18px 36px -6px rgba(0, 0, 0, 0.26),
              inset 0 1px 0 rgba(255, 255, 255, 0.22),
              inset 0 -1px 0 rgba(0, 0, 0, 0.18);
}

.hs-row .hs-panel:active {
  transform: perspective(1000px) translateY(1px) scale(0.985);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12),
              0 4px 10px rgba(0, 0, 0, 0.15),
              inset 0 1px 0 rgba(0, 0, 0, 0.15);
  transition: transform 0.08s ease, box-shadow 0.08s ease;
}

.hs-row .hs-panel:focus-visible {
  outline: 2px solid var(--accent, var(--brand-2, #3b82f6));
  outline-offset: 3px;
  box-shadow: 0 0 0 3px var(--panel),
              0 8px 24px rgba(0, 0, 0, 0.25);
}

/* Specular sheen glaze moving with cursor tilt */
.hs-row .hs-card-glaze {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  pointer-events: none;
  border-radius: inherit;
  background: radial-gradient(circle 220px at var(--hs-mx, 50%) var(--hs-my, 20%), rgba(255, 255, 255, 0.12), transparent 75%);
  opacity: 0.7;
  transition: opacity 0.3s ease;
  z-index: 1;
}

.hs-row .hs-panel:hover .hs-card-glaze {
  opacity: 1;
}

/* Material lighting glow per band */
.hs-row .hs-card-glow {
  position: absolute;
  top: 0;
  right: 0;
  width: 140px;
  height: 140px;
  pointer-events: none;
  border-radius: inherit;
  opacity: 0.65;
  transition: opacity 0.3s ease;
  z-index: 1;
}

.hs-row .hs-panel:hover .hs-card-glow {
  opacity: 0.9;
}

.hs-row .hs-panel.is-good .hs-card-glow {
  background: radial-gradient(circle at 100% 0%, var(--ok-bg), transparent 70%);
}

.hs-row .hs-panel.is-warn .hs-card-glow {
  background: radial-gradient(circle at 100% 0%, var(--warn-bg), transparent 70%);
}

.hs-row .hs-panel.is-bad .hs-card-glow {
  background: radial-gradient(circle at 100% 0%, var(--bad-bg), transparent 70%);
}

.hs-row .hs-panel.is-na .hs-card-glow {
  background: radial-gradient(circle at 100% 0%, var(--panel-3), transparent 70%);
}

/* Inner content with 3D layer parallax */
.hs-row .hs-card-content {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
  z-index: 2;
  transform-style: preserve-3d;
}

.hs-row .hs-card-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
  transform: translateZ(14px);
}

.hs-row .hs-icon-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--r-sm, 8px);
  background: var(--panel-2);
  border: 1px solid var(--line);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1),
              0 2px 4px rgba(0, 0, 0, 0.08);
  color: var(--ink-2);
  transition: color 0.25s ease, border-color 0.25s ease;
}

.hs-row .hs-panel:hover .hs-icon-badge {
  color: var(--ink);
  border-color: var(--line-strong);
}

.hs-row .hs-status-beacon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--panel-2);
  border: 1px solid var(--line);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
}

.hs-row .hs-beacon-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.hs-row .hs-panel.is-good .hs-beacon-dot {
  background: var(--ok);
  box-shadow: 0 0 8px var(--ok);
}

.hs-row .hs-panel.is-warn .hs-beacon-dot {
  background: var(--warn);
  box-shadow: 0 0 8px var(--warn);
}

.hs-row .hs-panel.is-bad .hs-beacon-dot {
  background: var(--bad);
  box-shadow: 0 0 8px var(--bad);
}

.hs-row .hs-panel.is-na .hs-beacon-dot {
  background: var(--muted);
  box-shadow: none;
}

/* Label & Typography */
.hs-row .hs-card-label-row {
  margin-bottom: 8px;
  min-height: 32px;
  display: flex;
  align-items: flex-start;
  transform: translateZ(12px);
}

.hs-row .hs-label {
  font-family: var(--font);
  font-size: 13px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--muted);
  white-space: normal;
  word-break: normal;
  overflow-wrap: break-word;
  transition: color 0.2s ease;
}

.hs-row .hs-panel:hover .hs-label {
  color: var(--ink-2);
}

/* Middle section: Value + Visual (Gauge or Neutral Count) */
.hs-row .hs-card-mid {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
  transform: translateZ(24px);
}

.hs-row .hs-value-wrap {
  min-width: 0;
  flex: 1 1 auto;
}

.hs-row .hs-value {
  font-family: var(--font-display, var(--font));
  font-size: 26px;
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.025em;
  color: var(--ink);
  white-space: nowrap;
}

.hs-row .hs-value small {
  font-size: 15px;
  font-weight: 500;
  color: var(--muted);
  letter-spacing: 0;
}

/* Bottom Detail */
.hs-row .hs-card-bot {
  margin-top: auto;
  padding-top: 10px;
  border-top: 1px solid var(--line);
  transform: translateZ(8px);
}

.hs-row .hs-detail {
  display: block;
  font-family: var(--font);
  font-size: 11.5px;
  line-height: 1.35;
  color: var(--muted);
  white-space: normal;
  word-break: normal;
  overflow-wrap: break-word;
}

/* Side visual element container */
.hs-row .hs-side-visual {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  transform: translateZ(20px);
}

/* 3D Dimensional Ring Gauge (pct tiles only) */
.hs-row .hs-gauge-dial {
  position: relative;
  width: 56px;
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.hs-row .hs-gauge-svg {
  position: absolute;
  top: 0;
  left: 0;
  width: 56px;
  height: 56px;
  overflow: visible;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.16));
}

.hs-row .hs-gauge-track {
  fill: none;
  stroke: var(--panel-3);
  stroke-width: 5;
}

.hs-row .hs-gauge-arc {
  fill: none;
  stroke-width: 5.5;
  stroke-linecap: round;
  transform: rotate(-90deg);
  transform-origin: 30px 30px;
  animation: hs-gauge-fill 1s var(--ease, cubic-bezier(0.16, 1, 0.3, 1)) 0.15s backwards;
}

@keyframes hs-gauge-fill {
  0% {
    stroke-dashoffset: 144.513;
  }
}

.hs-row .hs-panel.is-good .hs-gauge-arc {
  stroke: var(--ok);
}

.hs-row .hs-panel.is-warn .hs-gauge-arc {
  stroke: var(--warn);
}

.hs-row .hs-panel.is-bad .hs-gauge-arc {
  stroke: var(--bad);
}

.hs-row .hs-panel.is-na .hs-gauge-arc {
  stroke: var(--muted);
}

.hs-row .hs-gauge-center {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: linear-gradient(165deg, var(--panel-2) 0%, var(--panel) 100%);
  border: 1px solid var(--line);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12),
              inset 0 -1px 0 rgba(0, 0, 0, 0.1),
              0 2px 4px rgba(0, 0, 0, 0.12);
  z-index: 2;
}

.hs-row .hs-gauge-pct {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--ink);
}

/* Neutral Dimensional Treatment for Null-pct Tiles (Tiles 2 and 6) */
.hs-row .hs-count-badge {
  display: flex;
  align-items: center;
  justify-content: center;
}

.hs-row .hs-count-pill {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 54px;
  height: 48px;
  border-radius: 12px;
  background: linear-gradient(165deg, var(--panel-2) 0%, var(--panel-3) 100%);
  border: 1px solid var(--line-strong);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14),
              inset 0 -1px 0 rgba(0, 0, 0, 0.14),
              0 2px 6px rgba(0, 0, 0, 0.1);
  padding: 4px 6px;
  box-sizing: border-box;
}

.hs-row .hs-count-num {
  font-family: var(--font-mono);
  font-size: 15px;
  font-weight: 700;
  line-height: 1.1;
  color: var(--ink);
}

.hs-row .hs-panel.is-good .hs-count-num {
  color: var(--ok);
}

.hs-row .hs-count-sub {
  font-family: var(--font);
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  line-height: 1;
  margin-top: 2px;
}

/* Reduced Motion Disables All Movement */
@media (prefers-reduced-motion: reduce) {
  .hs-row,
  .hs-row *,
  .hs-row *::before,
  .hs-row *::after {
    animation-duration: 0.001ms !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    transition-delay: 0s !important;
    transform: none !important;
  }
  .hs-row svg animate {
    display: none !important;
  }
}


/* ===== SIZE PASS (post-review) ==============================================================
   The generated card measured 228px tall; six of them pushed the map and everything under it
   off a 1080p screen. Everything below is scale only -- no structural change, no change to the
   3D physics, and nothing that would let a gauge appear where there is no denominator.
   ========================================================================================== */
.hs-row .hs-panel{padding:11px 13px 10px 13px}
.hs-row .hs-icon-badge{width:26px;height:26px;border-radius:9px}
.hs-row .hs-icon-badge svg{width:15px;height:15px}
.hs-row .hs-label{font-size:11.5px;line-height:1.25}
.hs-row .hs-value{font-size:20px;line-height:1.1}
.hs-row .hs-value small{font-size:12.5px}
.hs-row .hs-detail{font-size:10.5px;line-height:1.3}
/* The gauge wrapper is .hs-gauge-dial, NOT .hs-gauge. The first size pass shrank the SVG and the
   centre but left the dial at 56px, so the ring and the percentage no longer shared a centre and
   the number sat outside its own box. Size the dial and the svg together and let the centre keep
   its own centring. */
.hs-row .hs-gauge-dial,.hs-row .hs-gauge-svg{width:44px;height:44px}
.hs-row .hs-gauge-center{width:30px;height:30px}
.hs-row .hs-gauge-pct{font-size:9.5px}
/* The rows themselves, not just their contents: the side visual still reserved the old 56px
   gauge box, the label row carried 32px for a 14px label, and the stacked margins added ~44px
   of air. Tightened to the same rhythm the rest of the dashboard uses. */
.hs-row .hs-side-visual{width:44px;height:44px}
.hs-row .hs-card-top{margin-bottom:6px}
.hs-row .hs-card-label-row{margin-bottom:4px;min-height:0}
.hs-row .hs-card-mid{margin-bottom:7px;gap:10px}
.hs-row .hs-card-bot{padding-top:7px}
.hs-row .hs-card-bot .hs-detail{margin-top:0}
.hs-row .hs-status-beacon{transform:scale(.85)}

/* ===== ICON + LABEL ON ONE LINE ==============================================================
   The renders earn their space only at size, and a 26px badge stacked above its label wasted a
   whole row on 26px of icon. Badge grows to 40px and the label moves up beside it, so the card
   reads "icon: what this is" on one line and the value below gets the height back.
   ========================================================================================== */
.hs-row .hs-card-top{justify-content:flex-start;gap:8px;margin-bottom:8px}
.hs-row .hs-icon-badge{width:36px;height:36px;border-radius:11px;flex:0 0 auto}
.hs-row .hs-icon-badge svg{width:20px;height:20px}
.hs-row .hs-card-top .hs-label{
  flex:1 1 auto;min-width:0;font-size:11px;line-height:1.2;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
}
/* The beacon is a status dot, not content: pinning it to the card corner instead of letting it
   sit in the flow gives the label back ~18px. Without that, "Employees present" (109px) did not
   fit the 97px it was left with and three of the six cards wrapped to two lines while the other
   three did not, so the row's icons no longer shared a baseline. The label keeps a padding-right
   so a long one never runs under the dot. */
.hs-row .hs-panel{position:relative}
.hs-row .hs-card-top .hs-status-beacon{position:absolute;top:11px;right:13px;margin:0}
.hs-row .hs-card-top .hs-label{padding-right:20px}


/* ===== ENTRANCE ANIMATIONS RUN ONCE, NOT ON EVERY POLL ========================================
   app.js re-renders the WHOLE page whenever the polled summary changes (app.js:2433 and :2443
   call render() on a signature change), and on a live site that summary changes constantly. Each
   re-render rebuilt the card row and replayed hs-panel-enter (0.75s) and hs-gauge-fill (1s), so
   the dashboard appeared to blink continuously. ui-v3.js now stamps `hs-animated` on the row from
   the second render onwards, so the entrance plays once per page load and never again.

   Fixed here rather than by suppressing the re-render: the polling and the render() call belong to
   app.js, which the redesign does not touch — the whole port stays revertible by deleting two tags.
   ============================================================================================ */
.hs-row.hs-animated .hs-panel{animation:none}
.hs-row.hs-animated .hs-gauge-arc{animation:none}

/* ===== 3D icon badges =========================================================================
   Generated by pt_make_icons.py from the PNG renders. Each is cropped to its own alpha bounding
   box, re-centred with even padding and resized to 96px, so icons with different silhouettes read
   at the same visual weight in a ~26px badge.

   The inline SVG stays in the markup underneath: `.has-img` hides it, so a badge with no image
   still draws something rather than leaving a hole.
   ============================================================================================ */
.hs-row .hs-icon-badge.has-img,
.v3m .mki.has-img{background-size:contain;background-repeat:no-repeat;background-position:center}
.hs-row .hs-icon-badge.has-img svg,
.v3m .mki.has-img svg{display:none}
.hs-row .hs-icon-badge.i-alert,.v3m .mki.i-alert{background-image:url(assets/inline/d979b1151c90.png)}
.hs-row .hs-icon-badge.i-camera,.v3m .mki.i-camera{background-image:url(assets/inline/71205a95f177.png)}
.hs-row .hs-icon-badge.i-employee,.v3m .mki.i-employee{background-image:url(assets/inline/50c82020ea5e.png)}
.hs-row .hs-icon-badge.i-health,.v3m .mki.i-health{background-image:url(assets/inline/59ac869111a5.png)}
.hs-row .hs-icon-badge.i-monitor,.v3m .mki.i-monitor{background-image:url(assets/inline/984b3642a352.png)}
.hs-row .hs-icon-badge.i-visitor,.v3m .mki.i-visitor{background-image:url(assets/inline/516b3b2a1b81.png)}

/* ===== MOBILE — ICONS + ALIGNMENT ONLY =======================================================
   The existing mobile console keeps its design. ui-v3.js wraps each screen in <div class="v3 v3m">
   purely so the shared tokens are in scope; the mobile renderers, markup and every data-m* handler
   are untouched.

   Deliberately NOT here: no new palette, no card redesign, no type-scale change, no layout swap.
   Two things only, per the operator's brief — the 3D icons the desktop already uses, and tidier
   alignment of what is already on screen.
   ============================================================================================ */

/* --- icons -----------------------------------------------------------------------------------
   The image rules themselves live in icons3d.css, emitted once for BOTH consumers
   (`.hs-row .hs-icon-badge.i-x` and `.v3m .mki.i-x`), so the phone and the desktop can never end
   up with different artwork. All that is needed here is the box the render sits in. */
.v3m .mki {
  width: 34px; height: 34px; border-radius: 10px; flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
}
.v3m .mki.has-img { background-color: transparent; }   /* the render carries its own shape */

/* --- alignment -------------------------------------------------------------------------------
   The KPI tile is icon + text. Centre them on the same axis and stop a long title from pushing
   the value out of the tile; nothing else about the tile changes. */
.v3m .mkpi { display: flex; align-items: center; gap: 10px; min-width: 0; }
.v3m .mktxt { min-width: 0; }
/* The title WRAPS. An earlier version clipped it to one line with an ellipsis, which turned
   "Monitoring health" into "Monitorin..." -- alignment was the goal, never truncation. */
.v3m .mkt { line-height: 1.25; }
.v3m .mkv { line-height: 1.15; }
.v3m .mkd { line-height: 1.3; }

/* Person rows: thumb, name block and any trailing value on one baseline, with the name column
   free to shrink so a long name truncates instead of wrapping under the thumbnail. */
.v3m .mrow-person { display: flex; align-items: center; gap: 10px; }
.v3m .mmain { min-width: 0; flex: 1 1 auto; }
.v3m .mname { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.v3m .mthumb { flex: 0 0 auto; }

/* Section headers: icon and label on one line rather than the icon sitting a pixel high. */
.v3m .mcard h5 { display: flex; align-items: center; gap: 7px; }

/* Nothing may push the phone viewport sideways. */
.v3m img, .v3m svg { max-width: 100%; }

/* --- narrow-screen label wrap ---------------------------------------------------------------
   The site-health card uses the desktop .hs-label, which is held to one line so the icon, label
   and status dot share a row at desktop width. On a phone that box is ~72px, so "Monitoring
   system" clipped to "Monitorin...". Let it wrap here; the desktop rule is untouched. */
@media (max-width: 820px) {
  .v3 .hs-label { white-space: normal; overflow: visible; text-overflow: clip; }
}

/* --- the mobile map card ---------------------------------------------------------------------
   .mmap was sized for the OLD isometric map: a fixed 320px box with overflow:hidden, which suited
   a graphic of known aspect. The two-floor plan is a viewBox 1095x844 SVG plus its own floor tabs
   and view chips, so inside a fixed 320px box the plan was cut off at the bottom and squeezed
   small enough that the room labels collided with each other.

   Let the card take the height its content needs and let the plan use the full card width. */
@media (max-width: 820px) {
  .v3m .mmap { height: auto !important; max-height: none !important; overflow: visible !important; }
  .v3m .mmap svg {
    width: 100%; height: auto; max-height: none !important; display: block;
  }
  /* Room labels are drawn at plan scale; at phone width they need the extra room the taller
     card now gives them, and the smallest ones stay legible rather than overlapping. */
  .v3m .mmap text { paint-order: stroke; }
}

/* At phone width the plan is ~310px wide, and its 14 "no area configured" sub-labels collide with
   each other and with the room names. Drop those on mobile only: the dashed outline already says
   the room has no configured area. Occupancy sub-labels are a different class and always render —
   a number is information, and hiding it would be a lie of omission. */
@media (max-width: 820px) {
  .v3m .mmap text.zn-unmapped { display: none; }
  .v3m .mmap text.zt { font-size: 11px; }
}

/* DASHBOARD TOP ROW. The map card carries .span2 but nothing implemented the span, so the two
   cards beside it wrapped below and none of the three lined up. Heights are pinned rather than
   left to the grid: with content-sized rows a long roster inflates its own row and stretches the
   map instead of scrolling. --dash-map-h drives all three, so the pair beside the map is exactly
   as tall as the map. Each list scrolls inside its card with the scrollbar hidden -- a visible
   gutter inside a card reads as a second panel. */
.v3 .grid{--dash-map-h:520px}
.v3 .grid > .card.span2{grid-row:span 2;height:var(--dash-map-h);
  display:flex;flex-direction:column;min-height:0}
.v3 .grid > .card.span2 > .planmap{flex:1 1 auto;min-height:0;display:flex;flex-direction:column}
.v3 .grid > .card.span2 > .planmap > svg{flex:1 1 auto;min-height:0;height:100%;max-height:none}
.v3 .grid > .card.span2 + .card,
.v3 .grid > .card.span2 + .card + .card{height:calc((var(--dash-map-h) - 14px) / 2);
  display:flex;flex-direction:column;min-height:0;overflow:hidden}
.v3 .grid > .card.span2 + .card > .list,
.v3 .grid > .card.span2 + .card + .card > .list{flex:1 1 auto;min-height:0;overflow-y:auto;
  scrollbar-width:none;-ms-overflow-style:none}
.v3 .grid > .card.span2 + .card > .list::-webkit-scrollbar,
.v3 .grid > .card.span2 + .card + .card > .list::-webkit-scrollbar{width:0;height:0}
/* Stacked layout: every cap is lifted so nothing is clipped on a narrow screen. */
@media (max-width:1100px){
  .v3 .grid > .card.span2{grid-row:auto;height:auto;display:block}
  .v3 .grid > .card.span2 > .planmap{display:block}
  .v3 .grid > .card.span2 > .planmap > svg{height:auto;max-height:400px}
  .v3 .grid > .card.span2 + .card,
  .v3 .grid > .card.span2 + .card + .card{height:auto;display:block;overflow:visible}
  .v3 .grid > .card.span2 + .card > .list,
  .v3 .grid > .card.span2 + .card + .card > .list{max-height:340px;overflow-y:auto}}

/* LIVE PRESENCE TABLE (2026-09-04).
   The Person column was clipping: .person had no min-width, so a long name was squeezed by the
   columns beside it. The cell now has a floor and its own ellipsis, and every name carries a
   title attribute so a truncated value is still readable on hover. */
.v3 #wfTable td:first-child{min-width:210px;max-width:330px}
.v3 .person{display:flex;align-items:center;gap:10px;min-width:0}
.v3 .person .pname{min-width:0;flex:1 1 auto}
.v3 .person .name,.v3 .person .meta{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Detection crop in the avatar slot. The initials sit underneath and are revealed by CSS alone
   when the image fails, so a missing crop degrades to the avatar this table already had. */
.v3 .person .av{position:relative;overflow:hidden;flex:0 0 auto}
.v3 .person .av.hasimg img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;border-radius:inherit}
.v3 .person .av.hasimg span{visibility:hidden}
.v3 .person .av.hasimg img[hidden]~span{visibility:visible}
/* The People-on-site pager is production's .detfoot / .aspg / .detgo markup
   (podFooter()), already styled in styles.css -- no rules needed here. */
