/* WaterWorksX Edge - shared UI theme (light industrial).
   Single source of truth: edit here, then sync into each app. */

@font-face{font-family:'IBM Plex Sans';font-weight:400;font-display:swap;src:url('/static/fonts/IBMPlexSans-Regular.woff2') format('woff2')}
  @font-face{font-family:'IBM Plex Sans';font-weight:500;font-display:swap;src:url('/static/fonts/IBMPlexSans-Medium.woff2') format('woff2')}
  @font-face{font-family:'IBM Plex Sans';font-weight:600;font-display:swap;src:url('/static/fonts/IBMPlexSans-SemiBold.woff2') format('woff2')}
  @font-face{font-family:'IBM Plex Mono';font-weight:400;font-display:swap;src:url('/static/fonts/IBMPlexMono-Regular.woff2') format('woff2')}
  @font-face{font-family:'IBM Plex Mono';font-weight:500;font-display:swap;src:url('/static/fonts/IBMPlexMono-Medium.woff2') format('woff2')}
  :root{
    --navy:#1F497D;--navy-d:#173a64;
    --bg:#F4F6F8;--surface:#FFFFFF;--surface2:#FAFBFC;--surface3:#EEF1F5;
    --border:#D6DCE4;--border-h:#C2CAD6;
    --text:#1A1F24;--text-bright:#1A1F24;--text-mid:#3D4754;--text-dim:#5C6470;
    --wx:#4F81BD;--green:#2E7D32;--ok:#2E9E5B;--red:#C0392B;--amber:#B8860B;
    /* De-coloured health-bar fill: a soft slate, easy on the eye against the
       light track (--surface3), never the heavy near-black --text-mid. */
    --meter:#A6B1C0;
    --mono:'IBM Plex Mono',ui-monospace,Consolas,monospace;
    --sans:'IBM Plex Sans',system-ui,-apple-system,'Segoe UI',sans-serif;
    --ease:cubic-bezier(.22,1,.36,1);
    /* S10: z-index scale — replaces scattered magic numbers, consistent stacking */
    --z-fab:60;--z-tabbar:99;--z-sticky:100;--z-backdrop:290;--z-drawer:300;--z-modal:300;--z-login:310;--z-pwgate:320;--z-aifab:1190;--z-aipanel:1200;
    /* ---- design-system tokens (single locked source) ----
       Locked 4-state status palette. CRITICAL: stale/degraded is SLATE, never the
       --st-ok green — a degraded or aging link must read as "uncertain", not "good".
       --st-ok green / --st-warn amber / --st-fault red / --st-stale slate. */
    --st-ok:#2E9E5B;--st-warn:#B8860B;--st-fault:#C0392B;--st-stale:#64748B;
    /* Typographic scale — 11/12/14/16/20/28. The old 9px tier is retired. */
    --fs-1:11px;--fs-2:12px;--fs-3:14px;--fs-4:16px;--fs-5:20px;--fs-6:28px;
    /* Spacing — strict 4px grid. */
    --sp-1:4px;--sp-2:8px;--sp-3:12px;--sp-4:16px;--sp-5:24px;--sp-6:32px;
    /* Single corner radius for the whole system. */
    --radius:4px;
    /* Elevation scale — one consistent depth system (previously only the modal +
       login card carried a shadow). Use --elev-1 for resting cards, -2 on hover /
       popovers, -3 for overlays. */
    --elev-1:0 1px 2px rgba(23,42,68,.06),0 1px 3px rgba(23,42,68,.05);
    --elev-2:0 2px 6px rgba(23,42,68,.08),0 4px 12px rgba(23,42,68,.06);
    --elev-3:0 8px 30px rgba(23,42,68,.14);
    /* Masthead colour is kept SEPARATE from the --navy accent so dark mode can drop
       the bar to near-black (matching the PDF report's brand band) without also
       recolouring every navy accent. Light: masthead == navy (unchanged). */
    --masthead:var(--navy);--masthead-txt:#fff;
    /* Chart ink + gridline — consumed by app trend charts; remapped in dark. */
    --chart-ink:#4F81BD;--chart-grid:rgba(23,42,68,.08)
  }
  input,select,button,td,th,.kv .v{font-variant-numeric:tabular-nums;font-feature-settings:"tnum"}
  *{margin:0;padding:0;box-sizing:border-box}html{-webkit-font-smoothing:antialiased}
  ::-webkit-scrollbar{width:9px;height:9px}::-webkit-scrollbar-thumb{background:rgba(0,0,0,.18)}::-webkit-scrollbar-track{background:transparent}
  body{font-family:var(--sans);background:var(--bg);color:var(--text);font-size:13px;line-height:1.5;min-height:100vh}

  .topbar{background:var(--masthead);color:var(--masthead-txt);padding:0 20px;height:52px;display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:var(--z-sticky);box-shadow:0 1px 4px rgba(0,0,0,.18)}
  .topbar-left{display:flex;align-items:center;gap:16px}.topbar-right{display:flex;align-items:center;gap:16px}
  /* Only when embedded in the configurator (which injects .embedded) do we clear
     its floating hamburger. Standalone (own window) -> logo sits flush left. */
  html.embedded .topbar-left{padding-left:38px}
  .logo-img{mix-blend-mode:screen;display:block}
  /* Topbar wordmark size - shared across all apps (login logo sets its own height inline) */
  .topbar-left .logo-img{height:30px}
  .div{width:1px;height:24px;background:rgba(255,255,255,.18)}
  .brand-sub{font-size:10px;color:rgba(255,255,255,.85);letter-spacing:1.4px;text-transform:uppercase}
  .strip-item{display:flex;align-items:center;gap:6px;font-size:11px;color:rgba(255,255,255,.85)}
  .strip-item .v{font-family:var(--mono);font-size:12px;color:#fff}
  .be-badge{font-family:var(--mono);font-size:9px;font-weight:500;letter-spacing:.5px;padding:2px 7px;border-radius:3px;border:1px solid rgba(255,255,255,.25);color:#cfe0f2;text-transform:uppercase}
  .be-badge.sim{color:#F0CE7A;border-color:rgba(240,206,122,.4)}
  .be-badge.live{color:#9fe0a9;border-color:rgba(159,224,169,.4)}
  .live{display:flex;align-items:center;gap:6px}
  /* legacy topbar liveness dot — KEPT, now mapped onto the locked status tokens
     (brightened against navy with a soft halo). New code should use .wx-live. */
  .live-dot{width:8px;height:8px;border-radius:50%;background:var(--st-ok);box-shadow:0 0 0 2px rgba(46,158,91,.25)}.live-dot.down{background:var(--st-fault);box-shadow:0 0 0 2px rgba(192,57,43,.25)}
  .live-lbl{font-size:9px;font-weight:700;letter-spacing:2px;color:#cfe0f2}.live-lbl.down{color:var(--st-fault)}
  /* S3: on phones, drop the topbar's secondary clusters so it never overflows.
     Height stays a fixed 52px so the sticky .tab-bar (top:52px) stays aligned. */
  @media(max-width:600px){
    .topbar{padding:0 12px}
    .topbar-left,.topbar-right{gap:10px;min-width:0}
    .brand-sub,.be-badge,.strip-item{display:none}
  }

  .tab-bar{display:flex;background:#fff;border-bottom:1px solid var(--border);padding:0 16px;position:sticky;top:52px;z-index:var(--z-tabbar);overflow-x:auto}
  .tab-btn{font-family:var(--sans);font-size:11px;font-weight:600;letter-spacing:.8px;text-transform:uppercase;padding:12px 16px;border:none;background:none;color:var(--text-dim);cursor:pointer;border-bottom:2px solid transparent;transition:color .15s;white-space:nowrap}
  .tab-btn:hover{color:var(--text-mid)}.tab-btn.active{color:var(--navy);border-bottom-color:var(--navy)}
  .tab-content{display:none;padding:20px}.tab-content.active{display:block}
  .two-col{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start}
  @media(max-width:900px){.two-col{grid-template-columns:1fr}}

  .panel{background:var(--surface);border:1px solid var(--border);border-radius:4px;margin-bottom:14px;overflow:hidden;box-shadow:var(--elev-1)}
  .panel-header{display:flex;justify-content:space-between;align-items:center;padding:11px 16px;border-bottom:1px solid var(--border);gap:10px;flex-wrap:wrap}
  .panel-title{font-size:11px;font-weight:600;letter-spacing:1.2px;text-transform:uppercase;color:var(--navy)}
  .panel-body{padding:16px}
  /* S1: wide data tables scroll horizontally inside the panel instead of being
     clipped by .panel{overflow:hidden}. :has() scopes the scroll to panel-bodies
     that actually contain a table, so non-table panels keep visible overflow.
     .table-scroll is the explicit wrapper for the skeleton/new markup. */
  .table-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch}
  .panel-body:has(table){overflow-x:auto;-webkit-overflow-scrolling:touch}

  .metric-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:10px}
  .metric{background:var(--surface2);border:1px solid var(--border);border-radius:4px;padding:14px 16px;transition:box-shadow .18s var(--ease),transform .18s var(--ease)}
  .metric:hover{box-shadow:var(--elev-2);transform:translateY(-1px)}
  .metric-label{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:1.2px;color:var(--text-dim);margin-bottom:6px}
  .metric-val{font-size:24px;font-weight:400;letter-spacing:-.5px;color:var(--text-bright);line-height:1;font-family:var(--mono)}
  .metric-val .u{font-size:12px;color:var(--text-dim);margin-left:2px}

  .field{display:flex;flex-direction:column;gap:5px;margin-bottom:14px}
  .field label{font-size:10px;font-weight:600;letter-spacing:.5px;text-transform:uppercase;color:var(--text-dim)}
  .row{display:flex;gap:14px;flex-wrap:wrap;align-items:flex-end}.row .field{flex:1;min-width:120px}
  input:not([type]),input[type=text],input[type=password],input[type=number],select{font-family:var(--mono);font-size:12px;background:#fff;color:var(--text-bright);border:1px solid var(--border);border-radius:3px;padding:8px 10px;outline:none;width:100%;transition:border-color .15s,box-shadow .15s}
  input:focus,select:focus{border-color:var(--wx);box-shadow:0 0 0 2px rgba(79,129,189,.18)}
  /* Clean, consistent dropdowns: replace the cramped native arrow with a custom
     chevron + room for it, so text no longer collides with the control. */
  select{appearance:none;-webkit-appearance:none;-moz-appearance:none;cursor:pointer;padding-right:26px;
    background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='11' height='11' viewBox='0 0 24 24' fill='none' stroke='%238a94a6' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>");
    background-repeat:no-repeat;background-position:right 8px center;background-size:11px;text-overflow:ellipsis}
  select::-ms-expand{display:none}
  input:disabled{background:var(--surface3);opacity:.6;cursor:default}
  /* S5: branded keyboard-focus ring. :focus-visible fires only for keyboard nav,
     so mouse clicks stay outline-free. Covers buttons, tabs, toggles, links, and
     any element given tabindex (the controls S4 makes keyboard-operable). */
  :focus-visible{outline:2px solid var(--wx);outline-offset:2px;border-radius:2px}

  .btn{font-family:var(--sans);font-size:11px;font-weight:500;padding:7px 14px;border-radius:3px;border:1px solid var(--border);background:#fff;color:var(--text-mid);cursor:pointer;transition:all .15s;white-space:nowrap}
  .btn:hover{border-color:var(--border-h);color:var(--text-bright);background:var(--surface3)}
  .btn:disabled{opacity:.45;cursor:default}
  .btn.primary{background:var(--navy);border-color:var(--navy);color:#fff}.btn.primary:hover{background:var(--navy-d)}
  .btn.danger{border-color:rgba(192,57,43,.4);color:var(--red);background:#fff}.btn.danger:hover{background:rgba(192,57,43,.07)}
  .btn.sm{padding:4px 9px;font-size:10px}
  body.viewer .btn.primary,body.viewer .btn.danger{opacity:.45;pointer-events:none}
  /* Standard action-button cluster (Home app cards, table action columns, toolbars):
     compact, evenly spaced, UNIFORM-WIDTH buttons so they line up across rows
     instead of going ragged. Use a div.actions in flex layouts, or td.actions on a
     table's action column. Roll-out standard - do not re-style action buttons per view. */
  .actions:not(td){display:flex;gap:6px;align-items:center;justify-content:flex-end;flex-wrap:nowrap}
  td.actions{text-align:right;white-space:nowrap}   /* stays a real table cell — NO flex (keeps row height + vertical-align) */
  .actions .btn,td.actions .btn{padding:4px 11px;font-size:10px;min-width:64px;text-align:center}
  td.actions .btn+.btn{margin-left:6px}

  .pill{display:inline-flex;align-items:center;gap:5px;padding:2px 8px;border-radius:3px;font-family:var(--mono);font-size:10px;font-weight:500;white-space:nowrap}
  /* legacy state pills — KEPT, colours now drawn from the locked status tokens. */
  .pill.on{background:rgba(46,158,91,.1);color:var(--st-ok);border:1px solid rgba(46,158,91,.3)}
  .pill.off{background:var(--surface3);color:var(--text-dim);border:1px solid var(--border)}
  .pill.warn{background:rgba(184,134,11,.12);color:var(--st-warn);border:1px solid rgba(184,134,11,.3)}
  .pill.info{background:rgba(79,129,189,.1);color:var(--wx);border:1px solid rgba(79,129,189,.35)}
  .src-pill{font-family:var(--mono);font-size:9px;padding:1px 7px;border-radius:3px;border:1px solid var(--border);color:var(--text-dim);text-transform:uppercase}

  /* S4: styled so it renders identically whether markup is a <div> or a real
     <button class="toggle" role="switch"> (the accessible form). */
  .toggle{position:relative;width:38px;height:21px;border-radius:11px;background:var(--surface3);border:1px solid var(--border);cursor:pointer;transition:all .15s;flex-shrink:0;display:inline-block;appearance:none;-webkit-appearance:none;padding:0;margin:0;font:inherit}
  .toggle.on{background:var(--wx);border-color:var(--wx)}
  .toggle::after{content:'';position:absolute;top:2px;left:2px;width:15px;height:15px;border-radius:50%;background:#fff;transition:all .15s var(--ease)}
  .toggle.on::after{transform:translateX(17px)}

  table{width:100%;border-collapse:collapse;font-size:12px}
  th{text-align:left;padding:9px 12px;font-size:9px;font-weight:600;letter-spacing:1px;text-transform:uppercase;color:var(--text-dim);border-bottom:1px solid var(--border);white-space:nowrap}
  td{padding:9px 12px;border-bottom:1px solid var(--border);vertical-align:middle;color:var(--text-mid)}
  tbody tr:hover td{background:rgba(79,129,189,.05)}
  .kv{display:flex;justify-content:space-between;padding:7px 0;border-bottom:1px solid var(--border);font-size:12px}
  .kv:last-child{border-bottom:none}.kv .k{color:var(--text-dim)}.kv .v{color:var(--text-bright);font-family:var(--mono)}
  /* S8: long tag paths / DNs / endpoints wrap instead of overflowing their row */
  .kv{min-width:0}.kv .v{overflow-wrap:anywhere;word-break:break-word;min-width:0}
  code,.mono{overflow-wrap:anywhere}
  .note{font-size:11px;color:var(--text-dim);margin-bottom:14px;line-height:1.6}
  .result{font-size:11px;font-family:var(--mono);margin-top:10px;min-height:16px}
  .result.ok{color:var(--green)}.result.err{color:var(--red)}

  /* chart */
  .chart-wrap{position:relative;width:100%;height:340px}
  canvas{width:100%;height:100%;display:block}
  .range-btns{display:flex;gap:4px}
  .range-btns .btn.active{background:var(--navy);border-color:var(--navy);color:#fff}
  .legend{display:flex;gap:14px;flex-wrap:wrap;margin-top:10px}
  .legend .li{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--text-mid)}
  .legend .sw{width:12px;height:3px;border-radius:2px}
  .pick-list{display:flex;flex-wrap:wrap;gap:8px}
  .pick{display:flex;align-items:center;gap:6px;font-size:11px;font-family:var(--mono);padding:5px 10px;border:1px solid var(--border);border-radius:3px;cursor:pointer;background:#fff;color:var(--text-mid)}
  .pick.on{border-color:var(--wx);background:rgba(79,129,189,.08);color:var(--navy)}

  /* tag browser modal */
  .modal-bg{position:fixed;inset:0;background:rgba(26,31,36,.45);z-index:var(--z-modal);display:none;align-items:flex-start;justify-content:center;padding:60px 16px}
  .modal-bg.show{display:flex}
  .modal{width:560px;max-width:100%;background:var(--surface);border:1px solid var(--border);border-radius:4px;box-shadow:0 8px 30px rgba(31,73,125,.25);overflow:hidden;display:flex;flex-direction:column;max-height:80vh}
  .modal-head{background:var(--masthead);color:#fff;padding:12px 16px;display:flex;justify-content:space-between;align-items:center}
  .modal-head .t{font-size:12px;font-weight:600;letter-spacing:.6px;text-transform:uppercase}.modal-head .x{cursor:pointer;color:rgba(255,255,255,.7);font-size:18px}
  .modal-body{padding:14px 16px;overflow:hidden;display:flex;flex-direction:column;gap:10px}
  .tag-list{overflow-y:auto;border:1px solid var(--border);border-radius:3px;max-height:48vh}
  .tag-item{display:flex;justify-content:space-between;gap:10px;padding:8px 12px;border-bottom:1px solid var(--border);cursor:pointer;font-family:var(--mono);font-size:12px;color:var(--text-mid)}
  .tag-item:hover{background:rgba(79,129,189,.07);color:var(--text-bright)}
  .tag-item>*{min-width:0}.tag-item>span,.tag-item .nm{overflow-wrap:anywhere;word-break:break-word}
  .tag-item .dt{font-size:10px;color:var(--text-dim);background:var(--surface3);border-radius:3px;padding:1px 7px}
  .tag-item.struct{color:var(--text-dim);cursor:default}.tag-item.struct:hover{background:transparent}

  #login,#pwgate{position:fixed;inset:0;z-index:var(--z-login);background:var(--bg);display:flex;align-items:center;justify-content:center;padding:16px}
  #pwgate{display:none;z-index:var(--z-pwgate)}
  /* S2: card shrinks on sub-360px phones instead of overflowing the viewport */
  .login-card{width:min(360px,calc(100% - 32px));background:var(--surface);border:1px solid var(--border);border-radius:4px;overflow:hidden;box-shadow:0 2px 12px rgba(31,73,125,.12)}
  .login-head{background:var(--masthead);color:#fff;padding:18px 22px;text-align:center}
  .login-body{padding:22px}.login-card h1{font-size:15px;font-weight:600;color:var(--text-bright);margin-bottom:4px}
  .login-card .sub{font-size:11px;color:var(--text-dim);margin-bottom:18px}
  .pw-show{font-size:10px;color:var(--wx);cursor:pointer;user-select:none;align-self:flex-end;margin-top:-2px}

  /* ---- WxTree: shared collapsible tri-state tree (wxtree.js) ---- */
  .wxt-grp,.wxt-leaf{display:flex;align-items:center;gap:8px;padding:5px 10px;border-bottom:1px solid var(--border);font-size:12px}
  .wxt-grp{background:var(--surface2);cursor:default;padding-left:calc(10px + var(--d,0)*16px)}
  .wxt-leaf{padding-left:calc(30px + var(--d,0)*16px)}
  .wxt-leaf:hover{background:rgba(79,129,189,.05)}
  .wxt-caret{appearance:none;-webkit-appearance:none;background:none;border:0;cursor:pointer;color:var(--text-dim);font-size:11px;width:14px;padding:0;line-height:1}
  .wxt-glabel{font-weight:600;color:var(--navy)}
  .wxt-gcount{color:var(--text-dim);font-size:10px}
  .wxt-lname{font-family:var(--mono);color:var(--text-bright);overflow-wrap:anywhere;min-width:0}
  .wxt-badge{font-family:var(--mono);font-size:10px;color:var(--text-dim);background:var(--surface3);border:1px solid var(--border);border-radius:3px;padding:0 6px}
  .wxt-added{font-size:9px;text-transform:uppercase;letter-spacing:.5px;color:var(--text-dim);border:1px solid var(--border);border-radius:3px;padding:0 5px}
  .wxt-lextra{margin-left:auto;display:flex;align-items:center;gap:10px}
  .wxt-leaf.wxt-dis{opacity:.5}
  .wxt-leaf.wxt-dis .wxt-lname{color:var(--text-dim)}
  .wxt-empty{padding:20px;text-align:center;color:var(--text-dim);font-size:12px}
  /* ---- editable config trees (dnp3 point map + connector tag/point maps) ----
     Fixed-width control columns so every leaf row lines up; read-only by default
     until Edit is enabled (see WxTree.editLock). Apply these to the controls a
     leafExtra() renders so rows align into columns regardless of value length. */
  .wxt-lextra select,.wxt-lextra input{margin-bottom:0;height:28px;box-sizing:border-box}
  .wxt-csel{width:140px}     /* enum / type dropdown (e.g. DNP3 type, datatype) */
  .wxt-cnum{width:80px}      /* numeric field (index, register addr, db, start) */
  .wxt-csm{width:92px}       /* small enum (class) / short field */
  .wxt-cwide{width:180px}    /* wider field (register string, CIP tag, address) */
  .wxt-leaf select:disabled,.wxt-leaf input:disabled,.wxt-drawer select:disabled,.wxt-drawer input:disabled{opacity:.7;cursor:default;background:var(--surface2)}  /* locked (view) mode reads cleanly (leaf row + peel-down drawer) */
  .wxt-grp input[type=checkbox],.wxt-leaf input[type=checkbox],.wxt-leaf input[type=radio]{width:auto;cursor:pointer;flex-shrink:0}
  /* Advanced-detail drawer (cfg.leafDrawer): the point "opens" like an envelope — the
     open row + the drawer share ONE lifted surface, a navy left spine and an outer
     shadow, so the dropped-down detail is unmistakably part of its parent point. */
  .wxt-dcaret{border:none;background:none;cursor:pointer;color:var(--text-dim);font-size:12px;line-height:1;padding:3px 7px;border-radius:4px;flex-shrink:0}
  .wxt-dcaret:hover{color:var(--navy);background:var(--surface3)}
  .wxt-leaf.wxt-open{background:var(--surface);box-shadow:inset 3px 0 0 var(--navy);border-bottom-color:transparent;position:relative;z-index:1}
  .wxt-leaf.wxt-open .wxt-dcaret{color:var(--navy)}
  .wxt-drawer{background:var(--surface);box-shadow:inset 3px 0 0 var(--navy),0 4px 7px -4px rgba(26,42,68,.20);
    border-bottom:1px solid var(--border-h);margin-bottom:3px;
    padding:11px 16px 13px calc(30px + var(--d,0)*16px + 20px);
    display:flex;flex-wrap:wrap;gap:9px 16px;align-items:center;font-size:12px;color:var(--text-mid)}
  /* animate only the user-initiated open (wxtree sets .wxt-anim then) — live-value
     rebuilds recreate the drawer DOM every tick and must not replay it (blink) */
  .wxt-drawer.wxt-anim{animation:wxt-drawer-open .15s var(--ease)}
  .wxt-drawer label{display:flex;align-items:center;gap:6px;white-space:nowrap}
  .wxt-drawer .note{color:var(--text-dim);font-size:11px}
  /* grouped sub-sections within a drawer (e.g. Event triggers vs Scaling) */
  .wxt-dgrp{display:inline-flex;align-items:center;gap:7px 12px;flex-wrap:wrap;padding:5px 11px;border:1px solid var(--border);border-radius:5px;background:var(--surface2)}
  .wxt-dgrp-l{font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.6px;color:var(--text-dim)}
  .wxt-dtrig{display:inline-flex;align-items:center;gap:6px}
  .wxt-dtrig:not(.on) input[type=number]{opacity:.45}
  .wxt-dunit{color:var(--text-dim);font-size:10px;margin-left:-2px}
  .wxt-drep{display:inline-flex;align-items:center;gap:14px;padding-left:14px;border-left:1px solid var(--border)}
  .wxt-dchk{font-weight:500}
  .wxt-dright{margin-left:auto}
  @keyframes wxt-drawer-open{from{opacity:0;transform:translateY(-5px)}to{opacity:1;transform:none}}
  @media(max-width:640px){ .wxt-drawer{padding-left:18px;flex-direction:column;align-items:stretch} .wxt-drep{border-left:none;padding-left:0} .wxt-dright{margin-left:0} }
  /* Sticky, right-aligned column header for an editable config tree. Place a
     .wxt-colhdr as the first child of the tree's scroll container; give each
     label cell the SAME width class (.wxt-csm/.wxt-cwide/.wxt-csel/.wxt-cnum) as
     the leaf control it captions so the header lines up over the .wxt-lextra
     columns (which are margin-left:auto, hence the leading .wxt-ch-grow). */
  .wxt-colhdr{position:sticky;top:0;z-index:2;display:flex;align-items:center;gap:10px;padding:6px 10px;background:var(--surface2);border-bottom:1px solid var(--border);font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--text-dim)}
  .wxt-colhdr .wxt-ch-grow{flex:1 1 auto;min-width:0}
  .wxt-colhdr>span:not(.wxt-ch-grow){flex-shrink:0;text-align:left}
  .wxt-colhdr .wxt-ch-btn{width:30px}
  .wxt-colhdr .wxt-ch-w{width:30px;text-align:center}
  /* converted span->button icon/text controls (S4 a11y): strip default button chrome */
  .rm,.pw-show,.modal-head .x,.modal .x{appearance:none;-webkit-appearance:none;background:transparent;border:0;box-shadow:none;font-family:inherit}

  /* S7: enlarge primary touch targets on touch/coarse-pointer devices only,
     so desktop density is unchanged. */
  @media(pointer:coarse){
    .btn{min-height:40px}
    .btn.sm{min-height:36px;padding:6px 12px}
    .tab-btn{min-height:44px}
    .pick{min-height:36px}
    .toggle{width:44px;height:24px}
    .toggle::after{width:18px;height:18px}
    .toggle.on::after{transform:translateX(20px)}
  }
  /* ---- WxLive: trust-critical liveness pill (wxlive.js) ----
     dot + label + "Ns ago". State class is set by the component from age alone.
     Colours come straight from the locked status tokens; green (is-live) is the
     ONLY socket-confirmed state — is-http (degraded transport) is slate, never green. */
  .wx-live{display:inline-flex;align-items:center;gap:var(--sp-2);padding:2px 8px;border-radius:var(--radius);font-family:var(--mono);font-size:var(--fs-1);font-weight:500;border:1px solid var(--border);background:var(--surface2);color:var(--text-mid);white-space:nowrap}
  .wx-live .wx-live-dot{width:8px;height:8px;border-radius:50%;background:var(--text-dim);flex-shrink:0}
  .wx-live .wx-live-lbl{font-weight:600;letter-spacing:.5px;text-transform:uppercase}
  .wx-live .wx-live-ago{color:var(--text-dim);font-variant-numeric:tabular-nums}
  .wx-live.is-live{color:var(--st-ok);border-color:rgba(46,158,91,.3)}
  .wx-live.is-live .wx-live-dot{background:var(--st-ok);box-shadow:0 0 0 3px rgba(46,158,91,.18);animation:wx-live-pulse 2s var(--ease) infinite}
  .wx-live.is-http{color:var(--st-stale);border-color:rgba(100,116,139,.35)}
  .wx-live.is-http .wx-live-dot{background:var(--st-stale)}
  .wx-live.is-stale{color:var(--st-warn);border-color:rgba(184,134,11,.3)}
  .wx-live.is-stale .wx-live-dot{background:var(--st-warn)}
  .wx-live.is-down{color:var(--st-fault);border-color:rgba(192,57,43,.35)}
  .wx-live.is-down .wx-live-dot{background:var(--st-fault)}
  /* Topbar liveness pills are RETIRED (2026-07-05): the chip row read as
     clutter. The component stays for in-page use; only topbar placement hides. */
  .topbar .wx-live,.topbar-right .wx-live{display:none!important}
  /* On the navy topbar the liveness pill drops its light chip (which only reads
     well on a light surface) and matches the brightened, box-free treatment of
     .be-badge/.live - so it reads as part of the bar, not a sticker on it. */
  .topbar .wx-live,.topbar-right .wx-live{background:transparent;border:none;padding:0;gap:7px;color:#cfe0f2;font-size:10px}
  .topbar .wx-live .wx-live-lbl,.topbar-right .wx-live .wx-live-lbl{font-weight:700;letter-spacing:2px;color:#cfe0f2}
  .topbar .wx-live .wx-live-ago,.topbar-right .wx-live .wx-live-ago{color:rgba(255,255,255,.55)}
  .topbar .wx-live .wx-live-dot,.topbar-right .wx-live .wx-live-dot{box-shadow:0 0 0 2px rgba(255,255,255,.12)}
  .topbar .wx-live.is-live .wx-live-lbl,.topbar-right .wx-live.is-live .wx-live-lbl{color:#9fe0a9}
  .topbar .wx-live.is-live .wx-live-dot,.topbar-right .wx-live.is-live .wx-live-dot{box-shadow:0 0 0 2px rgba(46,158,91,.3)}
  .topbar .wx-live.is-stale .wx-live-lbl,.topbar-right .wx-live.is-stale .wx-live-lbl{color:#f0ce7a}
  .topbar .wx-live.is-down .wx-live-lbl,.topbar-right .wx-live.is-down .wx-live-lbl{color:#f0a0a0}
  .topbar .wx-live.is-http .wx-live-lbl,.topbar-right .wx-live.is-http .wx-live-lbl{color:#b8c4d4}
  @keyframes wx-live-pulse{0%,100%{box-shadow:0 0 0 3px rgba(46,158,91,.18)}50%{box-shadow:0 0 0 3px rgba(46,158,91,.04)}}

  /* On narrow screens, stack the fixed-width editable-leaf controls vertically so
     a config row's select/number/class/wide fields don't overflow their leaf. */
  @media(max-width:640px){
    .wxt-lextra{flex-direction:column;align-items:stretch;gap:var(--sp-1)}
    .wxt-csel,.wxt-cnum,.wxt-csm,.wxt-cwide{width:100%}
    /* leaf controls stack vertically here, so the aligned column header no longer
       maps onto them — hide it rather than show a misaligned guide. */
    .wxt-colhdr{display:none!important}
    /* keep the login card inside the viewport on the smallest phones */
    .login-card{min-width:0;overflow-x:hidden}
  }

  /* S6: respect the OS "reduce motion" setting — neutralise transitions/animations */
  @media(prefers-reduced-motion:reduce){
    *,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important;scroll-behavior:auto!important}
  }

  /* ==========================================================================
     Dark "control-room" mode — OPT-IN (light is the default). wxtheme.js sets
     data-theme="dark" on <html> and persists the choice. Values derive from the
     same hues as light; the masthead drops to near-black to match the PDF report's
     brand band. Token-driven rules re-theme automatically; the rules below repaint
     the few surfaces that hardcode a light colour and so can't follow a token.
     ========================================================================== */
  html[data-theme=dark]{
    --navy:#5B8FD6;--navy-d:#3F6FB0;
    --bg:#0E1420;--surface:#161E2E;--surface2:#1B2536;--surface3:#232F44;
    --border:#2A374F;--border-h:#3A4A66;
    --text:#E7EDF6;--text-bright:#F2F6FC;--text-mid:#AEBACD;--text-dim:#7E8DA6;
    --wx:#5B8FD6;--green:#3FBE72;--ok:#3FBE72;--red:#EB6A54;--amber:#E0A93B;
    --meter:#6E7C96;
    --st-ok:#3FBE72;--st-warn:#E0A93B;--st-fault:#EB6A54;--st-stale:#8A99B3;
    --masthead:#0A0F1A;--masthead-txt:#EAF0F9;
    --chart-ink:#5B8FD6;--chart-grid:rgba(255,255,255,.07);
    --elev-1:0 1px 2px rgba(0,0,0,.4);--elev-2:0 4px 14px rgba(0,0,0,.5);--elev-3:0 10px 34px rgba(0,0,0,.6)
  }
  html[data-theme=dark] input:not([type]),html[data-theme=dark] input[type=text],
  html[data-theme=dark] input[type=password],html[data-theme=dark] input[type=number],
  html[data-theme=dark] select{background:var(--surface2);color:var(--text-bright)}
  html[data-theme=dark] .tab-bar{background:var(--surface)}
  html[data-theme=dark] .btn,html[data-theme=dark] .btn.danger,html[data-theme=dark] .pick{background:var(--surface2)}
  html[data-theme=dark] ::-webkit-scrollbar-thumb{background:rgba(255,255,255,.20)}

  /* ==========================================================================
     Shared feedback components — empty state, skeleton loader, toast. Token-driven,
     so they work in both themes. Replace the audit's blank/silent panels with these.
     ========================================================================== */
  .empty-state{text-align:center;padding:34px 16px;color:var(--text-dim)}
  .empty-state .es-ico{width:38px;height:38px;border-radius:50%;border:1px dashed var(--border-h);display:grid;place-items:center;margin:0 auto 12px;font-size:18px;color:var(--text-dim)}
  .empty-state h4{font-size:13px;color:var(--text-mid);margin-bottom:4px;font-weight:600}
  .empty-state p{font-size:11px;margin-bottom:14px;line-height:1.55}
  .skel{height:12px;border-radius:3px;margin:10px 0;background:linear-gradient(90deg,var(--surface3) 25%,var(--surface2) 37%,var(--surface3) 63%);background-size:400% 100%;animation:wx-shimmer 1.4s ease infinite}
  @keyframes wx-shimmer{0%{background-position:100% 0}100%{background-position:-100% 0}}
  .wx-toasts{position:fixed;right:20px;bottom:20px;display:flex;flex-direction:column;gap:8px;z-index:var(--z-modal)}
  .wx-toast{display:flex;gap:10px;align-items:center;background:var(--surface);border:1px solid var(--border-h);border-left:3px solid var(--st-ok);border-radius:var(--radius);box-shadow:var(--elev-3);padding:11px 15px;font-size:12px;color:var(--text);min-width:200px;animation:wx-toast-in .28s var(--ease)}
  .wx-toast.warn{border-left-color:var(--st-warn)}.wx-toast.err{border-left-color:var(--st-fault)}
  .wx-toast .wx-toast-dot{width:8px;height:8px;border-radius:50%;background:var(--st-ok);flex-shrink:0}
  .wx-toast.warn .wx-toast-dot{background:var(--st-warn)}.wx-toast.err .wx-toast-dot{background:var(--st-fault)}
  @keyframes wx-toast-in{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}

  /* theme toggle button (wxtheme.js injects it into the topbar-right) */
  .wx-theme-btn{appearance:none;-webkit-appearance:none;background:transparent;border:0;cursor:pointer;color:inherit;padding:5px;border-radius:var(--radius);display:inline-flex;align-items:center;justify-content:center;line-height:0;opacity:.85}
  .wx-theme-btn:hover{opacity:1;background:rgba(255,255,255,.12)}
