/* swg-panel — "Relay": a covert network-operations instrument.
   Self-hosted fonts (offline-safe, no third-party fetch). Cool blue-slate field,
   one warm-amber signal, a cool jewel-tone status spectrum. */
@font-face{font-family:"JetBrains Mono";src:url(vendor/fonts/jetbrains-mono-400.woff2) format("woff2");font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:"JetBrains Mono";src:url(vendor/fonts/jetbrains-mono-500.woff2) format("woff2");font-weight:500;font-style:normal;font-display:swap}
@font-face{font-family:"Hanken Grotesk";src:url(vendor/fonts/hanken-400.woff2) format("woff2");font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:"Hanken Grotesk";src:url(vendor/fonts/hanken-500.woff2) format("woff2");font-weight:500;font-style:normal;font-display:swap}
@font-face{font-family:"Hanken Grotesk";src:url(vendor/fonts/hanken-600.woff2) format("woff2");font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:"Hanken Grotesk";src:url(vendor/fonts/hanken-700.woff2) format("woff2");font-weight:700;font-style:normal;font-display:swap}
:root{
  --flowdot:rgba(240,248,255,.82);   /* travelling signal-flow dots (dark) */
  --bg:#0A0E15; --bg2:#070A10; --surface:#10151E; --surface-2:#0C1019; --raise:#161D29; --row-hover:#1E2836;
  --line:#1D2734; --line-bright:#2A3A4E55; --line-solid:#2A3A4E;
  --ink:#E7EEF6; --ink-2:#C2D0E0; --dim:#8EA0B7; --faint:#536679;
  --glow-1:#15212F; --glow-2:#121A27;   /* the two body backdrop radial glows */
  --track:#0A0F18;                       /* inset meter / usage-bar troughs */
  --appbar-bg:rgba(10,14,20,.72);        /* frosted top bar */
  --qr-scrim:rgba(0,0,0,.82);            /* full-screen QR zoom backdrop */
  --online:#3FD89A; --ready:#4FA8F0; --pending:#9A8BF0; --partial:#E2C84A; --fault:#F2994A;
  --partial-r:#F19AA4;   /* the "partial" badge's soft-red (a problem that isn't a stopper) */
  --dangling:#F26B82; --orphan:#B79BD6; --unknown:#62748B; --removing:#E2C84A;
  --brand:#1FC8D6; --brand-2:#5FE2EC; --brand-ink:#04232A; --smart:#F472B6;   /* smart routing — rose/pink, distinct from cascade purple */
  --tp-rx:#1FC8D6; --tp-tx:#818CF8;   /* throughput chart: rx (down) cyan, tx (up) indigo — kept distinct */
  --rate-up:#4FA8F0;   /* throughput UP value/arrow — a fixed blue, decoupled from the ready-status colour so it stays this light in both modes (upload reads slightly lighter than the green download) */
  --sel-ink:var(--brand);   /* text colour for a SELECTED brand-tinted control (Online pill, etc.); darkened on light */
  --dd-hover:color-mix(in srgb,var(--brand) 14%,transparent); --dd-sel:color-mix(in srgb,var(--brand) 22%,transparent);   /* ONE hover/selected tint for every dropdown row app-wide */
  --tport-bare:#7DC4F0; --tport-dock:#A974F2;   /* deploy-method pills (bare-metal blue / docker purple) */
  --sans:"Hanken Grotesk",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,system-ui,sans-serif;
  --mono:"JetBrains Mono",ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
  --r:11px; --r-sm:8px; --shadow:0 24px 60px -14px rgba(0,0,0,.62);
  --ease:cubic-bezier(.2,.7,.2,1);
}
/* LIGHT theme — the whole palette is variable-driven, so this only re-defines the structural surfaces / ink /
   lines / troughs / glows / shadow. The status + brand accents (green/cyan/orange/etc.) carry across both modes;
   the per-mode brand accent is injected by applyThemeColors(). Selected via <html data-theme="light">. */
:root[data-theme="light"]{
  --flowdot:rgba(255,255,255,.9);   /* travelling signal-flow dots (light) — bright pips on the coloured lines */
  --bg:#EDF1F7; --bg2:#E1E7F0; --surface:#FFFFFF; --surface-2:#F4F7FB; --raise:#FFFFFF; --row-hover:#E7EEF7;
  --line:#DCE3EC; --line-bright:#C3D0DE99; --line-solid:#C3D0DE;
  --ink:#101822; --ink-2:#33424F; --dim:#586878; --faint:#8695A4;
  --glow-1:#DEE9F6; --glow-2:#E7EEF7;
  --track:#E2E8F0;
  --appbar-bg:rgba(255,255,255,.78);
  --qr-scrim:rgba(226,232,240,.92);   /* light grey (the --track family) so the white QR sits on grey, not black */
  --shadow:0 20px 50px -18px rgba(30,52,84,.22);
  /* Status accents re-cut for a WHITE background: the dark-mode neons are too light to read here, so each is
     deepened to hold ~4.5:1 as text while its 12–16% tint stays a soft, legible chip. Same hues, more body. */
  --online:#0E9E63; --ready:#2B7CD3; --pending:#6A50D6; --partial:#B07A16; --fault:#D9772A;
  --partial-r:#D65468; --dangling:#D63A55; --orphan:#8A5CC4; --unknown:#5E7085; --removing:#B07A16;
  --smart:#D6247E;
  --tport-bare:#2472C4; --tport-dock:#8B45D0;   /* deploy pills deepened for white (point 6) */
  --sel-ink:color-mix(in srgb, var(--brand) 58%, #000);   /* a SELECTED control's text is a darker shade of the theme on white */
}
/* LIGHT-mode component tweaks that a variable swap can't express. */
/* Hover-bubble (peers/users online list, "connected via", status "why") headers read a touch darker on white. */
:root[data-theme="light"] .turnbub{color:var(--ink-2)}
/* the online-peers bubble header sits on a tinted band (a bit darker than the theme) with a darker-theme label. */
:root[data-theme="light"] .onlpop .onpop-h{color:var(--sel-ink);background:color-mix(in srgb,var(--brand) 20%,transparent);border-radius:7px}
:root[data-theme="light"] .onlpop a.onpop-h:hover{color:var(--ink)}   /* dark on hover — always visible even if the theme colour is light (cyan-on-cyan-band washes out) */
*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scrollbar-gutter:stable}   /* reserve the scrollbar space so the layout doesn't jump when one appears (e.g. a card re-render during a turn-proxy restart) */
/* Thin, dark, arrow-less scrollbars everywhere (matches the inner containers like .targetpick) so the panel
   looks the same in any browser — not the chunky classic bar some Chromium builds fall back to. */
*{scrollbar-width:thin;scrollbar-color:var(--line-solid) transparent}
::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-thumb{background:var(--line-solid);border-radius:6px;border:2px solid transparent;background-clip:padding-box}
::-webkit-scrollbar-thumb:hover{background:var(--dim);background-clip:padding-box}
::-webkit-scrollbar-track,::-webkit-scrollbar-corner{background:transparent}
::-webkit-scrollbar-button{display:none;width:0;height:0}
body{
  margin:0;color:var(--ink);font-family:var(--sans);font-size:15px;line-height:1.5;min-height:100vh;
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
  background:
    radial-gradient(1200px 560px at 88% -10%, var(--glow-1) 0%, transparent 60%),
    radial-gradient(820px 480px at -6% -6%, var(--glow-2) 0%, transparent 55%),
    var(--bg);
  background-attachment:fixed;
}
.mono{font-family:var(--mono)}
.dim{color:var(--dim)} .faint{color:var(--faint)}
a{color:inherit;text-decoration:none}
::selection{background:color-mix(in srgb,var(--brand) 32%,transparent);color:var(--ink)}   /* --ink adapts per theme so selected text stays legible in light AND dark */
:focus-visible{outline:2px solid var(--brand);outline-offset:2px;border-radius:5px}
/* buttons / toggles / tabs carry their own hover + active/selected feedback, so the full-strength global ring read
   as a stray heavy border on them — soften it here, but keep it VISIBLE for keyboard navigation (:focus-visible ⇒
   plain mouse clicks stay ring-free). Text inputs keep their own focus styling. */
button:focus-visible,[role=button]:focus-visible,.tabs a:focus-visible,summary:focus-visible,label:focus-visible{outline:2px solid color-mix(in srgb,var(--brand) 60%,transparent);outline-offset:2px}
@media(prefers-reduced-motion:reduce){*{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}
button{font-family:inherit}
h1,h2,h3{font-family:var(--sans)}

/* ───── app bar (command strip) ───── */
.appbar{position:sticky;top:0;z-index:30;display:flex;align-items:center;gap:22px;height:56px;padding:0 22px;
  background:var(--appbar-bg);backdrop-filter:blur(16px) saturate(1.3);border-bottom:1px solid var(--line)}
.brand{display:flex;align-items:center;gap:10px}
/* indicator-LED mark: an amber status light */
.brand-mark{width:18px;height:18px;border-radius:6px;background:radial-gradient(circle at 50% 38%,var(--brand-2),var(--brand));
  box-shadow:0 0 0 1px color-mix(in srgb,var(--brand) 35%,transparent),0 0 14px color-mix(in srgb,var(--brand) 50%,transparent);position:relative}
.brand-mark::after{content:"";position:absolute;inset:6px;border-radius:50%;background:var(--bg)}
.brand-name{font-weight:700;letter-spacing:.01em;font-size:16px;color:var(--brand)}
.brand-name span{color:var(--ink-2);font-weight:600}
.appver{font-family:var(--mono);font-size:10.5px;color:var(--faint);white-space:nowrap;letter-spacing:-.02em;margin-right:16px}
.appver b{color:var(--dim);font-weight:600}
.tabs{display:flex;gap:2px}
.tabs a{padding:8px 11px;font-family:var(--mono);font-size:10.5px;text-transform:uppercase;letter-spacing:.1em;
  color:var(--faint);font-weight:500;transition:color .15s;position:relative}
.tabs a:hover{color:var(--ink-2)}
.tabs a.active{color:var(--ink)}
.tabs a.active::after{content:"";position:absolute;left:11px;right:11px;bottom:-1px;height:2px;border-radius:2px;
  background:var(--brand);box-shadow:0 0 9px color-mix(in srgb,var(--brand) 55%,transparent)}
.appbar-right{margin-left:auto;display:flex;align-items:center;gap:14px}
.livepill{display:flex;align-items:center;gap:7px;font-size:11px;text-transform:uppercase;letter-spacing:.08em;
  color:var(--dim);font-family:var(--mono);padding:5px 11px;border:1px solid var(--line-solid);border-radius:99px;background:var(--surface-2)}
.livepill b{color:var(--online);font-weight:600;font-size:13px;letter-spacing:-.02em}
/* the node-detail update lifecycle pills (update to … / updating / up to date) — sized to match the reporting
   and docker/bare-metal badges (10px, tighter padding), a touch smaller than a normal livepill.
   Asymmetric top/bottom padding nudges the mono label ~1px down so it optically centres against the icon
   (the icon is pulled back up 1px so it stays put). */
.updpill,.upd-busy,.upd-uptodate{font-size:10px;padding:4px 9px 2px;gap:6px;letter-spacing:.06em;line-height:1}
button.upd-uptodate{cursor:pointer}   /* up-to-date pill is clickable → re-run/repair (heals the datapath with no new version) */
button.upd-uptodate:hover{border-color:var(--online);background:color-mix(in srgb,var(--online) 12%,transparent)}
.updpill b,.upd-busy b,.upd-uptodate b{font-size:11px}
.updpill svg,.upd-busy svg,.upd-uptodate svg{width:11px;height:11px;position:relative;top:-1px}
/* on the panel header (appbar) these pills sit next to the ONLINE / bare-metal pills — stretch to the same
   row height so the header reads as one uniform band; align-items:center keeps the label+icon centred, and
   the 1px padding-top bias still nudges the mono label down to sit on the icon's centre line */
.appbar-right .updpill,.appbar-right .upd-busy,.appbar-right .upd-uptodate{font-size:11px;padding:9px 12px 8px;letter-spacing:.08em}
.appbar-right .updpill b,.appbar-right .upd-busy b,.appbar-right .upd-uptodate b{font-size:12px}
.appbar-right .updpill svg,.appbar-right .upd-busy svg,.appbar-right .upd-uptodate svg{width:13px;height:13px}
.livepill .dot{width:7px;height:7px;border-radius:50%;background:var(--online);box-shadow:0 0 8px color-mix(in srgb,var(--online) 55%,transparent);animation:pulse 2.6s infinite}
@keyframes pulse{0%,100%{opacity:1;box-shadow:0 0 0 0 color-mix(in srgb,var(--online) 45%,transparent)}50%{opacity:.55}70%{box-shadow:0 0 0 6px color-mix(in srgb,var(--online) 0%,transparent)}}
.livepill.off b{color:var(--dim)}                /* no peers online → grey count … */
.livepill.off .dot{display:none}                 /* … and no glowing dot */
/* this panel host is mid re-install/convert (header slot, holds through the panel's own downtime) */
.hostproc-tag{display:inline-flex;align-items:center;gap:7px;font-family:var(--mono);font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--partial);border:1px solid var(--partial);border-radius:99px;padding:4px 11px;background:color-mix(in srgb,var(--partial) 10%,transparent)}   /* in-progress = YELLOW (matches the .upd-busy pill, so the header tag doesn't flip yellow→purple mid-update) */
.hostproc-tag.ok{color:var(--online);border-color:var(--online);background:rgba(63,216,154,.1)}        /* host re-install succeeded — green, auto-clears */
.hostproc-tag.aborted{color:var(--unknown);border-color:var(--unknown);background:color-mix(in srgb,var(--unknown) 12%,transparent)}  /* host re-install aborted — grey, dismissable */
.hostproc-tag.fail,.hostproc-tag.procuninstall{color:var(--dangling);border-color:var(--dangling);background:color-mix(in srgb,var(--dangling) 10%,transparent)}   /* failed / uninstalling — RED (error popup + × on fail) */
.hostproc-tag.procconvert{color:var(--pending);border-color:var(--pending);background:color-mix(in srgb,var(--pending) 10%,transparent)}   /* converting — PURPLE */
.hostproc-tag .xbtn{margin-left:4px;padding:1px}
.updslot{display:inline-flex;align-items:center;gap:14px}   /* match .appbar-right gap so HC ↔ update spacing is even */
/* the update-to pill is TEXT-ONLY (no icon), so it drops the icon-optical padding nudge from .updpill,.upd-busy,…
   above and uses SYMMETRIC vertical padding — the "update … to <version>" content sits dead-centre in the pill. */
.updpill{cursor:pointer;border-color:color-mix(in srgb,var(--ready) 42%,var(--line-solid));padding:4px 10px 3px;font-size:9.5px}   /* small (node) pill: a hair smaller; near-symmetric padding (a touch more top) optically centres the caps */
.appbar-right .updpill{padding:8px 12px;font-size:11px}
.updpill b{color:var(--ready);font-size:10.5px;font-weight:650}     /* the version — blue, only a hair larger than the label so the pill reads balanced */
.appbar-right .updpill b{font-size:11.5px}
.updpill:hover{border-color:var(--ready);background:color-mix(in srgb,var(--ready) 12%,transparent)}
/* fix pill — self-heal issues found; amber (attention), distinct from the blue "update to" pill */
.fixpill{border-color:color-mix(in srgb,#e0a83a 45%,var(--line-solid));color:#b7791f}
.fixpill b{color:#b7791f}
.fixpill svg{width:12px;height:12px;position:relative;top:-1px}
.fixpill:hover{border-color:#e0a83a;background:color-mix(in srgb,#e0a83a 12%,transparent)}
:root[data-theme="dark"] .fixpill,:root[data-theme="dark"] .fixpill b{color:#e6b84f}

.btn{cursor:pointer;border-radius:var(--r-sm);font-size:13px;border:1px solid transparent;transition:all .15s var(--ease);
  display:inline-flex;align-items:center;gap:7px;white-space:nowrap}
/* GLOBAL: every icon centres its own glyph so it sits dead-centre in a button/row — kills the inline-baseline gap
   that made SVGs sit high. Flex containers (.btn etc.) then centre the .ic item vertically via align-items. */
.ic{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}
.ic svg{display:block}
.btn-primary{background:var(--brand);color:var(--brand-ink);font-weight:700;padding:9px 15px}
.btn-primary:hover{filter:brightness(1.08);transform:translateY(-1px)}
.btn-primary .plus{font-size:16px;line-height:0;margin-top:-1px}
.btn-ghost{background:var(--surface-2);color:var(--dim);border-color:var(--line-solid);padding:8px 13px}
.btn-ghost:hover{color:var(--ink);border-color:var(--brand)}
.btn-ghost.danger{color:var(--dangling)}
.btn-ghost.danger:hover{color:var(--dangling);border-color:var(--dangling)}
.btn-mini{background:transparent;color:var(--dim);border-color:var(--line-solid);padding:5px 11px;font-size:12px;border-radius:7px}
.btn-mini:hover{color:var(--brand);border-color:var(--brand)}
/* cyan accent for panel-header actions only (Add peer, Load new interface, Edit interface, Adopt all…) */
.panel-head .btn-mini{color:var(--brand);border-color:color-mix(in srgb,var(--brand) 32%,var(--line-solid))}
.panel-head .btn-mini:hover{color:var(--brand);border-color:var(--brand);background:color-mix(in srgb,var(--brand) 10%,transparent)}
.btn-mini.warn:hover{color:var(--dangling);border-color:var(--dangling)}
.btn:disabled{opacity:.45;cursor:not-allowed;transform:none;filter:none}
.iconbtn:disabled,.livepill:disabled{opacity:.35;cursor:not-allowed;pointer-events:none}   /* down/converting node → only recovery actions stay live */

/* ───── view + screens ───── */
.view{max-width:1260px;margin:0 auto;padding:18px 22px 64px}
.screen{animation:rise .32s var(--ease)}
@keyframes rise{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.crumb{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--dim);margin-bottom:16px;flex-wrap:wrap}
.crumb a{color:var(--dim)} .crumb a:hover{color:var(--ink)}
.crumb .sep{color:var(--faint)}
.crumb b{color:var(--ink);font-weight:600}
.lbl{font-size:10.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--dim);font-weight:600}
.card{background:linear-gradient(180deg,var(--raise),var(--surface));border:1px solid var(--line);border-radius:var(--r);padding:22px}
.section-title{display:flex;align-items:center;gap:9px;margin:22px 2px 9px;scroll-margin-top:72px}   /* clear the 56px sticky appbar + a ~16px buffer when the rail jump-nav scrolls to a section */
.section-title h2{margin:0;font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--dim);font-weight:500}
.section-title .count{font-family:var(--mono);font-size:11px;color:var(--faint)}
.section-title .nodehint{font-size:11.5px;color:var(--faint);font-weight:400;letter-spacing:normal;text-transform:none;margin-left:8px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.grow{flex:1}   /* generic flex spacer — several rows (activity, flow footer, turn load) relied on this and only had it scoped */
.section-title .grow{flex:1}
@media(max-width:860px){.section-title .nodehint{display:none}}

/* ───── dashboard controls (overview): node selector + range ───── */
.dashbar{display:flex;align-items:flex-start;gap:14px;flex-wrap:wrap;margin-bottom:14px}   /* flex-start so Range lines up with the FIRST node row, not the middle of a wrapped 2-row selector */
.dashbar .dseg{height:26px}   /* slim, so Range shares a centre line with the (slim) first node-badge row */
.dash-nodes{padding-top:1px}
.snbadge{--c:var(--brand);display:inline-flex;align-items:center;gap:6px;font:inherit;font-family:var(--mono);font-size:11.5px;line-height:1;
  padding:5px 11px;border-radius:99px;border:1px solid var(--line);background:none;color:var(--dim);cursor:pointer;
  transition:color .12s,border-color .12s,background .12s,box-shadow .12s;white-space:nowrap}
.ndot{width:7px;height:7px;border-radius:50%;background:var(--c);flex:none}   .snbadge:hover{color:var(--ink);border-color:color-mix(in srgb,var(--c) 50%,var(--line))}
.snbadge.on{color:var(--ink);font-weight:600;border-color:color-mix(in srgb,var(--c) 60%,transparent);background:color-mix(in srgb,var(--c) 15%,transparent)}
.dseg-opt.on{background:color-mix(in srgb,var(--brand) 20%,transparent);color:var(--sel-ink)}
/* Peers/Mesh traffic badges — sit on the two traffic-doughnut card headers (right-aligned), shared state.
   Small + no dot; the ON state uses the saturated component colour (peers = cyan, mesh = violet). */
.dcard-traf{display:inline-flex;align-items:center;gap:5px}
.tbadge{--c:var(--brand);font:inherit;font-family:var(--mono);font-size:10px;font-weight:700;letter-spacing:.04em;line-height:1;
  padding:4px 9px;border-radius:99px;border:1px solid var(--line);background:none;color:var(--faint);cursor:pointer;
  transition:color .12s,border-color .12s,background .12s;white-space:nowrap}
.tbadge.peers{--c:var(--tp-rx)} .tbadge.mesh{--c:#9B8AFF}
.tbadge:hover{color:var(--dim);border-color:color-mix(in srgb,var(--c) 45%,var(--line))}
.tbadge.on{color:var(--c);border-color:color-mix(in srgb,var(--c) 55%,transparent);background:color-mix(in srgb,var(--c) 16%,transparent)}

/* Side rail — three identical menus (jump · ranges · nodes) pinned right-centre, ALWAYS visible. Each is a column of
   icons that slides its labels out when the panel is hovered; each row highlights on hover with the ICON in the theme
   colour (label stays neutral). */
.dashrail{position:fixed;top:50%;right:0;transform:translateY(-50%);z-index:60;pointer-events:none}
.dashrail-stack{display:flex;flex-direction:column;align-items:flex-end;gap:11px;max-height:94vh;pointer-events:none}
.railpanel{display:flex;flex-direction:column;gap:2px;padding:8px 9px;pointer-events:auto;
  background:color-mix(in srgb,var(--surface) 82%,transparent);
  border:1px solid color-mix(in srgb,var(--line-solid) 70%,transparent);border-right:none;border-radius:14px 0 0 14px;
  box-shadow:0 12px 40px rgba(0,0,0,.24);overflow-x:hidden}
.railmenu-nodes{overflow-y:auto;max-height:50vh}
/* uniform menu row */
.railmenu-b{appearance:none;border:none;background:none;cursor:pointer;font:inherit;font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:.02em;
  color:var(--ink-2);border-radius:8px;white-space:nowrap;display:flex;align-items:center;justify-content:flex-end;width:100%;
  transition:background .12s}
.railmenu-ic{display:flex;align-items:center;justify-content:center;flex:none;width:28px;height:28px;color:var(--dim);transition:color .12s}
.railmenu-ic svg{width:17px;height:17px}
.railmenu-emoji{font-size:15px;line-height:1}
.railmenu-t{max-width:0;opacity:0;margin-left:0;overflow:hidden;white-space:nowrap;transition:max-width .3s var(--ease),opacity .24s,margin-left .3s var(--ease)}
.railpanel:hover .railmenu-t{max-width:150px;opacity:1;margin-left:5px}                 /* hover the panel → slide labels out */
.railmenu-b:hover{background:color-mix(in srgb,var(--brand) 12%,transparent)}            /* row highlight */
.railmenu-b:hover .railmenu-ic{color:var(--brand)}                                      /* highlighted icon → theme colour (label stays neutral) */
.railmenu-b.on .railmenu-ic{color:var(--brand)}                                         /* active range → theme-coloured icon */
/* nodes menu — coloured dot: bright when selected, dimmed when off */
.railnode-dot{width:10px;height:10px;border-radius:50%;background:var(--c);transition:opacity .12s,box-shadow .12s}
.railmenu-b.node.off .railnode-dot{opacity:.32}
.railmenu-b.node.on .railnode-dot{box-shadow:0 0 6px 1px color-mix(in srgb,var(--c) 60%,transparent)}
.railmenu-b.node .railmenu-t{color:var(--ink-2)}
.railmenu-b.node.off .railmenu-t{color:var(--faint)}
/* live dot — dimmed & static when live is NOT the selected range; bright + glowing + animated only when selected */
.rlive-dot{position:relative;width:9px;height:9px;border-radius:50%;background:var(--online);opacity:.4}
.railmenu-b.on .rlive-dot{opacity:1;box-shadow:0 0 6px 1px color-mix(in srgb,var(--online) 75%,transparent)}
.railmenu-b.on .rlive-dot::after{content:"";position:absolute;inset:-4px;border-radius:50%;border:1.5px solid var(--online);animation:rlivePulse 1.5s ease-out infinite}
@keyframes rlivePulse{0%{transform:scale(.55);opacity:.75}100%{transform:scale(1.9);opacity:0}}

/* ───── doughnuts (overview) ───── */
.donutgrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
@media(max-width:720px){.donutgrid{grid-template-columns:1fr}}
.donutcard{border:1px solid var(--line);border-radius:var(--r);background:var(--surface);padding:14px 16px 16px}
.donutcard-h{display:flex;align-items:center;gap:8px;margin-bottom:10px;min-height:24px}   /* uniform height so the badge row never nudges the doughnut down vs a title-only card */
.donutcard-h h3{font-size:12.5px;font-weight:600;color:var(--ink);margin:0;letter-spacing:.01em}
.donut-body{display:flex;align-items:center;justify-content:center;gap:22px}   /* doughnut + legend are both fixed-width → the group width is constant, so centring pins the doughnut X and the values' right edge identically across cards, with equal left/right insets */
/* no loading dim — we keep the previous range's data on screen through a fetch, so dimming just reads as a flicker */
.mring{position:relative;flex:none}
.mring svg{display:block}
.mring-seg{cursor:pointer;transition:opacity .12s}
.mring-seg.dim{opacity:.28}
.mring-center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;pointer-events:none;padding:0 11%}
.mrc-def{display:flex;flex-direction:column;gap:1px}
.mrc-def .mrc-k{font-family:var(--mono);font-size:9.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--faint)}
.mrc-def .mrc-tot{font-size:19px;font-weight:600;color:var(--ink);font-family:var(--mono);white-space:nowrap;line-height:1.2}
.mrc-def .mrc-tot.up{color:var(--rate-up)}.mrc-def .mrc-tot.dn{color:var(--online)}
.mrc-hov .mrc-name{font-size:12px;font-weight:600;line-height:1.15;margin-bottom:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
.mrc-hov .mrc-val{font-size:15px;font-weight:600;color:var(--ink);font-family:var(--mono);white-space:nowrap}
.mrc-hov .mrc-sub{font-size:10.5px;color:var(--dim);margin-top:1px}
.mring-leg{display:flex;flex-direction:column;gap:5px;flex:0 0 240px;min-width:0;font-size:12px}   /* fixed width → swatches pin left AND values right-align to the same edge across every card */
.mrl-row{display:flex;align-items:center;gap:7px;min-width:0;padding:2px 5px;margin:0 -5px;border-radius:5px;transition:opacity .12s,background .12s}
.mrl-row.dim{opacity:.3}
.mrl-row.on{background:var(--row-hover)}
.mrl-nm{cursor:default}
.mrl-dv,.mrl-uv{font-family:var(--mono);font-size:11px;flex:none;cursor:default;transition:opacity .12s}
.mrl-dv{color:var(--online)}.mrl-uv{color:var(--rate-up)}
.mrl-dv.vdim,.mrl-uv.vdim{opacity:.28}
.mrl-sw{width:9px;height:9px;border-radius:50%;flex:none;background:var(--sw);
  box-shadow:0 0 6px color-mix(in srgb,var(--sw) 60%,transparent),0 0 2px color-mix(in srgb,var(--sw) 90%,transparent)}
.mrl-nm{color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:150px}   /* cap so a very long server name ellipsizes instead of spreading the legend wide */
.mrl-v{font-family:var(--mono);font-size:11px;color:var(--ink-2);flex:none}
.donut-note{font-family:var(--mono);font-size:10px;color:var(--faint);margin-top:10px;letter-spacing:.02em}

/* ───── live trend cards (overview) ───── */
.trends{display:grid;grid-template-columns:2fr 1fr;gap:10px;margin-top:10px}
@media(max-width:760px){.trends{grid-template-columns:1fr}}
.trendcard{border:1px solid var(--line);border-radius:var(--r);background:var(--surface);padding:12px 14px 10px}
.trend-now{font-family:var(--mono);font-size:18px;font-weight:600;color:var(--online)}
/* online-peers block chart: one seated bar per bucket (fixed low count per range) */
.oblk-wrap{position:relative;display:flex;align-items:flex-end;gap:3px;width:100%}
.oblk{flex:1 1 0;min-width:0;height:100%;display:flex;align-items:flex-end;justify-content:stretch;background:var(--track);border-radius:2px;overflow:hidden;transition:background .12s}
.oblk.hot{background:var(--row-hover)}
.oblk i{display:block;width:100%;border-radius:2px 2px 0 0;transition:height .25s ease}
.harea-empty{display:flex;align-items:center;justify-content:center;height:70px;color:var(--faint);font-size:12px;font-family:var(--mono)}

/* ───── traffic-flow node map (overview) ───── */
.flowcard{border:1px solid var(--line);border-radius:var(--r);background:var(--surface);padding:8px 10px 6px;position:relative}
/* signal-flow map (redesign P1) — SVG flows + HTML badges (like the wg/awg tags) + hover bubbles */
.flowmap2{position:relative;width:100%;max-width:960px;margin:0 auto;container-type:inline-size}
.flowmap2 svg{display:block;width:100%;height:auto}
/* animated "signal flow" — round dashes travelling along each line in its flow direction (egress out / ingress in); speed ∝ traffic (set inline) */
.fm2-flowdot{stroke-dasharray:0.01 15;stroke-linejoin:round;animation:fm2flow linear infinite;pointer-events:none}   /* dense round pips (the original look) */
@keyframes fm2flow{from{stroke-dashoffset:0}to{stroke-dashoffset:-15}}
.fm2-flowdot.dim{opacity:.08}
.fm2-pulse{stroke:var(--flowdot);opacity:.5}           /* soft travelling segment (SMIL dashoffset, no blur → cheap) */
.fm2-pulse.dim{opacity:.06}
.fm2-chev{opacity:.9}                                   /* travelling arrows (SMIL animateMotion — cheapest of the three) */
.fm2-chev.dim{opacity:.08}
@media (prefers-reduced-motion:reduce){.fm2-flowdot,.fm2-pulse,.fm2-chev{animation:none;visibility:hidden}}
/* flow-animation picker (top-right of the map) — faint until you hover the map */
.fm2-anim{position:absolute;top:9px;right:9px;display:flex;gap:2px;padding:2px;border-radius:8px;background:color-mix(in srgb,var(--surface) 82%,transparent);border:1px solid var(--line);z-index:4;opacity:.4;transition:opacity .15s}
.flowcard:hover .fm2-anim{opacity:1}
.fm2-anim-b{display:flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:none;background:none;border-radius:6px;color:var(--faint);cursor:pointer;transition:color .12s,background .12s}
.fm2-anim-b:hover{background:color-mix(in srgb,var(--brand) 12%,transparent);color:var(--ink)}
.fm2-anim-b.on{background:color-mix(in srgb,var(--brand) 20%,transparent);color:var(--brand)}
.fm2-anim-b svg{width:15px;height:15px;display:block}
.fm2-flow{opacity:.72;cursor:pointer;transition:opacity .12s}   /* dark: original balance (looked best on dark) */
.fm2-flow.dim{opacity:.12}
.fm2-flow.lit{opacity:.98}
:root[data-theme="light"] .fm2-flow{opacity:.86}                /* light: pull resting/dimmed lines closer to the lit colour */
:root[data-theme="light"] .fm2-flow.dim{opacity:.26}
:root[data-theme="light"] .fm2-flow.lit{opacity:1}
/* node badge — pill in the node colour, like an interface tag; sized by traffic via inline font-size */
.fm2-nb{position:absolute;transform:translate(-50%,-50%);cursor:pointer;appearance:none;
  font-family:var(--mono);font-weight:600;line-height:1;letter-spacing:.01em;padding:.5em .78em;border-radius:8px;
  border:1.5px solid color-mix(in srgb,var(--c) 70%,transparent);background:color-mix(in srgb,var(--c) 22%,var(--surface));
  color:var(--c);white-space:nowrap;box-shadow:0 0 0 4px color-mix(in srgb,var(--c) 9%,transparent),0 2px 8px rgba(0,0,0,.3);
  transition:opacity .12s,transform .1s;z-index:2}
.fm2-nb:hover{transform:translate(-50%,-50%) scale(1.05)}
.fm2-sb{position:absolute;transform:translate(-50%,-50%);cursor:pointer;appearance:none;border-radius:50%;
  display:flex;align-items:center;justify-content:center;padding:0;
  border:1.5px solid color-mix(in srgb,var(--c) 68%,transparent);background:color-mix(in srgb,var(--c) 20%,var(--surface));
  color:var(--c);box-shadow:0 0 0 3px color-mix(in srgb,var(--c) 8%,transparent),0 2px 8px rgba(0,0,0,.3);transition:opacity .12s,transform .1s;z-index:2}
.fm2-sb svg{width:var(--isz,62%);height:var(--isz,62%);display:block}   /* icon size set inline; grows proportionally with the circle */
.fm2-sb::before{content:"";position:absolute;inset:-6px;border-radius:50%}   /* hit area a touch larger than the circle; icon stays centred (circle is centred in it) */
.fm2-sb:hover{transform:translate(-50%,-50%) scale(1.12)}
/* dim = darken but stay OPAQUE, so a de-emphasised badge still covers (hides) the flow ends cut off inside it */
.fm2-nb.dim,.fm2-sb.dim{filter:brightness(.48) saturate(.55)}
/* related to the hovered element: badge recedes (subdued opaque bg) but its NAME / ICON stays full colour so you can read the link */
.fm2-nb.reldim{background:color-mix(in srgb,var(--c) 8%,var(--surface));border-color:color-mix(in srgb,var(--c) 30%,transparent);box-shadow:0 2px 8px rgba(0,0,0,.3)}
.fm2-sb.reldim{background:color-mix(in srgb,var(--c) 8%,var(--surface));border-color:color-mix(in srgb,var(--c) 30%,transparent);box-shadow:0 2px 8px rgba(0,0,0,.3)}
.fm2-bub{position:absolute;transform:translate(-50%,-118%);pointer-events:none;z-index:5;background:var(--surface-2);
  border:1px solid var(--line-solid);border-radius:9px;padding:8px 11px;min-width:130px;box-shadow:0 6px 22px rgba(0,0,0,.42)}
.fm2-bub-h{margin-bottom:7px;display:flex;flex-direction:column;gap:2px;font:600 12.5px/1.1 var(--sans)}
.fm2-bub-k{font:9px/1 var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--faint)}
.fm2-bub-r{display:flex;justify-content:space-between;gap:18px;font:11.5px/1.7 var(--mono)}
.fm2-bub-r b{color:var(--ink)}
.fm2-key{display:inline-block;width:10px;height:10px;border-radius:2px;vertical-align:middle;margin:0 5px 0 12px}
.fm2-key:first-child{margin-left:0}

/* ───── hero stats (overview) ───── */
.statgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px}
@media(max-width:760px){.statgrid{grid-template-columns:repeat(2,1fr)}}
.stat{background:var(--surface);border:1px solid var(--line);border-radius:var(--r);
  padding:13px 14px;position:relative;overflow:hidden;display:flex;gap:11px;align-items:flex-start}
a.stat{text-decoration:none;color:inherit;transition:border-color .15s var(--ease),background .15s var(--ease)}
a.stat.clk:hover{border-color:var(--line-solid);background:var(--raise)}
.stat-ic{width:30px;height:30px;border-radius:8px;flex:none;display:inline-flex;align-items:center;justify-content:center;
  background:color-mix(in srgb,var(--brand) 14%,transparent);color:var(--brand)}
.stat-ic svg{width:16px;height:16px}
.stat-c{min-width:0}
.stat .k{font-family:var(--mono);font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--faint);font-weight:500}
.stat.accent{border-color:color-mix(in srgb,var(--brand) 28%,transparent)}
.stat.accent .stat-ic{background:color-mix(in srgb,var(--online) 16%,transparent);color:var(--online)}
.stat .v{font-family:var(--mono);font-size:23px;font-weight:600;color:var(--ink);margin-top:6px;letter-spacing:-.02em;line-height:1.1}
.stat .v small{font-size:13px;color:var(--dim);font-weight:500}
.stat .sub{font-size:11.5px;color:var(--faint);margin-top:3px;font-family:var(--mono)}
.stat.accent .v{color:var(--online)}
.stat .spark{position:absolute;right:14px;top:14px;opacity:.9}

/* ───── Overview: Protection (blocking activity) ───── */
.protcard{background:linear-gradient(180deg,var(--raise),var(--surface));border:1px solid var(--line);border-radius:var(--r);overflow:hidden}
.prot-metrics{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:1px;background:var(--line)}
.prot-tile{background:var(--surface);padding:15px 16px 13px;min-width:0;position:relative;display:flex;flex-direction:column}   /* flex-col so a Popover <span> tile stacks its rows exactly like a plain <div> tile */
/* ── "who" bubble — which user / which peers triggered this metric (rendered in a Popover portal) ── */
.prot-k{border-bottom:1px dotted var(--line-solid);padding-bottom:2px}   /* divider under every tile label (consistent across the 3) */
.prot-tile.haswho{transition:background .12s;cursor:help}   /* whole tile is hoverable (has a "who" bubble) → help cursor across it */
.prot-tile.haswho .prot-k{border-bottom-color:var(--dim)}
.prot-tile.haswho:hover,.prot-tile.haswho.on{background:var(--raise);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--brand) 22%,transparent)}   /* highlight while its bubble is open */
/* who bubble: lighter than the card (--raise vs --surface) + brand-tinted border + deeper shadow so it stands out */
.deppop.prot-who-pop{overflow:visible;max-height:none;min-width:250px;max-width:430px;padding:11px 14px;
  background:var(--raise);border-color:color-mix(in srgb,var(--brand) 34%,var(--line-solid));box-shadow:0 16px 40px rgba(0,0,0,.6)}
.prot-who{display:flex;flex-direction:column}
.prot-who-h{font:10px/1 var(--mono);font-weight:700;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-2);margin-bottom:9px;display:flex;align-items:center}
.prot-who-sub{margin-left:8px;font-weight:600;letter-spacing:.06em;color:var(--faint);text-transform:none}
.prot-who-tot{margin-left:auto;color:var(--brand);font-weight:700}
.prot-who-list{display:flex;flex-direction:column;gap:4px}
.prot-who-list.scroll{max-height:330px;overflow-y:auto;margin:0 -6px;padding:0 6px}   /* ~15 rows, then scroll */
.prot-who-r{display:flex;align-items:baseline;gap:14px;min-width:0}   /* one row per peer: "user · peer" ······ when */
.prot-who-id{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.prot-who-nm{color:var(--ink-2);font-weight:600;font-size:12.5px}
.prot-who-un{color:var(--faint);font-weight:500;font-style:italic;font-size:12.5px}
.prot-who-sep{color:var(--faint);margin:0 6px}
.prot-who-pn{color:var(--dim);font-family:var(--mono);font-size:11px}
.prot-who-ago{margin-left:auto;font-family:var(--mono);font-size:10.5px;font-weight:600;flex:none;white-space:nowrap}
.prot-cat-row{padding:2px 0}
.prot-cat-nm{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ink-2);font-family:var(--sans);font-weight:600;font-size:12.5px;text-transform:capitalize}
.prot-cat-n{font-size:11px;font-weight:700}
.prot-who-pg{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:9px;padding-top:8px;border-top:1px solid var(--line)}
.prot-who-pglbl{font-family:var(--mono);font-size:10px;color:var(--faint);white-space:nowrap}
.prot-who-pgbtns{display:flex;gap:6px;flex:none}
.prot-who-pgb{appearance:none;font:inherit;font-family:var(--mono);font-size:11px;font-weight:600;padding:3px 11px;border-radius:7px;
  border:1px solid var(--line-solid);background:var(--surface);color:var(--dim);cursor:pointer;transition:color .12s,border-color .12s}
.prot-who-pgb:hover:not(:disabled){color:var(--brand);border-color:var(--brand)}
.prot-who-pgb:disabled{opacity:.38;cursor:default}
.prot-top{display:flex;align-items:center;gap:14px}   /* number on the left, label+sub stacked on the right — same row */
.prot-tc{min-width:0;flex:1}
.prot-v{font-family:var(--mono);font-size:30px;font-weight:600;letter-spacing:-.02em;line-height:1;color:var(--ink);flex:none}
.prot-k{font-family:var(--mono);font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--dim);font-weight:500}
.prot-sub{font-family:var(--mono);font-size:11px;color:var(--faint);margin-top:3px}
.prot-spark{margin-top:9px;height:24px}
.prot-spark .spark{display:block;width:100%;height:24px}
.prot-cov{display:flex;align-items:center;gap:11px;flex-wrap:wrap;padding:12px 16px;border-top:1px solid var(--line);background:var(--surface-2)}
.prot-cov-ic{width:26px;height:26px;flex:none;border-radius:8px;display:inline-flex;align-items:center;justify-content:center;
  background:color-mix(in srgb,var(--brand) 13%,transparent);color:var(--brand)}
.prot-cov-ic svg{width:15px;height:15px}
.prot-cov-txt{font-size:12.5px;color:var(--dim);line-height:1.5;min-width:0}
.prot-cov-txt b{color:var(--ink-2);font-family:var(--mono);font-weight:600;letter-spacing:-.01em}
.prot-mech{display:flex;gap:6px;flex-wrap:wrap;margin-left:auto}
.prot-chip{font-family:var(--mono);font-size:10px;letter-spacing:.03em;color:var(--dim);padding:3px 9px;border-radius:99px;
  border:1px solid var(--line-solid);background:var(--surface);white-space:nowrap}
.prot-chip i{color:var(--faint);font-style:normal}
@media(max-width:760px){.prot-mech{margin-left:0;width:100%}}

/* ───── fleet tiles ───── */
.fleet{display:grid;grid-template-columns:repeat(auto-fill,minmax(248px,1fr));gap:14px}
.tile{display:block;width:100%;text-align:left;cursor:pointer;background:linear-gradient(180deg,var(--raise),var(--surface));
  border:1px solid var(--line);border-radius:var(--r);padding:15px 16px;overflow:hidden;position:relative;
  transition:transform .18s var(--ease),border-color .25s,box-shadow .25s}
.tile:hover{transform:translateY(-2px);border-color:var(--line-solid);box-shadow:var(--shadow)}
.tile.stale{opacity:.6}
.tile .arrow{position:absolute;right:14px;top:15px;color:var(--faint);transition:transform .2s,color .2s}
.tile:hover .arrow{color:var(--dim);transform:translateX(2px)}
.dot{width:9px;height:9px;border-radius:50%;flex:none;background:var(--unknown)}
.dot.live{background:var(--online);animation:pulse 2.6s infinite}
.dot.stale{background:var(--unknown)}
.tport{display:inline-flex;align-items:center;font-family:var(--mono);font-size:10px;font-weight:500;letter-spacing:.06em;text-transform:uppercase;
  padding:2px 7px;border-radius:6px;margin-left:2px}   /* same shape as the wg/awg .iftype badge; colour per .docker/.baremetal */
.tport.baremetal{color:var(--tport-bare);background:color-mix(in srgb,var(--tport-bare) 14%,transparent)}
.tport.docker{color:var(--tport-dock);background:color-mix(in srgb,var(--tport-dock) 16%,transparent)}
/* panel-header deploy tag keeps the bordered livepill pill geometry (overrides the node-badge look above) */
#host-tport{font-size:11px;letter-spacing:.08em;border-radius:99px;padding:5px 11px;margin-left:0;border:1px solid var(--line-solid)}
#host-tport.baremetal{border-color:color-mix(in srgb,var(--tport-bare) 40%,transparent);background:color-mix(in srgb,var(--tport-bare) 9%,transparent)}
#host-tport.docker{border-color:color-mix(in srgb,var(--tport-dock) 45%,transparent);background:color-mix(in srgb,var(--tport-dock) 10%,transparent)}
/* "reporting" — bare status tag matching .nstat (the "updating" style); the only one that keeps a glowing, blinking dot */
.reporting{display:inline-flex;align-items:center;gap:6px;line-height:normal;font-family:var(--mono);font-size:10px;text-transform:uppercase;letter-spacing:.05em;
  font-weight:700;color:var(--online)}   /* line-height:normal (not 1) so the label baseline matches the .tport bare-metal/docker pill exactly */
.reporting::before{content:"";width:7px;height:7px;border-radius:50%;background:var(--online);box-shadow:0 0 8px rgba(63,216,154,.7);animation:blink 2.6s infinite}
/* one-line node ribbon: interface/turn tags left, sync/uptime right */
.noderibbon{display:flex;align-items:center;gap:12px;margin:0 0 22px;flex-wrap:wrap}
.node-nodata{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:15px;min-height:46vh;color:var(--unknown);text-align:center}   /* node not reporting + no snapshot: a single centered message instead of empty grids */
.node-nodata svg{width:40px;height:40px;opacity:.5}
.node-nodata p{font-size:15px;margin:0;letter-spacing:.02em}
.nr-tags{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.nr-sync{display:flex;align-items:center;gap:14px;font-family:var(--mono);font-size:11.5px;color:var(--dim);white-space:nowrap}
.meter{margin-top:9px;height:4px;border-radius:3px;background:var(--track);overflow:hidden}
.meter>i{display:block;height:100%;border-radius:3px;background:var(--m,var(--online));transition:width .6s var(--ease)}

/* ───── cards / panels ───── */
.panel{background:var(--surface);border:1px solid var(--line);border-radius:var(--r)}
.panel-pad{padding:16px}

/* ───── attention list ───── */
.attn{display:flex;flex-direction:column;gap:8px}
.attn-row{display:flex;align-items:center;gap:12px;padding:11px 14px;background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-sm);cursor:pointer;transition:border-color .15s,background .15s}
.attn-row:hover{border-color:var(--line-solid);background:var(--surface-2)}
.attn-row .name{color:var(--ink);font-weight:500}
.attn-row .why{font-size:12px;color:var(--dim);font-family:var(--mono)}
.attn-row .grow{flex:1}
.allclear{display:flex;align-items:center;gap:10px;padding:18px;color:var(--dim);font-size:13px;
  background:var(--surface);border:1px dashed var(--line-solid);border-radius:var(--r)}
.allclear svg{color:var(--online)}

/* ───── tables ───── */
.tablewrap{background:var(--surface);border:1px solid var(--line);border-radius:var(--r);overflow:hidden}
table{width:100%;border-collapse:collapse}
thead th{text-align:left;padding:8px 14px;font-family:var(--mono);font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--faint);font-weight:500;
  font-weight:600;border-bottom:1px solid var(--line);background:var(--surface-2)}
tbody td{padding:9px 14px;border-bottom:1px solid var(--line);font-size:13.5px;vertical-align:middle}
tbody tr:last-child td{border-bottom:0}
tbody tr.clk{cursor:pointer;transition:background .15s}
tbody tr.clk:hover{background:var(--surface-2)}
tbody tr.flash{animation:flash 2.6s var(--ease)}
@keyframes flash{0%{background:color-mix(in srgb,var(--online) 20%,transparent)}100%{background:transparent}}
/* just-created peer row — a one-shot branded glow that ramps 0 → peak → 0 over 1.5s (all peer grids) */
.peergrid tbody tr.pcreate td{animation:pcreate 1.5s var(--ease)}
@keyframes pcreate{0%{background:transparent}42%{background:color-mix(in srgb,var(--brand) 24%,transparent)}100%{background:transparent}}
.c-name{color:var(--ink);font-weight:500}
.addr{font-family:var(--mono);font-size:12.5px;color:var(--dim)}
.when{font-family:var(--mono);font-size:12px;color:var(--dim)}
.rate{font-family:var(--mono);font-size:12px;color:var(--dim);white-space:nowrap}
.rowarrow{color:var(--faint);transition:transform .15s,color .15s}
tbody tr.clk:hover .rowarrow{color:var(--dim);transform:translateX(2px)}
/* panel-host service health → needs attention (critical = crimson like a dangling peer, warn = amber) */
.attn-row.svc{cursor:pointer}
.attn-row.svc.critical{border-color:color-mix(in srgb,var(--dangling) 42%,var(--line))}
.svc-badge{display:inline-flex;align-items:center;gap:5px;font-family:var(--mono);font-size:11px;letter-spacing:.05em;text-transform:uppercase;padding:3px 9px;border-radius:99px;white-space:nowrap}
.svc-badge svg{width:12px;height:12px}
.svc-badge.critical{color:var(--dangling);background:color-mix(in srgb,var(--dangling) 15%,transparent)}
.svc-badge.warn{color:var(--partial);background:color-mix(in srgb,var(--partial) 15%,transparent)}
.svc-modal{display:flex;flex-direction:column;gap:12px;min-width:min(440px,86vw)}
.svc-item{border:1px solid var(--line);border-left-width:3px;border-radius:8px;padding:11px 13px;background:var(--surface)}
.svc-item.critical{border-left-color:var(--dangling)}
.svc-item.warn{border-left-color:var(--partial)}
.svc-head{display:flex;align-items:center;gap:8px}
.svc-dot{width:8px;height:8px;border-radius:50%;flex:none}
.svc-dot.critical{background:var(--dangling)}
.svc-dot.warn{background:var(--partial)}
.svc-tag{margin-left:auto;font-family:var(--mono);font-size:10px;letter-spacing:.06em;text-transform:uppercase;padding:2px 7px;border-radius:99px}
.svc-tag.critical{color:var(--dangling);background:color-mix(in srgb,var(--dangling) 14%,transparent)}
.svc-tag.warn{color:var(--partial);background:color-mix(in srgb,var(--partial) 14%,transparent)}
.svc-msg{margin-top:6px;color:var(--ink);font-size:13px}
.svc-cmd{margin-top:7px;font-size:11px;color:var(--dim)}
.svc-cmd code{font-family:var(--mono);background:var(--surface-2);padding:1px 5px;border-radius:4px;color:var(--dim)}
.svc-foot{color:var(--faint);font-size:12px;line-height:1.5}
.empty{padding:40px 16px;text-align:center;color:var(--faint);font-size:13px}
.empty b{display:block;color:var(--dim);margin-bottom:4px;font-weight:600;font-size:14px}

/* ───── status badges ───── */
.badge{display:inline-flex;align-items:center;gap:5px;padding:3px 10px 3px 8px;border-radius:99px;font-size:11px;line-height:1;
  font-weight:700;letter-spacing:.01em;border:1px solid color-mix(in srgb,currentColor 28%,transparent)}   /* subtle colour-matched border (grey for the stale/unknown badge) — matches the docker/reporting header pills */
.badge::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor}
.badge.ic{padding-left:7px}
.badge.ic::before{display:none}
.badge.ic svg{width:11px;height:11px}
.b-online{background:color-mix(in srgb,var(--online) 13%,transparent);color:var(--online)} .b-online::before{animation:blink 2.6s infinite}
@keyframes blink{0%,100%{opacity:1}50%{opacity:.4}}
.b-ready{background:color-mix(in srgb,var(--ready) 12%,transparent);color:var(--ready)}   /* blue — deployed everywhere, awaiting first connection */
.b-partial{background:color-mix(in srgb,var(--partial-r) 16%,transparent);color:var(--partial-r)}   /* LIGHT RED — a problem that isn't a stopper (present on some targets, missing on others) */
/* ORANGE faults: blocked = reaching the server but no handshake; faulty = handshake up but no inbound data */
.b-blocked,.b-faulty{background:color-mix(in srgb,var(--fault) 15%,transparent);color:var(--fault)}
.b-pending{background:color-mix(in srgb,var(--pending) 14%,transparent);color:var(--pending)}   /* violet — bg matches the --pending text/border (awaiting-enroll); was a mismatched yellow bg */
.b-removing{background:rgba(242,107,130,.14);color:var(--dangling)}
.b-dangling{background:color-mix(in srgb,var(--dangling) 14%,transparent);color:var(--dangling)}
.b-orphan{background:rgba(95,117,105,.16);color:var(--unknown)}   /* GREY — unmanaged/external peer, informational */
.b-creating{background:rgba(232,192,75,.16);color:var(--partial)}   /* YELLOW — being created (POST in flight or within the grace window) */
.b-rotating{background:rgba(232,192,75,.16);color:var(--partial)}   /* YELLOW — key rotation in flight (active) */
/* Access blocked — an INTENTIONAL admin revoke, not a fault. disabled=settled (grey), blocking=converging
   (yellow, node still dropping it), restoring=coming back after unblock (blue) */
.b-disabled{background:color-mix(in srgb,var(--dangling) 15%,transparent);color:var(--dangling)}   /* RED — access revoked (settled) */
.b-expired{background:color-mix(in srgb,var(--dangling) 15%,transparent);color:var(--dangling)}    /* RED — access date passed */
.b-expiring{background:color-mix(in srgb,var(--fault) 15%,transparent);color:var(--fault)}          /* ORANGE — about to expire */
/* access-lifecycle flag icon shown BEFORE the peer title / username when the status badge didn't already say it
   (i.e. a non-"ready" status) — red circle = blocked, red triangle = expired, orange triangle = about to expire */
.lc-ic{display:inline-flex;align-items:center;flex:none;margin-right:5px;vertical-align:middle}
.lc-ic svg{width:13px;height:13px}
.lc-blocked,.lc-expired{color:var(--dangling)} .lc-expiring{color:var(--fault)}
.u-name .lc-ic{margin-right:0}   /* the u-name flex row already gaps its children */
.b-blocking{background:rgba(232,192,75,.16);color:var(--partial)}   /* AMBER — block converging on the node */
.b-restoring{background:color-mix(in srgb,var(--ready) 12%,transparent);color:var(--ready)}   /* BLUE — restoring after unblock */
.ifc-orph{color:#F0913C;font-weight:600}   /* orange orphan (unmanaged) peer count on the interface card */
.b-unknown{background:rgba(95,117,105,.16);color:var(--unknown)}
.b-issue{background:color-mix(in srgb,var(--partial) 15%,transparent);color:var(--partial)}
.b-unassigned{background:rgba(95,117,105,.16);color:var(--unknown)}
.b-empty{background:rgba(95,117,105,.12);color:var(--faint)}

/* ───── users / peers (first-class model) ───── */
.tagchip{display:inline-flex;align-items:center;padding:2px 9px;border-radius:99px;font-size:11px;font-weight:600;
  background:rgba(125,211,252,.12);color:#7dd3fc}
.c-note{color:var(--faint);font-size:12px;max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.selwrap.mini{min-width:130px;padding:5px 8px;font-size:12px}
.peercard-head .addr{font-size:12.5px}
.peercard-head .grow{flex:1}
.row2{display:flex;gap:12px}
.row2 .field{flex:1;min-width:0}
/* node detail: interface + turn-proxy cards (drill-in) */
.ifgrid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px}
@media(max-width:1000px){.ifgrid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:560px){.ifgrid{grid-template-columns:1fr}}
.ifcard{display:block;border:1px solid var(--line-solid);border-radius:var(--r);padding:14px;background:var(--surface-2);text-decoration:none;color:inherit;transition:border-color .15s var(--ease),transform .15s var(--ease)}
.ifcard.pending{border-style:dashed;opacity:.5}   /* in-progress placeholder (installing/adopting/creating) → dimmed like any attention state */
.ifcard.pending .tg-warn svg{width:11px;height:11px;margin-right:3px}
.chiprow{display:flex;gap:8px}
a.ifcard:hover{border-color:var(--brand);box-shadow:0 6px 16px rgba(0,0,0,.28)}   /* lift via shadow, NOT transform — a transform would become the containing block for the online-count popover (position:fixed) and mis-anchor it */
.ifcard.tp{cursor:default}
.ifcard.tp.clickable{cursor:pointer}
.ifcard.down{opacity:.5;border-color:color-mix(in srgb,var(--dangling) 35%,var(--line-solid))}
.ifcard.down:hover{opacity:.72}
/* Two distinct dims, and they can co-occur:
     .down   — THIS card's own state needs attention (iface down/stopped/error, proxy failed, mesh link down).
               Red-tinted border: something here is wrong.
     .locked — the NODE forbids editing right now (offline, converting, re-installing, updating). Nothing is
               wrong with the card, so no red: just muted, matching the disabled buttons. Every card on the
               page carries it together, so a node-wide state never dims one section and leaves the rest lit. */
.ifcard.locked{opacity:.55}
.ifcard.locked:hover{opacity:.75}
.ifcard.locked.down{opacity:.45}             /* both → keep the red border, dim a little further */
.ifcard.tp.clickable:hover{border-color:var(--turn,#A974F2);transform:translateY(-1px)}
.cmderr{display:inline-flex;align-items:center;color:var(--dangling);cursor:pointer}
.cmderr:hover{color:#ff8585}
.cmderr.warn{color:var(--partial)}
.cmderr.warn:hover{color:#f0d97a}
.xbtn{display:inline-flex;align-items:center;justify-content:center;background:transparent;border:none;color:var(--dim);cursor:pointer;padding:2px;margin-left:3px;border-radius:5px}
.xbtn:hover{color:var(--dangling);background:rgba(240,90,90,.12)}
.ifcard-top{display:flex;align-items:center;gap:8px;margin-bottom:10px;overflow:hidden}   /* ONE line always: to fit a wide status, the name ellipsizes and the turn badges compact (turn→t, in JS); badges+status never shrink or wrap. Reverts to full when no wide status is shown. */
.ifcard-top .ifname{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}   /* the name gives up space first (aw…) */
.ifcard-top>.iftype,.ifcard-top>.drag-grip{flex:none}
.ifcard-top .tg,.ifcard-top .tg-off,.ifcard-top .tg-busy{flex:none;white-space:nowrap}   /* status badges stay whole on the line */
.tg-turn.mini{padding-left:7px;padding-right:7px;text-transform:uppercase}   /* compact "t" badge */
/* hover edit affordance on an interface card: absolutely positioned in the card's top-right (so its padded
   hit-area escapes .ifcard-top's overflow clip and is hoverable on every side), hidden until card hover;
   hovering the area highlights only the icon (no area background); click opens Edit (not the detail page).
   The header reserves a right gutter so badges never sit under it. */
/* the edit button sits inline right after the interface name, revealed on card hover */
/* the interface name IS the edit trigger: name + pencil are one button, so hovering the name reveals/highlights
   the pencil and a click opens the edit modal (the rest of the card still links to the detail page) */
/* the WG/AWG tag now lives INSIDE the button, so tag + name + pencil are one enlarged, hoverable edit target
   (padding + a hover surface make the whole area clickable from every side; negative margin keeps layout put) */
.ifc-edit{flex:0 1 auto;min-width:0;background:none;border:0;padding:4px 8px;margin:-4px -6px -4px -4px;border-radius:8px;cursor:pointer;color:inherit;font:inherit;display:inline-flex;align-items:center;gap:6px;transition:background .12s}
.ifc-edit:hover{background:var(--surface-2)}
.ifc-edit .iftype{flex:none}
.ifc-edit .ifname{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ifc-pic{flex:none;display:inline-flex;align-items:center;margin-top:-1px;opacity:0;transition:opacity .12s}
.ifcard:hover .ifc-pic{opacity:1}
.ifc-pic .ic{display:flex;color:var(--faint);transition:color .12s}
.ifc-pic .ic svg{width:14px;height:14px}
.ifcard:hover .ifc-pic .ic{animation:edithint 1s ease 1}   /* one-shot grey→cyan→grey pulse so the operator notices it */
.ifc-edit:hover .ifc-pic .ic{color:var(--brand)}
@keyframes edithint{0%,100%{color:var(--faint)}45%{color:var(--brand)}}
.ifcard-top .tg{flex:none}          /* status tags keep their full width — the name ellipsis-shrinks instead */
.ifcard-top .onlinetag{flex:none}   /* connections count in a turn card header — don't stretch */
/* turn-card header must NOT clip: the connection-count hover pill uses a negative-margin box (like the
   interface-card online count) that breathes into the card's padding — overflow:hidden would shear its
   top/right edge. The name still ellipsizes via .ifname's own overflow, so one-line layout is preserved. */
.ifcard.tp .ifcard-top{overflow:visible}
/* a status tag carrying a node message is clickable (opens it) — pointer + hover highlight as the affordance */
/* any error/detail tag that opens a popup on click — on hover just brighten its text/icon colour so it
   reads as clickable (no background box; the popup carries the details, so no native title tooltip) */
.tg-click{cursor:pointer;transition:filter .12s}
.tg-click:hover{filter:brightness(1.45)}
/* the name truncates (…) so a long fork name never pushes the status tag + cancel × out of the card */
.ifcard-top .ifname{font-weight:600;color:var(--ink);font-family:var(--mono);font-size:13.5px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ifcard-top .iftype,.ifcard-top .tg-busy,.ifcard-top .tg-ok,.ifcard-top .tg-warn,.ifcard-top .tg-del,.ifcard-top .cmderr,.ifcard-top .xbtn{flex:none}
.ifcard-top .rowarrow{color:var(--faint)}
.ifcard-top .grow{flex:1 1 0;min-width:0}
.ifcard-rows{display:flex;flex-direction:column;gap:6px}
/* drag-to-reorder: grip handle + drag/drop affordances (cards, turn cards, node cards) */
.drag-grip{flex:none;display:inline-flex;align-items:center;justify-content:center;width:16px;height:18px;margin:-2px 2px -2px -3px;
  color:var(--dim);cursor:grab;opacity:.6;border-radius:5px;transition:opacity .12s,color .12s,background .12s}
.drag-grip:hover{opacity:1;color:var(--ink-2);background:var(--surface-2)}
.drag-grip:active{cursor:grabbing}
.ifcard:hover .drag-grip,.ncard:hover .drag-grip,.ifcard.tp:hover .drag-grip{opacity:.9}
.dragging,.ifcard.dragging,.ncard.dragging{opacity:.18}   /* the source card left in place while dragging — beat .ifcard.down's opacity */
/* drop indicator: a cyan edge on each card flanking the gap (l/r for grids, t/b for vertical lists) */
.drop-l{box-shadow:inset 3px 0 0 var(--brand)}
.drop-r{box-shadow:inset -3px 0 0 var(--brand)}
.drop-t{box-shadow:inset 0 3px 0 var(--brand)}
.drop-b{box-shadow:inset 0 -3px 0 var(--brand)}
/* our own translucent card preview that follows the cursor while dragging (a clone pinned to the body) */
.drag-ghost{opacity:.82;background:var(--raise);border:1px solid var(--brand);border-radius:var(--r);
  box-shadow:0 20px 46px -10px rgba(0,0,0,.6);overflow:hidden;transition:none}
.drag-ghost .drag-grip{opacity:1;color:var(--brand)}
.ifrow{display:flex;justify-content:space-between;gap:10px;font-size:12px}
.ifrow .l{color:var(--dim)} .ifrow .r{font-family:var(--mono);color:var(--ink-2)}
.ifrow .r.addr{font-size:11px}
.iftype{font-family:var(--mono);font-size:10px;font-weight:500;letter-spacing:.06em;text-transform:uppercase;padding:2px 7px;border-radius:6px}
.iftype.wg{background:rgba(63,216,154,.14);color:var(--online)}
.iftype.awg{background:rgba(31,200,214,.15);color:var(--brand)}
.iftype.turn{color:var(--tfc,#B49BF7);background:color-mix(in srgb,var(--tfc,#A78BF5) 17%,transparent)}
.iftype.muted{background:color-mix(in srgb,var(--unknown) 13%,transparent);color:var(--unknown)}
/* per-fork colour: a .tf-<fork> class sets --tfc, consumed by both the .iftype.turn badge and the
   .tg-turn tag (cards / node header / nodes list) — distinguishable violet/purple variations. */
.tf-cacggghp{--tfc:#A974F2}
.tf-WINGS-N{--tfc:#7C5CFF}
.tf-samosvalishe{--tfc:#C77DFF}
.tf-kiper292{--tfc:#9D8DFF}
.tf-Moroka8{--tfc:#8E72E8}
.tf-anton48{--tfc:#C04FD6}

.ptitle,.ptitle-h{display:inline-flex;align-items:center;gap:4px}
.ptitle b{color:var(--ink);font-size:13.5px}
.ptitle .editname,.ptitle-h .editname{background:none;border:none;color:var(--faint);cursor:pointer;padding:2px;display:inline-flex}
.ptitle .editname:hover,.ptitle-h .editname:hover{color:var(--ink)}
.ptitle-edit input{padding:4px 8px;font-size:13px;width:160px}
.rowacts{white-space:nowrap}
.rowacts .btn-mini{margin-left:6px}
/* pinned, explained failure on a row (optimistic rollback) */
.rowerr{display:inline-flex;align-items:center;gap:5px;margin-left:8px;padding:2px 8px;border-radius:99px;
  background:color-mix(in srgb,var(--dangling) 13%,transparent);color:var(--dangling);font-size:11px;font-weight:600;white-space:nowrap;max-width:280px;overflow:hidden;text-overflow:ellipsis}
.rowerr svg{flex:none}
.rowerr-x{background:none;border:none;color:var(--dangling);cursor:pointer;font-size:14px;line-height:1;padding:0 0 0 2px;opacity:.7}
.rowerr-x:hover{opacity:1}
/* multi-target picker (new peer) */
.targetpick{display:flex;flex-direction:column;gap:6px;max-height:478px;overflow-y:auto;padding-right:3px;scrollbar-width:thin;scrollbar-color:var(--line-solid) transparent}   /* ~10 rows, then scroll */
.targetpick::-webkit-scrollbar{width:10px}
.targetpick::-webkit-scrollbar-track{background:transparent}
.targetpick::-webkit-scrollbar-thumb{background:var(--line-solid);border-radius:8px;border:2px solid var(--surface)}
.targetpick::-webkit-scrollbar-thumb:hover{background:var(--dim)}
.targetopt{display:flex;align-items:center;gap:10px;border:1px solid var(--line);border-radius:10px;padding:7px 10px;background:var(--surface-2)}
.targetopt.sel{border-color:var(--line-solid)}
.targetopt .topt-main{display:flex;align-items:center;gap:9px;flex:1;cursor:pointer;min-width:0}
.targetopt.locked .topt-main{cursor:default}
.targetopt .box{width:17px;height:17px;border:1.5px solid var(--line-solid);border-radius:5px;display:inline-flex;align-items:center;justify-content:center;flex:none;color:var(--brand)}
.targetopt.sel .box{border-color:var(--brand);background:color-mix(in srgb,var(--brand) 16%,transparent)}
.targetopt .nm{font-weight:600}
.targetopt .tp{font-size:13.5px;color:var(--faint);font-family:var(--mono)}
/* an existing deployment whose interface is gone — flagged so it reads as removable-only, not a fresh option */
.targetopt.missing{border-color:color-mix(in srgb,var(--dangling) 34%,var(--line-solid));background:color-mix(in srgb,var(--dangling) 5%,transparent)}
.topt-missing{font-family:var(--mono);font-size:9px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--dangling);border:1px solid color-mix(in srgb,var(--dangling) 34%,transparent);border-radius:99px;padding:1px 6px}
.targetopt .tg{flex:none;margin-right:2px}   /* wg/awg type tag sits just before the address */
.targetopt .topt-ip{width:130px;flex:none;padding:5px 8px;font-size:12.5px}
.targetopt .topt-ip.existing{display:inline-flex;align-items:center;justify-content:flex-end;font-family:var(--mono);color:var(--ink-2);text-align:right}   /* already-deployed address, read-only */
.targetopt .topt-ip[readonly]{color:var(--ink-2);text-align:right;font-family:var(--mono)}
.targetpick.scroll{max-height:232px}   /* Add-peers carousel grid: scroll after ~5 rows */

/* Add-peers — two grids (this user's peers · unassigned pool). Each peer is a bordered block; a multi-deployment
   peer stacks its targets under one border, primary row tinted and tagged, backups beneath, so it reads as one identity. */
.pg2-sec{margin:0 0 20px}
.pg2-sec:last-child{margin:0}
.pg2-hdr{display:flex;align-items:center;gap:10px;margin:0 0 9px}
.pg2-hdr label{margin:0;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--dim)}
.pg2-fresh{flex:none}
.pg2{display:grid;grid-template-columns:auto minmax(140px,1.7fr) minmax(140px,1.1fr) 66px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--line-solid) transparent}
.pg2::-webkit-scrollbar{width:9px}
.pg2::-webkit-scrollbar-track{background:transparent}
.pg2::-webkit-scrollbar-thumb{background:var(--line-solid);border-radius:6px;border:2px solid transparent;background-clip:padding-box}
.pg2::-webkit-scrollbar-thumb:hover{background:var(--dim);background-clip:padding-box}
.pg2-peer{grid-column:1/-1;display:grid;grid-template-columns:subgrid;border:1px solid var(--line-solid);border-radius:10px;background:var(--surface-2);margin:0 0 8px;overflow:hidden}
.pg2-peer:last-child{margin:0}
.pg2-peer.pcreate{animation:pcreate 1.5s var(--ease)}   /* same glow as a just-created users/peers-grid row */
.pg2-dep{grid-column:1/-1;display:grid;grid-template-columns:subgrid;align-items:center;column-gap:12px;padding:8px 11px}
.pg2-dep+.pg2-dep{border-top:1px dashed var(--line)}
.pg2-dep.clk{cursor:pointer;transition:background .12s}
.pg2-dep.clk:hover{background:var(--row-hover)}
.pg2-dep.primary{background:color-mix(in srgb,var(--brand) 7%,transparent)}
.pg2-dep.primary.clk:hover{background:color-mix(in srgb,var(--brand) 12%,transparent)}
.pg2-c{min-width:0}
.pg2-title{display:flex;align-items:center;gap:10px}
.pg2-nm{font-weight:600;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pg2-rel{flex:none;margin-left:auto;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;border-radius:4px;padding:1.5px 6px}
.pg2-rel.primary{color:var(--brand);background:color-mix(in srgb,var(--brand) 15%,transparent)}
.pg2-bk{font-style:italic;font-weight:400;color:var(--dim)}
.pg2-if{display:flex;align-items:center;gap:6px;white-space:nowrap;font-family:var(--mono);font-size:12px}
.pg2-ifn{color:var(--muted)}
.pg2-ip{color:var(--ink-2)}
.pg2-ctl{display:flex;justify-content:flex-end;gap:5px}
.pg2-act{flex:none;width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;border-radius:7px;border:1px solid var(--line);background:transparent;color:var(--dim);cursor:pointer;transition:all .14s}
.pg2-act svg{width:17px;height:17px}
.pg2-act.add:hover{color:var(--brand);border-color:var(--brand)}
.pg2-act.free:hover{color:var(--online);border-color:var(--online)}
.pg2-act.mine:hover{color:#e0574d;border-color:#e0574d}
.pg2-empty{padding:16px;text-align:center;color:var(--dim);border:1px dashed var(--line-solid);border-radius:10px;font-size:13px}
.lockeduser{padding:10px 12px;border:1px solid var(--line);border-radius:var(--r-sm);background:var(--surface-2);color:var(--ink);font-weight:500}

/* ───── node pips (redundancy signature) ───── */
.pips{display:inline-flex;gap:5px;align-items:center}
.pip.on{background:var(--pc);box-shadow:0 0 7px -1px var(--pc)}
.pip.present{background:color-mix(in srgb, var(--pc) 45%, transparent)}
.pip.miss{border-color:var(--dangling);background:transparent}
.pip.miss::after{content:"";position:absolute;inset:2px;border-radius:50%;background:var(--dangling);opacity:.5}
.pip.unk{border-color:var(--unknown);border-style:dotted}

/* ───── toolbar (users) ───── */
.toolbar{display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-bottom:14px}
.search{flex:1;min-width:180px;position:relative}
/* compact search living in a panel header (interface peers), next to the title */
.panel-head .search.hdr{flex:1;min-width:160px;margin-left:6px}   /* dynamic width — fill the header row */
.panel-head .search.hdr ~ .grow{flex:0}   /* don't let the spacer split the slack; the search absorbs it all */
.panel-head .search.hdr input{height:32px}
.search svg{position:absolute;left:12px;top:50%;transform:translateY(-50%);opacity:.5;pointer-events:none}
input,select{background:var(--surface-2);border:1px solid var(--line-solid);color:var(--ink);border-radius:var(--r-sm);
  padding:9px 12px;font-family:var(--mono);font-size:13px;transition:border-color .15s,box-shadow .15s;width:100%}
input::placeholder{color:var(--faint)}
input:focus,select:focus{outline:none;border-color:var(--brand)}
textarea.ta{background:var(--surface-2);border:1px solid var(--line-solid);color:var(--ink);border-radius:var(--r-sm);padding:10px 12px;font-family:var(--mono);font-size:13px;width:100%;resize:vertical;line-height:1.6;min-height:84px;word-break:break-all}
textarea.ta:focus{outline:none;border-color:var(--brand)}
textarea.ta::placeholder{color:var(--faint)}
textarea.ta{scrollbar-width:thin;scrollbar-color:var(--line-solid) transparent}
textarea.ta::-webkit-scrollbar{width:10px}
textarea.ta::-webkit-scrollbar-track{background:transparent}
textarea.ta::-webkit-scrollbar-thumb{background:var(--line-solid);border-radius:8px;border:2px solid var(--surface-2)}
textarea.ta::-webkit-scrollbar-thumb:hover{background:var(--dim)}
.search input{padding-left:34px}
select{cursor:pointer;-webkit-appearance:none;appearance:none;padding-right:32px;
  background-image:linear-gradient(45deg,transparent 50%,var(--dim) 50%),linear-gradient(135deg,var(--dim) 50%,transparent 50%);
  background-position:calc(100% - 15px) 52%,calc(100% - 10px) 52%;background-size:5px 5px,5px 5px;background-repeat:no-repeat}
.selwrap{width:auto;min-width:150px}
/* in a form field a dropdown should match the text inputs (full width), not size to its content */
.field .selwrap:not(.mini){width:100%;min-width:0}
.chips{display:flex;gap:7px;flex-wrap:wrap}
.chip{font-size:12px;padding:6px 12px;border-radius:99px;cursor:pointer;background:var(--surface-2);
  border:1px solid var(--line);color:var(--dim);display:flex;align-items:center;gap:6px;transition:all .15s}
.chip:hover{color:var(--ink)}
.chip.on{color:var(--ink);border-color:var(--brand);background:color-mix(in srgb,var(--brand) 10%,transparent)}
/* protocol picker (Load new interface): AmneziaWG=purple, WireGuard=green, Existing=orange when selected */
.proto3{flex-wrap:wrap}
.chip.c-awg.on{color:#C9B6FF;border-color:#B49BF7;background:color-mix(in srgb,#B49BF7 16%,transparent)}
.chip.c-wg.on{color:#8DEFC4;border-color:var(--online);background:color-mix(in srgb,var(--online) 14%,transparent)}
.chip.c-ex.on{color:#F6B873;border-color:#F0913C;background:color-mix(in srgb,#F0913C 16%,transparent)}
.chip .swatch{width:7px;height:7px;border-radius:50%;background:var(--cc,var(--dim))}
.chip b{font-family:var(--mono);font-weight:600;color:var(--ink-2)}
.meta{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-sm);padding:13px 14px}
.meta .k{font-size:10px;letter-spacing:.11em;text-transform:uppercase;color:var(--dim);font-weight:600}
.meta .v{font-family:var(--mono);font-size:13px;color:var(--ink-2);margin-top:6px;word-break:break-all}
/* universal gap between a screen's header line(s) and its content */
.detail-head{display:flex;align-items:flex-start;gap:16px;flex-wrap:wrap;margin-bottom:22px}
.detail-head:has(+ .noderibbon){margin-bottom:14px}   /* node detail: space between the title and the tags/sync ribbon */
.detail-head .title{display:flex;align-items:center;gap:12px}
.dh-ver{display:flex;align-items:center;gap:8px;align-self:center;margin-right:-4px}   /* version + check/update + node controls, level with the title */
.dh-sep{width:1px;height:18px;background:var(--line-solid);margin:0 3px}
.dh-ver .nm-ver{margin-left:0}
.upd-busy{pointer-events:none;cursor:default;color:var(--partial);border-color:color-mix(in srgb,var(--partial) 45%,var(--line-solid))}
.updspin{animation:spin .8s linear infinite;transform-box:view-box;transform-origin:center;margin-left:1px}   /* any updspin arrow animates — header upd-busy AND .hostproc-tag. view-box (not fill-box): the viewBox centre is known at parse time, so it spins perfectly centred even on a fresh load (fill-box needs the SVG laid out first → off-centre after Ctrl+Shift+R) */
/* "panel updated — hard reload" modal */
.updone p{margin:0 0 18px;line-height:1.6;color:var(--ink-2);font-size:13.5px}
.updone p:last-child{margin-bottom:0}
.updone b{color:var(--ink);font-family:var(--mono)}
.updone-hint{display:flex;align-items:center;gap:7px;color:var(--brand);font-weight:600;letter-spacing:.01em}
.updone kbd{display:inline-flex;align-items:center;justify-content:center;min-width:26px;height:26px;padding:0 8px;
  font-family:var(--mono);font-size:11.5px;font-weight:600;color:var(--ink);background:var(--surface-2);
  border:1px solid var(--line-solid);border-bottom-width:2px;border-radius:6px}
/* "check for updates": cyan + spinning while polling, back to grey when done. The extra
   .appbar-right selector beats `.appbar-right .iconbtn{color:--dim}` (same specificity, later). */
.iconbtn.checking, .appbar-right .iconbtn.checking{color:var(--brand);pointer-events:none}
/* Spin the whole BUTTON, not the root <svg>. transform-box:view-box/fill-box on a root HTML-embedded SVG isn't
   reliably honoured by Chrome on first paint (esp. right after Ctrl+Shift+R) → the icon sometimes span around an
   off-centre point. .iconbtn is a fixed 30/36px flex-CENTRED box, so its border-box centre exists from parse time
   and the icon sits dead-centre → rotating the button pivots exactly on the icon's own centre, every time. */
.iconbtn.checking{animation:spin .8s linear infinite;transform-origin:center}
.detail-head .title h1{margin:0;font-family:var(--mono);font-size:22px;line-height:1;font-weight:600;color:var(--ink)}
.detail-head .grow{flex:1}

/* ───── user profile ───── */
.identity{display:flex;flex-wrap:wrap;gap:22px;margin:16px 0 4px}
.identity .item .k{font-size:10px;letter-spacing:.11em;text-transform:uppercase;color:var(--dim);font-weight:600}
.identity .item .v{font-family:var(--mono);font-size:13.5px;color:var(--ink-2);margin-top:5px;display:flex;align-items:center;gap:8px}
.copybtn{background:none;border:0;color:var(--faint);cursor:pointer;padding:0;line-height:0}
.copybtn:hover{color:var(--ready)}
.deploy{background:var(--surface);border:1px solid var(--line);border-radius:var(--r);overflow:hidden;
  display:flex;flex-direction:column}
.deploy-head{display:flex;align-items:center;gap:10px;padding:13px 15px;border-bottom:1px solid var(--line)}
.deploy-head .nm{font-family:var(--mono);font-size:12.5px;font-weight:600;color:var(--ink)}
.deploy-head .nmlink{cursor:pointer} .deploy-head .nmlink:hover{text-decoration:underline}
.deploy-head .grow{flex:1}
.deploy-body{padding:15px;display:flex;gap:15px;position:relative}
.deploy .qr{background:#fff;border-radius:var(--r-sm);padding:8px;line-height:0;flex:none;cursor:zoom-in}
.deploy .qr .qrimg{display:block;width:min(300px,72vw);height:auto;image-rendering:pixelated;image-rendering:crisp-edges}
.qr-overlay{position:fixed;inset:0;z-index:9999;background:var(--qr-scrim);display:flex;cursor:zoom-out;padding:24px;overflow:auto}
/* margin:auto (not align/justify-center) keeps the block reachable when it overflows a short window */
.qr-overlay-inner{display:flex;flex-direction:column;align-items:center;gap:16px;margin:auto;max-width:100%}
.qr-overlay-card{background:#fff;padding:18px;border-radius:12px;line-height:0;max-width:100%}
/* bound by BOTH axes and reserve ~160px for the card padding + caption, so the caption never gets pushed off */
.qr-overlay-card .qrimg{display:block;width:auto;height:auto;max-width:min(86vw,820px);max-height:min(calc(100vh - 160px),820px);image-rendering:pixelated;image-rendering:crisp-edges}
.qr-overlay-cap{color:var(--ink);font-size:14px;font-weight:600;display:flex;align-items:center;justify-content:center;gap:10px;flex-wrap:wrap;text-align:center}
.qr-overlay-cap .qrc-srv{font-family:var(--mono)}
.deploy .qr-fail,.deploy .qr-none{width:240px;height:240px;max-width:60vw;display:flex;align-items:center;justify-content:center;text-align:center;
  font-size:11px;color:var(--faint);background:var(--surface-2);border-radius:var(--r-sm);padding:10px;line-height:1.4}
.deploy .dmeta{flex:1;min-width:0;display:flex;flex-direction:column;gap:9px}
.deploy .dmeta .row{display:flex;justify-content:space-between;gap:8px;font-size:12px}
.deploy .dmeta .row .k{color:var(--dim)} .deploy .dmeta .row .vv{font-family:var(--mono);color:var(--ink-2)}
.deploy .acts{padding:0 15px 15px;display:flex;gap:8px}
.deploy.adder{border-style:dashed;align-items:center;justify-content:center;min-height:160px;cursor:pointer;
  color:var(--dim);transition:border-color .15s,color .15s}
.deploy.adder:hover{border-color:var(--brand);color:var(--ink)}
.deploy.adder .inner{display:flex;flex-direction:column;align-items:center;gap:9px;text-align:center;padding:18px}
.deploy.adder .ring{width:40px;height:40px;border-radius:50%;border:1.5px dashed currentColor;display:flex;align-items:center;justify-content:center;font-size:20px}

.notice{display:flex;gap:9px;align-items:flex-start;padding:11px 14px;border-radius:var(--r-sm);font-size:12.5px;margin-bottom:16px}   /* consistent gap below every notice; :last-child trims it */
.notice:last-child{margin-bottom:0}
.notice.warn{background:rgba(232,192,75,.08);border:1px solid rgba(232,192,75,.25);color:var(--pending)}
.notice svg{flex:none;margin-top:1px}
/* error/log modal body — captured installer output / command errors, shown line-by-line, scrollable */
.logview{font-family:var(--mono);font-size:12.5px;line-height:1.55;color:var(--ink-2);background:var(--surface-2);border:1px solid var(--line-solid);border-radius:var(--r-sm);padding:10px 12px;max-height:48vh;overflow:auto}
.logline{white-space:pre-wrap;word-break:break-word;min-height:1.55em}
.logview.raw{color:var(--dim)}
.btn.logtoggle{margin-right:auto}   /* pin the raw/rendered toggle to the left of the footer */
/* full-width attention banner (e.g. store_configs off) — sits at the top of a screen */
.banner{display:flex;gap:11px;align-items:flex-start;margin-bottom:16px;padding:12px 15px;border-radius:var(--r-sm);font-size:13px;line-height:1.5}
.banner svg{flex:none;margin-top:2px}
.banner.warn{background:rgba(232,192,75,.1);border:1px solid rgba(232,192,75,.35);color:var(--pending)}
.banner.warn svg{color:var(--partial)}
.banner b{color:var(--ink)}
.banner code{font-family:var(--mono);font-size:11.5px;padding:1px 5px;border-radius:4px;background:rgba(255,255,255,.07);color:var(--ink)}
.banner .cmdrow{margin-top:10px}
.banner .tokenbox{font-size:11.5px;padding:9px 11px}

/* ───── modal / create sheet ───── */
.overlay{position:fixed;inset:0;background:rgba(4,8,6,.66);backdrop-filter:blur(4px);z-index:50;
  display:none;align-items:flex-start;justify-content:center;padding:48px 18px;overflow:auto}
.overlay.show{display:flex;animation:fade .2s}
@keyframes fade{from{opacity:0}to{opacity:1}}
.sheet{background:var(--surface);border:1px solid var(--line-solid);border-radius:16px;width:min(560px,100%);
  box-shadow:var(--shadow);animation:pop .24s var(--ease)}
@keyframes pop{from{opacity:0;transform:translateY(14px) scale(.98)}to{opacity:1;transform:none}}
.sheet-head{display:flex;align-items:center;padding:18px 20px;border-bottom:1px solid var(--line)}
.sheet-head h3{margin:0;font-size:15px;color:var(--ink);font-weight:650;letter-spacing:.01em}
.sheet-head h3 .sheet-ver{font-family:var(--mono);font-size:12px;font-weight:500;color:var(--ready);margin-left:4px}   /* deployed version shown right in the turn-proxy sheet title */
.sheet-head .x{margin-left:auto;background:none;border:0;color:var(--dim);cursor:pointer;font-size:22px;line-height:1;position:relative;top:-2px}   /* the "×" glyph renders ~2.5px LOW in its box — pull it up so its optical centre lines up with the header text row */
.sheet-head .x:hover{color:var(--ink)}
/* Blocked-access treatment: a modal whose peer/user is blocked reads red at a glance — a red border + soft
   ring, a faintly red-tinted header, and a solid BLOCKED chip. Restrained on purpose (no full-red fill) so the
   configs/QRs inside stay legible. */
.sheet.blocked{border-color:color-mix(in srgb,var(--dangling) 60%,transparent);
  box-shadow:var(--shadow),0 0 0 3px color-mix(in srgb,var(--dangling) 22%,transparent)}
.sheet.blocked .sheet-head{background:color-mix(in srgb,var(--dangling) 9%,transparent);
  border-bottom-color:color-mix(in srgb,var(--dangling) 30%,transparent)}
.blocked-tag{display:inline-flex;align-items:center;gap:4px;margin-left:10px;padding:3px 9px;border-radius:99px;
  background:var(--dangling);color:#fff;font-size:10.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;line-height:1}
.blocked-tag svg{width:11px;height:11px}
.sheet-body{padding:20px}
.field{margin-bottom:15px}
/* two settings fields per row (turn client-settings forms); textarea/wide fields span both columns */
.fld2{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));column-gap:20px}
.fld2>.field.span2{grid-column:1/-1}
@media (max-width:560px){.fld2{grid-template-columns:1fr}}
.field>label{display:block;font-size:10.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--dim);font-weight:600;margin-bottom:7px}
.field .hint{font-size:11.5px;color:var(--faint);margin-top:6px}
/* validation: invalid field + error hint */
input.bad,.bad{border-color:var(--dangling) !important;box-shadow:0 0 0 3px color-mix(in srgb,var(--dangling) 12%,transparent)}
.hint.err{color:var(--dangling)}
.advbad{color:var(--dangling);font-size:11px;font-weight:600;margin-left:6px}
/* per-user VK link field: an editable icon-box mirroring the sub-link box; amber (empty/invalid) → blue (set) */
.vkfield.warn .vkbox{border-color:#e0a85f;background:color-mix(in srgb,#e0a85f 8%,var(--bg))}
.vkfield.warn .vkbox > .ic{color:#e0a85f}
.vkfield.set .vkbox{border-color:#3b9eff;background:color-mix(in srgb,#3b9eff 9%,var(--bg))}
.vkfield.set .vkbox > .ic{color:#3b9eff}
.hint.vk-warn{color:#b5741a;line-height:1.5}
:root[data-theme="dark"] .hint.vk-warn{color:#e0a85f}
/* multi-VK-link manager (VkLinksSheet) — a CRUD grid of rows: [primary radio] [url box] [delete] */
.vklinks-grid{display:flex;flex-direction:column;gap:8px;margin:12px 0}
.vklinks-row{display:flex;align-items:center;gap:10px}
.vklinks-row.warn .vkbox{border-color:#e0a85f;background:color-mix(in srgb,#e0a85f 8%,var(--bg))}
.vklinks-prim{display:flex;align-items:center;gap:6px;cursor:pointer;flex:0 0 auto;min-width:74px;font-size:12px;font-weight:700;color:var(--dim);user-select:none}
.vklinks-prim input{accent-color:var(--brand);cursor:pointer}
.vklinks-prim input:focus,.vklinks-prim input:focus-visible{outline:none}   /* no bluish square around the radio on click */
.vklinks-prim:has(input:checked){color:var(--brand)}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]) .hint.vk-warn{color:#e0a85f}}
.inline{display:flex;gap:8px}.inline input{flex:1}
@media(max-width:480px){}
.advtoggle{background:none;border:0;color:var(--dim);font-size:12px;cursor:pointer;padding:2px 0;display:flex;align-items:center;gap:6px}
.advtoggle:hover{color:var(--ink)}
.adv{display:none}.adv.open{display:block;margin-top:6px}
.sheet-foot{display:flex;gap:10px;align-items:center;padding:16px 20px;border-top:1px solid var(--line)}
.sheet-foot .grow{flex:1}
.discard-msg{display:inline-flex;align-items:center;gap:7px;font-size:12.5px;color:var(--pending)}
.discard-msg svg{flex:none}
.formmsg{font-size:12.5px;font-family:var(--mono);min-height:16px;margin-top:10px}
.formmsg.err{color:var(--dangling)} .formmsg.ok{color:var(--online)} .formmsg.work{color:var(--dim)} .formmsg.warn{color:#F0913C}

/* ───── toasts ───── */
.toasts{position:fixed;right:20px;bottom:20px;z-index:70;display:flex;flex-direction:column;gap:10px;max-width:360px}
.toast{background:var(--raise);border:1px solid var(--line-solid);border-left-width:3px;border-radius:10px;padding:12px 14px;
  font-size:13px;color:var(--ink);box-shadow:var(--shadow);display:flex;gap:10px;align-items:flex-start;animation:slidein .25s var(--ease)}
.toast.ok{border-left-color:var(--online)} .toast.err{border-left-color:var(--dangling)} .toast.info{border-left-color:var(--ready)}
.toast svg{flex:none;margin-top:1px}
@keyframes slidein{from{transform:translateX(22px);opacity:0}to{transform:none;opacity:1}}
.toast.out{animation:slideout .25s var(--ease) forwards}
@keyframes slideout{to{transform:translateX(22px);opacity:0}}

.loading{display:flex;align-items:center;justify-content:center;padding:60px;color:var(--dim);gap:12px;font-family:var(--mono);font-size:13px}
.spin{width:16px;height:16px;border:2px solid var(--line-solid);border-top-color:var(--brand);border-radius:50%;animation:spin .8s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
.ic-spin{display:inline-flex;animation:spin .8s linear infinite}

/* ───── responsive: collapse tables to cards ───── */
@media(max-width:680px){
  .appbar{gap:14px;padding:0 14px}.tabs a{padding:7px 11px}   /* keep just the panel version on small screens */
  .view{padding:16px 13px 70px}
  .statgrid{grid-template-columns:1fr 1fr}
  thead{display:none}
  tbody td{display:flex;justify-content:space-between;align-items:center;gap:14px;padding:8px 14px;border:0;text-align:right}
  tbody td::before{content:attr(data-label);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--dim);font-weight:600}
  tbody tr.clk{display:block;border:1px solid var(--line);border-radius:var(--r-sm);margin:10px 10px;background:var(--surface);padding:5px 0}
  .deploy-body{flex-direction:column;align-items:center}
}
@media(prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}

/* ───────── nodes management screen ───────── */
/* nodes list: one full-width block per line, actions on the right */
.nodegrid{display:flex;flex-direction:column;gap:15px}
/* grid: title row spans the full width (so CPU load can reach over the controls);
   data row (col 1) and controls (col 2) share the second row, so they line up. */
/* 4-column grid: each line's items share columns so they align vertically — name/peers, mesh/interfaces,
   cpu/throughput, gear/trash. Turn-proxies span the middle columns on a third row. */
/* 7-column grid. Left cluster: gutter · name/peers · interfaces/turn (hug peers). Flex gap. Right cluster:
   mesh (hugs cpu) · cpu/throughput (right-aligned metric) · gear/trash. */
/* FIXED column widths so every card's data lines up at the same x (cross-card alignment): the name/peers
   column is fixed (name spans past it into the gap), so interfaces/turn always start at the same x; the right
   cluster (mesh · cpu/throughput · icons) is fixed and right-anchored, so it's identical across cards. */
.ncard{display:grid;grid-template-columns:16px 124px minmax(0,auto) minmax(14px,1fr) 148px 316px 28px;align-items:center;column-gap:14px;row-gap:15px;cursor:pointer;
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r);overflow:hidden;transition:background .15s;padding:16px}
.ncard.removing{opacity:.82;border-style:dashed}
.ncard:hover{background:var(--surface-2)}
.nc-gutter{grid-column:1;display:flex;align-items:center;gap:6px}
.nc-name{grid-column:2 / 5;display:flex;align-items:center;gap:9px;min-width:0}   /* spans past the fixed peers col so long names+badges have room */
.nc-peers{grid-column:2}
.nc-ifaces{grid-column:3;min-width:0}          /* interfaces hug peers (start at a fixed x) */
.nc-turn{grid-column:3 / 7;min-width:0}         /* turn-proxies start by peers, room to the right */
.nc-mesh{grid-column:5;min-width:0;justify-self:end}   /* mesh sits at the right of its col, hugging cpu */
.nc-cpu.nm-item{grid-column:6;justify-self:end;gap:9px}    /* right-aligned in col6; label↔bar = 9 (needs .nm-item specificity to beat the generic .nm-item gap:8) */
/* CPU meter → per-vCPU bubble. The bar+number is always the hover target; the triangle is only an
   attention marker, shown when at least one vCPU is saturated. */
.cpupop{display:inline-flex;align-items:center;cursor:default}
.health-cols > .cpupop{display:block;min-width:0}      /* the Popover span is the grid item — keep the column shape */
.cpu-warn{display:inline-flex;align-items:center;margin-left:5px;color:var(--dangling)}
.cpu-warn svg{width:13px;height:13px}
.cpu-hot-n{color:var(--dangling);font-weight:600}
.deppop.cpu-bubble{min-width:264px;width:max-content}
.cpu-bubble .cpu-row{display:grid;grid-template-columns:auto 1fr 38px 58px 14px;align-items:center;gap:9px;padding:4px 8px;border-radius:6px;white-space:nowrap}
.cpu-bubble .cpu-row.hot .on-name{color:var(--dangling)}
.cpu-bubble .cpu-corebar{width:100%;min-width:56px}
.cpu-bubble .cpu-use{font-family:var(--mono);font-size:11px;text-align:right}
.cpu-bubble .cpu-idle{font-family:var(--mono);font-size:10px;color:var(--faint);text-align:right}
.cpu-bubble .cpu-sat{display:inline-flex;color:var(--dangling)}
.cpu-bubble .cpu-sat svg{width:12px;height:12px}
.nc-thru{grid-column:6;justify-self:end}   /* right-aligned so the upload "/s" sits exactly under the cpu number */
.nc-ctl{grid-column:7;flex:none}
.ncard .nname{font-family:var(--mono);font-size:15px;line-height:1;font-weight:600;color:var(--ink)}
.ncard:hover .nname{color:var(--brand)}
/* CPU load lives at the right of the title row, reaching over the controls */
.ncard .nm-cpuitem{flex:none}
.upd-dot{display:inline-flex;align-items:center;color:#F0913C;flex:none}
.nm-ver{font-family:var(--mono);font-size:11px;color:var(--dim);display:inline-flex;align-items:center;gap:8px;margin-left:12px}
.nm-ver.out{color:#F0913C}
.nm-ver-tag{font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#F0913C;background:rgba(240,145,60,.16);padding:1px 6px;border-radius:99px;display:inline-flex;align-items:center;gap:5px}
#appver .ver-upd{margin-left:10px;vertical-align:middle}
.spin.sm{width:10px;height:10px;border-width:1.5px}
.ncard .grow{flex:1}
.nstat{display:inline-flex;align-items:center;gap:6px;line-height:normal;font-size:10px;letter-spacing:.05em;text-transform:uppercase;font-weight:700;font-family:var(--mono)}   /* line-height:normal matches .reporting + the .tport pills so every status tag's label sits on the same baseline */
.nstat::before{content:"";width:7px;height:7px;border-radius:50%;background:currentColor}
.nstat.online{color:var(--online)} .nstat.online::before{animation:pulse 2.6s infinite}
.nstat.offline{color:var(--unknown)} .nstat.offline::before{display:none}   /* offline: GREY, info icon (no dot) */
.nstat.dangling{color:var(--pending)}
.nstat.stale{color:var(--unknown)} .nstat.stale::before{display:none}      /* stale / no fresh snapshot: GREY, info icon */
.nstat.down{color:var(--dangling)} .nstat.down::before{display:none}       /* down on the node: RED, warn icon */
.nstat.stopped{color:var(--unknown)} .nstat.stopped::before{display:none}  /* stopped by operator: GREY, stop icon */
.nstat.removing{color:var(--dangling)} .nstat.removing::before{display:none}  /* flagged for removal: RED, trash icon */
.nstat.procbusy{color:var(--partial)} .nstat.procbusy::before{display:none}        /* active work (re-installing / updating / installing): YELLOW */
.nstat.procconvert{color:var(--pending)} .nstat.procconvert::before{display:none}  /* converting: PURPLE */
.nstat.procuninstall{color:var(--dangling)} .nstat.procuninstall::before{display:none}  /* uninstalling: RED */
.nstat.enroll{color:#F0913C} .nstat.enroll::before{display:none}   /* awaiting enroll: ORANGE clock (node hasn't checked in yet) */
.nstat.uninst{color:var(--unknown)} .nstat.uninst::before{display:none}   /* uninstalled: GREY (kept for re-install; recover with the same token) */
.nstat.procok{color:var(--online)} .nstat.procok::before{display:none}        /* success: green ✓, auto-clears after ~5s (no ×) */
.nstat.procaborted{color:var(--unknown)} .nstat.procaborted::before{display:none}   /* aborted: grey, dismissable via × */
.nstat.procfail{color:var(--dangling)} .nstat.procfail::before{display:none}   /* failed: red, error popup + dismissable × */
.nstat .xbtn{margin-left:1px;padding:1px}
.b-removing{background:rgba(242,107,130,.13);color:var(--dangling)}
/* data row: four fixed-start columns so peers/interfaces/turn/throughput line up across
   cards regardless of content. Peers fits 555/999, interfaces ~two 8-char tags, turn takes
   the rest, throughput (no label) sits at the right with down/up each a fixed width. */
/* Peers on the left; to its right two rows — interfaces (+throughput, right) then turn-proxies (+controls,
   right). Tags wrap to new lines when there are too many; the right-hand item stays pinned top-right. */
.nmeta{display:grid;grid-template-columns:118px 1fr;gap:10px 18px;align-items:start}
.nm-item{display:flex;align-items:baseline;gap:8px;min-width:0}
.nm-rows{display:flex;flex-direction:column;gap:14px;min-width:0}   /* breathing room between the interfaces row and the turn-proxies row */
.nm-row{display:flex;align-items:flex-start;gap:10px;min-width:0}
.nm-row>.nm-l{flex:none;width:88px;padding-top:3px}
.nm-row>.tags{flex:1;display:flex;flex-wrap:wrap;gap:5px;min-width:0;padding-top:1px}
.nm-row>.nm-thru{flex:none;width:236px;margin-left:8px;justify-content:flex-end}   /* stack the control icons vertically at the card's right */
.nm-l{font-family:var(--mono);font-size:9px;text-transform:uppercase;letter-spacing:.08em;color:var(--faint);white-space:nowrap}
.nm-v{font-family:var(--mono);font-size:12.5px;color:var(--ink-2)}
.nm-v.nm-peers{font-size:15px;color:var(--ink)}
.nm-v.nm-peers small{font-size:.72em;color:var(--faint)}
.nm-v .down{color:var(--online)} .nm-v .up{color:var(--rate-up)}
/* idle throughput greys out like every other rate readout (rateCell/xferCell) — colour only once bytes move */
.nm-v.thru.idle .down,.nm-v.thru.idle .up{color:var(--faint)}
.nm-thru{display:flex;align-items:baseline;gap:8px}   /* throughput numbers, right-aligned; gap matches CPU load's label↔value spacing */
.nm-thru .thru{display:flex;gap:16px}
.nm-thru .down,.nm-thru .up{min-width:74px;white-space:nowrap;text-align:right}
.nm-cpu{display:flex;align-items:center;gap:5px;min-width:0}   /* bar↔value-box = 5 → bar↔value-number ≈ 6 (2px more than the 4px label↔bar gap) */
.nm-cpu .hm-bar{width:204px;flex:none}   /* long bar; right edge sits ~6px off the cpu number, left edge 9px off the CPU LOAD title (title stays 3px-left-shifted) */
/* reserve room for up to 333.33 and right-align, so the bar->value gap shrinks as the number grows */
.nm-cpu .nm-v{flex:none;min-width:31px;text-align:right}
@media(max-width:680px){.nmeta{grid-template-columns:1fr}}
.btn-danger{background:color-mix(in srgb,var(--dangling) 10%,transparent);color:var(--dangling);border:1px solid color-mix(in srgb,var(--dangling) 32%,transparent);padding:5px 11px;font-size:12px;border-radius:7px;cursor:pointer;transition:all .15s var(--ease)}
/* Set expiry / Reset expiry — same size + outlined style as .btn-danger (Block) so the footer trio matches; neutral when no expiry, orange once one is set. */
.btn-exp{background:transparent;color:var(--dim);border:1px solid var(--line-solid);padding:5px 11px;font-size:12px;border-radius:7px}
.btn-exp:hover{color:var(--ink);border-color:color-mix(in srgb,var(--dim) 45%,var(--line-solid))}
.btn-exp.on{color:#e0a85f;background:color-mix(in srgb,#e0a85f 10%,transparent);border-color:color-mix(in srgb,#e0a85f 34%,transparent)}
.btn-exp.on:hover{background:color-mix(in srgb,#e0a85f 16%,transparent);border-color:color-mix(in srgb,#e0a85f 46%,transparent)}
.btn-danger:hover{background:color-mix(in srgb,var(--dangling) 20%,transparent)}
.btn-danger:disabled{opacity:.5;cursor:not-allowed}
.btn-warn{background:color-mix(in srgb,#C0821A 11%,transparent);color:#C0821A;border:1px solid color-mix(in srgb,#C0821A 34%,transparent);padding:5px 11px;font-size:12px;border-radius:7px;cursor:pointer;transition:all .15s var(--ease);display:inline-flex;align-items:center;gap:5px}
.btn-warn .ic svg{width:13px;height:13px}
.btn-warn:hover{background:color-mix(in srgb,#C0821A 20%,transparent)}
.btn-warn:disabled{opacity:.5;cursor:not-allowed}
:root[data-theme="dark"] .btn-warn{color:#EBB43C;border-color:color-mix(in srgb,#EBB43C 34%,transparent)}
.confirm-type b.ct-learn{color:#C0821A}
:root[data-theme="dark"] .confirm-type b.ct-learn{color:#EBB43C}
.confirm-type b.ct-all{color:var(--dangling)}
/* one-time token + enrollment command */
.cmdrow{display:flex;gap:8px;align-items:stretch}
.cmdrow .tokenbox{flex:1}
.tokenbox{font-family:var(--mono);font-size:12.5px;color:var(--ink);background:var(--bg);border:1px solid var(--line-solid);border-radius:var(--r-sm);padding:11px 13px;word-break:break-all;user-select:all;line-height:1.55}
.copyaction{flex:none;background:var(--surface-2);border:1px solid var(--line-solid);border-radius:var(--r-sm);color:var(--dim);cursor:pointer;display:flex;align-items:center;gap:6px;font-size:12px;padding:0 13px;white-space:nowrap;transition:all .15s var(--ease)}
.copyaction:hover{color:var(--ink);border-color:var(--brand)}
.copyaction svg{flex:none}
/* node name + inline colour swatch (Node settings / Add node) — the swatch opens the native palette */
.namerow{display:flex;align-items:center;gap:9px}
.namerow input:not(.tf-color){flex:1}
.namerow .tf-color{width:34px;height:34px}

/* node health meters */
.health{display:flex;flex-direction:column;gap:12px}
/* CPU / Memory / Disk side by side — each takes a third */
.health-cols{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(0,1fr);gap:18px}   /* one equal column per meter — only BOUNDED percentages get a bar (cpu/mem/disk). Load is unbounded and system-wide, so it is a plain number in the CPU bubble, never a clamped bar. */
.hcol{display:flex;flex-direction:column;gap:6px;min-width:0}
.hcol-top{display:flex;justify-content:space-between;align-items:baseline;gap:8px}
.hcol-l{font-family:var(--mono);font-size:9px;text-transform:uppercase;letter-spacing:.08em;color:var(--faint);white-space:nowrap}
.hcol-v{font-family:var(--mono);color:var(--ink-2);font-size:11px;white-space:nowrap}
.health-hist{position:relative}
.health-hist .hist-cap{font-family:var(--mono);font-size:9px;text-transform:uppercase;letter-spacing:.1em;color:var(--faint);display:block;margin-bottom:3px}
.harea{display:block;width:100%}
.harea-wrap{position:relative;width:100%}
.ch-guide{position:absolute;top:0;bottom:0;width:1px;background:rgba(231,238,246,.28);pointer-events:none}
.ch-dot{position:absolute;width:7px;height:7px;border-radius:50%;border:1.5px solid var(--surface);transform:translate(-50%,-50%);pointer-events:none}
.ch-tip{position:absolute;top:2px;background:var(--raise);border:1px solid var(--line-solid);border-radius:6px;padding:3px 8px;font-size:11px;font-family:var(--mono);color:var(--ink);white-space:nowrap;pointer-events:none;z-index:6;box-shadow:0 4px 14px rgba(0,0,0,.4)}
.harea-empty{font-family:var(--mono);font-size:10.5px;color:var(--faint);padding:6px 0}
/* shown in place of a chart when there isn't enough history for the chosen range */
.harea-msg{display:flex;align-items:center;gap:9px;min-height:62px;padding:12px 4px;color:var(--faint);font-family:var(--mono);font-size:11.5px;line-height:1.55}
.harea-msg svg{width:15px;height:15px;flex:none;color:var(--ready)}
.harea-msg b{color:var(--ink-2);font-weight:600}
.hm-top{display:flex;justify-content:space-between;align-items:baseline;font-size:11.5px}
.hm-l{color:var(--dim);letter-spacing:.02em}
.hm-r{font-family:var(--mono);color:var(--ink-2);font-size:11px}
.hm-row{display:flex;align-items:center;gap:9px}
.hm-row .hm-bar{flex:1}
.spark{flex:none;opacity:.85}
.hm-bar{height:5px;border-radius:3px;background:var(--track);overflow:hidden}
.hm-fill{display:block;height:100%;border-radius:3px;background:var(--online);transition:width .6s var(--ease)}
.hm-fill.warn{background:var(--fault)}
.hm-fill.hot{background:var(--dangling)}
.hm-foot{font-family:var(--mono);font-size:11px;color:var(--faint);margin-top:1px}
.halerts{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:2px}
.halert{display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:600;padding:2px 8px;border-radius:99px}
.halert svg{flex:none;width:12px;height:12px}
.halert.hot{background:color-mix(in srgb,var(--dangling) 14%,transparent);color:var(--dangling)}
.halert.warn{background:rgba(232,192,75,.14);color:var(--pending)}
.actlist{display:flex;flex-direction:column;gap:1px;border:1px solid var(--line);border-radius:var(--r);overflow:hidden}
.act-row{display:flex;align-items:center;gap:10px;padding:9px 13px;background:var(--surface);text-decoration:none;color:inherit;transition:background .12s}
.act-row:hover{background:var(--surface-2)}
.act-row .name{color:var(--ink-2);font-size:13px}
.act-row .when{font-family:var(--mono);font-size:11.5px;color:var(--faint)}
/* Live screen — Peers/Users mode toggle (segmented switch: active Peers = green, active Users = blue) */
.pmode{display:inline-flex;flex:none;border:1px solid var(--line-solid);border-radius:9px;overflow:hidden;background:var(--surface-2);height:36px}
.pm-opt{appearance:none;border:none;background:none;cursor:pointer;font:inherit;font-size:12.5px;font-weight:600;padding:0 16px;color:var(--dim);transition:background .12s var(--ease),color .12s var(--ease);white-space:nowrap}
.pm-opt:hover{color:var(--ink)}
.pm-peers.on{background:color-mix(in srgb,var(--online) 20%,transparent);color:var(--online)}
.pm-users.on{background:color-mix(in srgb,var(--ready) 20%,transparent);color:var(--ready)}
/* Online filter — a single click-to-toggle pill (cyan when on, grey when off) instead of a checkbox */
.onlbtn{appearance:none;flex:none;height:36px;padding:0 18px;border:1px solid var(--line-solid);border-radius:9px;background:var(--surface-2);color:var(--dim);font:inherit;font-size:12.5px;font-weight:600;cursor:pointer;transition:background .12s var(--ease),color .12s var(--ease),border-color .12s var(--ease)}
.onlbtn:hover{color:var(--ink)}
.onlbtn.on{background:color-mix(in srgb,var(--brand) 20%,transparent);color:var(--sel-ink);border-color:color-mix(in srgb,var(--brand) 45%,transparent)}
/* endpoint cell: "Local turn-proxy" (fork-coloured) — hover shows the same Connected-via bubble as the dot */
.peergrid .turnep{cursor:help}
.turnep.off{opacity:.65;font-style:italic}   /* offline: it WAS relayed — attribution kept, just muted */
.condot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--unknown)}
/* online / turn dots glow with the same animated pulse as the peer-grid status badge (.sdot) */
.condot.on{background:var(--online);color:var(--online);animation:sdotpulse 1.6s ease-in-out infinite}
.condot.turn{background:var(--tfc);color:var(--tfc);animation:sdotpulse 1.6s ease-in-out infinite}
.condot.faulty{background:var(--fault);color:var(--fault);animation:sdotpulse 1.6s ease-in-out infinite}   /* orange, pulsing — handshake up but no data */
.condot.blocked{background:var(--fault)}   /* orange, static — reaching but no handshake */
.condot.off{background:var(--faint)}

/* ───── inline metadata tags (dense, colored row signature) ───── */
.tags{display:inline-flex;gap:4px;flex-wrap:wrap;margin-left:9px;vertical-align:middle}
.tg{display:inline-flex;align-items:center;font-family:var(--mono);font-size:9px;font-weight:500;text-transform:uppercase;
  letter-spacing:.04em;padding:2px 5px;border-radius:4px;line-height:1.45;white-space:nowrap}
.tg-wg{background:rgba(63,216,154,.13);color:var(--online)}
.tg-awg{background:rgba(31,200,214,.14);color:var(--brand)}
.tg-turn{color:var(--tfc,#B49BF7);background:color-mix(in srgb,var(--tfc,#A78BF5) 17%,transparent)}
/* the general turn-proxy accent (matches the turn-section panel icon .panel-ic.t-turn) */
.tf-gen{--tfc:#7C5CFF}
/* collapsed "turn ×N" badge (>3 proxies forward to one iface): general colour + a highlighted, ringed area.
   Ring is INSET (not an outward glow) so .ifcard-top's overflow:hidden can't clip it at the card's right edge. */
.turncollwrap{display:inline-flex;cursor:pointer}
.tg-turn.turncoll{gap:4px;margin-right:1px;background:color-mix(in srgb,var(--tfc) 24%,transparent);
  box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--tfc) 60%,transparent)}
.tg-turn.turncoll .turnx{font-weight:700;font-size:.92em;letter-spacing:.02em;opacity:.9}
.turncollwrap.on .tg-turn.turncoll{background:color-mix(in srgb,var(--tfc) 32%,transparent)}   /* brighten while the bubble is open */
/* the hover bubble listing each forwarding proxy — fork badge (own colour) + optional title, no header */
.deppop.turnlist{min-width:0;width:max-content;padding:7px;display:flex;flex-direction:column;gap:5px}
.turnlist-row{display:flex;align-items:center;gap:8px;white-space:nowrap}
.turnlist-row.muted{opacity:.5}
.turnlist-ttl{font-size:11.5px;color:var(--dim)}
/* "Connected via <proxy>" hover bubble on a turn-proxied interface badge */
.turnwrap{position:relative;display:inline-flex;align-items:center;gap:5px}
.turnbub-t{color:var(--ink);font-weight:600}
/* turn-connected status badge: fork colour + a glowing animated dot in place of the normal status dot */
.badge.b-turn{background:color-mix(in srgb,var(--tfc) 13%,transparent);color:var(--tfc);border-color:color-mix(in srgb,var(--tfc) 32%,transparent)}
/* online + turn badges use a glowing animated dot (.sdot, in the badge's own colour) instead of the ::before dot / check */
.badge.b-online::before,.badge.b-turn::before{display:none}
.badge .sdot{width:6px;height:6px;border-radius:50%;background:currentColor;flex:none;animation:sdotpulse 1.6s ease-in-out infinite}
@keyframes sdotpulse{0%,100%{box-shadow:0 0 5px currentColor,0 0 2px currentColor;opacity:1}50%{box-shadow:0 0 9px currentColor,0 0 3px currentColor;opacity:.7}}
.turnbub{position:absolute;top:calc(100% + 7px);left:0;white-space:nowrap;
  background:var(--surface-2);border:1px solid var(--line-solid);border-radius:8px;padding:6px 9px;font-size:11px;
  color:var(--dim);display:none;align-items:center;gap:6px;z-index:40;box-shadow:0 6px 22px rgba(0,0,0,.45);text-transform:none;letter-spacing:0}
.turnwrap:hover .turnbub{display:inline-flex}
/* transparent bridge over the gap so moving from the tag onto the bubble keeps it open (bubble is a
   descendant of .turnwrap, so hovering the bridge keeps .turnwrap:hover true) */
.turnbub::before{content:"";position:absolute;left:0;right:0;top:-9px;height:9px}
/* blocked/faulty "why" bubble — same surface as the turn bubble but allowed to wrap onto a couple of lines,
   with a colour-headed first line (the status name) over the explanation. */
.turnbub.statusbub{white-space:normal;flex-direction:column;align-items:flex-start;gap:3px;width:250px;line-height:1.45}
.statusbub-h{display:inline-flex;align-items:center;gap:5px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;font-size:10.5px}
.statusbub-h svg{width:12px;height:12px}
/* status pills (busy/ok/warn/del) — ONE uniform shape; colour per state. Defined after .tg so the
   pill shape wins even when written as "tg tg-busy" on cards (the type tags .tg-wg/.tg-awg stay small). */
/* status pills — ONE shape/size/font for all; only colour differs (busy=cyan, ok=green, warn=yellow, del=red, off=grey) */
.tg-busy,.tg-ok,.tg-warn,.tg-del,.tg-off,.tg-pending,.tg-convert,.tg-ready{display:inline-flex;align-items:center;gap:5px;border-radius:99px;padding:1px 8px;font-family:var(--mono);font-size:9.5px;font-weight:700;line-height:1.45;text-transform:uppercase;letter-spacing:.04em;white-space:nowrap;border:1px solid transparent}
.tg-busy{background:rgba(232,192,75,.16);color:var(--partial);border-color:color-mix(in srgb,var(--partial) 32%,transparent)}   /* active work: installing / creating — YELLOW */
.tg-pending{background:rgba(240,145,60,.13);color:#F0913C;border-color:color-mix(in srgb,#F0913C 30%,transparent)}   /* queued / awaiting the node — ORANGE */
.tg-convert{background:color-mix(in srgb,var(--pending) 16%,transparent);color:var(--pending);border-color:color-mix(in srgb,var(--pending) 32%,transparent)}   /* converting bare↔docker — PURPLE */
.tg-ready{background:color-mix(in srgb,var(--ready) 13%,transparent);color:var(--ready);border-color:color-mix(in srgb,var(--ready) 30%,transparent)}   /* just came up — BLUE (5s flash, then no tag) */
.tg-busy.del{background:rgba(240,90,90,.15);color:var(--dangling);border-color:color-mix(in srgb,var(--dangling) 38%,transparent)}
.tg-busy.warn{background:rgba(232,192,75,.16);color:var(--partial);border-color:color-mix(in srgb,var(--partial) 38%,transparent)}
.tg-ok{background:rgba(63,216,154,.16);color:var(--online);border-color:color-mix(in srgb,var(--online) 30%,transparent)}
.tg-fwd{background:color-mix(in srgb,var(--brand) 14%,transparent);color:var(--brand);border-color:color-mix(in srgb,var(--brand) 34%,transparent)}   /* traffic cascade: forwards out via another node — BRAND cyan */
.tg-direct{background:color-mix(in srgb,var(--dim) 12%,transparent);color:var(--dim);border-color:color-mix(in srgb,var(--dim) 28%,transparent)}   /* traffic exits directly from this node — neutral */
.tg-fwd svg{width:11px;height:11px}
/* bare traffic/cascade badges — coloured icon + text, no pill background */
.egb{display:inline-flex;align-items:center;gap:4px;font-family:var(--mono);font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;line-height:1;white-space:nowrap}
.egb svg{width:13px;height:13px}
.egb-fwd{color:var(--brand)}              /* smart / → node — cyan */
.egb-direct{color:#5B8CFF}                /* direct — blue */
.egb-cascade{color:var(--pending)}        /* cascade — Node-connections icon colour (purple) */
.egb-smart{color:var(--smart)}            /* smart routing — rose/pink */
/* on a node-connection card, the cascade badge + status dot sit at the TOP of the header (flex-start) so the
   dot is equidistant from the card's top and right borders (both = the card padding), not centred on the name */
.ifcard.tp .ifcard-top .egb,.ifcard.tp .ifcard-top .lkdot{align-self:flex-start}
.carry-tags{display:inline-flex;flex-wrap:wrap;gap:4px;justify-content:flex-end}   /* Carrying: forwarded interface tags, right-aligned */
/* connection-modal Forwarding / smart-cascade list */
.fwd-head{display:flex;align-items:center;gap:7px;margin-bottom:8px;font-family:var(--mono);font-size:12px}
.fwd-to{color:var(--faint)}
.fwd-ifaces{display:flex;flex-wrap:wrap;gap:5px}   /* cascade: all forwarded ifaces on one line */
.fwd-list{display:flex;flex-direction:column;gap:7px}   /* smart cascade: one iface + its rules per line */
.fwd-row{display:flex;align-items:center;gap:7px;font-family:var(--mono);font-size:12px;color:var(--dim);flex-wrap:wrap}

/* ── Panel settings screen: sticky Back/Save header + left section rail + content pane ── */
.setscreen{max-width:1000px}
.sethead{display:flex;align-items:center;gap:10px;margin-bottom:16px;padding-right:4px}   /* Save sits ~4px inside the content-card right edge */
.sethead .grow{flex:1}   /* push Back/Save to the right; the two grows centre the saved-flash between title and buttons */
.savedflash{color:var(--online);font-weight:600;font-size:13px;white-space:nowrap;display:inline-flex;align-items:center;gap:6px}
.savedflash svg{width:15px;height:15px}
.sethead b{font-size:18px;letter-spacing:-.01em}
.setbody{display:flex;gap:20px;align-items:flex-start}
.setrail{flex:none;width:188px;display:flex;flex-direction:column;gap:2px;position:sticky;top:16px}
.setrail-i{display:flex;align-items:center;justify-content:space-between;text-align:left;background:none;border:0;color:var(--faint);font:inherit;font-size:13px;padding:8px 12px;border-radius:8px;cursor:pointer;transition:background .12s,color .12s}
/* save-confirmation modal: the list of modified values + the reprovisioning warning */
.savediff-h{font-weight:600;margin-bottom:8px}
.savediff ul{margin:0;padding-left:18px;display:flex;flex-direction:column;gap:5px}
.savediff li{font-size:13px;color:var(--ink)}
.savediff-w{margin-top:12px;padding-top:10px;border-top:1px solid var(--line-solid);color:var(--partial);font-size:12.5px;line-height:1.5}
.savediff-note{margin-top:10px;color:var(--muted);font-size:12px;line-height:1.5}
.setrail-i:hover{color:var(--ink);background:color-mix(in srgb,var(--ink) 4%,transparent)}
.setrail-i.on{color:var(--ink);background:color-mix(in srgb,var(--brand) 16%,transparent);font-weight:600}
.setpane{flex:1;min-width:0;position:relative}   /* anchors the node-badge bar to the card's top-right */
.setpane .card{max-width:none}
.georefresh{display:flex;align-items:center;justify-content:flex-end;gap:10px;margin-top:16px}
/* Geo data → list providers + update schedule */
.provlist{display:flex;flex-direction:column;gap:6px;margin-bottom:6px}
.provrow{display:flex;align-items:center;gap:12px;padding:7px 11px;border:1px solid var(--line);border-radius:9px;background:color-mix(in srgb,var(--ink) 2%,transparent)}
/* disabled row: dim the content but NOT the colour swatch (.tswrow) — opacity on the whole row would wash out its
   picker + preview bubbles, so exclude them so the colours stay solid/visible */
.provrow.off > :not(.tswrow):not(.prov-swro){opacity:.55}
.cl-row.off > :not(.tswrow){opacity:.55}
.prov-swro{display:inline-flex;align-items:center;flex:none}
.bprov-meta{display:flex;flex-direction:column;align-items:flex-start;gap:2px;min-width:0;flex:1 1 auto}
.bprov-top{display:flex;align-items:center;gap:9px;min-width:0}
.bprov-note{font-size:11.5px;color:var(--dim);line-height:1.3;white-space:normal}
.bprow{align-items:center}
/* Curated (panel-maintained) provider: a locked check where the enable switch would be — same 34px column so rows stay aligned */
.prov-lock{display:inline-flex;align-items:center;justify-content:center;width:34px;height:19px;flex:none;color:var(--online)}
.prov-lock .ic svg{width:15px;height:15px}
.prov-meta{display:flex;align-items:center;gap:9px;min-width:0}
.prov-name{font-size:13.5px;font-weight:600;color:var(--ink);text-decoration:none;white-space:nowrap}
.prov-desc{font-size:11.5px;color:var(--dim);text-align:right;margin-left:auto;white-space:nowrap}
.prov-name:hover{color:var(--brand)}
.prov-tiers{display:flex;gap:4px}
.prov-err{display:inline-flex;align-items:center;gap:4px;font-size:11px;color:var(--partial)}
.prov-err .ic svg{width:12px;height:12px}
.prov-upd{font-size:11px;color:var(--dim);font-variant-numeric:tabular-nums}
.prov-upd.never{opacity:.6;font-style:italic}
.prov-st{display:inline-flex;align-items:center;gap:4px;font-size:11.5px;font-weight:600;white-space:nowrap}
.prov-st .ic svg{width:13px;height:13px}
.prov-st.upd{color:var(--dim)}
.prov-st.ok{color:var(--online)}
.prov-st.err{color:var(--partial)}
.prov-repo{font-family:var(--mono);font-size:11.5px;color:var(--faint);white-space:nowrap;text-decoration:none}   /* GitHub repo link on a routing-list provider row */
.prov-repo:hover{color:var(--ready);text-decoration:underline}
.provrow .tf-arrow{display:inline-flex;animation:spin 1s linear infinite}
.provrow .tf-arrow .ic svg{width:13px;height:13px}
.schedrow{display:flex;gap:18px;flex-wrap:wrap;align-items:flex-start}
.schedrow .field{flex:1;min-width:180px}
.timein{width:100%;box-sizing:border-box;padding:8px 10px;font-family:var(--mono);font-size:13px;color:var(--ink);background:var(--bg);border:1px solid var(--line-solid);border-radius:7px}
.timein:disabled{opacity:.5}
.cllist{display:flex;flex-direction:column;gap:6px}
.cl-row{display:flex;align-items:center;gap:10px;padding:7px 11px;border:1px solid var(--line);border-radius:9px;background:color-mix(in srgb,var(--ink) 2%,transparent)}
.cl-row .grow{flex:1}   /* push the owner link + wg/awg capability tags to the right edge */
.cl-name{background:none;border:0;color:var(--ink);font:inherit;font-weight:600;cursor:pointer;padding:0;text-align:left;flex:none;white-space:nowrap}
.cl-name:hover{color:var(--brand)}
.cl-meta{font-size:11.5px}
/* inline entry preview: whole entries that fit on the line + a "(N more)" tail; clicking it opens the editor */
.cl-preview{flex:1;min-width:0;display:flex;align-items:baseline;gap:7px;overflow:hidden;background:none;border:0;padding:0;cursor:pointer;text-align:left}
.cl-ptext{font-family:var(--mono);font-size:11.5px;color:var(--dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
.cl-preview:hover .cl-ptext{color:var(--ink)}
.cl-more{font-size:11px;color:var(--faint);white-space:nowrap;flex:none}
.tf-swatch{width:10px;height:10px;border-radius:50%;background:var(--c,var(--brand));flex:none;box-shadow:0 0 0 1px color-mix(in srgb,var(--c,var(--brand)) 40%,transparent)}
.tf-name{font-weight:600;color:var(--ink);font-family:var(--mono);font-size:12.5px;text-decoration:none;cursor:pointer}
.tf-name:hover{text-decoration:underline dotted;text-underline-offset:3px}
/* version chip — tinted with the fork's picker colour (--tfc, set on .tf-verwrap) so the picker visibly drives it */
.tf-verwrap{position:relative;display:inline-flex}
.tf-ver{font-family:var(--mono);font-size:11px;color:var(--tfc,var(--ready));background:color-mix(in srgb,var(--tfc,var(--ready)) 13%,transparent);padding:1px 7px;border-radius:99px;white-space:nowrap;cursor:default;border:1px solid color-mix(in srgb,var(--tfc,var(--ready)) 22%,transparent)}
.tf-ver.none{color:var(--faint);background:none;font-style:italic}
/* hover bubble: the nodes running this fork (+ live updating/updated during an Update) */
.tf-verpop{position:absolute;top:calc(100% + 6px);left:0;z-index:40;min-width:max-content;display:none;flex-direction:column;gap:4px;
  padding:7px 9px;border:1px solid var(--line-solid);border-radius:9px;background:var(--surface-2);box-shadow:0 8px 26px rgba(0,0,0,.45)}
.tf-verwrap:hover .tf-verpop{display:flex}
.tf-vg-node{display:flex;align-items:center;gap:6px;font-size:11.5px}
.tf-vg-dot{width:7px;height:7px;border-radius:50%;flex:none}
.tf-vg-nm{color:var(--ink);font-family:var(--mono);font-size:11px}
.tf-vg-st{margin-left:auto;display:inline-flex;align-items:center;gap:3px;font-size:10px;font-family:var(--mono);padding:0 5px;border-radius:99px}
.tf-vg-st svg{width:11px;height:11px}
.tf-vg-st.upd{color:#F0913C;background:color-mix(in srgb,#F0913C 15%,transparent)}
.tf-vg-st.ok{color:var(--online);background:color-mix(in srgb,var(--online) 15%,transparent)}
/* update-check status per fork */
.tf-chk{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-family:var(--mono);white-space:nowrap;color:var(--partial)}
.tf-chk.ok{color:var(--online)}
.tf-chk.upd{color:#F0913C}
.tf-chk.upd svg{width:13px;height:13px}
.tf-chk.ok svg{width:13px;height:13px}
button.tf-updbtn{background:none;border:none;cursor:pointer;padding:2px 5px;border-radius:6px;line-height:1}   /* clickable "update to …" — updates every deployed instance of this fork */
button.tf-updbtn:hover{background:color-mix(in srgb,#F0913C 15%,transparent)}
.tf-arrow{display:inline-flex;align-items:center;justify-content:center;width:13px;height:13px;line-height:0;color:var(--partial)}
.tf-arrow svg{display:block;width:13px;height:13px;transform-box:view-box;transform-origin:center;animation:spin .8s linear infinite}   /* spin the SVG on its viewBox centre (same pattern as .updspin / .iconbtn) so it's centred even on a fresh load */
/* which interface types the fork's client supports — wg always; awg only for the non-WG-only forks */
.cl-caps{display:inline-flex;align-items:center;gap:4px;flex:none;margin-left:-2px;cursor:help}
.warntext{color:var(--partial);font-weight:600}
/* per-fork colour picker (native input, themed to a small rounded swatch) */
.tf-color{width:24px;height:24px;flex:none;padding:0;border:1px solid var(--line-solid);border-radius:6px;background:none;cursor:pointer}
.tf-color::-webkit-color-swatch-wrapper{padding:2px}
.tf-color::-webkit-color-swatch{border:none;border-radius:4px}
.tf-color::-moz-color-swatch{border:none;border-radius:4px}
.cllist.dimmed{opacity:.5;pointer-events:none}
.tf-gear{flex:none;margin-left:4px}
/* per-OS platform buttons: fill = native(emerald)/crossplatform(sky) @30% · border = plain(red)/obfs(neutral) · icon = sidecar(yellow)/app(ink) */
.tf-plats{display:flex;align-items:center;gap:9px;margin-right:12px}
.tf-plat{width:32px;height:32px;flex:none;border-radius:8px;display:grid;place-items:center;border:1px solid var(--line-solid);background:transparent;color:var(--ink-2);cursor:pointer;padding:0;transition:transform .1s,box-shadow .1s}
.tf-plat:hover{transform:translateY(-1px);box-shadow:0 3px 8px -3px rgba(30,52,84,.4)}
.tf-plat .ic{display:grid;place-items:center;line-height:0}
.tf-plat .ic svg{width:17px;height:17px;display:block}
/* solid-neon fill by client kind (cli's hue wins over nat/cross for a CLI); plain = dark-red glyph; off = 25% ghosted grey tile */
.tf-plat.nat,.tf-plat.cross,.tf-plat.cli{color:#06121c;background:linear-gradient(158deg,color-mix(in srgb,var(--pc) 94%,#fff 6%),color-mix(in srgb,var(--pc) 72%,#000 14%));border:1px solid color-mix(in srgb,var(--pc) 50%,#fff 10%);box-shadow:0 4px 12px -5px color-mix(in srgb,var(--pc) 55%,transparent),inset 0 1px 0 color-mix(in srgb,#fff 40%,transparent)}
.tf-plat.nat{--pc:#14E39A}
.tf-plat.cross{--pc:#38CCF5}
.tf-plat.cli{--pc:#FFC531}
.tf-plat.plain{color:#9E2E1B}
.tf-plat.off{cursor:not-allowed;color:#06121c;background:linear-gradient(158deg,#9dabc0,#7a8698);border:1px solid #7f8ca0;box-shadow:none;opacity:.25}
.tf-plat.off:hover{transform:none;box-shadow:none}
/* a disabled fork dims its row via opacity — keep the platform GROUP at full opacity (so the hover bubble stays solid)
   and dim just the buttons instead; the bubble is a sibling of the button, so it reads clearly even when the fork is off */
.cl-row.off > .tf-plats{opacity:1}
.cl-row.off .tf-plat:not(.off){opacity:.5}
.cl-row.off .tf-plat.off{opacity:.2}
/* hover bubble on a platform button: app · by author / with <core> core, obfuscation type aligned right */
.tf-platwrap{display:inline-flex}
.tf-plbub{left:auto;right:18px;top:calc(100% - 9px);flex-direction:row;align-items:flex-start;gap:20px;white-space:nowrap;z-index:5}
.tf-plbub-l{display:flex;flex-direction:column;gap:2px}
.tf-plbub-app{color:var(--ink);font-weight:600}
.tf-plbub-by{color:var(--dim);font-weight:400}
.tf-plbub-core{color:var(--dim)}
.tf-plbub-obf{margin-left:auto;align-self:center;font-family:var(--mono);font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#A974F2;background:color-mix(in srgb,#A974F2 16%,transparent);border-radius:4px;padding:2px 6px}
.tf-plbub-obf.plain{color:var(--dangling);background:color-mix(in srgb,var(--dangling) 15%,transparent)}
/* Server "Clients" sheet — equal-width OS segmented control (icon over label; greyed when the fork has no app for
   that OS) + the per-app config reference */
.osseg{display:grid;grid-template-columns:repeat(5,1fr);border:1px solid var(--line-solid);border-radius:10px;overflow:hidden;background:var(--surface-2)}
.osseg-cell{display:flex;align-items:center;justify-content:center;gap:7px;padding:10px 6px;background:none;border:0;border-left:1px solid var(--line);color:var(--ink-2);font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:.02em;cursor:pointer;transition:background .12s,color .12s;min-width:0}
.osseg-cell:first-child{border-left:0}
.osseg-ic{display:flex;align-items:center;justify-content:center;flex:none}
.osseg-lbl{white-space:nowrap}
.osseg-cell:not(.off):hover{background:color-mix(in srgb,var(--brand) 8%,transparent);color:var(--ink)}
.osseg-cell.on{background:color-mix(in srgb,var(--brand) 15%,transparent);color:var(--brand)}
.osseg-cell.off{opacity:.36;cursor:not-allowed;color:var(--dim)}
.clientcfg-row .ccl{color:var(--ink-2);font-weight:600;min-width:92px;flex:none}
.clientcfg-row a.ccr,.clientcfg-row .ccr.lnk{color:var(--brand);text-decoration:none;word-break:break-all}
.clientcfg-row a.ccr:hover{text-decoration:underline}
/* styled OS dropdown (glyph + name), used in the turn-config modal — mirrors the Clients-sheet OS visuals */
.turncfg-os{display:flex;align-items:center;gap:12px;margin:0 0 12px}
.turncfg-os > label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-2)}
.osdd{position:relative;display:inline-block}
.osdd-btn{display:inline-flex;align-items:center;gap:8px;padding:7px 11px;border:1px solid var(--line-solid);border-radius:8px;background:var(--surface-2);color:var(--ink);font-family:var(--mono);font-size:12px;font-weight:600;cursor:pointer;min-width:150px}
.osdd-btn:hover,.osdd-btn.open{border-color:color-mix(in srgb,var(--brand) 45%,var(--line-solid))}
.osdd-btn .osdd-car{margin-left:auto;color:var(--dim);font-size:10px}
.osdd-ic{display:flex;align-items:center}
.osdd-lbl{white-space:nowrap}
.osdd-menu{position:absolute;top:calc(100% + 4px);left:0;z-index:50;min-width:100%;background:var(--surface);border:1px solid var(--line-solid);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.18);padding:4px;display:flex;flex-direction:column;gap:1px}
.osdd-opt{display:flex;align-items:center;gap:8px;padding:7px 9px;border:0;border-radius:6px;background:none;color:var(--ink-2);font-family:var(--mono);font-size:12px;font-weight:600;cursor:pointer;text-align:left;white-space:nowrap}
.osdd-opt:hover{background:color-mix(in srgb,var(--brand) 9%,transparent);color:var(--ink)}
.osdd-opt.on{color:var(--brand)}
.osdd-opt .ic{margin-left:auto}
/* Turn client picker — styled dropdown; each row = name·by author·[with core] + three badges (relation / obfuscation / kind) */
.cpick{position:relative}
.cpick-btn{display:flex;align-items:center;gap:10px;width:100%;text-align:left;padding:9px 12px;border:1px solid var(--line-solid);border-radius:8px;background:var(--surface);color:var(--ink);cursor:pointer;font:inherit}
.cpick-btn.open{border-color:var(--brand)}
.cpick-car{margin-left:4px;color:var(--muted);flex:0 0 auto}
.cpick-menu{position:absolute;z-index:50;top:calc(100% + 4px);left:0;right:0;background:var(--surface);border:1px solid var(--line-solid);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.18);padding:4px;max-height:340px;overflow-y:auto;display:flex;flex-direction:column;gap:1px}
.cpick-opt{display:flex;align-items:center;gap:10px;width:100%;text-align:left;padding:8px 10px;border:0;border-radius:6px;background:none;color:var(--ink-2);cursor:pointer;font:inherit}
.cpick-opt:hover{background:color-mix(in srgb,var(--brand) 9%,transparent);color:var(--ink)}
.cpick-opt.on{background:color-mix(in srgb,var(--brand) 12%,transparent);color:var(--ink)}
.ce-name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px}
.ce-by{color:var(--muted)}
.ce-auth{font-weight:700}
.ce-badges{display:flex;align-items:center;gap:5px;flex:0 0 auto}
.ce-tag{display:inline-flex;align-items:center;font-family:var(--mono);font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;padding:2px 5px;border-radius:4px;line-height:1.45;white-space:nowrap;background:color-mix(in srgb,currentColor 15%,transparent)}
.ce-native{color:var(--online)}
.ce-cross{color:var(--ready)}
.ce-obf{color:#A974F2}
.ce-plain{color:var(--dangling)}
.ce-cli{color:#D9B84A}
.ce-app{color:#5FA8E0}
.ce-launch{margin:9px 0 0;font-size:12px;color:var(--muted)}
/* ★ primary-connection toggle on a peer's deployment cards */
.primtog{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border:1px solid var(--line-solid);border-radius:999px;background:none;color:var(--muted);cursor:pointer;font-size:11px;font-weight:600;line-height:1.5;white-space:nowrap}
.primtog:hover:not(:disabled){border-color:#E0B93E;color:#E0B93E}
.primtog.on{border-color:#E0B93E;color:#E0B93E;cursor:default}
.primtog:disabled{cursor:default}
.primstar{font-size:13px;line-height:1}
/* client-roster drift check modal */
.rosterlist{display:flex;flex-direction:column;gap:8px}
.roster-row{border:1px solid var(--line-solid);border-radius:9px;padding:9px 12px}
.roster-row.changed{border-color:color-mix(in srgb,var(--fault) 40%,var(--line-solid))}
.roster-hd{display:flex;align-items:center;gap:10px;margin-bottom:3px}
.roster-nm{font-weight:600;font-size:13px}
.roster-by{font-size:11.5px;color:var(--dim);font-weight:600}
.roster-file{display:flex;align-items:center;gap:10px;font-size:11.5px;padding:2px 0;color:var(--ink-2);flex-wrap:wrap}
.roster-file a{color:var(--brand);text-decoration:none}
.roster-file a:hover{text-decoration:underline}
.roster-file a.faint{color:var(--dim)}
.roster-fdiff{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
.fd-sum{font-family:var(--mono);font-size:11px;font-weight:600;white-space:nowrap}
.fd-sum.add{color:var(--online)}
.fd-sum.rem{color:var(--dangling)}
.fd-approx{font-size:10px;color:var(--dim);font-style:italic;cursor:help;white-space:nowrap}
.revlist{display:flex;flex-direction:column;gap:2px}
.rev-all{display:flex;align-items:center;gap:8px;padding:4px 2px 8px;font-size:11px;font-weight:600;color:var(--dim);text-transform:uppercase;letter-spacing:.04em;border-bottom:1px solid var(--line);margin-bottom:4px}
.rev-item{display:flex;align-items:center;gap:8px;padding:5px 2px;font-size:12px;border-radius:6px;cursor:pointer}
.rev-item:hover{background:var(--surface-2)}
.rev-item input[type=checkbox],.rev-all input[type=checkbox]{width:15px;height:15px;flex:0 0 auto;margin:0;accent-color:var(--brand);cursor:pointer}
.rev-item.disabled{cursor:default;opacity:.72}
.rev-item.disabled:hover{background:none}
.rev-file{color:var(--ink-2);font-size:11px}
/* roster grid: per-field counters, version picker, needs-wiring tag */
.rc-counts{display:inline-flex;gap:5px;align-items:center}
.rc-cnt{font-family:var(--mono);font-size:11px;font-weight:700;border-radius:5px;padding:1px 6px;border:1px solid transparent;white-space:nowrap}
.rc-cnt.add{color:var(--online);background:color-mix(in srgb,var(--online) 13%,transparent);border-color:color-mix(in srgb,var(--online) 32%,transparent)}
.rc-cnt.rem{color:var(--dangling);background:color-mix(in srgb,var(--dangling) 13%,transparent);border-color:color-mix(in srgb,var(--dangling) 32%,transparent)}
.rc-cnt.wire{color:var(--fault);background:color-mix(in srgb,var(--fault) 13%,transparent);border-color:color-mix(in srgb,var(--fault) 32%,transparent)}
.rc-cnt.val{color:var(--ready);background:color-mix(in srgb,var(--ready) 13%,transparent);border-color:color-mix(in srgb,var(--ready) 32%,transparent)}
.fd-sum.val{color:var(--ready)}
.rc-ver{font-family:var(--mono);font-size:11px;padding:3px 6px;border-radius:6px;border:1px solid var(--line-solid);background:var(--surface-2);color:var(--ink);max-width:170px}
.rc-wtag{font-size:10px;font-weight:600;color:var(--fault);background:color-mix(in srgb,var(--fault) 12%,transparent);border-radius:5px;padding:1px 6px;white-space:nowrap}
.tg-plat{background:var(--surface-2);border:1px solid var(--line);color:var(--dim);border-radius:5px;padding:1px 6px;font-size:10px;font-weight:600}
/* Server-defaults ExecStart box: a read-only auto/obfuscation block above an editable free-flags textarea, as one field */
.obfrow{display:flex;align-items:center;gap:16px;flex-wrap:wrap}
.obfctl{display:flex;align-items:center;gap:8px;text-transform:none;letter-spacing:0;font-size:12px;cursor:pointer}
.execbox{border:1px solid var(--line);border-radius:8px;overflow:hidden;background:var(--bg)}
.execbox-auto{background:var(--surface-2);color:var(--dim);font-family:var(--mono);font-size:12px;line-height:1.55;padding:9px 11px;white-space:pre-wrap;word-break:break-word;border-bottom:1px dashed var(--line-solid);user-select:text}
.execbox-extra{display:block;width:100%;box-sizing:border-box;border:0;background:transparent;padding:9px 11px;font-family:var(--mono);font-size:12px;line-height:1.55;color:var(--ink);resize:vertical;min-height:54px;outline:none;box-shadow:none}
.execbox-extra:focus{outline:none;box-shadow:none}
/* palette pickers (Panel settings → Interfaces / Display): a concept label + a themed (dark|light) swatch pair */
.palrow{display:flex;flex-wrap:wrap;gap:12px 22px;margin:0 0 6px}
.palcell{display:inline-flex;align-items:center;gap:11px}
.palcell .pallbl{font-size:13px;color:var(--ink-2);min-width:88px}
/* themed swatch pair: two label-less pickers (dark, light); hover shows the colour previewed on that mode's bg */
.tswrow{display:inline-flex;gap:7px;align-items:center}
.tsw{position:relative;display:inline-flex}
/* preview sits directly UNDER the swatch on hover; while the OS picker is OPEN (focus-within) it slides 5px to the
   LEFT of the swatch so the picker (which opens below/over the swatch) can't cover it, then returns under on close. */
.tsw-bub{position:absolute;top:calc(100% + 8px);left:50%;transform:translateX(-50%);display:none;
  flex-direction:column;align-items:center;gap:8px;padding:11px 13px 8px;border-radius:10px;
  border:1px solid var(--line-solid);z-index:70;box-shadow:0 12px 30px rgba(0,0,0,.4);white-space:nowrap}
.tsw:hover .tsw-bub,.tsw:focus-within .tsw-bub{display:inline-flex}
.tsw:focus-within .tsw-bub{top:50%;left:auto;right:calc(100% + 5px);transform:translateY(-50%)}
.tsw-bub::before{content:"";position:absolute;left:0;right:0;top:-9px;height:9px}   /* hover bridge above */
.tsw-dark{background:#0A0E15;color:#E7EEF6}
.tsw-light{background:#EDF1F7;color:#101822}
.tsw-cap{font-family:var(--mono);font-size:8px;text-transform:uppercase;letter-spacing:.14em;opacity:.5}
/* theme-accent preview: an outline button + a filled control chip, both in the picked colour */
.tsw-theme{display:inline-flex;align-items:center;gap:8px}
.tsw-btn{font-size:11px;padding:4px 9px;border-radius:7px;border:1px solid currentColor;font-family:var(--sans)}
.tsw-chip{width:15px;height:15px;border-radius:4px;background:currentColor}
/* peer-health DETECTION toggles (checkbox + example badge + explanation) — Interfaces settings */
.condrow{display:grid;grid-template-columns:34px 88px 1fr;align-items:center;gap:16px;padding:11px 0;border-top:1px solid var(--line)}
.condrow:first-of-type{border-top:none;padding-top:2px}
.cond-b{display:flex;align-items:center}
.cond-t{font-size:12.5px;color:var(--dim);line-height:1.45}
.vklink-in{width:100%;max-width:560px}
/* turn-proxy client configs modal */
.turncfg{display:flex;flex-direction:column;gap:14px}
.turntabs{display:flex;flex-wrap:wrap;gap:8px}
.turntab{font-size:12px}
.turnclients{margin-top:8px}
.xtag{font-family:var(--mono);font-size:9px;letter-spacing:.06em;text-transform:uppercase;padding:1px 5px;border-radius:4px;margin-left:6px;background:rgba(232,192,75,.14);color:var(--pending);border:1px solid rgba(232,192,75,.3)}
/* the head "experimental" tag explains itself in a hover/focus bubble (justified) instead of an on-screen banner */
.xtag-tip{position:relative;cursor:help;outline:none}
.xtip-bubble{display:none;position:absolute;top:calc(100% + 7px);right:0;z-index:60;width:min(280px,72vw);
  text-align:justify;text-transform:none;letter-spacing:normal;font-family:var(--sans);font-size:11px;font-weight:400;line-height:1.5;
  color:var(--pending);background:color-mix(in srgb,#E8C04B 9%,var(--surface-2));border:1px solid rgba(232,192,75,.25);border-radius:8px;padding:8px 10px;box-shadow:0 8px 24px rgba(0,0,0,.28)}
.xtag-tip:hover .xtip-bubble,.xtag-tip:focus .xtip-bubble,.xtag-tip:focus-visible .xtip-bubble{display:block}
.turncfg-item>.hint{font-size:11px;line-height:1.5}
/* redundant same-fork proxies: title left, dropdown right, on one line */
.turninst{display:flex;align-items:center;gap:12px}
.turninst label{flex:none;font-size:11px;color:var(--faint);text-transform:uppercase;letter-spacing:.06em}
.turninst .selwrap{margin-left:auto;min-width:220px;max-width:360px}
.turncfg-item{display:flex;flex-direction:column;gap:4px}
.turncfg-qr{display:flex;justify-content:center;padding:10px 0}
.turncfg-qr .qr{background:#fff;border:1px solid var(--line);border-radius:var(--r-sm);padding:8px;line-height:0;cursor:zoom-in}
.turncfg-qr .qr .qrimg{display:block;width:min(208px,58vw);height:auto;image-rendering:pixelated;image-rendering:crisp-edges}
.turncfg-qr.qr-pending{color:var(--faint);font-size:12px;padding:34px 0}
.turncfg-head{display:flex;align-items:center;gap:9px;flex-wrap:wrap}
.turncfg-foot{display:flex;align-items:center;gap:8px;margin-top:6px}
.turncfg-foot .grow{flex:1}
.tcf-label{font-size:11px;color:var(--faint);text-transform:uppercase;letter-spacing:.06em}
.turncfg-cmd{position:relative;margin-bottom:6px}
.turncfg-cmd .tokenbox{font-size:11.5px;line-height:1.5;color:var(--dim);background:var(--surface-2);border-radius:8px;padding:9px 11px 30px}
.cmd-copy{position:absolute;right:7px;bottom:7px;width:26px;height:26px;border-radius:6px;border:1px solid var(--line-solid);
  background:var(--surface);color:var(--dim);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:all .12s}
.cmd-copy:hover{color:var(--ink);border-color:var(--brand);background:var(--raise)}
/* hovering the command box sparks the copy button so it's discoverable — plain cyan border like the other buttons */
.turncfg-cmd:hover .cmd-copy{color:var(--brand);border-color:var(--brand);background:color-mix(in srgb,var(--brand) 10%,transparent)}
.turncfg-ta{width:100%;box-sizing:border-box;font-family:var(--mono);font-size:11.5px;line-height:1.5;color:var(--dim);
  background:var(--surface-2);border:1px solid var(--line-solid);border-radius:8px;padding:9px 11px;
  white-space:pre-wrap;word-break:break-all;overflow:hidden;resize:none;min-height:52px}
.turnhead{display:flex;align-items:center;gap:10px}
.turnhead .grow{flex:1}
/* ───── Settings → Integrations (external API + webhooks) ───── */
.notice.ok{background:color-mix(in srgb,var(--online) 9%,transparent);border:1px solid color-mix(in srgb,var(--online) 28%,transparent);color:var(--online)}
.tok-add{display:flex;gap:8px;margin:0 0 10px}
.tok-add input{flex:1}
.tokreveal{display:flex;align-items:center;gap:8px;margin:0 0 14px;padding:9px 11px;border:1px solid var(--line-solid);border-radius:9px;background:var(--bg)}
.tokval{flex:1;font-family:var(--mono);font-size:12.5px;color:var(--ink);word-break:break-all;user-select:all}
.toklist{display:flex;flex-direction:column;gap:7px;margin:2px 0 4px}
.tokrow{display:flex;align-items:center;gap:10px;padding:8px 11px;border:1px solid var(--line);border-radius:9px;background:color-mix(in srgb,var(--ink) 2%,transparent)}
.tokrow.off{opacity:.6}
.tokrow-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.tokrow-label{font-size:13px;color:var(--ink);font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tokrow-label.mono{font-family:var(--mono);font-weight:500;font-size:12px}
.tokrow-meta{font-size:11px;color:var(--faint)}
.wh-events{display:grid;grid-template-columns:1fr 1fr;gap:8px 16px;margin:2px 0 14px}
.wh-ev{display:flex;align-items:center;gap:8px;font-size:12.5px;cursor:pointer}
.wh-ev .mono{font-family:var(--mono);font-size:12px;color:var(--ink)}
.wh-ev-lbl{color:var(--faint);font-size:11.5px}
.wh-en{display:flex;align-items:center;gap:9px;font-size:12.5px;color:var(--dim)}
.apiendpoints{display:flex;flex-direction:column;gap:4px;margin:2px 0 6px}
.apiep{display:flex;align-items:center;gap:10px;font-size:12px;padding:5px 0;border-bottom:1px solid var(--line)}
.apiep:last-child{border-bottom:none}
.apiep-m{font-family:var(--mono);font-size:10.5px;font-weight:700;color:var(--online);background:color-mix(in srgb,var(--online) 12%,transparent);padding:1px 7px;border-radius:5px;flex:none}
.apiep .mono{font-family:var(--mono);color:var(--ink);flex:none}
.apiep-d{color:var(--faint);margin-left:auto;font-size:11.5px}
.apisnip{margin:10px 0 0;border:1px solid var(--line-solid);border-radius:9px;overflow:hidden}
.apisnip-h{display:flex;align-items:center;justify-content:space-between;padding:7px 11px;font-size:11px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--faint);background:color-mix(in srgb,var(--ink) 3%,transparent);border-bottom:1px solid var(--line)}
.apisnip-c{display:block;font-family:var(--mono);font-size:12px;color:var(--ink);padding:11px 13px;white-space:pre;overflow-x:auto;line-height:1.5}
/* ───── turn-proxy collected client IPs (header bubble + settings grid) ───── */
.tipdot{width:7px;height:7px;border-radius:50%;background:var(--dim);flex:none;opacity:.55}
.tipdot.on{background:var(--online);opacity:1;animation:sdotpulse 1.6s ease-in-out infinite}
.turnips-wrap{margin-left:auto}                        /* push the "Turn IPs" control to the right of the sheet head… */
.turnips-wrap + .x{margin-left:8px}                    /* …and sit it right next to the close button (no split gap) */
.turnips-hd{font-size:12px;color:var(--dim);cursor:pointer;padding:3px 9px;border:1px solid var(--line-solid);border-radius:7px;white-space:nowrap}
.turnips-wrap.on .turnips-hd,.turnips-hd:hover{color:var(--brand);border-color:var(--brand)}
.turnips-hd b{color:var(--ink);font-weight:700}
.turnips-pop{min-width:220px;max-width:320px}          /* inherit the standard .deppop 5px padding — same as the other hover bubbles */
.tipbub-row{display:flex;align-items:center;gap:9px;padding:5px 8px;border-radius:6px;font-size:12.5px}
.tipbub-row:hover{background:var(--surface)}
.tipbub-ip{font-family:var(--mono);color:var(--ink)}
.tipbub-when{font-size:11px;color:var(--dim);white-space:nowrap}
.tipbub-more{font-size:11px;color:var(--dim);padding:4px 8px 2px}
.tipbub-empty{font-size:12px;color:var(--dim);padding:6px 8px}
.tipbub-flush{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px 8px 5px;margin-top:3px;border:0;border-top:1px solid var(--line);background:none;color:var(--dangling);font-size:11.5px;font-family:inherit;cursor:pointer}
.tipbub-flush:hover{filter:brightness(1.15)}
.tipbub-flush .ic svg{width:13px;height:13px}
.tipgrid{border:1px solid var(--line);border-radius:9px;overflow:hidden}
.tipgrid-h,.tiprow{display:grid;grid-template-columns:minmax(0,1.3fr) minmax(0,1fr) minmax(0,1.4fr) 30px;align-items:center;gap:10px;padding:8px 12px}
.tipgrid-h{font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--faint);background:color-mix(in srgb,var(--ink) 3%,transparent)}
.tiprow{border-top:1px solid var(--line)}
.tiprow:hover{background:color-mix(in srgb,var(--ink) 2%,transparent)}
.tip-ip{display:flex;align-items:center;gap:8px;font-family:var(--mono);font-size:12.5px;color:var(--ink);min-width:0}
.tip-last{font-size:12px;color:var(--dim)}
.tip-by{display:flex;flex-wrap:wrap;gap:4px;min-width:0}
.tipfoot{display:flex;justify-content:flex-end;margin-top:10px}
/* on/off switch */
/* Access&TLS: the co-located node's loopback port — static, theme-tinted text (not an editable field) */
.localnode-note{margin:6px 2px 12px;font-size:12.5px;line-height:1.6;color:color-mix(in srgb,var(--brand) 42%,var(--ink))}
.localnode-note b{color:var(--brand);font-weight:700}
/* interface-key escrow toggle row (encryption settings) */
.ivk-escrow{margin:12px 0 4px;padding-top:12px;border-top:1px solid var(--line)}
.ivk-esc-row{display:flex;align-items:flex-start;gap:11px;font-size:13px;line-height:1.55;color:var(--dim);cursor:pointer}
.ivk-esc-row b{color:var(--ink)}
.field>label.toggle-row{display:flex;align-items:center;gap:12px;margin-bottom:7px}   /* switch + a one-line title; must set display:flex here — .field>label{display:block} out-specifies .ivk-esc-row's flex, so without this the row is block and align-items does nothing. The description sits below as a normal .hint */
.swt{position:relative;display:inline-flex;align-items:center;width:34px;height:19px;flex:none;cursor:pointer}
.swt input{position:absolute;inset:0;width:100%;height:100%;margin:0;opacity:0;cursor:pointer}
.swt .track{width:34px;height:19px;border-radius:99px;background:var(--surface-2);border:1px solid var(--line-solid);transition:background .15s,border-color .15s}
.swt .knob{position:absolute;left:3px;top:3px;width:13px;height:13px;border-radius:50%;background:var(--dim);transition:transform .15s,background .15s}
.swt input:checked~.track{background:color-mix(in srgb,var(--brand) 28%,transparent);border-color:var(--brand)}
.swt input:checked~.knob{transform:translateX(15px);background:var(--brand)}
/* header user-icon dropdown */
.acct-wrap{position:relative;display:inline-flex}
@media (max-width:680px){.setbody{flex-direction:column}.setrail{flex-direction:row;flex-wrap:wrap;width:auto;position:static}}
/* per-node selector: a clean wrapping row ABOVE the section card, right-aligned (the .snbadge look is unified
   with the Overview .dchip up top). */
.setnodes{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:6px;margin-bottom:12px}
/* unsaved-edit markers: a cyan dot on a rail section that changed, a cyan glow on a node badge changed in THIS section */
.dirtydot{width:7px;height:7px;border-radius:50%;background:var(--brand);flex:none;box-shadow:0 0 6px var(--brand)}
.snbadge.dirty{border-color:var(--brand);box-shadow:0 0 0 1px var(--brand),0 0 9px color-mix(in srgb,var(--brand) 55%,transparent)}
/* Back/Save now live in a footer BELOW the content card, right-aligned + nudged a touch past the card edge */
.setfoot{display:flex;align-items:center;gap:10px;margin-top:18px;margin-right:-4px}
.setfoot .grow{flex:1}   /* expand the spacer so Back/Save sit right (the saved-flash stays left) */
.setfoot .btn{padding:9px 26px;min-width:104px;justify-content:center}   /* Back + Save: wide + equal */
.setfoot .savedflash{margin-left:2px}
.rrcatch{min-width:0}   /* the routing catch-all ("everything else") dropdown — matches the per-rule route-to dropdowns exactly */
/* Phase 3 smart-routing rule editor. Row + catch-all footer share ONE grid template (grip · dropdown · arrow ·
   dropdown · xbtn), so the "everything else" dropdown + arrow line up exactly under the per-rule ones. The side
   columns are `auto` (icon-width) and identical in both, so the two 1fr dropdown columns come out equal. A flex
   row can't do this reliably — a <span> and a <select> don't split a flex track equally. */
.rrlist{display:flex;flex-direction:column;gap:6px;margin-top:2px}
.rrrow{display:grid;grid-template-columns:16px minmax(0,1fr) auto minmax(0,1fr) auto;align-items:center;gap:8px;padding:5px 8px;border:1px solid var(--line);border-radius:8px;background:color-mix(in srgb,var(--ink) 2%,transparent)}
/* custom IPs/domains rule: a full-width auto-growing textarea on its own grid row. Starts one row tall (like a
   textbox); JS grows it to fit wrapped content (resize off, no scrollbar). */
.rrdoms{grid-column:1/-1;width:100%;box-sizing:border-box;display:block;font-family:var(--mono);font-size:12px;line-height:1.55;padding:5px 8px;border:1px solid var(--line);border-radius:7px;background:var(--bg);color:var(--ink);resize:none;overflow:hidden}
.rrdoms::placeholder{color:var(--faint)}
.rrrow.warn{border-color:color-mix(in srgb,var(--partial) 45%,transparent)}
.rrrow .drag-grip{cursor:grab;opacity:.62}.rrrow:hover .drag-grip{opacity:.95}
.rrrow .selwrap{min-width:0}
.rrrow .catpick{min-width:0}
.rrarrow{color:var(--faint)}
/* Searchable provider-catalog category picker (CatPicker) — the button mirrors .selwrap; the popover is portal'd to <body>. */
.catpick{position:relative;min-width:0}
.catpick-btn{display:flex;align-items:center;gap:6px;width:100%;min-width:0;padding:7px 9px;font-family:inherit;font-size:13px;
  color:var(--ink);background:var(--bg);border:1px solid var(--line-solid);border-radius:7px;cursor:pointer;text-align:left;transition:border-color .12s}
.catpick-btn:hover{border-color:color-mix(in srgb,var(--brand) 40%,var(--line-solid))}
.catpick-btn.on{border-color:var(--brand)}
.catpick-lbl{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.catpick-caret{color:var(--faint);font-size:11px;flex-shrink:0}
.catpick-pop{position:fixed;z-index:90;max-width:min(440px,92vw);background:var(--surface-2);border:1px solid var(--line-solid);
  border-radius:var(--r-sm);box-shadow:0 12px 34px rgba(0,0,0,.5);overflow:hidden;display:flex;flex-direction:column}
.catpick-pop.flip{transform:translateY(-100%)}   /* opened upward: anchor the popover's bottom to the trigger's top */
.catpick-search{display:flex;align-items:center;gap:7px;padding:9px 11px;border-bottom:1px solid var(--line)}
.catpick-search .ic{color:var(--faint);flex-shrink:0}
.catpick-search input{flex:1;min-width:0;border:0;background:none;color:var(--ink);font-family:inherit;font-size:13px;outline:none}
.catpick-search input::placeholder{color:var(--faint)}
.catpick-list{max-height:var(--catpick-maxh,min(46vh,380px));overflow-y:auto;padding:5px}
.catpick-grp{font-family:var(--mono);font-size:9.5px;letter-spacing:.11em;text-transform:uppercase;color:var(--dim);padding:8px 8px 4px}
.catpick-row{display:flex;align-items:center;gap:8px;width:100%;padding:7px 9px;border:0;border-radius:6px;background:none;
  color:var(--ink);font-family:inherit;font-size:13px;text-align:left;cursor:pointer}
.catpick-row:hover:not(:disabled){background:var(--dd-hover);color:var(--ink)}
.catpick-row.sel{background:var(--dd-sel);color:var(--sel-ink)}
.catpick-row.off{cursor:not-allowed;color:var(--faint)}
.catpick-row.off .capb{opacity:.5}
.catpick-rlbl{flex:1;min-width:0;display:flex;align-items:center;gap:7px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.catpick-rlbl .ic{color:var(--faint);flex-shrink:0}
.catpick-builtin{font-family:var(--mono);font-size:9px;letter-spacing:.06em;text-transform:uppercase;color:var(--dim);
  padding:1px 5px;border:1px solid var(--line-solid);border-radius:5px;flex-shrink:0}
/* provider source tag — colour-coded per provider via --pc (falls back neutral for Custom/built-in). Readable on
   light+dark and on a brand-highlighted row. */
.catpick-src{font-family:var(--mono);font-size:9px;letter-spacing:.03em;flex-shrink:0;padding:1px 5px;border-radius:5px;
  color:var(--pc,var(--ink-2));border:1px solid color-mix(in srgb,var(--pc,var(--line-solid)) 40%,transparent);
  background:color-mix(in srgb,var(--pc,var(--ink)) 13%,var(--surface))}
.catpick-src.legacy{color:var(--dim);border-color:var(--line-solid);background:var(--surface)}
/* swatch preview pill — transparent base so it adapts to the dark/light preview cells (like .tg-turn) */
.sw-sample{font-family:var(--mono);font-size:9px;letter-spacing:.03em;padding:1px 6px;border-radius:5px;white-space:nowrap;
  color:var(--pc);border:1px solid color-mix(in srgb,var(--pc) 38%,transparent);background:color-mix(in srgb,var(--pc) 16%,transparent)}
/* the provider's raw id shown next to the friendly title ("Telegram  telegram") */
.catpick-rid{font-family:var(--mono);font-size:10.5px;color:var(--faint)}
/* rich catalog-browse row (Add from catalog): title + dimmed id + provider · description/sample · counts · caps */
.catrow{display:flex;align-items:flex-start;gap:9px;width:100%;padding:8px 9px;border:0;border-radius:7px;background:none;
  color:var(--ink);font:inherit;text-align:left;cursor:pointer}
.catrow:hover{background:var(--dd-hover)}
.catrow.sel{background:var(--dd-sel)}
.catrow.off{opacity:.5;cursor:not-allowed}
.catrow.off:hover{background:none}
.catrow .catpick-tick{margin-top:2px}
.catrow-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.catrow-l1{display:flex;align-items:baseline;gap:8px;min-width:0}
.catrow-title{font-size:13.5px;font-weight:600;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.catrow-id{font-family:var(--mono);font-size:10.5px;color:var(--faint);flex-shrink:0}
.catrow-heavy{font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--partial);border:1px solid color-mix(in srgb,var(--partial) 45%,transparent);border-radius:4px;padding:0 4px;flex-shrink:0}
.catrow-l1 .catpick-src{margin-left:auto}
.catrow-l2{font-size:11.5px;color:var(--dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
.catrow-desc{color:var(--ink-2)}
.catrow-eg{color:var(--faint);font-family:var(--mono);font-size:10.5px}
.catrow-right{display:flex;align-items:center;gap:8px;flex-shrink:0;align-self:center}
.catrow-size{font-size:11px;color:var(--dim);white-space:nowrap;font-variant-numeric:tabular-nums}
/* "view this list on GitHub" info link, on catalog rows + chips — clearer colour + a larger hit area */
.catrow-info,.ccchip-info{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;margin:-4px -2px;
  color:var(--brand);flex-shrink:0;border-radius:6px;transition:color .12s,background .12s}
.catrow-info:hover,.ccchip-info:hover{color:var(--brand);background:color-mix(in srgb,var(--brand) 14%,transparent)}
.catrow-info .ic svg,.ccchip-info .ic svg{width:16px;height:16px}
/* curated preset info icon → hover bubble with the list's description (no external URL to link) */
.descinfo{position:relative;cursor:help;outline:none}
.descbub{position:absolute;bottom:calc(100% + 8px);right:0;display:none;z-index:90;width:max-content;max-width:240px;
  padding:7px 10px;border-radius:8px;background:var(--surface);border:1px solid var(--line-solid);color:var(--ink);
  font-size:11.5px;line-height:1.45;font-weight:500;text-align:left;white-space:normal;box-shadow:0 10px 26px rgba(0,0,0,.4)}
.descinfo:hover .descbub,.descinfo:focus .descbub,.descinfo:focus-within .descbub{display:block}
/* ── Routing lists redesign: mode banner + two list grids + fleet popover ── */
.rmode-banner{position:relative;padding:11px 16px 14px;border-radius:10px;margin-bottom:22px;
  border:1px solid var(--line-solid);background:color-mix(in srgb,var(--mc,var(--brand)) 6%,var(--surface-2));border-left:3px solid var(--mc,var(--brand))}
.rmode-intro{margin:0 0 14px;line-height:1.55}
.rmode-top{display:flex;align-items:center;gap:12px;flex-wrap:nowrap;min-width:0}
.rmode-node{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:220px}
/* mode colours — mid-tones legible on BOTH light and dark grounds (bright amber washed out on white) */
.rmode-banner.m-kernel{--mc:#3E8FC4}.rmode-banner.m-forcedns{--mc:#C0821A}.rmode-banner.m-sni_kernel{--mc:#5B6EE1}.rmode-banner.m-sni{--mc:#8A5CD0}
:root[data-theme="dark"] .rmode-banner.m-kernel{--mc:#5FB0E8}
:root[data-theme="dark"] .rmode-banner.m-forcedns{--mc:#EBB43C}
:root[data-theme="dark"] .rmode-banner.m-sni_kernel{--mc:#8A98F0}
:root[data-theme="dark"] .rmode-banner.m-sni{--mc:#B08CE8}
.rmode-icon{display:inline-flex;align-items:center;justify-content:center;width:42px;height:42px;flex:none;line-height:1;font-size:30px}
.rmode-info{display:inline-flex;flex:none}
.rmode-infobtn{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:6px;color:var(--brand);cursor:help;transition:color .12s,background .12s}
.rmode-infobtn .ic svg{width:14px;height:14px}
.rmode-info.on .rmode-infobtn,.rmode-infobtn:hover{color:var(--brand);background:color-mix(in srgb,var(--brand) 14%,transparent)}
.rmode-info-pop{max-width:420px;font-size:12.5px;line-height:1.55;color:var(--dim)}
.rmode-info-body{padding:5px 8px;text-align:justify}
.rmode-info-body b{color:var(--ink);font-weight:600}
.rmode-head{display:flex;align-items:center;gap:9px}
.rmode-head b{font-size:19px;color:var(--ink)}
.rmode-pill{font-family:var(--mono);font-size:10px;color:var(--mc);padding:2px 7px;border-radius:20px;background:color-mix(in srgb,var(--mc) 15%,transparent)}
.rmode-desc{font-size:12px;color:var(--dim);line-height:1.55;margin-top:11px;max-width:none}
.rmode-head{flex:0 1 auto;min-width:0}   /* node name truncates before the picker shrinks */
/* framing line → four mode-icon tabs → the selected mode's full-width detail card (all colour off the banner's --mc) */
.rmode-frame{font-size:12.5px;color:var(--dim);line-height:1.5;margin:12px 0 11px}
.rmode-frame b{color:var(--ink);font-weight:600}
.rmode-tabs{display:flex;gap:8px;margin:0 0 14px}
.rmtab{--mc:var(--brand);flex:0 0 auto;width:54px;height:44px;display:inline-flex;align-items:center;justify-content:center;
  border:1px solid var(--line-solid);background:var(--surface-2);border-radius:9px;color:var(--faint);cursor:pointer;
  transition:color .12s,background .12s,border-color .12s,box-shadow .12s}
.rmtab .ic svg{width:22px;height:22px}
.rmtab.m-kernel{--mc:#3E8FC4}.rmtab.m-forcedns{--mc:#C0821A}.rmtab.m-sni_kernel{--mc:#5B6EE1}.rmtab.m-sni{--mc:#8A5CD0}
:root[data-theme="dark"] .rmtab.m-kernel{--mc:#5FB0E8}
:root[data-theme="dark"] .rmtab.m-forcedns{--mc:#EBB43C}
:root[data-theme="dark"] .rmtab.m-sni_kernel{--mc:#8A98F0}
:root[data-theme="dark"] .rmtab.m-sni{--mc:#B08CE8}
.rmtab:hover{color:var(--mc);border-color:color-mix(in srgb,var(--mc) 55%,var(--line-solid));background:color-mix(in srgb,var(--mc) 8%,var(--surface-2))}
.rmtab.on{color:#fff;background:var(--mc);border-color:var(--mc);box-shadow:0 3px 10px color-mix(in srgb,var(--mc) 38%,transparent)}
/* Routing & Blocking — context caption (left) + tab control (right), sharing one ruled baseline */
.rltabs{display:flex;align-items:flex-end;gap:16px;margin:16px 0 14px;border-bottom:1px solid var(--line-solid)}
.rltab-cap{flex:1;min-width:0;padding-bottom:8px;color:var(--ink);font-size:15px;font-weight:600;letter-spacing:-.01em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rltab-cap b{font-weight:700;font-size:16px}
.rltab-group{display:flex;gap:4px;margin-left:auto}
.rltab-note{display:flex;gap:7px;align-items:flex-start;margin:-4px 0 16px;color:var(--faint);font-size:12px;line-height:1.5}
.rltab-note svg{flex:0 0 auto;width:14px;height:14px;margin-top:1px;opacity:.8}
.rltab-warn{display:flex;gap:7px;align-items:flex-start;margin:-11px 0 16px;color:var(--partial);font-size:12px;line-height:1.5}
.rltab-warn svg{flex:0 0 auto;width:14px;height:14px;margin-top:1px}
.rltab-warn b{font-weight:700}
.rltab{flex:0 0 auto;display:inline-flex;align-items:center;gap:7px;padding:8px 16px 9px;border:0;background:none;cursor:pointer;
  color:var(--muted);font:inherit;font-weight:600;font-size:13px;border-bottom:2px solid transparent;margin-bottom:-1px}
.rltab .ic svg{width:16px;height:16px}
.rltab:hover{color:var(--ink)}
.rltab.on{color:var(--brand);border-bottom-color:var(--brand)}
/* Blocking tab — click-to-expand category rows: avail · name/lists-under · count · match · Default ON/OFF · + Add list · caret */
.bkitem{border-top:1px solid var(--line)}
.bkitem:first-child{border-top:none}
.bkitem.open{border-top-color:transparent;background:color-mix(in srgb,var(--brand) 5%,transparent);border-radius:10px}
.bkitem.open + .bkitem{border-top-color:transparent}
.bkrow{display:grid;grid-template-columns:40px minmax(0,1fr) auto auto auto auto auto auto;align-items:center;gap:14px;padding:10px 4px;cursor:pointer}
.bk-kind{display:flex;align-items:center;justify-content:flex-end}
.bk-size{display:flex;align-items:center;justify-content:flex-end}
.bk-count{font-family:var(--mono);font-size:10.5px;color:var(--faint);font-variant-numeric:tabular-nums;white-space:nowrap}
.bk-lrow .bk-count{flex-shrink:0}
.bkrow:hover{background:color-mix(in srgb,var(--ink) 2.5%,transparent)}
.bkrow.lg-lock{opacity:.5}
.bk-cat{min-width:0}
.bk-catline{display:flex;align-items:baseline;gap:7px;min-width:0}
.bk-catline .lg-title{flex:0 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.bk-fleet{display:flex;align-items:center;gap:8px;white-space:nowrap}
.bk-nabadge{flex-shrink:0;font-family:var(--mono);font-size:9px;letter-spacing:.05em;font-weight:600;white-space:nowrap;padding:1px 5px;border-radius:5px;color:#b7791f;background:color-mix(in srgb,#e0a83a 15%,transparent)}
:root[data-theme="dark"] .bk-nabadge{color:#e6b84f}
.bk-lists{font-family:var(--mono);font-size:10.5px;line-height:1.35;color:var(--faint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:1px}
.bk-cap{display:flex;align-items:center;justify-content:flex-end;gap:4px}
/* the 9px badge, though box-centred, reads ~2px high next to the larger pill/chip text — nudge its baseline onto theirs */
.bk-cap .capb,.bk-kind .capb{position:relative;top:1.5px}
.bk-nocap{color:var(--faint);font-size:12px}
.bk-defchip{border:1px solid var(--line-solid);background:none;color:var(--muted);font:inherit;font-size:11px;font-weight:600;padding:3px 11px;border-radius:999px;cursor:pointer;white-space:nowrap}
.bk-defchip:hover{color:var(--ink);border-color:var(--muted)}
.bk-defchip.on{color:var(--brand);border-color:var(--brand);background:color-mix(in srgb,var(--brand) 12%,transparent)}
.bk-defchip.on:hover{background:color-mix(in srgb,var(--brand) 18%,transparent)}

/* Per-interface Block-traffic section: grouped drop-category chips. ON = protection active → green (--online) with an
   orange ⊘ block glyph (--fault); OFF = quiet dashed outline. Green carries "protected", orange ⊘ says "blocking". */
.blk-field label{display:block}
.blk-grp{margin-top:11px}
.blk-grp:first-of-type{margin-top:9px}
.blk-gtitle{font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--faint);margin:0 0 7px}
.blk-chips{display:flex;flex-wrap:wrap;gap:7px}
.blk-empty{margin:0}
.blkchip{display:inline-flex;align-items:center;gap:7px;font-size:12px;font-weight:600;padding:6px 12px;border-radius:99px;cursor:pointer;
  background:var(--surface-2);color:var(--dim);border:1px dashed var(--line-solid);white-space:nowrap;transition:background .12s,border-color .12s,color .12s}
.blkchip:hover{color:var(--ink-2)}
.blkchip-g{font-size:13px;line-height:1;color:var(--dim);opacity:.5;flex:none}   /* ⊘ block glyph */
.blkchip.on{color:var(--online);border:1px solid color-mix(in srgb,var(--online) 62%,transparent);background:color-mix(in srgb,var(--online) 12%,transparent)}
.blkchip.on .blkchip-g{color:var(--fault);opacity:1}
.blkchip.inert{opacity:.5;cursor:not-allowed}
.blkchip.inert.on{opacity:.7}
.blkchip-tag{font-family:var(--mono);font-size:9px;letter-spacing:.05em;font-weight:600;padding:1px 5px;border-radius:5px;color:var(--brand);background:color-mix(in srgb,var(--brand) 15%,transparent)}

/* Reusable disclosure section (interface + turn-proxy modals): full-width collapsible with a caret, title and a
   right-aligned summary/count. Open header connects seamlessly to its body. */
.disc{display:flex;align-items:center;gap:10px;width:100%;box-sizing:border-box;padding:11px 13px;border:1px solid var(--line);border-radius:10px;
  background:var(--surface-2);cursor:pointer;margin-top:9px;text-align:left}
.disc:hover{border-color:var(--line-bright)}
.disc-car{color:var(--dim);font-size:10px;width:9px;flex:none;transition:transform .15s}
.disc.open{border-color:var(--line-bright);border-bottom-left-radius:0;border-bottom-right-radius:0}
.disc.open .disc-car{transform:rotate(90deg)}
.disc-t{font-size:13px;font-weight:600;color:var(--ink)}
.disc-sum{margin-left:auto;font-family:var(--mono);font-size:11px;color:var(--faint)}
.disc-sum b{font-weight:700}
.disc-sum.on b{color:var(--online)}      /* active filters — green */
.disc-sum.route b{color:var(--smart)}    /* routing rule count — rose */
.disc-body{border:1px solid var(--line-bright);border-top:none;border-radius:0 0 10px 10px;padding:13px 13px 4px;
  background:color-mix(in srgb,var(--surface-2) 45%,transparent)}
.disc-body>.field:last-child{margin-bottom:6px}
.bk-addwrap .btn-mini{white-space:nowrap}
.bk-chev{flex-shrink:0;color:var(--faint);font-size:9px;line-height:1;transition:transform .15s ease}
.bkrow:hover .bk-chev{color:var(--muted)}
.bkrow.open .bk-chev{transform:rotate(180deg);color:var(--brand)}
.bk-expand{display:flex;flex-direction:column;gap:0;padding:2px 10px 12px 68px}
.bk-lrow{display:flex;align-items:center;gap:10px;padding:5px 4px 5px 2px}
.bk-lrow.bk-ldis{opacity:.5}
.bkitem.bk-dis .bk-cat,.bkitem.bk-dis .bk-size{opacity:.5}   /* category with no enforceable list in this mode — dimmed, but still expandable to add an IP list */
.bk-lrow .capb{flex-shrink:0}
.bk-linfo{display:flex;flex-direction:column;gap:0;min-width:0;flex:0 1 auto}
.bk-ltop{display:flex;align-items:center;gap:8px;min-width:0}
.bk-llabel{flex:0 1 auto;font-size:13px;font-weight:600;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.bk-lprov{flex-shrink:0;font-family:var(--mono);font-size:9px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--pc,var(--faint));background:color-mix(in srgb,var(--pc,var(--ink)) 11%,transparent);border:1px solid color-mix(in srgb,var(--pc,var(--ink)) 26%,transparent);padding:1px 6px;border-radius:5px}
.bk-ldesc{min-width:0;font-size:11px;color:var(--faint);line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.bk-lremove{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;margin:-4px -2px;border:0;background:none;color:var(--muted);cursor:pointer;border-radius:7px}
.bk-lremove svg{width:15px;height:15px}
.bk-lremove:hover{color:#e0524a;background:color-mix(in srgb,#e0524a 12%,transparent)}
.bk-empty{color:var(--faint);font-size:12px;padding:2px 0}
.bk-catfoot{display:flex;align-items:center;margin-top:8px}
.bk-delcat{display:inline-flex;align-items:center;gap:6px;border:1px solid color-mix(in srgb,#e0524a 45%,var(--line-solid));background:none;color:#e0524a;font:inherit;font-size:12px;font-weight:600;padding:5px 13px;border-radius:8px;cursor:pointer}
.bk-delcat svg{width:13px;height:13px}
.bk-delcat:hover{background:color-mix(in srgb,#e0524a 12%,transparent);border-color:color-mix(in srgb,#e0524a 60%,var(--line-solid))}
.bk-newcat{margin-top:14px}
.bk-kindpick{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.bk-kindopt{display:flex;flex-direction:column;gap:2px;text-align:left;border:1px solid var(--line-solid);background:none;border-radius:9px;padding:10px 12px;cursor:pointer;color:var(--ink)}
.bk-kindopt b{font-size:13px}
.bk-kindopt small{color:var(--muted);font-size:11px;line-height:1.3}
.bk-kindopt:hover{border-color:var(--muted)}
.bk-kindopt.on{border-color:var(--brand);background:color-mix(in srgb,var(--brand) 8%,transparent)}
.bk-kindopt.on b{color:var(--brand)}
.bk-pick{width:288px;max-height:340px;display:flex;flex-direction:column}
.bk-picksearch{width:100%;box-sizing:border-box;padding:9px 11px;border:0;border-bottom:1px solid var(--line);background:none;color:var(--ink);font:inherit;font-size:13px;outline:none}
.bk-picklist{overflow-y:auto;padding:6px;scrollbar-width:thin}
.bk-pickprov{display:flex;align-items:center;gap:6px;font-size:10px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--pc,var(--faint));padding:7px 8px 3px}
.bk-pickprov::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--pc,var(--faint));box-shadow:0 0 5px color-mix(in srgb,var(--pc,transparent) 70%,transparent)}
.bk-pickitem{display:flex;flex-direction:column;width:100%;text-align:left;border:0;background:none;color:var(--ink);font:inherit;padding:6px 8px;border-radius:6px;cursor:pointer;gap:1px}
.bk-pilabel{font-size:13px;font-weight:600}
.bk-pidesc{font-size:11px;color:var(--muted)}
.bk-pickitem:hover{background:color-mix(in srgb,var(--brand) 12%,transparent)}
.bk-pickitem:hover .bk-pilabel{color:var(--brand)}
.bk-pickempty{color:var(--faint);font-size:12px;padding:10px 8px}
.rmode-detail{border:1px solid color-mix(in srgb,var(--mc) 30%,var(--line-solid));background:color-mix(in srgb,var(--mc) 6%,var(--surface-2));border-radius:9px;padding:13px 15px}
.rd-head{display:flex;align-items:flex-start;gap:12px;margin-bottom:7px;padding-right:200px}   /* right gutter reserves the floated buttons' space so the title/summary never slide under them */
.rd-headmain{display:flex;flex-direction:column;gap:5px;flex:1;min-width:0}   /* title, then summary right below it (top-aligned — no longer stretched to the buttons) */
.rd-titlerow{display:flex;align-items:center;gap:9px;flex-wrap:wrap}
.rd-ic{display:inline-flex;color:var(--mc)}.rd-ic svg{width:20px;height:20px}
.rd-name{font-size:15px;color:var(--ink);font-weight:700}
.rd-head .rmode-tabs{margin:0;flex:none;align-self:flex-start}   /* the 4 mode icons live in the header now */
.rd-adds{font-size:12.5px;color:var(--ink);font-weight:700;line-height:1.5}
.rd-lines{display:flex;flex-direction:column;gap:3px;margin-bottom:8px}   /* even spacing between every +/- row */
.rmc-tag{font-family:var(--mono);font-size:9px;letter-spacing:.03em;color:var(--mc);padding:2px 7px;border-radius:20px;background:color-mix(in srgb,var(--mc) 14%,transparent);white-space:nowrap}
.rmc-bene,.rmc-cost{font-size:12px;line-height:1.45;display:flex;gap:7px;align-items:baseline}
.rmc-bene b,.rmc-cost b{flex:none;font-family:var(--mono);font-weight:700;font-size:12.5px}
.rmc-bene{color:var(--dim)}.rmc-bene b{color:#3EA76B}
.rmc-cost{color:var(--faint)}.rmc-cost b{color:#C0821A}
:root[data-theme="dark"] .rmc-bene b{color:#57C98A}:root[data-theme="dark"] .rmc-cost b{color:#EBB43C}
.rmode-banner .rmode-desc{margin-top:8px}
.rd-headside{position:absolute;top:11px;right:16px;display:flex;flex-direction:column;align-items:flex-end;gap:13px}   /* floated out of flow → buttons + lists stay top-right; the left text + bullets flow back up to their original spots */
.rd-headside .rmode-tabs{align-self:flex-end}
.rmode-lists{font-size:12px;color:var(--dim);line-height:1.55;text-align:right}   /* lists this mode uses — sits under the 4 mode buttons */
.rmode-lists b{color:var(--ink);font-weight:700}
/* status bar ABOVE the mode card (outside it): "<node> currently runs on <mode>" + status under it, reset/info right */
.rmode-runbar{display:flex;align-items:baseline;gap:10px;margin-bottom:12px}   /* status text (left) and caption (right) share a baseline */
.rmr-left{display:flex;flex-direction:column;align-items:flex-start;gap:7px;min-width:0}   /* status (+ reset when there's no 2nd status line), left-aligned */
.rmr-left .rmode-health{margin-top:0}
.rmr-right{display:flex;flex-direction:column;align-items:flex-end;gap:7px;flex:none}   /* caption + toggle + info on top, reset under (when degraded), right-aligned */
.rmr-rtop{display:flex;align-items:center;gap:7px}
.rmr-actions{display:flex;align-items:center;gap:8px;flex:none}   /* default-routing runbar: reset + info, right-aligned (no status/toggle) */
.learn-toggle{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;padding:0;border-radius:6px;border:none;background:transparent;color:#D65B5B;cursor:pointer;transition:color .12s,background .12s}   /* IP-learning OFF = red */
.learn-toggle:hover{background:color-mix(in srgb,currentColor 15%,transparent)}
.learn-toggle.on{color:#3EA76B}   /* ON = green */
:root[data-theme="dark"] .learn-toggle{color:#E06B6B}
:root[data-theme="dark"] .learn-toggle.on{color:#57C98A}
.learn-toggle .ic svg{width:15px;height:15px}
.rmr-title{font-size:13px;color:var(--dim)}
.rmr-node{color:var(--ink);font-weight:700}   /* node name: bold, default ink (no node colour) */
.rmr-mode{color:var(--ink);font-weight:600}
/* live host-layer health strip (engine alive? + reset count) below the mode detail */
.rmode-health{display:flex;align-items:center;flex-wrap:wrap;gap:7px;row-gap:2px;margin-top:12px;font-size:12px;color:var(--dim)}
.rmode-health > b,.rmode-health > span{white-space:nowrap}   /* wrap the strip between units, never mid-phrase */
.rmode-health .rmh-note{white-space:normal}
.rmode-health b{color:var(--ink);font-weight:600}
.rmh-dot{width:8px;height:8px;border-radius:50%;flex:none}
.rmode-health.ok .rmh-dot{background:#3EA76B}
.rmode-health.down{color:#D65B5B}
.rmode-health.down b{color:#D65B5B}
.rmode-health.down .rmh-dot{background:#D65B5B}
.rmh-sep{color:var(--faint)}
.rmh-note{flex-basis:100%;color:var(--faint);font-style:italic}   /* degraded note drops onto its own line under the status */
.rmh-info{display:inline-flex}
.rmh-infobtn{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;color:var(--brand);cursor:help;transition:color .12s}
.rmh-info.on .rmh-infobtn,.rmh-infobtn:hover{color:var(--brand)}
.rmh-infobtn .ic svg{width:14px;height:14px}
:root[data-theme="dark"] .rmode-health.ok .rmh-dot{background:#57C98A}
.rmode-reset{display:inline-flex;align-items:center;gap:5px;flex:none;font-size:11.5px;padding:1px 9px;border-radius:7px;cursor:pointer;
  border:1px solid var(--line-solid);background:transparent;color:var(--dim);transition:color .12s,border-color .12s,background .12s}
.rmode-reset .ic svg{width:13px;height:13px}
.rmode-reset:hover{color:#D65B5B;border-color:color-mix(in srgb,#D65B5B 50%,var(--line-solid));background:color-mix(in srgb,#D65B5B 8%,transparent)}
/* Reset routing as a red circle-arrows icon (after the info icon) with a hover bubble (no native title) */
.rmode-resetwrap{display:inline-flex;flex:none}
.rmode-reset-ic{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:6px;flex:none;
  border:none;background:none;color:#D65B5B;cursor:pointer;padding:0;transition:color .12s,background .12s}
.rmode-reset-ic .ic svg{width:14px;height:14px}
.rmode-reset-ic:hover{color:#E86B6B;background:color-mix(in srgb,#D65B5B 15%,transparent)}
.rmode-reset-pop{max-width:280px;font-size:12px;line-height:1.5;color:var(--dim)}
/* type-to-confirm field inside a destructive ConfirmSheet */
.confirm-type{display:flex;flex-direction:column;gap:6px;margin:13px 2px 2px}
.confirm-type>span{font-size:12.5px;color:var(--dim)}
.confirm-type b{color:#D65B5B;font-family:var(--mono);letter-spacing:.08em}
.ctype-input{font-family:var(--mono);font-size:13px;letter-spacing:.06em;padding:8px 11px;border-radius:8px;border:1px solid var(--line-solid);background:var(--surface-2);color:var(--ink);outline:none;transition:border-color .12s}
.ctype-input:focus{border-color:color-mix(in srgb,#D65B5B 55%,var(--line-solid))}
/* live ASN-resolution feedback under a rule/list target field */
.asn-hint{display:flex;flex-wrap:wrap;gap:6px;margin-top:5px}
.asn-tok{font-family:var(--mono);font-size:10.5px;padding:2px 7px;border-radius:20px;white-space:nowrap}
.asn-tok.load{color:var(--faint);background:color-mix(in srgb,var(--faint) 12%,transparent)}
.asn-tok.ok{color:#3EA76B;background:color-mix(in srgb,#3EA76B 13%,transparent)}
.asn-tok.bad{color:#D65B5B;background:color-mix(in srgb,#D65B5B 12%,transparent)}
:root[data-theme="dark"] .asn-tok.ok{color:#57C98A}
/* Reusable styled dropdown (Dropdown/ModeSelect) — one look everywhere, replacing native <select> option lists. */
.dropdown{position:relative;min-width:0}
.field .dropdown{width:100%}   /* field dropdowns fill their column like the native selects did */
.ddbtn{display:flex;align-items:center;gap:8px;width:100%;min-width:0;justify-content:space-between;text-align:left;cursor:pointer;
  background:var(--surface-2);border:1px solid var(--line-solid);color:var(--ink);border-radius:var(--r-sm);
  padding:9px 12px;font-family:var(--mono);font-size:13px;transition:border-color .15s,box-shadow .15s}
.ddbtn:hover,.ddbtn.on{border-color:color-mix(in srgb,var(--brand) 45%,var(--line-solid))}
.ddbtn .catpick-caret{color:var(--dim);font-size:11px}
.ddbtn:disabled{opacity:.55;cursor:not-allowed}
.ddlbl{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
.ddbtn .catpick-caret{flex:none}
.ddpop{position:fixed;z-index:90;max-width:min(480px,92vw);max-height:var(--ddmaxh,320px);overflow-y:auto;
  background:var(--surface-2);border:1px solid var(--line-solid);border-radius:var(--r-sm);box-shadow:0 12px 34px rgba(0,0,0,.5);padding:5px}
.ddpop.flip{transform:translateY(-100%)}
.ddgrp{font-family:var(--mono);font-size:9.5px;letter-spacing:.11em;text-transform:uppercase;color:var(--dim);padding:8px 8px 4px}
.ddopt{display:block;width:100%;text-align:left;padding:8px 10px;border:0;border-radius:6px;background:none;color:var(--ink);font:inherit;font-size:13px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ddopt:hover:not(:disabled){background:var(--dd-hover);color:var(--ink)}
.ddopt.sel{background:var(--dd-sel);color:var(--sel-ink);font-weight:600}
.ddopt.off{color:var(--faint);cursor:not-allowed}
.lgrid-head{display:flex;align-items:center;gap:10px;margin-bottom:6px}
.lg-htitle{display:flex;align-items:center;gap:9px}   /* title · count · sub — all vertically centred on one line */
.lg-htitle .seclabel{border-bottom:none;padding-bottom:0;margin:0}   /* no underline under Provider/Custom list headers */
.lg-count{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:18px;padding:0 6px;font-family:var(--mono);
  font-size:11px;color:var(--dim);background:color-mix(in srgb,var(--ink) 9%,transparent);border-radius:20px}
.lg-sub{font-size:11px}
/* list grid — flex rows (no header), every cell vertically centred, clean separators */
.lgrid{display:flex;flex-direction:column}
.lgrow{display:grid;grid-template-columns:40px minmax(0,1fr) 120px 86px 78px 52px;align-items:center;gap:12px;
  padding:9px 4px;border-top:1px solid var(--line)}   /* pull · category(fills, truncates last) · size · fleet · caps · actions — fixed right cols keep rows vertically aligned */
.lgrow:first-child{border-top:none}
.lgrow:hover{background:color-mix(in srgb,var(--ink) 2.5%,transparent)}
.lgrow.lg-lock{opacity:.5}
.lg-pull{justify-self:start;display:flex;align-items:center}
.lg-cat{display:flex;flex-direction:column;justify-content:center;gap:0;min-width:0}   /* title + tag on top, id tight under */
.lg-catmain{display:flex;align-items:center;gap:8px;min-width:0;line-height:1.25}
.lg-title{font-weight:600;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
button.lg-title{border:0;background:none;padding:0;font:inherit;font-weight:600;color:var(--ink);cursor:pointer}
button.lg-title:hover{color:var(--brand)}
.lg-id{font-family:var(--mono);font-size:10.5px;line-height:1;color:var(--faint);align-self:flex-start;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
button.lg-id{border:0;background:none;padding:0;cursor:pointer}button.lg-id:hover{color:var(--brand)}
.lg-caps{display:flex;justify-content:flex-end}
.lg-size{color:var(--dim);white-space:nowrap;font-variant-numeric:tabular-nums;font-size:12px;text-align:right;justify-self:end}
.lg-act{display:flex;align-items:center;justify-content:flex-end;gap:10px}
.lg-act .ccchip-info,.lg-act .ccchip-x{margin:0}
.lg-legend{margin-top:18px;padding-top:13px;border-top:1px solid var(--line);display:flex;flex-direction:column;gap:6px;font-size:11.5px;color:var(--dim)}
.lg-leg-row{display:flex;align-items:baseline;gap:0}
.lg-leg-row .capb,.lg-leg-row .bk-nabadge{margin-right:7px}
.btn-add{background:var(--brand);color:var(--brand-ink);font-weight:600;padding:6px 12px;font-size:12.5px;border-radius:7px;display:inline-flex;align-items:center;gap:5px;border:0}
.btn-add:hover{filter:brightness(1.07)}
.btn-add .ic svg{width:14px;height:14px}
.catpick-pop.wide{max-width:none}
/* fleet-assignment popover */
.fleet-trig{cursor:pointer;font-size:12px;color:var(--dim);white-space:nowrap;padding:3px 8px;border:1px solid var(--line-solid);border-radius:20px;display:inline-flex;align-items:center;gap:5px}
.fleet-trig:hover{color:var(--ink);border-color:color-mix(in srgb,var(--brand) 40%,var(--line-solid))}
.fleet-trig b{color:var(--ink);font-variant-numeric:tabular-nums}
.fleet-caret{font-size:9px;color:var(--faint)}
.fleetpop{padding:6px}
.fleetlist{display:flex;flex-direction:column;gap:1px;min-width:170px}
.fleetlist-h{font-family:var(--mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--faint);padding:3px 8px 5px}
.fleetrow{display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:6px;white-space:nowrap}
.fleetrow:hover{background:var(--surface)}
.fleetrow.off{opacity:.5;cursor:not-allowed}
.swt-off{opacity:.5;cursor:not-allowed}
.fleet-dot{width:7px;height:7px;border-radius:50%;background:var(--c);flex:none}
.fleet-nm{font-size:12.5px;color:var(--ink)}
.catpick-empty{padding:16px 10px;text-align:center;color:var(--faint);font-size:12.5px}
.capbs{display:flex;gap:4px;flex-shrink:0}
.capb{font-family:var(--mono);font-size:9px;letter-spacing:.05em;font-weight:600;padding:1px 5px;border-radius:5px}
.capb.ip{color:var(--brand);background:color-mix(in srgb,var(--brand) 15%,transparent)}
.capb.host{color:var(--smart);background:color-mix(in srgb,var(--smart) 15%,transparent)}
.capb.custom{color:var(--brand);background:color-mix(in srgb,var(--brand) 15%,transparent)}
.catpick-foot{display:flex;align-items:center;gap:8px;padding:7px 11px;border-top:1px solid var(--line)}
.catpick-count{font-family:var(--mono);font-size:10.5px;color:var(--dim);font-variant-numeric:tabular-nums}
.catpick-nav{display:flex;align-items:center;gap:6px}
.catpick-note{padding:8px 11px;border-top:1px solid var(--line);font-size:11px;line-height:1.45;color:var(--dim);background:color-mix(in srgb,var(--smart) 6%,transparent)}
.catpick-note b{color:var(--ink-2);font-weight:600}
.catpick-tick{display:inline-flex;justify-content:center;width:14px;color:var(--brand);font-size:12px;flex-shrink:0}
.catpick-add .btn-mini.on{border-color:var(--brand);color:var(--brand)}

.ccchip-lbl{font-size:12.5px;color:var(--ink)}
.ccchip-caps{display:flex;gap:3px}
.ccchip-x{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;padding:0;border:0;border-radius:50%;background:none;color:var(--faint);cursor:pointer}
/* Uses --dangling like every other destructive control (.btn-danger, .iconbtn.danger). It previously
   referenced an undefined token, which made both declarations invalid-at-computed-value-time — so the
   delete "x" had NO hover state at all. Never reference a custom property that isn't defined in :root. */
.ccchip-x:hover{background:color-mix(in srgb,var(--dangling) 18%,transparent);color:var(--dangling)}
.ccchip-x .ic svg{width:12px;height:12px}
.ccchip-tick{display:inline-flex;align-items:center}
.ccchip-tick input{appearance:none;-webkit-appearance:none;width:14px;height:14px;margin:0;border:1.5px solid var(--line-solid);border-radius:4px;background:var(--bg);cursor:pointer;position:relative;flex:none}
.ccchip-tick input:checked{background:var(--brand);border-color:var(--brand)}
.ccchip-tick input:checked::after{content:"";position:absolute;left:50%;top:50%;width:5px;height:5px;border-radius:50%;background:var(--brand-ink);transform:translate(-50%,-50%)}
.ccchip-lbl.asbtn{border:0;background:none;padding:0;color:var(--ink);font:inherit;cursor:pointer}
.ccchip-lbl.asbtn:hover{color:var(--brand)}
/* list size summary ("142 hosts · 38 nets") + hover sample tooltip */
.listsize{cursor:default;font-size:11px;color:var(--dim);white-space:nowrap;font-variant-numeric:tabular-nums}
.listsize:hover{color:var(--ink-2)}
.lti-h{font-size:11.5px;color:var(--ink-2)}
.lti-e.faint{color:var(--faint)}
.rrrow .rrlint{grid-column:1/-1}   /* lint messages drop to their own full-width grid row */
.rrlint{font-size:10px;color:var(--partial);font-family:var(--mono)}
/* the catch-all footer reuses the row's grid template + the row's content inset (transparent padding+border) so
   its columns start/end identically. The grip/xbtn ghosts are invisible width-matched spacers for cols 1 & 5. */
.rrfoot{display:grid;grid-template-columns:16px minmax(0,1fr) auto minmax(0,1fr) auto;align-items:center;gap:8px;margin-top:8px;padding:0 8px;border:1px solid transparent}
.rrfoot-ghost{visibility:hidden;pointer-events:none}
.rrfoot-lead{grid-column:1/3;min-width:0;display:flex;align-items:center;gap:8px}
.rrfoot-label{font-size:13.5px;color:var(--dim);font-weight:600;white-space:nowrap;margin-left:auto}   /* right-align within the column so it sits next to the → arrow */
/* Add-rule: flush with the grey rule-row's left border above (undo the 8px row padding), a touch larger, bold cyan + */
.rrfoot-lead .btn-mini{margin-left:-8px;padding:7px 13px}
.rrfoot-lead .btn-mini .ic{color:var(--brand)}
.rrfoot-lead .btn-mini .ic svg{width:16px;height:16px}
/* square icon-only mini button (the gear shortcuts next to Add rule / Create interface / Setup proxy):
   a definite 1:1 box (aspect-ratio can't resolve with two auto dims) with the glyph dead-centre. */
.btn-mini.ico{width:28px;height:28px;padding:0;gap:0;justify-content:center;flex:none}
.rrfoot-lead .btn-mini .ic path{stroke-width:2.75}
/* routing-lists gear in the footer's last column: its box is pinned to the rule-row X width so the grid's
   auto track (and thus the catch-all dropdown) never shifts; a larger glyph overflows the box harmlessly. */
.rrfoot-gear{width:18px;height:18px;padding:0;box-sizing:border-box;justify-content:center;overflow:visible;color:var(--dim)}
.rrfoot-gear:hover{color:var(--brand);background:transparent}
.rrfoot-gear .ic{overflow:visible}
.rrfoot-gear .ic svg{width:22px;height:22px}
/* node-connection link health: a single glowing dot (green up / amber connecting / red down) */
.lkdot{width:9px;height:9px;border-radius:50%;flex:none;background:var(--unknown);--g:var(--unknown)}
.lkdot.up{background:var(--online);--g:var(--online);animation:pulse 2.6s infinite}
.lkdot.connecting{background:var(--partial);--g:var(--partial);animation:pulse 1.5s infinite}
.lkdot.down{background:var(--dangling);--g:var(--dangling)}
.lkdot.up,.lkdot.connecting,.lkdot.down{box-shadow:0 0 8px -1px var(--g)}
.tg-warn{background:rgba(232,192,75,.16);color:var(--partial);border-color:color-mix(in srgb,var(--partial) 35%,transparent)}
.tg-del{background:rgba(242,107,130,.16);color:var(--dangling);border-color:color-mix(in srgb,var(--dangling) 35%,transparent)}   /* interface being deleted */
.tg-off{background:color-mix(in srgb,var(--unknown) 16%,transparent);color:var(--unknown);border-color:color-mix(in srgb,var(--unknown) 30%,transparent)}   /* stopped / stale — a deliberate, non-error state */
.tg-busy svg,.tg-ok svg,.tg-warn svg,.tg-del svg,.tg-off svg{width:11px;height:11px}
.ifcard-top .tg-busy,.ifcard-top .tg-ok,.ifcard-top .tg-warn,.ifcard-top .tg-del,.ifcard-top .tg-off{flex:none}   /* status pills keep full width in a card header */
.tg-iface{background:color-mix(in srgb,var(--tgc,var(--dim)) 14%,transparent);color:var(--tgc,var(--dim))}
.tg-gen{background:var(--surface-2);color:var(--dim);box-shadow:inset 0 0 0 1px var(--line-solid)}
/* inactive items (offline/dangling/disconnected): grey the tags out */
.tg.muted{background:color-mix(in srgb,var(--unknown) 13%,transparent)!important;color:var(--unknown)!important;box-shadow:none!important}
/* rate cell: lights up when traffic is flowing */
.ratecell{font-family:var(--mono);color:var(--faint);font-size:12px;white-space:nowrap}
.ratecell .up{opacity:.7}
.ratecell.live{color:var(--online)}
.ratecell.live .up{color:var(--rate-up);opacity:1}

/* ───── titled icon-headed panels (information grouping) ───── */
.panel{background:var(--surface);border:1px solid var(--line);border-radius:var(--r);margin:0 0 14px;overflow:hidden}
.panel-head{display:flex;align-items:center;gap:10px;padding:10px 14px;border-bottom:1px solid var(--line)}
.panel-ic{width:27px;height:27px;border-radius:8px;flex:none;display:inline-flex;align-items:center;justify-content:center;
  background:color-mix(in srgb,var(--brand) 15%,transparent);color:var(--brand)}
.panel-ic .ic{display:flex;align-items:center;justify-content:center}   /* kill the inline baseline gap so the glyph centres H+V */
.panel-ic svg{width:15px;height:15px;display:block}
.panel-ic.t-online{background:color-mix(in srgb,var(--online) 15%,transparent);color:var(--online)}
.panel-ic.t-ready{background:color-mix(in srgb,var(--ready) 15%,transparent);color:var(--ready)}
.panel-ic.t-pending{background:color-mix(in srgb,var(--pending) 16%,transparent);color:var(--pending)}
.panel-ic.t-orphan{background:color-mix(in srgb,var(--orphan) 16%,transparent);color:var(--orphan)}
.panel-ic.t-warn{background:color-mix(in srgb,var(--partial) 16%,transparent);color:var(--partial)}
.panel-ic.t-turn{background:color-mix(in srgb,#7C5CFF 16%,transparent);color:#7C5CFF}   /* matches the WINGS-N turn-fork accent */
.panel-head h3{margin:0;font-size:14px;font-weight:600;color:var(--ink);letter-spacing:-.01em}
.panel-count{font-family:var(--mono);font-size:11.5px;color:var(--faint)}
.panel-head .grow{flex:1}
.panel-body{padding:14px}
.panel-body.flush{padding:0}
/* key/value definition rows inside a panel */
.dl{display:grid;grid-template-columns:auto 1fr;gap:9px 18px;align-items:baseline}
.dl dt{font-size:12.5px;color:var(--dim);white-space:nowrap}
.dl dd{margin:0;font-family:var(--mono);font-size:12.5px;color:var(--ink-2);text-align:right;word-break:break-all}
/* interface-detail summary: endpoint / server address / dns / mtu on one line */
.iface-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px 22px}
.iface-grid .ig-item{display:flex;flex-direction:column;gap:6px;min-width:0}
.ig-l{font-family:var(--mono);font-size:9px;text-transform:uppercase;letter-spacing:.08em;color:var(--faint)}
.ig-v{font-family:var(--mono);font-size:12.5px;color:var(--ink-2);word-break:break-all}
.iface-amnezia{display:flex;flex-direction:column;gap:6px;margin-top:16px}
.iface-amnezia .ig-v{display:flex;flex-direction:column;gap:3px}
/* edit-interface intro: one sentence per line, label colour, small */
.iface-intro{display:flex;flex-direction:column;gap:4px;margin:0 0 16px;font-size:11px;line-height:1.4;color:var(--dim)}
/* turn-proxy installed-version row (edit modal) — sits just above the foot, so trim its gap */
.field.turn-ver{margin-bottom:0}
.turn-ver-row{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
/* blue "update to vX" button — matches the panel's .updpill version-update tag */
.btn.btn-upd{border-color:color-mix(in srgb,var(--ready) 45%,transparent);color:var(--ready)}
.btn.btn-upd:hover{border-color:var(--ready);background:color-mix(in srgb,var(--ready) 12%,transparent)}
/* header "up to date" flash (5s after a check-for-updates that found nothing) */
.upd-uptodate{pointer-events:none;cursor:default;color:var(--online);border-color:color-mix(in srgb,var(--online) 42%,var(--line-solid))}
.turn-ver-row .ver{font-size:13px;color:var(--ink)}
.turn-ver-row .ver-err{font-size:12px;color:var(--dangling)}
.iface-intro.big{gap:9px;margin:4px 0 18px;font-size:13px;line-height:1.55;color:var(--ink-2)}
/* AmneziaWG param editor: four columns grouped by prefix (J / S / H / I) */
.awg-cols{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:9px 10px;align-items:start}
.awg-col{display:flex;flex-direction:column;gap:9px;min-width:0}
.awg-f{display:flex;flex-direction:column;gap:3px;min-width:0}
.awg-f>span{font-family:var(--mono);font-size:9px;text-transform:uppercase;letter-spacing:.06em;color:var(--faint)}
.awg-f input{font-size:12px;padding:6px 8px}
@media(max-width:560px){.awg-cols{grid-template-columns:repeat(2,minmax(0,1fr))}}
/* interface public key inline next to its label with a small copy icon */
.ipk-field{display:flex;align-items:center;gap:10px;margin-top:4px;margin-bottom:-6px}
.ipk-field>label{margin:0;white-space:nowrap}
.ipk-field .grow{flex:1}
.ipk-val{font-family:var(--mono);font-size:12px;color:var(--ink-2);word-break:break-all;min-width:0;text-align:right}
.subnet-row{margin-bottom:14px}   /* breathing room before the Endpoint / Listen-port row */
.subnet-row .ipk-val{text-align:left;word-break:normal;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.seclabel{font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--faint);margin:18px 0 8px;padding-bottom:6px;border-bottom:1px solid var(--line)}   /* Access & TLS apply status, shown left in the settings footer like the saved-flash */
/* AmneziaWG obfuscation params grid (panel-settings editable / node-settings read-only) */
.awg-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:8px}
.awg-cell{display:flex;flex-direction:column;gap:3px;min-width:0}
.awg-cell.wide{grid-column:1/-1}
.awg-cell label{font-size:10px;letter-spacing:.04em;text-transform:uppercase;color:var(--faint);font-weight:600}
.awg-cell input{width:100%;font-family:var(--mono);font-size:12px;padding:5px 7px}
.awg-val{font-family:var(--mono);font-size:12px;color:var(--ink);padding:5px 7px;background:var(--surface-2);border:1px solid var(--line-solid);border-radius:6px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ipk-custom{margin-top:6px;width:100%;font-family:var(--mono);font-size:12.5px}
/* mesh-health tag (node-detail header + nodes-list) + its hover bubble */
.mh-pop{display:inline-flex}
.mh-tag{display:inline-flex;align-items:center;gap:16px;white-space:nowrap;cursor:default}   /* label↔value = value↔value = 16 (identical, roomy enough for 2-digit counts like 20/20) */
.mh-tag-hdr{gap:7px}
.mh-lbl-hdr{font-size:14px;font-weight:600;color:var(--ink);letter-spacing:-.01em}   /* match the panel header h3 */
.nm-meshitem{margin-right:26px;display:inline-flex;align-items:center}   /* clear gap before CPU load */
.mh-grp{display:inline-flex;align-items:center;gap:4px}      /* ↓ + number as one tight unit */
.mh-grp + .mh-grp{margin-left:0}                            /* green↔blue = the 12px tag gap, identical to label↔value */
.mh-num{font-family:var(--mono);font-weight:400;font-size:12px}          /* numbers NOT bold */
.mh-num-hdr{font-family:inherit;font-size:14px;font-weight:600;letter-spacing:-.01em}   /* header number: SAME sans font/size/weight as the label text (not the mono used in the nodes-list tag) */
/* numbers: all-up → arrow colour (inbound green · outbound blue); partial → orange; none → red */
.mh-num.mhn-down{color:var(--online)} .mh-num.mhn-up{color:var(--ready)} .mh-num.mhn-warn{color:#F0913C} .mh-num.mhn-bad{color:var(--dangling)}
.mh-ar{font-family:var(--mono);line-height:1;font-size:12.5px}   /* identical to the throughput arrows: same MONO glyph, size + weight */
.mh-ar.mh-down.s-up{color:var(--online)} .mh-ar.mh-down.s-connecting{color:var(--partial)} .mh-ar.mh-down.s-down{color:var(--dangling)}
.mh-ar.mh-up.s-up{color:var(--ready)} .mh-ar.mh-up.s-connecting{color:var(--partial)} .mh-ar.mh-up.s-down{color:var(--dangling)}
.mh-row{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:4px 14px 4px 8px}   /* name left (aligns title) · arrows right-aligned, inset a bit more than the names */
.mh-rn{font-family:var(--mono);font-size:12.5px;flex:none}
.mh-rn.mh-bold{font-weight:700} .mh-rn.mh-dim{opacity:.5}
.mh-rar{display:inline-flex;gap:6px;flex:none}
.mh-rar .mh-ar{font-size:13px}   /* small arrows, matching the line */
.mh-rar .mh-ar.s-up{text-shadow:0 0 8px currentColor}   /* static glow only for up status — green (down) / blue (up); yellow + red get no glow */
.deppop.mh-bubble{width:max-content;min-width:120px}   /* dynamic width — sized to the longest node name */
/* node↔node connection info card (NOC-style: status pill + protocol tag + labeled grid) */
.conncard{border:1px solid var(--line);border-radius:12px;padding:15px 17px;background:color-mix(in srgb,var(--ink) 2.5%,transparent)}
.conncard-top{display:flex;align-items:center;gap:9px;margin-bottom:16px}
.lkpill{display:inline-flex;align-items:center;gap:7px;padding:3px 10px 3px 9px;border-radius:999px;border:1px solid var(--line);font-family:var(--mono);font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;line-height:1;color:var(--ink-2)}
.lkpill.up{color:var(--online);border-color:color-mix(in srgb,var(--online) 38%,transparent);background:color-mix(in srgb,var(--online) 12%,transparent)}
.lkpill.connecting{color:var(--partial);border-color:color-mix(in srgb,var(--partial) 38%,transparent);background:color-mix(in srgb,var(--partial) 12%,transparent)}
.lkpill.down{color:var(--dangling);border-color:color-mix(in srgb,var(--dangling) 38%,transparent);background:color-mix(in srgb,var(--dangling) 12%,transparent)}
.lkpill .lkdot{width:6px;height:6px}   /* match the node-header "reporting" pill dot */
.conn-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px 20px}
.conn-cell{display:flex;flex-direction:column;gap:5px;min-width:0}
.conn-cell .cl{font-size:10px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--faint)}
.conn-cell .cv{font-family:var(--mono);font-size:13px;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.35}
.conn-cell .cv a{color:var(--brand)}
.ipk-field .copybtn:hover{color:var(--ready)}
@media(max-width:760px){.iface-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}

/* ───── fleet: merged node card (main block + health block) ───── */
.fleet2{display:flex;flex-direction:column;gap:10px}
.fnode{display:grid;grid-template-columns:minmax(0,0.9fr) minmax(0,1.16fr);border:1px solid var(--line);border-radius:var(--r);   /* left −10% / right +10% */
  background:var(--surface);overflow:hidden;text-decoration:none;color:inherit;transition:border-color .15s,background .15s}
.fnode:hover{border-color:var(--line-solid);background:var(--raise)}
.fnode.stale{opacity:.82}
.fnode-main{padding:13px 15px;display:flex;flex-direction:column;gap:11px;min-width:0}
.fnode-top{display:flex;align-items:center;gap:8px}
.fnode-name{font-family:var(--mono);font-weight:600;font-size:14px;color:var(--ink)}
.fnode-top .tport{font-family:var(--mono);font-size:9px;text-transform:uppercase;letter-spacing:.06em;color:var(--faint);background:var(--surface-2);padding:2px 6px;border-radius:5px}
.fnode-top .rowarrow{color:var(--faint)}
.fnode-stats{display:flex;gap:22px;flex-wrap:wrap;align-items:center}
.fnode-ifs{margin:0 auto;display:flex;gap:6px;align-items:center;align-self:flex-end;padding-bottom:1px}   /* centred in the empty space; sit on the VALUES row (bottom of the stat blocks) */
.fnode-ifs .iftype{font-size:10px;padding:2.5px 8px;font-weight:600}   /* same .iftype badge as the node interfaces, just a touch bigger */
.fnode-stats .fl{display:block;font-family:var(--mono);font-size:9px;text-transform:uppercase;letter-spacing:.08em;color:var(--faint);margin-bottom:3px}
.fnode-stats .fv{font-family:var(--mono);font-size:12.5px;color:var(--ink-2)}
.fnode-health{padding:13px 15px;border-left:1px solid var(--line);background:var(--surface-2);display:flex;align-items:center;min-width:0}
.fnode-health .health{width:100%}
.fnode-nohealth{font-family:var(--mono);font-size:11px;color:var(--faint)}
@media(max-width:680px){.fnode{grid-template-columns:1fr}.fnode-health{border-left:none;border-top:1px solid var(--line)}}

/* ───── recent activity (action-first) ───── */
.act-row{display:flex;align-items:center;gap:11px;padding:9px 13px;background:var(--surface);text-decoration:none;color:inherit;transition:background .12s;border-bottom:1px solid var(--line)}
.actlist{border:1px solid var(--line);border-radius:var(--r);overflow:hidden}
.act-row:last-child{border-bottom:0}
.act-row:hover{background:var(--surface-2)}
.act-ic{width:26px;height:26px;border-radius:7px;flex:none;display:inline-flex;align-items:center;justify-content:center;background:var(--surface-2);color:var(--dim)}
.act-ic svg{width:14px;height:14px}
.act-ic.t-user{background:color-mix(in srgb,var(--ready) 14%,transparent);color:var(--ready)}
.act-ic.t-peer{background:color-mix(in srgb,var(--brand) 14%,transparent);color:var(--brand)}
.act-what{font-size:13.5px;color:var(--ink-2)}
.act-name{font-family:var(--mono);font-size:12.5px;color:var(--ink);font-weight:500}
.act-ic.t-node{background:color-mix(in srgb,var(--pending) 14%,transparent);color:var(--pending)}
.act-detail{font-family:var(--mono);font-size:11px;color:var(--faint);padding:2px 7px;border-radius:5px;background:var(--surface-2)}
/* activity item-category tints (icon chip): peer/user/node above, the rest here */
.act-ic.t-interface{background:color-mix(in srgb,#6E9BF2 15%,transparent);color:#6E9BF2}
.act-ic.t-turnproxy{background:color-mix(in srgb,#E0729F 15%,transparent);color:#E0729F}
.act-ic.t-mesh{background:color-mix(in srgb,#9B7BE8 16%,transparent);color:#9B7BE8}
.act-ic.t-settings{background:var(--surface-2);color:var(--dim)}
.act-ic.t-update{background:color-mix(in srgb,#38B2AC 15%,transparent);color:#38B2AC}
.act-row .act-arrow{color:var(--faint);display:flex;transition:color .15s,transform .15s}
.act-row .act-arrow svg{width:14px;height:14px}
.act-row:hover .act-arrow{color:var(--dim);transform:translateX(2px)}
.act-row.noclk{cursor:default}
.act-row.noclk:hover{background:var(--surface)}
.act-more{font-family:var(--mono);font-size:11.5px;color:var(--brand);text-decoration:none;letter-spacing:.02em}
.act-more:hover{text-decoration:underline}
.act-morewrap{display:flex;justify-content:flex-end;margin-top:9px}   /* "Show all history" sits under the feed, right-aligned */
/* Overview empty-state — no nodes on the panel yet (fresh install / all deleted) */
.nonodes{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;min-height:64vh;gap:15px;padding:24px}
.nonodes-ic{width:64px;height:64px;border-radius:16px;display:flex;align-items:center;justify-content:center;background:var(--surface-2);color:var(--dim)}
.nonodes-ic svg{width:30px;height:30px}
.nonodes h2{margin:0;font-size:20px;color:var(--ink);letter-spacing:-.01em}
.nonodes p{margin:0;max-width:440px;color:var(--dim);font-size:13.5px;line-height:1.6}
.nonodes .btn{margin-top:4px}
/* activity history grid — same bordered list, plus a category pill + delete control per row */
.acthist{display:flex;flex-direction:column;border:1px solid var(--line);border-radius:var(--r);overflow:hidden}
.acthist .act-row{border-bottom:1px solid var(--line)}
.acthist .act-row:last-child{border-bottom:0}
.act-link{display:inline-flex;align-items:center;gap:9px;text-decoration:none;color:inherit;min-width:0}
.act-link:hover .act-what{color:var(--brand)}
.act-cat{font-family:var(--mono);font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:var(--dim);background:var(--surface-2);border:1px solid var(--line);padding:2px 7px;border-radius:5px;white-space:nowrap}
.acthist .iconbtn{width:26px;height:26px}
/* grouped Needs-attention row: the count leads, so a bolded number reads at a glance */
.attn-row .name b{color:var(--ink);font-weight:700}

/* ───── name cells ───── */
.namecell{display:flex;align-items:center;gap:9px;min-width:0}
.namecell>span{min-width:0;overflow:hidden;text-overflow:ellipsis}

a.tg{cursor:pointer;transition:filter .15s}
a.tg:hover{filter:brightness(1.25)}

/* ───── throughput area chart ───── */
.tp-wrap{display:flex;flex-direction:column;gap:8px}
.tp-legend{display:flex;align-items:center;gap:14px;font-family:var(--mono);font-size:11px;color:var(--ink-2)}
.tp-legend .tp-k{display:inline-flex;align-items:center;gap:6px}
.tp-legend .sw{width:10px;height:3px;border-radius:2px;display:inline-block}
.tp-legend .sw.rx{background:var(--tp-rx)} .tp-legend .sw.tx{background:var(--tp-tx)}
.tp-legend .tp-peak{color:var(--faint)}
.tp-legend .tp-scale{color:var(--faint);border:1px solid var(--line);border-radius:99px;padding:0 7px;font-size:9.5px;letter-spacing:.03em}   /* the fixed vertical-scale tier (100 Mbps / 500 Mbps / auto) */
/* idle/empty throughput: a slim strip instead of a tall empty chart */
.tp-idle{height:34px;display:flex;align-items:center;justify-content:center;font-family:var(--mono);font-size:10.5px;
  letter-spacing:.04em;color:var(--faint);border:1px dashed var(--line);border-radius:8px;background:rgba(255,255,255,.012)}

/* ───── ranked horizontal bars ───── */
.rankcard{border:1px solid var(--line);border-radius:var(--r);background:var(--surface);padding:6px 4px}
.rankbars{display:flex;flex-direction:column}
.rb{position:relative;display:grid;grid-template-columns:140px 1fr auto;align-items:center;gap:14px;padding:8px 12px;text-decoration:none;color:inherit;border-radius:7px;transition:background .12s}
.rb:not(.static):hover{background:var(--surface-2)}
.rb.static{cursor:default}
.rb-label{display:flex;align-items:center;gap:6px;min-width:0;font-family:var(--mono);font-size:12.5px;color:var(--ink)}
.rb-nm{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.rb-n{flex:none;display:inline-flex;align-items:center;justify-content:center;min-width:16px;height:15px;padding:0 5px;border-radius:7px;background:var(--track);color:var(--dim);font-size:9.5px;font-weight:700;letter-spacing:.02em}
.rb-track{height:8px;border-radius:5px;background:var(--track);overflow:hidden}
.rb-track i{display:block;height:100%;border-radius:5px;transition:width .6s var(--ease);opacity:.9}
.rb-val{font-family:var(--mono);font-size:11.5px;color:var(--dim);white-space:nowrap}
@media(max-width:640px){.rb{grid-template-columns:96px 1fr}.rb-val{display:none}.rb-bub{left:122px}}   /* 12 + 96 + 14 */
/* per-peer breakdown popover for an aggregated talker — a compact mini rank-bar list (name · bar · value),
   sized to 30% of the row/content width. Hover bridge (::before) keeps it open across the gap. */
.rb-bub{position:absolute;top:calc(100% - 2px);left:166px;z-index:40;width:50%;min-width:190px;box-sizing:border-box;display:none;   /* left = row padding(12) + label col(140) + gap(14): aligns with the bar's left end */
  grid-template-columns:minmax(0,max-content) 1fr max-content;align-items:center;gap:7px 12px;
  background:var(--surface-2);border:1px solid var(--line-solid);border-radius:8px;padding:10px 13px;
  box-shadow:0 8px 26px rgba(0,0,0,.45);white-space:nowrap}
.rb.hasbub:hover .rb-bub{display:grid}
.rb-bub::before{content:"";position:absolute;left:0;right:0;top:-9px;height:9px}
.rb-bub-n{display:flex;align-items:center;gap:6px;min-width:0;font-family:var(--mono);font-size:11px;color:var(--ink)}
.rb-bub-n .tg{flex:none;min-width:30px;justify-content:center}   /* uniform tag width (wg==awg) so the names line up across rows */
.rb-bub-nm{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.rb-bub-track{height:7px;border-radius:5px;background:var(--track);overflow:hidden}
.rb-bub-track i{display:block;height:100%;border-radius:5px;opacity:.9}
.rb-bub-v{font-family:var(--mono);font-size:10.5px;color:var(--dim);text-align:right}

/* ───── inline usage bar (rows) ───── */
.usebar{display:inline-flex;align-items:center;gap:9px}
.usebar-t i{display:block;height:100%;border-radius:4px;transition:width .6s var(--ease)}
.usebar-n small{color:var(--faint);font-size:10.5px}

.ug-head{display:flex;align-items:center;gap:12px;padding:11px 14px;border-bottom:1px solid var(--line);background:var(--surface-2)}
.ug-id{min-width:0;display:flex;flex-direction:column;gap:2px}
.ug-name{display:flex;align-items:center;gap:8px;font-size:15px;font-weight:600;color:var(--ink)}
.ug-name a{color:var(--ink)} .ug-name a:hover{color:var(--brand)}
.ug-note{font-size:12px;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:340px}
.ug-head .usebar{margin-left:2px}
.ug-peers{display:flex;flex-direction:column}
.ug-empty{padding:14px;font-size:13px;color:var(--dim)}

/* Users list — collapsible rows (status · name · tag · note · peers · last · rate · total · controls) */
.urows{display:flex;flex-direction:column;gap:8px}
/* overflow VISIBLE so the Nodes hover bubble (in the header) isn't clipped; rounded corners kept via explicit
   radii on the header (full when collapsed, top-only when open) and the body (bottom). */
.urow{border:1px solid var(--line);border-radius:var(--r);background:var(--surface);overflow:visible;transition:border-color .12s}
.urow>.urow-head{border-radius:calc(var(--r) - 1px)}
.urow.open>.urow-head{border-radius:calc(var(--r) - 1px) calc(var(--r) - 1px) 0 0}
.urow-body{border-radius:0 0 calc(var(--r) - 1px) calc(var(--r) - 1px)}
.urow.open{border-color:color-mix(in srgb,var(--brand) 42%,var(--line-solid));box-shadow:0 0 0 1px color-mix(in srgb,var(--brand) 18%,transparent)}
.urow.open>.urow-head{background:color-mix(in srgb,var(--brand) 7%,transparent)}
.editfoot{display:flex;align-items:center;gap:8px;margin-top:20px}
.editfoot .grow{flex:1}
/* one-line rows (status · name+tag+note · peers/online · last-online · rate · total · controls). The per-row
   labels are gone — the sortable header line carries the titles. Deterministic tracks so every row + the header
   line up. A touch taller than a peer row, but a single line. */
/* The right cluster (Online / Rate / Total / actions) is one fixed 524px track holding a GAP-FREE sub-grid whose
   inner widths (86/168/158/112) + 14px cell padding exactly mirror the expanded peer table — so those three
   columns land pixel-aligned above the peer grid's, regardless of the (gapped) left columns. */
.urow-head,.uhead{display:grid;grid-template-columns:20px 74px minmax(150px,1fr) auto;align-items:center;gap:12px;padding:0 14px}
/* the right cluster mirrors the embedded peer table's right columns EXACTLY (peers↔address 168, nodes↔node 120,
   then online 86 / rate 168 / total 158 / acts 112) as a gap-free sub-grid, anchored to the row's right edge — so
   Peers/Nodes/Online/Rate/Total all land pixel-aligned above the peer grid. Live drops the acts track. */
.u-right{display:grid;grid-template-columns:168px 120px 86px 168px 158px 112px;align-items:center;min-width:0}
.u-right.live{grid-template-columns:168px 120px 86px 168px 158px}
.u-right>*{padding:0 14px;box-sizing:border-box;min-width:0}
.urow-head{padding:11px 14px;cursor:pointer;transition:background .12s}
/* sortable header line above the users list */
.uhead{padding:2px 14px 7px;font-family:var(--mono);font-size:9.5px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--faint)}
.uhead .clk{cursor:pointer;user-select:none;white-space:nowrap} .uhead .clk:hover{color:var(--ink)}
.uhead .uh-r{justify-self:end;text-align:right}
.urow-head:hover{background:var(--surface-2)}
.u-exp{display:flex;color:var(--faint);transition:transform .16s}
.u-exp svg{width:14px;height:14px}
.urow.open .u-exp{transform:rotate(90deg);color:var(--dim)}
/* user-row status: bare node-style tag (dot + uppercase mono label), like the nodes list "reporting/offline"
   but a touch smaller — NOT the pill Badge used inside the grids */
.ustat{display:inline-flex;align-items:center;gap:6px;font-family:var(--mono);font-size:10px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;line-height:normal;white-space:nowrap;color:var(--unknown)}
.ustat::before{content:"";width:7px;height:7px;border-radius:50%;background:currentColor;flex:none}
.ustat.s-online{color:var(--online)} .ustat.s-online::before{box-shadow:0 0 8px currentColor;animation:blink 2.6s infinite}
.ustat.s-ready{color:var(--ready)}
.ustat.s-partial{color:#F19AA4}
.ustat.s-pending{color:var(--pending)}
.ustat.s-creating,.ustat.s-rotating{color:var(--partial)}
.ustat.s-dangling,.ustat.s-removing{color:var(--dangling)}
.ustat.s-unknown,.ustat.s-empty,.ustat.s-off{color:var(--unknown)}   /* s-off = Live "offline" (grey, no animation) */
.ustat.s-blocked,.ustat.s-faulty{color:var(--fault)}   /* orange — a peer is blocked / faulty */
.ustat.s-disabled,.ustat.s-expired{color:var(--dangling)}   /* red — access revoked / expired */
.ustat.s-expiring{color:var(--fault)}   /* orange — about to expire */
.ustat.s-blocking{color:var(--partial)}   /* amber — block converging */
.ustat.s-restoring{color:var(--ready)}   /* blue — restoring after unblock */
/* name row: name · tag · note all on one line, name + note ellipsize */
.u-name{display:flex;align-items:center;gap:8px;min-width:0;font-size:14px;font-weight:600}
.u-name>a{color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:0 1 auto} .u-name>a:hover{color:var(--brand)}
.u-name>.un{color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:0 1 auto}   /* plain (non-link) username */
.u-note{font-size:11.5px;font-weight:400;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:0 1 auto}
/* peers / online counts on one line — online GREEN only when actually online (>0) */
.u-counts{font-size:12.5px;white-space:nowrap}
.u-counts .u-pc{color:var(--ink-2)}
.u-counts .u-onc{color:var(--dim);font-weight:600} .u-counts .u-onc.on{color:var(--online)}   /* green only when >0 */
.u-counts .u-dot{color:var(--faint)}
/* servers column: node badges the user has peers on (up to 3 + "N more"); hover → by-node bubble w/ iface tags */
.u-servers{min-width:0;overflow:visible;font-size:12.5px}   /* visible so the hover bubble isn't clipped */
.srvwrap{max-width:100%;cursor:default}
.srvchips{display:inline-flex;align-items:center;gap:6px;min-width:0}
.nsrv{font-family:var(--mono);font-size:9.5px;font-weight:500;letter-spacing:.02em;padding:2px 7px;border-radius:5px;flex:none;
  color:var(--c);background:color-mix(in srgb,var(--c) 15%,transparent);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:92px}
.nsrv-more{font-size:11.5px;color:var(--dim);white-space:nowrap;flex:none}
/* multi-node aggregate: a theme-coloured badge with a server icon (like the "exits via" node badge) */
.nsrv-agg{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:600;padding:3px 9px;border-radius:6px;flex:none;
  color:var(--sel-ink);background:color-mix(in srgb,var(--brand) 16%,transparent);white-space:nowrap}
.nsrv-agg svg{width:12px;height:12px}
/* one row per (node, interface): node badge · iface badge · peer count — as a 3-col grid so all rows align. */
.turnbub.servbub{grid-template-columns:max-content max-content max-content;align-items:center;gap:6px 12px;white-space:nowrap;padding:10px 12px;max-width:none}
.turnwrap:hover .turnbub.servbub{display:grid}
.servbub-row{display:contents}
.servbub-pc{font-size:11px;color:var(--dim);justify-self:end}
/* .uh-srv is just a hook for the responsive-hide; the header inherits the standard .uhead .clk look (weight/colour). */
/* value columns (labels removed — the header carries the titles); rate/total right-align under their headers */
.u-last{font-size:11.5px;white-space:nowrap;color:var(--ink-2)}
.u-thru,.u-total{justify-self:end;text-align:right;white-space:nowrap}
.u-never{font-size:11px;color:var(--faint)}
.u-acts{display:flex;align-items:center;gap:3px;justify-content:flex-end}
.urow-body{border-top:1px solid var(--line);padding:12px 14px;background:var(--surface-2)}
.peerspanel{display:flex;flex-direction:column}
.toolbar.sub{margin-bottom:10px;gap:10px}
.urow-body .peergrid{background:var(--surface)}
@media (max-width:900px){
  .urow-head,.uhead{grid-template-columns:20px auto 1fr auto}
  .u-counts,.u-servers,.uh-srv,.u-last,.u-thru,.u-total,.uhead .clk:nth-child(n+4){display:none}
}
.iconbtn{cursor:pointer;width:30px;height:30px;border-radius:7px;border:1px solid transparent;background:transparent;color:var(--dim);
  display:inline-flex;align-items:center;justify-content:center;transition:all .14s}
.iconbtn[hidden]{display:none}   /* the display:inline-flex above otherwise defeats the hidden attribute (e.g. the vault-lock padlock) */
.iconbtn .ic{display:flex;align-items:center;justify-content:center}   /* kill the inline baseline gap so the glyph centres vertically (was sitting high) */
.iconbtn:hover{color:var(--ink);background:var(--raise);border-color:var(--line-solid)}
.iconbtn.danger:hover{color:var(--dangling);border-color:var(--dangling)}
/* Restore (recreate a missing interface — green, the recovery action) + Correct (fix an out-of-subnet IP — amber) */
.iconbtn.restore{color:var(--online)}
.iconbtn.restore:hover{color:var(--online);border-color:var(--online);background:color-mix(in srgb,var(--online) 12%,transparent)}
.iconbtn.correct{color:var(--fault)}
.iconbtn.correct:hover{color:var(--fault);border-color:var(--fault);background:color-mix(in srgb,var(--fault) 12%,transparent)}
/* QR (show configs) — the panel's theme/brand colour, in the peers + users grids */
.iconbtn.qr{color:var(--brand)}
.iconbtn.qr:hover{color:var(--brand);border-color:var(--brand);background:color-mix(in srgb,var(--brand) 12%,transparent)}
/* Toolbar batch buttons — same tones as the per-row icons */
.btn-restore{background:color-mix(in srgb,var(--online) 12%,transparent);color:var(--online);border:1px solid color-mix(in srgb,var(--online) 34%,transparent);font-weight:700;padding:9px 13px;border-radius:7px;cursor:pointer;transition:all .15s var(--ease);display:inline-flex;align-items:center;gap:6px}
.btn-restore:hover{background:color-mix(in srgb,var(--online) 20%,transparent)}
.btn-correct{background:color-mix(in srgb,var(--fault) 12%,transparent);color:var(--fault);border:1px solid color-mix(in srgb,var(--fault) 34%,transparent);font-weight:700;padding:9px 13px;border-radius:7px;cursor:pointer;transition:all .15s var(--ease);display:inline-flex;align-items:center;gap:6px}
.btn-correct:hover{background:color-mix(in srgb,var(--fault) 20%,transparent)}
/* fix buttons INSIDE modals reuse the ghost button shape (like Targets / Block / Rotate keys) — just recoloured,
   matching the .btn-ghost.danger pattern — so they don't read heavier than their neighbours */
.btn-ghost.restore{color:var(--online)}
.btn-ghost.restore:hover{color:var(--online);border-color:var(--online)}
.btn-ghost.correct{color:var(--fault)}
.btn-ghost.correct:hover{color:var(--fault);border-color:var(--fault)}
/* missing-interface card on the node detail (expected but gone). The Restore control sits in the header next to
   the MISSING tag (a green pill at the same weight), and the body text is justified. */
.ifcard.missing{border-color:color-mix(in srgb,var(--dangling) 40%,var(--line-solid));background:color-mix(in srgb,var(--dangling) 5%,transparent)}
.ifcard.missing .ifcard-top{overflow:visible;flex-wrap:wrap;row-gap:6px}
.ifcard.missing .ifcard-top .ifname{flex-shrink:0;overflow:visible;text-overflow:clip}   /* don't ellipsize the name to fit the two pills — wrap the row instead */
.mi-text{color:var(--dim);font-size:12px;line-height:1.5;text-align:justify}
.mi-ok{color:var(--online);font-weight:600}
.mi-bad{color:var(--dangling);font-weight:600}
.mi-restore{display:inline-flex;align-items:center;gap:4px;font-family:var(--mono);font-size:9.5px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--online);border:1px solid color-mix(in srgb,var(--online) 34%,transparent);border-radius:99px;padding:1px 8px;background:transparent;cursor:pointer;line-height:1.45;white-space:nowrap}
.mi-restore:hover{background:color-mix(in srgb,var(--online) 12%,transparent)}
.mi-restore:disabled{opacity:.5;cursor:not-allowed}
.mi-restore .ic svg{width:11px;height:11px}
/* GHOST interface — lost + keyless (recreate fresh + rekey, not restore). Crimson, semi-transparent phantom
   card (opaque on hover, like .ifcard.down); the recreate affordances reuse --dangling everywhere. */
.ifcard.ghost{opacity:.5;border-color:color-mix(in srgb,var(--dangling) 40%,var(--line-solid));background:color-mix(in srgb,var(--dangling) 5%,transparent)}
.ifcard.ghost:hover{opacity:.85}
.ifcard.ghost .ifcard-top{overflow:visible;flex-wrap:wrap;row-gap:6px}
.ifcard.ghost .ifcard-top .ifname{flex-shrink:0;overflow:visible;text-overflow:clip}
.iftype.gh{background:color-mix(in srgb,var(--dangling) 14%,transparent);color:var(--dangling)}
.mi-restore.ghost{color:var(--dangling);border-color:color-mix(in srgb,var(--dangling) 40%,transparent)}
.mi-restore.ghost:hover{background:color-mix(in srgb,var(--dangling) 12%,transparent)}
.iconbtn.ghost{color:var(--dangling)}
.iconbtn.ghost:hover{color:var(--dangling);border-color:var(--dangling);background:color-mix(in srgb,var(--dangling) 12%,transparent)}
.btn-mini.ghost,.btn-ghost.gh{color:var(--dangling)}
.btn-mini.ghost:hover,.btn-ghost.gh:hover{border-color:var(--dangling);background:color-mix(in srgb,var(--dangling) 10%,transparent)}
.notice.danger{background:color-mix(in srgb,var(--dangling) 9%,transparent);border:1px solid color-mix(in srgb,var(--dangling) 30%,transparent);color:var(--dim)}
/* Restore on the interface-detail (read-only) screen keeps the full 'Restore interface' btn-mini styling */
.btn-mini.restore{color:var(--online);border-color:color-mix(in srgb,var(--online) 34%,var(--line-solid))}
.btn-mini.restore:hover{color:var(--online);border-color:var(--online);background:color-mix(in srgb,var(--online) 10%,transparent)}
/* Confirm-modal list of what's about to be restored/corrected */
.restore-list{margin:10px 0 2px;padding-left:18px;max-height:200px;overflow:auto;font-size:13px;line-height:1.7}
.restore-list li{color:var(--ink)}
.restore-list .rl-new{color:var(--fault);font-size:11px;border:1px solid color-mix(in srgb,var(--fault) 34%,transparent);border-radius:5px;padding:0 5px;margin-left:4px}
.iconbtn-add:hover{color:var(--brand);border-color:var(--brand);background:color-mix(in srgb,var(--brand) 12%,transparent)}   /* Add-peer (+) highlights in the theme colour */
/* down-node Recover button (replaces the rotate-token icon): green, labelled */
.iconbtn.recover{width:auto;gap:5px;padding:0 10px;color:var(--online);border-color:color-mix(in srgb,var(--online) 45%,transparent);background:color-mix(in srgb,var(--online) 12%,transparent);font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase}
.iconbtn.recover:hover{color:#04110b;background:var(--online);border-color:var(--online)}
.iconbtn.recover svg{width:14px;height:14px}
.linkbtn{background:none;border:0;color:var(--brand);cursor:pointer;padding:0;font:inherit}
.linkbtn.danger{color:var(--dangling)}
/* server-key drift resolution — two equal-width actions: green Restore (safe) / red Adopt (destructive) */
.keydrift-acts{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:11px}
.keydrift-acts.one{grid-template-columns:1fr}   /* re-seeded node: Adopt is the only option */
.kd-act{display:flex;flex-direction:column;gap:5px;min-width:0}
.kd-btn{width:100%;display:inline-flex;align-items:center;justify-content:center;padding:8px 12px;font-size:12.5px;font-weight:600;border-radius:7px;cursor:pointer;border:1px solid;transition:all .15s var(--ease)}
.kd-btn.kd-restore{background:color-mix(in srgb,var(--online) 11%,transparent);color:var(--online);border-color:color-mix(in srgb,var(--online) 34%,transparent)}
.kd-btn.kd-restore:hover{background:color-mix(in srgb,var(--online) 20%,transparent)}
.kd-btn.kd-adopt{background:color-mix(in srgb,var(--dangling) 11%,transparent);color:var(--dangling);border-color:color-mix(in srgb,var(--dangling) 34%,transparent)}
.kd-btn.kd-adopt:hover{background:color-mix(in srgb,var(--dangling) 20%,transparent)}
.kd-hint{font-size:11.5px;line-height:1.35}
.advtoggle{background:none;border:0;color:var(--ink-2);cursor:pointer;font:inherit;font-size:12.5px;font-weight:600;display:flex;align-items:center;gap:7px;margin:16px 0 2px;padding:0;letter-spacing:.02em}
.advtoggle:hover{color:var(--brand)}
.advtoggle .advcaret{font-size:10px;color:var(--faint);width:10px;display:inline-block}
/* One-line section strip (proxy sheet): Server / Client parameters / Version & rollback — click a segment to reveal its panel below. */
.secbar{display:flex;margin:22px 0 0;border:1px solid var(--line);border-radius:10px;overflow:hidden;background:color-mix(in srgb,var(--ink) 3%,transparent)}
.secbar-item>span:first-child{overflow:hidden;text-overflow:ellipsis}
.secbar-item.on{background:color-mix(in srgb,var(--brand) 14%,transparent);color:var(--ink)}
.secbar-item.on .secbar-car{color:var(--brand)}
/* User QR modal: one group per peer, its deployment QRs on a single (scrollable) horizontal line. */
.usercfg{display:flex;flex-direction:column;gap:18px}
/* One peer's QR cards on a single line — never wraps; pages with ‹ › when there are more than 3.
   Used by BOTH the peer QR modal and each peer row of the user QR modal. */
.qrrowwrap{display:flex;flex-direction:column;gap:10px}
.qrrow{display:flex;gap:14px;overflow-x:auto;scroll-snap-type:x mandatory;scrollbar-width:none;padding-bottom:2px}
.qrrow:has(> .deploy:only-child),.qrrow:has(> .upc-wrap:only-child){justify-content:center}
.qrrow::-webkit-scrollbar{display:none}
/* every carousel item is a uniform 256px column, whether a bare .deploy (peer modal) or a .upc-wrap (user modal) —
   uniform width is what keeps the scroll step and the "N of M" range honest */
.qrrow > .deploy,.qrrow > .upc-wrap{width:256px;flex:none;scroll-snap-align:start}
.qrrow > .upc-wrap > .deploy{width:100%}
.qrrow .deploy .qr,.qrrow .deploy .qr-none,.qrrow .deploy .qr-fail{flex:1;min-width:0}
.qrrow .deploy .qr-none,.qrrow .deploy .qr-fail{width:auto;height:auto;aspect-ratio:1/1}
.qrrow .deploy .qr .qrimg{width:100%}
.qrrow .deploy .acts{justify-content:center}
.qrnav{display:flex;align-items:center;justify-content:center;gap:16px}
.qrnavbtn{appearance:none;width:34px;height:34px;border-radius:50%;border:1px solid var(--line);background:var(--panel);color:var(--ink);font-size:18px;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:0}
.qrnavbtn:disabled{opacity:.35;cursor:default}
.qrnavbtn:active:not(:disabled){transform:scale(.94)}
/* ── redesigned peer/user QR modals ── */
.qrhd{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
.qrhd-nm{font-weight:700}
.qrhd-tag{font-size:12px;font-weight:600;color:var(--faint);letter-spacing:.02em}
.qrhd-count{font-family:var(--mono);font-size:10.5px;font-weight:600;letter-spacing:.04em;color:var(--faint);text-transform:uppercase;margin-left:14px}   /* peer count sits next to the username, with a clear gap; the subscription status goes on the far right */
.qrfoot-count{align-self:center;font-family:var(--mono);font-size:10.5px;font-weight:600;letter-spacing:.04em;color:var(--faint);text-transform:uppercase;white-space:nowrap}   /* peer/config count in the user QR modal foot — left-aligned; a grow spacer pushes the action buttons right */
.substatus{display:flex;align-items:center;justify-content:flex-end;gap:7px;margin:-2px 0 12px;font-size:12px;color:var(--muted,#8a97ad)}
.substatus b{font-weight:700}
/* compact "In subscription" badge in the sheet title row, right-aligned just before the ×/Go-back control */
.hsub{margin-left:auto;display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--online);white-space:nowrap}
.hsub b{font-weight:700}
/* right-aligned peer/config count in the user-modal title row */
.hcount{margin-left:auto;font-family:var(--mono);font-size:10.5px;font-weight:600;letter-spacing:.04em;color:var(--faint);text-transform:uppercase;white-space:nowrap}
.sheet-head .hsub + .x,.sheet-head .hsub + .sheet-back,.sheet-head .hcount + .x,.sheet-head .hcount + .sheet-back{margin-left:12px}
.substatus.active{color:var(--online)} .substatus.active .substatus-dot{background:var(--online)}
.substatus.disabled{color:var(--faint)} .substatus.disabled .substatus-dot{background:var(--faint)}
/* subscription lifecycle colours — the whole sentence takes one colour: green active / orange about-to-expire /
   red blocked|expired. Used by the subscription sentence (.substat) and the peer modal's clause. */
.s-active{color:var(--online)} .s-blocked,.s-expired{color:var(--dangling)} .s-expiring{color:var(--fault)}
/* the subscription sentence ("Subscription is active", "… is about to expire on …", "… expired on …"). header =
   the modal title row: Alice left · sentence centred (flex:1 fills the gap) · peer count right. center = its own
   centred line at the top of the edit-user card. */
.substat{font-size:12px;font-weight:600;white-space:nowrap}
.substat-hd{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;text-align:center;padding:0 12px}   /* centred in the user modal title row (subscription) */
.substat-hr{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;text-align:right;padding:0 12px}    /* right-aligned in the peer modal title row (subscription) */
.substat-bar{display:block;text-align:right;margin:-12px 0 10px;white-space:normal}                       /* a right-aligned line UNDER the header (peer modal / edit-peer): the peer's own status — pulled up close to the divider */
.substat-center{display:block;text-align:center;margin:0 0 14px;font-size:13px}
/* access-expiry date input (edit-user / edit-peer) */
.daterow{display:flex;align-items:center;gap:8px}
.datein{font:inherit;padding:7px 10px;border:1px solid var(--line);border-radius:9px;background:var(--panel);color:var(--ink);color-scheme:light dark}
.unlockpanel{margin:0 0 16px;padding:13px 15px;border:1px solid var(--line);border-radius:12px;background:var(--panel-2,rgba(127,127,127,.05));display:flex;flex-direction:column;gap:10px}
.unlockpanel-msg{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--ink)}
.unlockpanel-msg .ic{color:var(--brand);flex:none}
.unlockpanel-row{display:flex;gap:8px;align-items:center}
.unlockpanel-row .subpw{flex:1}
.unlockpanel .vp-keep-row{font-size:12.5px;color:var(--muted)}
.sublink{margin:0 0 16px;display:flex;flex-direction:column;gap:9px}
/* enabled sub-link: green textbox + Rotate/Disable on the same line */
.sublink.sublink-row{flex-direction:row;align-items:center;gap:8px}
.sublink-row .suburl{flex:1;min-width:0}
.sublink-row .btn{flex:none}
.vkfield-row{display:flex;gap:8px;align-items:center}
/* the box mirrors .suburl exactly (icon + padding + radius); the input inside is borderless, so heights match */
.vkbox{flex:1;min-width:0;box-sizing:border-box;min-height:30px;display:flex;align-items:center;gap:9px;padding:5px 11px;border:1px solid var(--line);border-radius:8px;transition:border-color .12s,background .12s}
.vkbox > .ic{flex:none}
.vkbox-input{flex:1;min-width:0;border:0;background:transparent;padding:0;font-family:var(--mono);font-size:12px;line-height:16px;color:var(--ink);outline:none;box-shadow:none}
.vkbox-input:focus{box-shadow:none;outline:none;border:0}   /* the .vkbox draws the border; kill the global input:focus glow so there's no bluish inner border */
/* save status sits at the LEFT of the button group, before the Save button (margin-right:auto pushes it left) */
.vk-status{margin-right:auto;display:inline-flex;align-items:center;gap:4px;font-size:12px;font-weight:600;white-space:nowrap}
.vk-status.ok{color:var(--online)}
.savedmsg{font-size:11.5px;font-weight:600;white-space:normal;line-height:1.35;max-width:62%;align-items:flex-start}
.savedmsg .ic{margin-top:1px}
.obfctl-lbl{color:var(--ink-2);font-weight:600}
.notgen{font-size:11.5px;font-weight:600;color:#e0a85f;white-space:nowrap}
.vk-status.err{color:var(--dangling)}
.vk-status .ic{width:14px;height:14px}
.vkfield-row .btn{flex:none}
/* fixed-width trailing button group → the VK input and the sub-link box line up to the SAME width, and every
   button (Save / Rotate / Disable) is the same size so Save sits right under Disable */
.fieldbtns{flex:none;display:flex;gap:8px;justify-content:flex-end;align-items:center}
.fbtn{width:112px;flex:none;justify-content:center;text-align:center;white-space:nowrap;padding-left:8px;padding-right:8px}   /* the 4 link-row buttons all one width; the input shrinks to fit */
.sublink-field{margin-bottom:14px}
.sublink-field .sublink{margin:0}
/* user-modal peer card: the whole card opens the peer modal (except the QR + action buttons).
   `.hot` is set by JS only while the pointer is over a clickable region, so hovering the QR or a button
   never lights the card up. */
.upc-wrap{cursor:pointer;border-radius:var(--r);transition:box-shadow .12s}
.upc-wrap.upc-static{cursor:default}
.upc-wrap.hot .deploy{border-color:color-mix(in srgb,var(--brand) 55%,var(--line-solid))}
.upc-wrap.hot .upc-head{background:color-mix(in srgb,var(--brand) 9%,transparent)}
.upc-head{padding:10px 14px;border-bottom:1px solid var(--line);display:flex;flex-direction:column;gap:5px;transition:background .12s}
.upc-l1,.upc-l2{display:flex;align-items:center;gap:7px}
.upc-nm{font-family:var(--mono);font-weight:700;font-size:13px;color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.upc-srv{font-family:var(--mono);font-size:12px;font-weight:600}
.upc-deps{font-family:var(--mono);font-size:10.5px;font-weight:600;color:var(--faint);letter-spacing:.02em}
/* Primary badge overlays the gap above the QR (peer modal): centered and tucked just above the QR, so it
   never pushes the QR down — the QR stays aligned with the sibling cards */
.qr-primary{position:absolute;top:11px;left:50%;transform:translateX(-50%);z-index:1;font-family:var(--mono);font-size:9px;font-weight:800;letter-spacing:.07em;text-transform:uppercase;color:var(--online);border:1px solid color-mix(in srgb,var(--online) 40%,transparent);border-radius:5px;padding:1px 6px;line-height:1.3;background:var(--surface)}
/* "← Go back" in place of × (child modal opened from the user view) */
.sheet-back{appearance:none;margin-left:auto;display:inline-flex;align-items:center;gap:5px;background:transparent;border:1px solid var(--line-solid);border-radius:8px;color:var(--ink);font:inherit;font-size:12.5px;font-weight:600;padding:5px 11px;cursor:pointer}
.sheet-back:hover{background:var(--raise);border-color:var(--brand)}
.sheet-back .ic{width:14px;height:14px}
/* disabled-subscription: message + Enable button on one line */
.sublink-off{flex-direction:row;align-items:center;justify-content:space-between;gap:14px}
.sublink-off-msg{font-size:12px;color:var(--muted,#8a97ad);line-height:1.4}
.sublink-off .btn{flex:none}
.qrnavcount{font-family:var(--mono);font-size:12px;color:var(--muted);min-width:44px;text-align:center}
.nmwrap{display:flex;flex-direction:column;gap:1px;min-width:0}
.deploy-primary{font-family:var(--mono);font-size:8.5px;text-transform:uppercase;letter-spacing:.09em;color:var(--online);line-height:1}
.fieldrow{display:flex;gap:14px;flex-wrap:wrap}
.fieldrow > .field{flex:1;min-width:140px;margin:0}
/* Access & TLS */
.mt8{margin-top:8px}
.subaddr{display:flex;flex-direction:column;gap:6px;padding:12px 14px;border:1px solid var(--line);border-radius:10px;background:var(--card-2,rgba(127,127,127,.05))}
.subaddr-row{display:flex;gap:12px;align-items:baseline}
.subaddr-k{flex:0 0 96px;color:var(--muted);font-size:12.5px}
.subaddr-v{flex:1;min-width:0;word-break:break-all;font-size:13px}
.ci{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;border-radius:50%;
  font-size:11px;font-weight:700;font-style:normal;color:var(--brand-ink);background:var(--warn,#e0a030);cursor:help;vertical-align:middle}
/* inline "port not reachable" hint — exclamation-in-triangle in the warn colour (no circle background).
   Sized to the 10.5px uppercase label and nudged up so it sits on the caps line, not below it. */
.ciw{display:inline-flex;align-items:center;color:var(--dangling);cursor:help;vertical-align:middle;position:relative;top:-2px}
.ciw svg{width:13px;height:13px}
/* subscription languages picker (Settings → Subscriptions) */
.sublangs{display:flex;flex-direction:column;gap:8px}
.sublang{display:flex;align-items:center;gap:12px;padding:8px 12px;border:1px solid var(--line);border-radius:9px}
.sublang.on{border-color:color-mix(in srgb,var(--brand) 40%,var(--line))}
.sublang-en{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:600;color:var(--ink)}
.sublang-en input{width:16px;height:16px;accent-color:var(--brand)}
.sublang-def{margin-left:auto;appearance:none;border:1px solid var(--line);background:transparent;color:var(--muted);font:inherit;font-size:12px;font-weight:700;padding:5px 11px;border-radius:20px;cursor:pointer}
.sublang-def.on{background:color-mix(in srgb,var(--brand) 16%,transparent);border-color:var(--brand);color:var(--brand)}
.sublang-def:disabled{opacity:.4;cursor:default}

/* ── subscription: per-user panel + the URL/copy strip ── */
.subpanel{margin:0 0 18px;padding:14px 16px;border:1px solid var(--line);border-radius:12px;background:var(--panel-2,rgba(127,127,127,.05));display:flex;flex-direction:column;gap:10px}
.subpanel-hd .ic{color:var(--brand,#1FC8D6)}
.subpanel .hint{margin:0;font-size:12px;line-height:1.5;color:var(--muted,#8a97ad)}
.subpanel .hint.err{color:var(--danger,#e5484d)}
.subpanel .hint.warn{color:var(--warn,#f0a020)}
.subpanel .btn{align-self:flex-start}
.subpw{width:100%;max-width:420px;padding:8px 11px;border:1px solid var(--line);border-radius:9px;background:var(--bg);color:var(--ink);font:inherit;font-size:13px}
/* vault-unlock prompt (encryption key needed for a user-triggered action) */
.vaultprompt{display:flex;flex-direction:column;gap:12px}
.vaultprompt .vp-reason{margin:0;color:var(--ink-2);line-height:1.55}
.vaultprompt .vp-skip{margin-top:2px}
.vp-keep-row{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px}
.vp-keep-row input{flex:none;width:16px;height:16px;padding:0;margin:0 0 0 -2px;cursor:pointer;accent-color:var(--brand)}   /* base input{width:100%;padding} otherwise blows the checkbox up to full width; -2px aligns the glyph with the text above */
.vp-keep-row + .hint{margin-top:5px;font-size:11.5px;color:var(--faint);line-height:1.45}   /* form-style explanation under the checkbox (also covers the unlock bar, outside .field) */
.vaultprompt .vp-keep,.notice .vp-keep{margin-top:2px}
/* standout reassurance beside "re-distribute the configs" warnings — subscriptions auto-update */
.sub-auto{display:flex;align-items:flex-start;gap:8px;margin-top:10px;padding:8px 11px;border-radius:8px;
  background:rgba(63,216,154,.10);border:1px solid rgba(63,216,154,.34);color:var(--ink);font-size:12.5px;line-height:1.45}
.sub-auto svg{flex:none;margin-top:1px;width:15px;height:15px;color:#2fb883}
.sub-auto b{color:var(--ink)}
/* the URL + copy row — one hover target; clicking anywhere copies */
.suburl{display:flex;align-items:center;gap:9px;padding:5px 11px;border:1px solid #22c55e;border-radius:8px;background:color-mix(in srgb,#22c55e 16%,var(--bg));cursor:pointer;transition:border-color .12s,background .12s}
.suburl:hover{border-color:#22c55e;background:color-mix(in srgb,#22c55e 26%,var(--bg))}
.suburl > .ic{color:#22c55e;flex:none}
.suburl-txt{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--mono);font-size:12px;color:var(--ink)}
.suburl-copy{flex:none;display:inline-flex;color:var(--muted,#8a97ad)}
.suburl:hover .suburl-copy{color:#22c55e}
.suburl-copy.ok{color:var(--online)}
.suburl-hint{font-style:italic}
.cfgsheet .deploy{width:256px;flex:none}
.cfgsheet .deploy .qr{flex:1;min-width:0}
.cfgsheet .deploy .qr .qrimg{width:100%}
.cfgsheet .deploy .acts{justify-content:center}
@media(max-width:640px){.ug-head{flex-wrap:wrap}}

/* ───── login page ───── */
.login-wrap{min-height:calc(100vh - 56px);display:flex;align-items:center;justify-content:center;padding:24px}
.login-card{width:100%;max-width:360px;background:var(--surface);border:1px solid var(--line);border-radius:var(--r);padding:26px 24px;box-shadow:var(--shadow)}
.login-brand{display:flex;align-items:center;gap:10px;margin-bottom:16px}
.login-card h2{margin:0 0 18px;font-size:18px;font-weight:600;color:var(--ink)}
.login-card .field{margin-bottom:12px}
/* header icon buttons (account / logout) */
.iconbtn.lg{width:36px;height:36px;border-radius:9px}
.appbar-right .iconbtn{color:var(--dim)}
/* logged-out: keep only the wordmark in the bar */
body.loggedout .tabs,body.loggedout .appbar-right,body.loggedout .appver{display:none}

/* ───── history range tabs (live/day/week/month) ───── */
.chartwrap{display:flex;flex-direction:column;gap:8px}
.chart-head{display:flex;align-items:center;gap:10px 18px;min-height:22px;flex-wrap:wrap}
.chart-head .hist-cap{margin:0}
/* node-detail Health: meters occupy ~60% (each ~20%), range tabs ride the same row, right */
.chart-head .health-cols{flex:1 1 58%;min-width:300px;gap:14px 22px}
.rangetabs{display:inline-flex;gap:2px;background:var(--surface-2);border:1px solid var(--line);border-radius:8px;padding:2px}
.panel-tools{display:flex;align-items:center;gap:10px;flex-wrap:wrap;justify-content:flex-end}   /* header-hosted controls: Peers/Mesh + range picker */
.rtab{cursor:pointer;border:0;background:transparent;color:var(--dim);font-family:var(--mono);font-size:10px;text-transform:uppercase;
  letter-spacing:.06em;padding:4px 9px;border-radius:6px;transition:all .14s}
.rtab:hover{color:var(--ink-2)}
.rtab.on{background:var(--brand);color:var(--brand-ink);font-weight:600}
/* "paused" pill: node isn't reporting right now, but the last-collected data stays charted */
.rt-paused{display:inline-flex;align-items:center;gap:5px;font-family:var(--mono);font-size:9.5px;text-transform:uppercase;
  letter-spacing:.07em;color:var(--pending);background:color-mix(in srgb,var(--pending) 14%,transparent);
  border:1px solid color-mix(in srgb,var(--pending) 34%,transparent);border-radius:6px;padding:3px 8px;white-space:nowrap}
.rt-paused::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--pending);animation:rtpulse 1.6s var(--ease) infinite}
@keyframes rtpulse{0%,100%{opacity:1}50%{opacity:.3}}

/* node-detail: push sync/uptime + chart range tabs to the right */
.noderibbon .nr-sync{margin-left:auto}
.chart-head .grow{flex:1}
.tp-legend .grow{flex:1}
/* plain (info) notice — used by the node-removal sheet */
.notice{background:var(--surface-2);color:var(--ink-2)}
.notice .ic{color:var(--ready);flex:none}

/* ───── peers grid ───── */
/* clickable sortable headers (order-by) */
.peergrid th.clk{cursor:pointer;user-select:none}
.peergrid th.clk:hover{color:var(--ink)}
/* Live grid: status is just a green (online) / grey (offline) dot — a tiny column; the rest shifts left */
/* live grids are now always `loc` (dot + iface badge in the status) → the old bare-dot 26px rule only applies to a
   non-loc live grid, if any. loc live status width is set by the h-status rules. */
.peergrid.live:not(.loc) th:first-child,.peergrid.live:not(.loc) td[data-label="Status"]{width:26px;text-align:center;padding-left:12px;padding-right:0}
/* cumulative transfer — SAME colours as the rate cell: faint when nothing moved, green ↓ / blue ↑ once it has */
.xfer{font-family:var(--mono);color:var(--faint);white-space:nowrap} .xfer .up{opacity:.7}
.xfer.live{color:var(--online)} .xfer.live .up{color:var(--rate-up);opacity:1}
.peergrid .xfer{font-size:11.5px}
.peergrid td[data-label="Rate"],.peergrid td[data-label="Total"],.peergrid th{white-space:nowrap}   /* keep 4-digit rates (1023 MB/s) on one line */
/* Rate/Total are numeric — right-align header + values so the header text ends right above where the values end */
.peergrid th.h-rate,.peergrid th.h-total,.peergrid td[data-label="Rate"],.peergrid td[data-label="Total"]{text-align:right}
.peergrid td.rowacts{text-align:right;white-space:nowrap}
/* the user-EXPANDED peer grid uses a FIXED column layout so its Online/Rate/Total/actions columns sit at
   stable, content-independent x-positions — the user-row header is aligned to exactly these widths so the
   two line up vertically regardless of the data. (The standalone Peers screen stays auto-width.) */
/* All peer grids use FIXED column layout. Standalone grids (interface detail / Peers module / live Peers) get
   balanced % widths (Title roomy, the rest even). Embedded (user-expanded) grids override with px widths that
   mirror the user-row .u-right so Peers/Nodes/Online/Rate/Total align vertically above them. */
.peergrid{width:100%;table-layout:fixed}
.peergrid th.h-status{width:9%}
.peergrid.loc th.h-status{width:11%}   /* loc grids put the iface badge next to the status → a touch wider */
.peergrid th.h-user{width:14%}
.peergrid th.h-title{width:13%}
.peergrid th.h-ep{width:13%}
.peergrid th.h-addr{width:11%}
.peergrid th.h-node{width:16%}   /* aggregate view packs node name + iface badge here — give it room so names don't ellipsize */
.peergrid th.h-online{width:8%}
.peergrid th.h-rate{width:15%}
.peergrid th.h-total{width:14%}
.peergrid th.h-acts{width:11%}
.urow-body .peergrid th.h-status{width:150px}
.urow-body .peergrid th.h-title,.urow-body .peergrid th.h-ep{width:auto}   /* flexible left in the embedded fixed layout */
.urow-body .peergrid th.h-addr{width:168px}   /* ↔ user-row Peers */
.urow-body .peergrid th.h-node{width:152px}   /* ↔ user-row Nodes — fit full names (spb-rostelecom / sgp-digitalocean) */
.urow-body .peergrid th.h-online{width:86px}
.urow-body .peergrid th.h-rate{width:168px}
.urow-body .peergrid th.h-total{width:158px}
.urow-body .peergrid th.h-acts{width:112px}
/* embedded (loc) grid: the interface badge sits beside the STATUS so you see which iface is online. */
.peergrid td.c-status{white-space:nowrap}
.peergrid td.c-status>*{vertical-align:middle}
.peergrid.loc td.c-status>.tg{margin-left:7px}
/* live grids (standalone + embedded): a touch more air between the status dot and the iface badge. */
.peergrid.loc.live td.c-status>.tg{margin-left:9px}
.peergrid.loc.live td.c-status>.turnwrap{gap:9px}
/* in LIVE embedded the status has just a dot + iface (no wide pill) → narrower, so Title sits closer to it. */
.urow-body .peergrid.loc.live th.h-status,.urow-body .peergrid.loc.live td.c-status{width:112px;text-align:left;padding-left:14px}
/* a long title / user name must not widen the table past its wrap: the fixed layout ellipsis-truncates cells
   instead of widening, so the wrap never needs to scroll — and we keep it overflow:visible so hover bubbles
   (status "why", nodes breakdown, "connected via") are NEVER clipped by the wrap, wherever the row sits.
   The wrap gave the rounded frame via its clip; with overflow:visible the table rounds its own corner cells
   (peergrid cells use only bottom-borders, so border-collapse:separate renders identically). */
.tablewrap:has(>table.peergrid){overflow:visible}
table.peergrid{border-collapse:separate;border-spacing:0}
.peergrid thead th:first-child{border-top-left-radius:calc(var(--r) - 1px)}
.peergrid thead th:last-child{border-top-right-radius:calc(var(--r) - 1px)}
.peergrid tbody tr:last-child td:first-child{border-bottom-left-radius:calc(var(--r) - 1px)}
.peergrid tbody tr:last-child td:last-child{border-bottom-right-radius:calc(var(--r) - 1px)}
.peergrid td.c-name>b,.peergrid td.c-name>span{display:inline-block;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}
.peergrid td.usercell .namecell>span{max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* fleet-wide column: server name (left) + interface tag (right) on one line */
.peergrid .srvcell{display:flex;align-items:center;justify-content:space-between;gap:10px;min-width:0}
.peergrid .srv-name{font-family:var(--mono);font-size:12px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* user cell is a link to the user screen — blue (matches the upload total) so admins know it navigates away */
.peergrid .namecell{color:var(--ready);justify-content:space-between;width:100%} .peergrid .namecell:hover{text-decoration:underline}   /* Live/Users lists: username is plain text, not a link */
.peergrid .namecell svg{width:14px;height:14px;color:var(--ready);flex:none;opacity:0;transition:opacity .12s;position:relative;top:3px}   /* nudge down to sit on the text's baseline */
/* hovering an assigned user's cell tints the whole row blue + reveals the user icon */
.peergrid tbody tr.clk:has(td.usercell.linked:hover){background:color-mix(in srgb,var(--ready) 9%,transparent)}
.peergrid td.usercell.linked:hover .namecell svg{opacity:1}
/* peers-grid pagination bar */
.pager{display:flex;align-items:center;justify-content:flex-end;gap:12px;margin-top:14px;padding:6px 2px;font-size:12.5px}
.pager-size{display:flex;align-items:center;gap:9px;font-family:var(--mono);font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--dim)}
.pager-size .selwrap{width:auto;min-width:62px;padding:6px 26px 6px 10px}
.pager-info{font-family:var(--mono);color:var(--faint)}
.pager-pg{font-family:var(--mono);min-width:52px;text-align:center;color:var(--ink-2)}
.pager .btn{padding:6px 12px}
/* extra-deployment count next to the address; hover or tap opens a bubble (.deppop) of the others */
.depmore{position:relative;margin-left:7px;padding:1px 5px;border-radius:5px;color:var(--ready);font-family:var(--mono);font-size:11px;cursor:pointer;
  background:color-mix(in srgb,var(--ready) 10%,transparent);border:1px solid color-mix(in srgb,var(--ready) 28%,transparent);transition:background .12s,border-color .12s;user-select:none}
.depmore:hover,.depmore.on{background:color-mix(in srgb,var(--ready) 20%,transparent);border-color:color-mix(in srgb,var(--ready) 55%,transparent)}
.deppop{position:fixed;z-index:80;min-width:210px;padding:5px;background:var(--surface-2);border:1px solid var(--line-solid);
  border-radius:var(--r-sm);box-shadow:0 12px 34px rgba(0,0,0,.5);cursor:default}
.deppop .deprow{display:flex;align-items:center;gap:9px;padding:6px 8px;border-radius:6px;white-space:nowrap}
/* header update-widget hover bubbles (update-to / fix-issues) — rich content in place of the plain title tooltip */
.hostupd-bub{z-index:90;min-width:440px;max-width:680px;padding:14px 17px 12px;white-space:normal;line-height:1.5;text-transform:none;letter-spacing:0;font-family:var(--sans)}
.hub-h{display:flex;align-items:baseline;gap:10px;margin-bottom:11px;padding-bottom:10px;border-bottom:1px solid var(--line)}
.hub-title{font-size:13px;font-weight:600;color:var(--ink)}
.hub-title b{color:var(--ready);font-weight:700}
.hub-date{margin-left:auto;flex:none;font-family:var(--mono);font-size:10.5px;color:var(--faint);font-weight:400}
.hub-list{display:flex;flex-direction:column;gap:7px}
.hub-row{display:flex;gap:10px;font-size:12px;color:var(--dim);align-items:flex-start}
.hub-row.faint{color:var(--faint)}
.hub-bul{flex:none;width:5px;height:5px;border-radius:50%;background:var(--ready);opacity:.7;margin-top:6px}
.hub-txt{flex:1;min-width:0}
.hub-txt b{color:var(--ink-2);font-weight:600}
.hub-dot{width:7px;height:7px;border-radius:50%;flex:none;position:relative;top:5px;background:var(--muted)}
.hub-dot.critical{background:#e0524a}
.hub-dot.warn{background:#e0a83a}
.hub-foot{margin-top:12px;font-size:10.5px;color:var(--faint);border-top:1px solid var(--line);padding-top:8px}
.deppop .deprow:hover{background:var(--surface)}
.deppop .dep-name{font-size:12px;color:var(--ink)}
.deppop .dep-ip{margin-left:auto;font-size:11.5px;color:var(--ink-2)}
/* online users / peers breakdown bubble + its trigger tag */
/* the hover trigger: a slightly enlarged hit area + a clearer highlight (the header uses .bare = none) */
.onlinetag{cursor:pointer;border-radius:6px;padding:3px 7px;margin:-3px -4px;transition:background .12s,box-shadow .12s}
.onlinetag:hover,.onlinetag.on{background:rgba(63,216,154,.2);box-shadow:inset 0 0 0 1px rgba(63,216,154,.45)}
.onlinetag.bare,.onlinetag.bare:hover,.onlinetag.bare.on{background:none;box-shadow:none;padding:0;margin:0;display:inline-flex;align-items:center;gap:7px}
.onlinetag.nm-peerpop{display:inline-flex;align-items:baseline;gap:8px}
.oncount.on{color:var(--online)}   /* first number green when any peer is online */
.ifc-conn .oncount{font-size:12px}   /* turn-card connection count — match the interface-card "Peers N/M" size (.ifrow is 12px) */
.onlpop{min-width:210px;max-height:50vh;overflow:auto}
.onlpop .onpop-h{display:block;font-family:var(--mono);font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--dim);padding:4px 8px 6px;text-decoration:none}
.onlpop a.onpop-h:hover{color:var(--brand)}
.onlpop .onrow{display:flex;align-items:baseline;justify-content:space-between;gap:14px;padding:5px 8px;border-radius:6px;white-space:nowrap}
.onlpop .onrow:hover{background:var(--surface)}
.onlpop .hrow{display:block;white-space:normal}   /* health-issue rows: let long descriptions wrap */
.onlpop .hrow .on-name{font-size:12px;line-height:1.35}
.onlpop .onrow.un .on-name{color:var(--dim);font-style:italic}
.onlpop .on-name{font-size:12.5px;color:var(--ink)}
.onlpop .on-ct{font-size:12px;color:var(--online);font-family:var(--mono)}
.onlpop .on-user{font-size:11.5px}
.onlpop .onpop-orph{display:block;font-size:12.5px;color:#F0913C;text-decoration:none;padding:8px 8px 4px;margin-top:3px;border-top:1px solid var(--line)}
.onlpop a.onpop-orph:hover{color:#C7741E}
.onlpop .onpop-viewall{display:block;font-size:11.5px;color:var(--brand);text-decoration:none;padding:7px 8px 4px;margin-top:3px;border-top:1px solid var(--line)}
.onlpop .onpop-viewall:hover{text-decoration:underline}
.assigncell{display:flex;align-items:center;gap:6px}
.assigncell .usercombo{min-width:120px;max-width:150px}   /* the in-table "Assign to…" picker: keep it compact, don't stretch the column */
/* searchable user picker */
.usercombo{position:relative;min-width:150px}
.uc-input{width:100%;background:var(--surface-2);border:1px solid var(--line-solid);border-radius:7px;color:var(--ink);font:inherit;font-size:12.5px;padding:5px 9px}
.uc-input:focus{outline:none;border-color:var(--brand)}
.uc-list{position:absolute;z-index:40;top:calc(100% + 4px);left:0;min-width:200px;max-height:260px;overflow:auto;
  background:var(--raise);border:1px solid var(--line-solid);border-radius:9px;box-shadow:var(--shadow);padding:4px}
/* portaled to <body> and anchored to the input (position:fixed) so the list is never clipped by a grid/table
   overflow:hidden; z-index above modals so it works inside a Sheet too */
.uc-list.uc-pop{position:fixed;z-index:90}
.uc-opt{display:flex;align-items:center;gap:8px;width:100%;border:0;background:transparent;color:var(--ink);cursor:pointer;
  padding:6px 8px;border-radius:6px;font:inherit;font-size:13px;text-align:left}
.uc-opt:hover{background:var(--surface-2)}
.uc-empty{padding:8px;color:var(--faint);font-size:12px}
/* peer view sheet */
.pv-head{display:flex;align-items:center;gap:12px}
.pv-id{flex:1;min-width:0}
.pv-title{font-size:16px;font-weight:600;color:var(--ink)}
.pv-sub{font-size:13px;color:var(--dim)}
.pv-sub .pv-user{color:var(--ready)} .pv-sub .pv-user:hover{text-decoration:underline}
.pv-deps{display:flex;flex-direction:column;gap:8px}
.pv-dep{border:1px solid var(--line);border-radius:var(--r-sm);padding:10px 12px;background:var(--surface-2)}
.pv-dep.hl{border-color:var(--brand)}
.pv-dep-top{display:flex;align-items:center;gap:8px;margin-bottom:8px}
/* server↔rate, interface↔total, address↔last line up in 3 columns, two rows */
.pv-dep-grid{display:grid;grid-template-columns:minmax(0,1.3fr) minmax(0,1fr) minmax(0,1fr);gap:8px 18px;font-family:var(--mono);font-size:12px;color:var(--ink-2)}
.pv-dep-grid>span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pv-dep-grid .k{color:var(--faint);font-size:9px;text-transform:uppercase;letter-spacing:.06em;margin-right:6px}

/* ───── Two-factor (TOTP) card — Settings → Authentication ───── */
.btnrow{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.twofa-setup{display:flex;gap:20px;align-items:flex-start;flex-wrap:wrap}
.twofa-qr{width:200px;height:200px;border-radius:10px;background:#fff;padding:8px;flex:0 0 auto;box-shadow:0 0 0 1px var(--line)}
.twofa-qr.empty{display:flex;align-items:center;justify-content:center;color:var(--faint);font-size:12px;background:var(--surface-2)}
.twofa-manual{flex:1 1 240px;min-width:220px}
.twofa-manual>label{display:block;font-size:11px;color:var(--faint);text-transform:uppercase;letter-spacing:.06em;margin-bottom:6px}
.twofa-secret{display:inline-block;font-family:var(--mono);font-size:14px;letter-spacing:.12em;word-break:break-all;background:var(--surface-2);border:1px solid var(--line);border-radius:8px;padding:8px 10px;color:var(--ink)}
.twofa-codes{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}
.twofa-codes code{font-family:var(--mono);font-size:14px;letter-spacing:.08em;text-align:center;background:var(--surface-2);border:1px solid var(--line);border-radius:8px;padding:9px 6px;color:var(--ink)}

/* Address-migration ribbon — shown on the OLD panel address after a rebind confirms elsewhere: this tab keeps
   working through the node-migration grace, then stops. Fixed to the top of the viewport so the countdown is
   impossible to miss; deep red, high z-index above the header. */

/* "You're on a previous panel address" — persistent, informational (amber, not the migrate ribbon's alarm-red),
   shown on any tab loaded at a non-current panel address so an old address reads as expected, not a fault. The
   sticky .appbar is pushed down by the measured ribbon height so it's never hidden. */
.addr-old-ribbon{position:fixed;top:0;left:0;right:0;z-index:31;display:flex;align-items:center;justify-content:center;
  gap:12px;flex-wrap:wrap;padding:9px 16px;background:#c62b42;color:#fff;font-size:13px;font-weight:600;
  box-shadow:0 2px 10px rgba(0,0,0,.28);border-bottom:1px solid rgba(0,0,0,.25)}
.addr-old-ribbon a{color:#fff;text-decoration:underline;text-underline-offset:2px}
.addr-old-ribbon .btn-mini{background:rgba(255,255,255,.16);border:1px solid rgba(255,255,255,.5);color:#fff;
  text-decoration:none;padding:3px 10px;border-radius:7px;font-weight:700;white-space:nowrap}
.addr-old-ribbon .btn-mini:hover{background:rgba(255,255,255,.28)}
body.has-oldaddr .appbar{margin-top:var(--oldaddr-h,40px);top:var(--oldaddr-h,40px)}
