/* ═══════════════════════════════════════════════════════════════
   KillNAT customer app — restyled to match apps/console-prototype.
   Design tokens + components lifted from the prototype and the
   design system (docs/design/DESIGN-SYSTEM.md). One blue accent;
   semantic status colour kept separate. Mobile-first: no blur on
   touch devices, sidebar becomes a bottom tab bar.
   ═══════════════════════════════════════════════════════════════ */
/* ── themes ───────────────────────────────────────────────────
   Light is the default and matches killnat.com. Dark is a real second
   palette, not an inversion: on a near-black ground the accent has to get
   LIGHTER to stay legible, and the shadows have to get heavier to still
   read as depth.

   Three states, not two. An explicit choice stamps data-theme on <html>;
   the default "system" setting stamps nothing, so most people are only
   ever separated by prefers-color-scheme. Hence all three blocks: bare
   :root carries the whole light palette, the media query is guarded with
   :not([data-theme="light"]) so an explicit light choice beats a dark OS,
   and [data-theme="dark"] wins the other direction.

   Every colour is a token. A colour written directly into a component
   rule is a colour that only works in one theme — see --side-solid, which
   was a leftover #141210 from the old amber build and turned the mobile
   tab bar near-black underneath light-grey labels. */
:root{
  /* iOS-native light (Firewalla-clean): grey grouped ground, white cards, system blue */
  --bg:#F2F2F7; --surf:#FFFFFF; --surf-2:#EDEDF0; --surf-3:#E7E7EC;
  --line:#E5E5EA; --line-2:#D8D8DD;
  --tx:#1C1C1E; --tx-2:#8E8E93; --tx-3:#AEAEB2;
  --ac:#6C4DFF; --ac-2:#5638E0;          /* KillNAT brand violet */
  --ac-on:#6C4DFF;                       /* accent TEXT on an --ac-soft ground */
  --ac-soft:rgba(108,77,255,.12); --ac-line:rgba(108,77,255,.30);
  --good:#34C759; --good-soft:rgba(52,199,89,.14); --good-line:rgba(52,199,89,.30);
  --warn:#FF9F0A; --warn-soft:rgba(255,159,10,.14); --warn-line:rgba(255,159,10,.30);
  --bad:#FF3B30;  --bad-soft:rgba(255,59,48,.12);   --bad-line:rgba(255,59,48,.30);
  --good-glow:rgba(52,199,89,.40);
  --top-bg:rgba(242,242,247,.85);        /* sticky header, blurred (desktop) */
  --top-solid:#F2F2F7;                   /* header + status-bar strip, opaque (phones) */
  --side-bar:rgba(255,255,255,.92);      /* bottom tab bar, blurred */
  --side-solid:#FFFFFF;                  /* bottom tab bar, fully opaque (no content bleed) */
  --av-bg:linear-gradient(145deg,#D8D8DD,#EDEDF0);
  --sel-arrow:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8'><path d='M1 1l5 5 5-5' fill='none' stroke='%236E6E73' stroke-width='1.8' stroke-linecap='round'/></svg>");
  --r:18px; --r-s:12px;
  --sh:0 1px 3px rgba(0,0,0,.05);
  --sh-sm:0 1px 2px rgba(0,0,0,.08);
  --ac-glow:rgba(108,77,255,.55); --ac-glow-2:rgba(108,77,255,.85);
  --icon-sun:none; --icon-moon:block;   /* on light, offer the moon */
  /* aliases kept so the legal pages (terms/privacy/aup) render on the theme */
  --accent:#6C4DFF; --fg:#1C1C1E;
  color-scheme:light;
}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){
    --bg:#0B0B0D; --surf:#161719; --surf-2:#1E2023; --surf-3:#24262A;
    --line:#2A2C30; --line-2:#34373C;
    --tx:#F4F5F7; --tx-2:#9BA0A8; --tx-3:#6C717A;
    --ac:#6C4DFF; --ac-2:#7C8AFF;
    --ac-on:#A7B4FF;
    --ac-soft:rgba(91,108,255,.16); --ac-line:rgba(91,108,255,.42);
    --good:#3DD68C; --good-soft:rgba(61,214,140,.14); --good-line:rgba(61,214,140,.32);
    --warn:#F0A742; --warn-soft:rgba(240,167,66,.14); --warn-line:rgba(240,167,66,.32);
    --bad:#F4695E;  --bad-soft:rgba(244,105,94,.14);  --bad-line:rgba(244,105,94,.34);
    --good-glow:rgba(61,214,140,.38);
    --top-bg:rgba(14,15,18,.82);
    --top-solid:rgba(14,15,18,.97);
    --side-bar:rgba(22,24,29,.96);
    --side-solid:#161719;
    --av-bg:linear-gradient(145deg,#343B47,#22262E);
    --sel-arrow:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8'><path d='M1 1l5 5 5-5' fill='none' stroke='%239AA1AE' stroke-width='1.8' stroke-linecap='round'/></svg>");
    --sh:0 1px 3px rgba(0,0,0,.44),0 12px 34px -16px rgba(0,0,0,.72);
    --sh-sm:0 1px 2px rgba(0,0,0,.5);
  --ac-glow:rgba(91,108,255,.62); --ac-glow-2:rgba(91,108,255,.80);
  --icon-sun:block; --icon-moon:none;
    --ac-glow:rgba(91,108,255,.62); --ac-glow-2:rgba(91,108,255,.80);
  --icon-sun:block; --icon-moon:none;
    --icon-sun:block; --icon-moon:none;
    --accent:#5B6CFF; --fg:#E9EBF0;
    color-scheme:dark;
  }
}
:root[data-theme="dark"]{
  --bg:#0B0B0D; --surf:#161719; --surf-2:#1E2023; --surf-3:#24262A;
  --line:#2A2C30; --line-2:#34373C;
  --tx:#F4F5F7; --tx-2:#9BA0A8; --tx-3:#6C717A;
  --ac:#6C4DFF; --ac-2:#7C8AFF;
  --ac-on:#A7B4FF;
  --ac-soft:rgba(91,108,255,.16); --ac-line:rgba(91,108,255,.42);
  --good:#3DD68C; --good-soft:rgba(61,214,140,.14); --good-line:rgba(61,214,140,.32);
  --warn:#F0A742; --warn-soft:rgba(240,167,66,.14); --warn-line:rgba(240,167,66,.32);
  --bad:#F4695E;  --bad-soft:rgba(244,105,94,.14);  --bad-line:rgba(244,105,94,.34);
  --good-glow:rgba(61,214,140,.38);
  --top-bg:rgba(14,15,18,.82);
  --top-solid:rgba(14,15,18,.97);
  --side-bar:rgba(22,24,29,.96);
  --side-solid:#161719;
  --av-bg:linear-gradient(145deg,#343B47,#22262E);
  --sel-arrow:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8'><path d='M1 1l5 5 5-5' fill='none' stroke='%239AA1AE' stroke-width='1.8' stroke-linecap='round'/></svg>");
  --sh:0 1px 3px rgba(0,0,0,.44),0 12px 34px -16px rgba(0,0,0,.72);
  --sh-sm:0 1px 2px rgba(0,0,0,.5);
  --ac-glow:rgba(91,108,255,.62); --ac-glow-2:rgba(91,108,255,.80);
  --icon-sun:block; --icon-moon:none;
  --accent:#5B6CFF; --fg:#E9EBF0;
  color-scheme:dark;
}
*{box-sizing:border-box}
/* Scrolling: the VIEWPORT (html) is the scroller, not <body>. Putting
   overflow-x:clip on <body> made body a scroll container — on a real iPhone,
   touch-drag scrolls the document (which then has nothing to scroll) so the page
   felt frozen, and it also broke position:sticky. Clipping horizontal on <html>
   instead keeps the natural document scroll that iOS touch drives, so swiping
   works and sticky is reliable. body just grows with its content. */
html{height:100%;overflow-x:clip}
body{margin:0;min-height:100%;background:var(--bg);color:var(--tx);
  font-family:-apple-system,"SF Pro Text",system-ui,Roboto,"Segoe UI",sans-serif;
  font-size:14px;line-height:1.5;
  -webkit-font-smoothing:antialiased}
h1,h2,h3{margin:0;font-family:inherit;font-weight:700;letter-spacing:-.02em;line-height:1.15}
p{margin:0}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
svg{display:block}
::selection{background:var(--ac);color:#fff}
:focus-visible{outline:2px solid var(--ac);outline-offset:2px;border-radius:4px}
.mono{font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,monospace;font-variant-numeric:tabular-nums}
.num{font-family:inherit;font-weight:700;
  letter-spacing:-.01em;font-variant-numeric:tabular-nums;line-height:1}
.lab{font-family:Inter,sans-serif;font-size:9.5px;font-weight:400;
  letter-spacing:.07em;text-transform:uppercase;color:var(--tx-3)}
.accent{color:var(--ac)}

/* ── shell ──────────────────────────────────────────────── */
/* the shell only shows once authed; the gate (auth/claim) hides it */
body.guest .app{display:none}
body.authed .gate{display:none}

.app{display:grid;grid-template-columns:246px 1fr;min-height:100vh}
.side{background:var(--surf);border-right:1px solid var(--line);
  display:flex;flex-direction:column;position:sticky;top:0;height:100vh;padding:18px 14px}
.brand{display:flex;align-items:center;gap:10px;padding:4px 8px 20px}
.mbrand{display:none}

@media(max-width:1000px){
  body.authed{padding-bottom:calc(88px + env(safe-area-inset-bottom))}
  .app{grid-template-columns:1fr}
  .side{position:fixed;left:0;right:0;bottom:0;top:auto;height:auto;z-index:100;
    flex-direction:row;gap:2px;padding:8px 6px max(8px, calc(env(safe-area-inset-bottom) - 12px));
    border-right:0;border-top:1px solid var(--line);
    background:var(--side-bar);backdrop-filter:blur(16px) saturate(180%)}
  .side .brand,.side .sidefoot{display:none}
  .navg{display:contents}
  .navg > .lab{display:none}
  .nv{flex:1;flex-direction:column;gap:5px;padding:6px 3px;min-height:58px;
    font-size:10.5px;font-weight:600;letter-spacing:0;text-align:center;
    justify-content:center;border-radius:10px}
  .nv svg{width:26px;height:26px}
  .nv .ct{display:none}
  .mbrand{display:flex;align-items:center;gap:8px;margin-right:2px}
}
.mark{width:28px;height:28px;border-radius:8px;flex:0 0 auto;
  background:linear-gradient(145deg,var(--ac),var(--ac-2));
  display:flex;align-items:center;justify-content:center;
  box-shadow:0 4px 12px -4px var(--ac-glow)}
.brand b{font-family:Montserrat,Inter,sans-serif;
  font-variation-settings:"wdth" 100,"wght" 800,"opsz" 20;font-size:16px;letter-spacing:-.05em}
.brand b i{font-style:normal;color:var(--ac)}

.navg{margin-bottom:16px}
@media(max-width:1000px){.navg{display:contents}.navg .lab{display:none}}
.navg > .lab{padding:0 8px 8px}
.nv{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:var(--r-s);
  color:var(--tx-2);font-size:13.5px;font-weight:500;width:100%;text-align:left;
  transition:background .18s,color .18s;white-space:nowrap}
.nv svg{width:16px;height:16px;flex:0 0 auto;stroke:currentColor;fill:none;
  stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
.nv:hover{background:var(--surf-2);color:var(--tx)}
.nv.on{background:var(--ac-soft);color:var(--ac-on);box-shadow:inset 0 0 0 1px var(--ac-line)}
.nv .ct{margin-left:auto;font-family:Inter,sans-serif;font-size:9.5px;color:var(--tx-3)}
.nv.on .ct{color:var(--ac-on)}

.sidefoot{margin-top:auto;display:flex;flex-direction:column;gap:12px}
@media(max-width:1000px){.sidefoot{display:none}}
.boxcard{background:var(--surf-2);border:1px solid var(--line);border-radius:var(--r);padding:13px}
.boxcard .r1{display:flex;align-items:center;gap:9px}
.boxcard .nm{font-size:13px;font-weight:500}
.me{display:flex;align-items:center;gap:10px;padding:8px;border-radius:var(--r-s);transition:background .18s}
.me:hover{background:var(--surf-2)}
.av{width:30px;height:30px;border-radius:50%;flex:0 0 auto;font-size:11.5px;font-weight:600;
  display:flex;align-items:center;justify-content:center;
  background:var(--av-bg);border:1px solid var(--line-2);color:var(--tx)}
.logoutbtn{margin-top:2px;color:var(--tx-3);font-size:12px;font-family:Inter,sans-serif;
  letter-spacing:.06em;text-transform:uppercase;padding:6px 8px;border-radius:var(--r-s);text-align:left}
.logoutbtn:hover{color:var(--bad);background:var(--surf-2)}

/* ── topbar ─────────────────────────────────────────────── */
.main{min-width:0;display:flex;flex-direction:column}
/* Always paint the iOS status-bar / Dynamic Island strip opaque. position:sticky
   on the header is unreliable inside the WKWebView (the body's overflow-x:clip
   can break it), so on a real device scrolled content was sliding UNDER the clock.
   This fixed strip covers that zone no matter what. Height is 0 on desktop
   (safe-area inset = 0), so it only shows where there's a notch/island. */
body.authed::before{content:"";position:fixed;top:0;left:0;right:0;
  height:env(safe-area-inset-top);background:var(--top-solid);z-index:50;pointer-events:none}
/* Centered title with a round button on each side (Firewalla-style). */
.top{position:sticky;top:0;z-index:40;display:flex;align-items:center;gap:12px;
  padding:calc(14px + env(safe-area-inset-top)) 20px 12px;background:var(--top-bg);
  backdrop-filter:blur(14px) saturate(180%)}
.top h1{flex:1;text-align:center;font-size:18px;font-weight:700;letter-spacing:-.02em}
.top .crumb{display:none}
.rbtn{width:38px;height:38px;border-radius:50%;flex:0 0 auto;
  background:var(--surf);border:1px solid var(--line);
  display:flex;align-items:center;justify-content:center;box-shadow:var(--sh);
  transition:background .18s,transform .12s}
.rbtn:active{transform:scale(.92)}
.rbtn[hidden]{display:none}
.rbtn svg{width:20px;height:20px;stroke:var(--tx);fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.rbtn .ti-sun{display:var(--icon-sun)} .rbtn .ti-moon{display:var(--icon-moon)}

.btn{display:inline-flex;align-items:center;justify-content:center;gap:7px;
  font-weight:600;font-size:13px;padding:8px 14px;border-radius:var(--r-s);min-height:40px;
  background:var(--ac);color:#fff;border:1px solid var(--ac);
  transition:background .18s,transform .18s cubic-bezier(.2,.9,.25,1),box-shadow .18s}
.btn:hover{background:var(--ac-2);transform:translateY(-1px);box-shadow:0 8px 18px -10px var(--ac-glow-2)}
.btn:disabled{opacity:.55;cursor:not-allowed;transform:none;box-shadow:none}
.btn svg{width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round}
.btn.g{background:var(--surf);border-color:var(--line);color:var(--tx)}
.btn.g:hover{background:var(--surf-2);border-color:var(--line-2);box-shadow:none}
.btn.sm{padding:5px 10px;font-size:12px;min-height:36px}
.btn.block{width:100%}
.btn.icon{padding:8px;width:40px;min-height:40px;flex:0 0 auto}
.btn.icon svg{width:17px;height:17px;stroke-width:1.7}
.ti-sun{display:var(--icon-sun)}
.ti-moon{display:var(--icon-moon)}
.btn.danger{background:transparent;border-color:var(--bad-line);color:var(--bad)}
.btn.danger:hover{background:var(--bad-soft);box-shadow:none}

/* phone: backdrop-filter is the expensive part — go opaque */
@media(max-width:1000px), (hover:none){
  .top{background:var(--top-solid);backdrop-filter:none;-webkit-backdrop-filter:none}
  .side{background:var(--side-solid);backdrop-filter:none;-webkit-backdrop-filter:none}
}
@media(max-width:620px){
  .top .btn .bl{display:none}
  .top .btn{padding:9px 12px;min-height:44px}
  .top h1{font-size:17px}
  .seg button{padding:8px 12px;min-height:36px}
  .kpi .v{font-size:29px}
  .ch{padding:14px 15px}.cb,.kpi{padding:15px}
  thead th{padding:9px 15px}tbody td{padding:12px 15px}
  .plan,.rt,.dr{padding:12px 15px}
  .plan{flex-wrap:wrap}.plan .pp{margin-left:auto;text-align:right}
  .plan .btn,.plan .chip{flex:1 1 100%;margin-top:10px}.plan .chip{justify-content:center}
  .cb.ringrow{flex-wrap:wrap}.ringwrap{margin:0 auto}
}

/* ── content ────────────────────────────────────────────── */
.body{padding:18px 22px 40px;flex:1}
@media(max-width:700px){.body{padding:18px 16px 36px}}
.view{animation:vin .45s cubic-bezier(.2,.9,.25,1)}
@keyframes vin{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
.sub{color:var(--tx-2);font-size:13.5px;margin-top:4px}

.grid{display:grid;grid-template-columns:repeat(12,1fr);gap:13px}
.c3{grid-column:span 3}.c4{grid-column:span 4}.c5{grid-column:span 5}
.c6{grid-column:span 6}.c7{grid-column:span 7}.c8{grid-column:span 8}.c12{grid-column:span 12}
@media(max-width:1180px){.c3{grid-column:span 6}.c4,.c5{grid-column:span 6}.c7,.c8{grid-column:span 12}}
@media(max-width:760px){.c3,.c4,.c5,.c6,.c7,.c8{grid-column:span 12}}

.card{background:var(--surf);border:none;border-radius:var(--r);
  box-shadow:var(--sh);overflow:hidden;transition:border-color .25s}
.card:hover{border-color:var(--line-2)}
.ch{display:flex;align-items:center;gap:10px;padding:12px 16px;border-bottom:1px solid var(--line)}
.ch h3{font-size:13.5px;font-variation-settings:"wdth" 96,"wght" 700,"opsz" 16}
.ch .sp{margin-left:auto;display:flex;align-items:center;gap:8px}
.cb{padding:16px}
.cb.ringrow{display:flex;gap:18px;align-items:center}

/* KPI */
.kpi{padding:13px 15px 15px}
.kpi .k1{display:flex;align-items:center;justify-content:space-between;gap:10px}
.kpi .v{font-size:26px;margin-top:10px}
.kpi .v s{text-decoration:none;font-size:14px;color:var(--tx-3);
  font-family:Inter,sans-serif;font-weight:400;letter-spacing:0}
.kpi .f{display:flex;align-items:center;gap:8px;margin-top:11px}
.kbox{width:24px;height:24px;border-radius:7px;display:flex;align-items:center;justify-content:center;
  flex:0 0 auto;border:1px solid}
.kbox svg{width:12px;height:12px;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.kbox.a{background:var(--ac-soft);border-color:var(--ac-line)} .kbox.a svg{stroke:var(--ac-on)}
.kbox.g{background:var(--good-soft);border-color:var(--good-line)} .kbox.g svg{stroke:var(--good)}
.kbox.w{background:var(--warn-soft);border-color:var(--warn-line)} .kbox.w svg{stroke:var(--warn)}
.kbox.n{background:var(--surf-3);border-color:var(--line-2)} .kbox.n svg{stroke:var(--tx-2)}
.hint{font-size:12px;color:var(--tx-3)}

/* chips */
.chip{display:inline-flex;align-items:center;gap:6px;font-size:11.5px;font-weight:600;
  padding:3.5px 9px;border-radius:99px;border:1px solid;white-space:nowrap}
.chip.live{color:var(--good);background:var(--good-soft);border-color:var(--good-line)}
.chip.warnc{color:var(--warn);background:var(--warn-soft);border-color:var(--warn-line)}
.chip.badc{color:var(--bad);background:var(--bad-soft);border-color:var(--bad-line)}
.chip.neut{color:var(--tx-2);background:var(--surf-3);border-color:var(--line-2)}
.chip.acc{color:var(--ac-on);background:var(--ac-soft);border-color:var(--ac-line)}
.pd{width:6px;height:6px;border-radius:50%;background:currentColor;flex:0 0 auto}
.chip.live .pd{animation:pp 2.4s ease-out infinite}
@keyframes pp{0%{box-shadow:0 0 0 0 var(--good-glow)}70%{box-shadow:0 0 0 7px transparent}100%{box-shadow:0 0 0 0 transparent}}

/* segmented */
.seg{display:inline-flex;background:var(--surf-2);border:1px solid var(--line);border-radius:var(--r-s);padding:2px}
.seg button{padding:5px 11px;border-radius:7px;font-size:12px;font-weight:600;color:var(--tx-3);transition:background .2s,color .2s}
.seg button.on{background:var(--surf-3);color:var(--tx);box-shadow:var(--sh-sm)}
.seg button:hover:not(.on){color:var(--tx-2)}

/* table */
.wrap{overflow-x:auto}
table{width:100%;border-collapse:collapse}
thead th{text-align:left;padding:9px 16px;border-bottom:1px solid var(--line);
  font-family:Inter,sans-serif;font-size:9.5px;font-weight:400;letter-spacing:.07em;
  text-transform:uppercase;color:var(--tx-3);white-space:nowrap}
tbody td{padding:11px 16px;border-bottom:1px solid var(--line);font-size:13.5px;vertical-align:middle}
tbody tr:last-child td{border-bottom:0}
tbody tr{transition:background .18s}
tbody tr:hover{background:var(--surf-2)}
td.ipc{font-family:Inter,sans-serif;font-size:13px;font-weight:500;white-space:nowrap}
.sec{color:var(--tx-2);font-size:12.5px}

/* routes / meters */
.rt{display:flex;align-items:center;gap:12px;padding:12px 18px;border-bottom:1px solid var(--line)}
.rt:last-child{border-bottom:0}
.rt .cty{font-size:13.5px;font-weight:500;min-width:88px}
.rt .cty em{display:block;font-style:normal;font-size:10px;color:var(--tx-3);font-family:Inter,sans-serif;margin-top:2px}
.meter{flex:1;height:5px;border-radius:99px;background:var(--surf-3);overflow:hidden;min-width:40px}
.meter i{display:block;height:100%;border-radius:99px;width:0;background:linear-gradient(90deg,var(--ac-2),var(--ac));transition:width 1.2s cubic-bezier(.2,.9,.25,1)}

/* device — compact card so several boxes fit on one screen */
.dico{width:34px;height:34px;border-radius:9px;background:var(--ac-soft);display:flex;align-items:center;justify-content:center;flex:0 0 auto}
.dico svg{width:19px;height:19px;stroke:var(--ac-on);fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.dgrid{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--line)}
.dcell{background:var(--surf);padding:11px 16px;display:flex;flex-direction:column;gap:3px;min-width:0}
.dcell.full{grid-column:1 / -1}
.dcell .lab{font-size:10.5px;font-weight:600;text-transform:uppercase;letter-spacing:.03em;color:var(--tx-3)}
.dcell .v{font-size:14px;font-weight:600;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.kv{display:flex;justify-content:space-between;gap:14px;padding:9px 0;border-bottom:1px solid var(--line);font-size:13px}
.kv:last-child{border-bottom:0}
.kv span:first-child{color:var(--tx-3)}
.kv span:last-child{font-weight:500;text-align:right}

/* progress ring */
.ring{transform:rotate(-90deg)}
.ring .trk{stroke:var(--surf-3)}
.ring .arc{stroke:var(--ac)}
.ring circle{fill:none;stroke-linecap:round}
.ringwrap{position:relative;width:120px;height:120px;flex:0 0 auto}
.ringwrap .mid{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center}

/* plans */
.plan{display:flex;align-items:center;gap:14px;padding:16px 18px;border-bottom:1px solid var(--line)}
.plan:last-child{border-bottom:0}
.plan.cur{background:var(--ac-soft)}
.plan .pn{font-size:14px;font-weight:600}
.plan .pp{margin-left:auto;text-align:right}
.plan .pp b{font-family:Montserrat,Inter,sans-serif;
  font-variation-settings:"wdth" 90,"wght" 800,"opsz" 24;font-size:20px;letter-spacing:-.03em}

/* rule rows (bypass ports / groups) */
.dr{display:flex;align-items:center;gap:12px;padding:14px 18px;border-bottom:1px solid var(--line)}
.dr:last-child{border-bottom:0}
.dr .drt{font-size:13.5px;font-weight:500}
.dr .drm{font-size:12px;color:var(--tx-3);margin-top:2px}
.dr .sp{margin-left:auto}

/* forms / inputs */
label.fl{display:block;font-family:Inter,sans-serif;font-size:9.5px;letter-spacing:.07em;
  text-transform:uppercase;color:var(--tx-3);margin:16px 0 7px}
input.in,textarea.in{width:100%;background:var(--surf-2);border:1px solid var(--line);border-radius:var(--r-s);
  color:var(--tx);font:inherit;font-size:14px;padding:11px 13px;transition:border-color .2s,box-shadow .2s}
input.in::placeholder{color:var(--tx-3)}
input.in:focus,textarea.in:focus{outline:0;border-color:var(--ac-line);box-shadow:0 0 0 3px var(--ac-soft)}
input.in.mono{font-family:Inter,sans-serif;letter-spacing:.04em}
.check{display:flex;align-items:flex-start;gap:9px;margin-top:14px;font-size:13px;color:var(--tx-2);line-height:1.45}
.check input{width:16px;height:16px;margin-top:2px;flex:0 0 auto;accent-color:var(--ac)}
.linkbtn{color:var(--ac);font-weight:600;font-size:13px}
.linkbtn:hover{text-decoration:underline}

/* empty state — honest, no fake data */
.empty{padding:34px 22px;text-align:center;color:var(--tx-2)}
.empty .eh{font-size:15px;font-weight:600;color:var(--tx);margin-bottom:6px}
.empty .es{font-size:13px;color:var(--tx-3);max-width:360px;margin:0 auto}
.empty svg{width:34px;height:34px;stroke:var(--tx-3);fill:none;stroke-width:1.4;margin:0 auto 14px}

/* ── auth / claim gate (no shell) ───────────────────────── */
.gate{min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;
  padding:32px 18px calc(32px + env(safe-area-inset-bottom))}
.gate .glogo{display:flex;align-items:center;gap:11px;margin-bottom:26px}
.gate .glogo b{font-family:Montserrat,Inter,sans-serif;font-variation-settings:"wdth" 100,"wght" 800,"opsz" 20;
  font-size:19px;letter-spacing:-.05em}
.gate .glogo b i{font-style:normal;color:var(--ac)}
.gcard{width:100%;max-width:400px;background:var(--surf);border:1px solid var(--line);
  border-radius:var(--r);box-shadow:var(--sh);padding:26px 24px}
.gate h1{font-size:26px;font-variation-settings:"wdth" 94,"wght" 750,"opsz" 30;text-align:center}
.gate .gsub{color:var(--tx-2);font-size:13.5px;text-align:center;margin:8px auto 4px;max-width:360px}

/* "or" divider between the password form and QR sign-in */
.ordiv{display:flex;align-items:center;gap:12px;margin:16px 0 4px;color:var(--tx-3);font-size:12px}
.ordiv::before,.ordiv::after{content:"";flex:1;height:1px;background:var(--line)}

/* QR sign-in (web): the code + its states */
.qrbox{display:flex;align-items:center;justify-content:center;min-height:236px;
  background:#fff;border:1px solid var(--line);border-radius:var(--r-s);padding:8px}
.qrimg{width:220px;height:220px;image-rendering:pixelated;display:block}
.qrx{color:var(--tx-2);font-size:14px;font-weight:600}

/* camera overlay for scanning a desktop QR (native) */
.scanov{position:fixed;inset:0;z-index:300;background:#000;display:flex;flex-direction:column;
  align-items:center;justify-content:flex-end;gap:18px;
  padding:0 0 calc(40px + env(safe-area-inset-bottom))}
.scanov video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.scanmask{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}
.scanwin{width:64vw;max-width:280px;aspect-ratio:1;border:3px solid rgba(255,255,255,.9);
  border-radius:18px;box-shadow:0 0 0 100vmax rgba(0,0,0,.5)}
.scantip{position:relative;z-index:2;color:#fff;font-size:14px;font-weight:600;
  text-shadow:0 1px 3px rgba(0,0,0,.6);padding:0 24px;text-align:center}

/* toast */
.toast{position:fixed;left:50%;bottom:calc(24px + env(safe-area-inset-bottom));transform:translateX(-50%);
  z-index:200;background:var(--surf-3);border:1px solid var(--line-2);color:var(--tx);
  padding:12px 18px;border-radius:var(--r-s);box-shadow:var(--sh);font-size:13.5px;font-weight:500;
  max-width:90vw;text-align:center}
.toast.err{border-color:var(--bad-line);color:var(--bad)}
.toast.ok{border-color:var(--good-line);color:var(--good)}
.toast[hidden]{display:none}
@media(max-width:1000px){.toast{bottom:calc(80px + env(safe-area-inset-bottom))}}

/* loading shimmer */
.load{color:var(--tx-3);font-size:13px;padding:22px;text-align:center;font-family:Inter,sans-serif;letter-spacing:.06em}

/* ── legal pages (terms / privacy / aup) ── */
.topbar{display:flex;align-items:center;padding:14px 20px;border-bottom:1px solid var(--line);background:var(--surf);position:sticky;top:0;z-index:10}
.topbar .brand{padding:0}
.topbar .brand a{display:flex;align-items:center;gap:10px;font-family:Montserrat,Inter,sans-serif;
  font-variation-settings:"wdth" 100,"wght" 800,"opsz" 20;font-size:16px;letter-spacing:-.05em}
.muted{color:var(--tx-2)}
.legal{max-width:760px;margin:0 auto;padding:26px 18px 80px}
.legal h1{font-size:30px;font-variation-settings:"wdth" 94,"wght" 750,"opsz" 34}
.legal h2{margin-top:1.6em;font-size:17px}
.legal p,.legal li{color:var(--tx);font-size:14.5px;line-height:1.65}
.legal a{color:var(--ac)}
.legal a:hover{text-decoration:underline}

@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{animation-duration:.001ms !important;animation-iteration-count:1 !important;transition-duration:.001ms !important}
  .view{animation:none}
}

/* ── identity documents ───────────────────────────────────── */
.shots{display:grid;gap:10px;margin-top:14px}
.shotbox{display:flex;align-items:center;gap:14px;padding:16px;cursor:pointer;
  border:1.5px dashed var(--line-2);border-radius:var(--r);background:var(--surf-2);
  transition:border-color .18s,background .18s}
.shotbox:hover{border-color:var(--ac);background:var(--surf)}
.shotbox .t{font-weight:600;font-size:15px}
.shotbox .h{font-size:13px;color:var(--tx-2);margin-top:3px;line-height:1.45}
.shotbox .state{margin-left:auto;flex:0 0 auto;font-size:13px;font-weight:600;
  color:var(--ac);white-space:nowrap}
.shotbox.done{border-style:solid;border-color:var(--good);background:var(--good-soft)}
.shotbox.done .state{color:var(--good)}
.shotbox.busy{opacity:.65;pointer-events:none}

/* ── stepped verification ─────────────────────────────────── */
.vsteps{display:flex;gap:6px;margin-bottom:18px}
.vsteps i{flex:1;height:4px;border-radius:99px;background:var(--line-2);transition:background .35s}
.vsteps i.on{background:var(--ac)}
.vh{font-size:19px;margin:0 0 6px}
.vpick{display:grid;gap:10px;margin-top:14px}
.vopt{display:block;width:100%;text-align:left;padding:15px 16px;border-radius:var(--r);
  border:1.5px solid var(--line-2);background:var(--surf-2);cursor:pointer;
  transition:border-color .18s,background .18s}
.vopt:hover{border-color:var(--ac)}
.vopt.on{border-color:var(--ac);background:var(--ac-soft)}
.vopt b{display:block;font-size:15.5px;font-weight:600}
.vopt span{display:block;font-size:13px;color:var(--tx-2);margin-top:3px}
.vin{width:100%;font:inherit;font-size:15px;padding:12px 14px;border-radius:var(--r-s);
  border:1.5px solid var(--line-2);background:var(--surf);color:var(--tx);margin-top:14px}
.vin:focus{outline:none;border-color:var(--ac)}
.vlab{display:block;font-size:13px;font-weight:500;color:var(--tx-2);
  margin:14px 0 5px}
.vrow{display:flex;gap:12px;flex-wrap:wrap}
.vrow .vin{margin-top:0}
select.vin{appearance:none;cursor:pointer;
  background-image:var(--sel-arrow);
  background-repeat:no-repeat;background-position:right 14px center;padding-right:38px}
/* Named dl-* rather than a-*: the first version used .av, which is already the
   sidebar avatar. Its border-radius, background and centring landed on every
   value in this list, and its flex:1 stretched the avatar into an ellipse. */
.dl-row{display:flex;gap:20px;padding:13px 0;border-bottom:1px solid var(--line);
  font-size:15px;align-items:baseline}
.dl-row:last-child{border-bottom:0}
.dl-k{flex:0 0 9.5rem;color:var(--tx-2);font-size:14px}
.dl-v{flex:1;min-width:0;word-break:break-word;text-align:left;font-weight:500}
.dl-v .faint{font-weight:400}
@media(max-width:560px){
  .dl-row{flex-direction:column;gap:3px;padding:11px 0}
  .dl-k{flex:none;font-size:13px}
}

/* ── dashboard: connection hero ───────────────────────────── */
.hero .cb{display:flex;flex-direction:column;gap:16px}
.heroTop{display:flex;align-items:flex-start;gap:18px}
.heroState{font-size:33px;margin-top:7px}
.heroSub{color:var(--tx-2);font-size:13.5px;margin-top:7px}
.heroRate{margin-left:auto;text-align:right;flex:0 0 auto}
.heroNum{font-size:26px;margin-top:7px}
.heroNum s{text-decoration:none;font-size:13px;color:var(--tx-3);
  font-family:Inter,sans-serif;font-weight:400;letter-spacing:0}
.heroChips{display:flex;gap:8px;flex-wrap:wrap}
.heroFoot{display:flex;gap:26px;flex-wrap:wrap;
  padding-top:15px;border-top:1px solid var(--line)}
.heroVal{font-size:15px;font-weight:500;margin-top:5px}
@media(max-width:560px){
  .heroState{font-size:27px}
  .heroFoot{gap:16px}
  .heroFoot > div{flex:1 1 44%}
}

/* ── dashboard: ports ─────────────────────────────────────── */
/* Ports are LISTED, not lit. Nothing here encodes up/down, because the box
   does not report link state — see PORTS in app.js. If that ever changes,
   add a modifier class; do not repurpose these neutral styles. */
.pmap{display:flex;gap:7px;flex-wrap:wrap;margin-top:9px}
.pport{display:flex;flex-direction:column;gap:2px;padding:7px 10px;border-radius:var(--r-s);
  background:var(--surf-2);border:1px solid var(--line);min-width:60px}
.pport b{font-size:12px;font-weight:600}
.pport i{font-style:normal;font-size:10px;color:var(--tx-3)}
.pport.wan{border-color:var(--ac-line);background:var(--ac-soft)}
.pport.wan b{color:var(--ac-on)}
.pnote{font-size:12px;color:var(--tx-3);margin-top:10px;line-height:1.5}

/* ── dashboard: traffic + quota ───────────────────────────── */
.tstats{display:flex;gap:24px;flex-wrap:wrap;margin-bottom:15px}
.tnum{font-size:21px;margin-top:5px}
.chart{width:100%;overflow:hidden}
.chart svg{width:100%;height:190px;display:block}
.tax{display:flex;justify-content:space-between;margin-top:7px;
  font-size:10.5px;color:var(--tx-3);font-variant-numeric:tabular-nums}
.leg{display:flex;gap:18px;flex-wrap:wrap;margin-top:12px;font-size:12px;color:var(--tx-2)}
.leg div{display:flex;align-items:center;gap:7px}
.leg i{width:9px;height:9px;border-radius:3px;flex:0 0 auto}
.qbar{height:7px;border-radius:99px;background:var(--surf-3);overflow:hidden;margin-top:15px}
.qbar i{display:block;height:100%;border-radius:99px;background:var(--ac);
  transition:width 1s cubic-bezier(.2,.9,.25,1)}

/* A lit port is a measurement from the box; an unlit one in the fallback list
   is only a label. They must not look like the same claim, so the fallback
   uses neither .up nor .down. */
.pport.up{border-color:var(--good-line);background:var(--good-soft)}
.pport.up b{color:var(--good)}
.pport.down{opacity:.55}

/* ── console density: figures, availability strip, plot axis ─────────────
   Everything here exists to raise information-per-row. The dashboard read as
   a marketing page because four integers were taking a full screen row and a
   chart carried no axis. */
.kpi .klab{font-family:Inter,sans-serif;font-size:9.5px;font-weight:400;
  letter-spacing:.07em;text-transform:uppercase;color:var(--tx-3);
  line-height:1.3;min-width:0}
.figs{display:flex;gap:26px;flex-wrap:wrap;margin-bottom:16px}
.figs > div{min-width:0}
.figv{font-size:22px;line-height:1.1}
.figv s{text-decoration:none;font-size:12px;color:var(--tx-3);
  font-family:Inter,sans-serif;font-weight:400;letter-spacing:0}
.figl{font-family:Inter,sans-serif;font-size:9.5px;font-weight:400;letter-spacing:.07em;
  text-transform:uppercase;color:var(--tx-3);margin-top:6px}
@media(max-width:620px){.figs{gap:16px}.figs > div{flex:1 1 42%}.figv{font-size:19px}}

/* availability strip — one segment per bucket, coloured by real check-ins */
.strip{display:flex;gap:2px;height:26px}
.strip .seg{flex:1;border-radius:2px;background:var(--surf-3);transition:opacity .2s}
.strip .seg.g{background:var(--good)}
.strip .seg.w{background:var(--warn)}
.strip .seg.n{background:var(--surf-3)}
.strip .seg:hover{opacity:.75}

/* plot: y-axis gutter beside the chart */
.plot{display:flex;gap:9px}
.yax{position:relative;width:52px;flex:0 0 auto;height:168px}
.yax div{position:absolute;right:0;transform:translateY(-50%);
  font-size:10px;color:var(--tx-3);font-variant-numeric:tabular-nums;white-space:nowrap}
.plot .chart{flex:1;min-width:0}
.plot .chart svg{height:168px}
.tax-in{margin-left:61px}
@media(max-width:620px){.yax{width:42px}.tax-in{margin-left:51px}}
.upd{font-size:11.5px;color:var(--tx-3);font-variant-numeric:tabular-nums;white-space:nowrap}
.ksub{font-size:11.5px;color:var(--tx-3);margin-top:7px;line-height:1.4}

/* ── site-to-site ───────────────────────────────────────────────────────
   The mapping is the whole product here, so it is set as a statement the
   customer can apply themselves — same last number — rather than a lookup
   table they would have to keep. */
.steps{margin:14px 0 4px;padding-left:20px;color:var(--tx-2);font-size:13.5px;line-height:1.9}
.steps li::marker{color:var(--ac);font-weight:600}
.sites{display:grid;gap:12px;margin-top:16px}
.site{border:1px solid var(--line);border-radius:var(--r);padding:14px 15px;background:var(--surf-2)}
.site-h{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.site-name{flex:1;min-width:170px;font:inherit;font-size:14.5px;font-weight:600;
  color:var(--tx);background:transparent;border:0;border-bottom:1px dashed var(--line-2);
  padding:3px 2px;transition:border-color .18s}
.site-name:hover{border-bottom-color:var(--ac)}
.site-name:focus{outline:0;border-bottom-color:var(--ac);border-bottom-style:solid}
.site-name::placeholder{color:var(--tx-3);font-weight:400}
.site-map{display:flex;align-items:flex-end;gap:16px;margin-top:14px;flex-wrap:wrap}
.mapv{font-size:17px;font-weight:600;margin-top:5px}
.mapv.acc{color:var(--ac-on)}
.site-map .arrow{color:var(--tx-3);font-size:17px;padding-bottom:3px}
@media(max-width:560px){.site-map{gap:10px}.mapv{font-size:15px}}

/* ═══════════════════════════════════════════════════════════════════════
   NATIVE APP PHONE SCALE (mobile/app-src only — the web customer-app never
   loads this file).

   The base sheet is desktop-first and its 560/620/700/760px breakpoints do
   NOT all fire inside the Capacitor webview (measured: "Netherlands" renders
   at the desktop 22px .figv, not the 19px the 620px query would give). Result
   was a desktop layout at desktop type on a phone — oversized display numbers,
   rows wasting a whole screen, and the 5-tab bar overflowing off the edge.

   This block keys on (hover:none) — true on every phone regardless of the
   width the webview reports — so the phone scale always applies. It tightens
   type, raises density, and stops the nav overflow.
   ═══════════════════════════════════════════════════════════════════════ */
@media (hover:none), (max-width:900px){
  .body{padding:6px 11px 28px}
  .grid{gap:10px}
  .card{border-radius:13px}
  .ch{padding:11px 13px;flex-wrap:wrap;gap:8px}
  .ch h3{font-size:13px}
  .ch .sp{gap:6px;flex-wrap:wrap}
  .cb{padding:12px}
  .chip{font-size:10px;padding:2.5px 7px}
  .lab,.figl,.kpi .klab{font-size:8px;letter-spacing:.06em}

  /* Display numbers on a phone: SMALL and lighter. The base .num is Montserrat
     wght-800 wdth-90 — a heavy poster weight. On a 402px control screen it has
     to be a plain, compact figure. This drives the whole "too big" fix. */
  .num{font-variation-settings:"wdth" 100,"wght" 620,"opsz" 20 !important;letter-spacing:-.01em !important}

  /* Stat tiles 2-up, so the screen is DENSE (Firewalla) instead of one giant
     number per full-width card. Base sheet forces .c3 to span 12 on phones. */
  .kpi.c3{grid-column:span 6}
  .kpi{padding:10px 11px 11px}
  .kpi .k1{gap:6px}
  .kpi .v{font-size:17px;margin-top:4px}
  .kbox{width:20px;height:20px}.kbox svg{width:11px;height:11px}
  .ksub{font-size:10px;margin-top:5px;line-height:1.35}

  /* connection hero — four compact figures, two per row */
  .figs{gap:10px 12px;margin-bottom:10px}
  .figs > div{flex:1 1 42%}
  .figv{font-size:14px;font-variation-settings:"wdth" 100,"wght" 640,"opsz" 18 !important}
  .figl{font-size:8px;margin-top:2px}

  /* inline-styled big numbers (quota 574 MB, ring, billing) */
  #quotaCard .num[style]{font-size:18px !important}
  .ringwrap .mid .num[style]{font-size:17px !important}
  .ringwrap{width:96px;height:96px}
  .qbar{margin-top:12px}

  /* traffic — the graph is the hero, but compact */
  #trafficBody .plot,#trafficBody .chart,#trafficBody .chart svg{height:158px}
  #trafficBody .plot{min-height:158px}
  .yax{height:158px;width:44px}
  .tax-in{margin-left:53px}
  .tstats{gap:14px;margin-bottom:10px}
  .tnum,#trafficBody .tnum{font-size:16px}

  /* details / key-value rows — denser */
  .dl-row{padding:8px 0;gap:2px}
  .dl-v{font-size:13.5px}
  .dl-k{font-size:11.5px}
  .kv{padding:7px 0;font-size:12px}

  /* topbar + headings */
  /* Compact header: sit close under the Dynamic Island and drop the redundant
     "Network/Account" crumb line, so the cards start high instead of after a
     tall two-line header + gap. The safe-area inset (island clearance) is the
     only unavoidable top space. */
  .top{padding:calc(5px + env(safe-area-inset-top)) 14px 8px}
  .top h1{font-size:17px}
  .top .crumb{display:none}
  .heroState{font-size:18px}
  .heroNum{font-size:17px}

  /* bottom tab bar — never overflow, never clip a label */
  .nv{min-width:0;white-space:normal;font-size:10.5px;line-height:1.15;padding:6px 2px}
  .nv svg{width:26px;height:26px;stroke-width:1.7}

  /* phone hero sizes — keep the spacious iOS scale */
  .big{font-size:32px}
  .two .stat{font-size:29px}
}

/* ═══════════════════════════════════════════════════════════════════════
   NATIVE FIREWALLA-STYLE OVERVIEW  (approved mockup → real app)
   Full-width stacked cards, system font, charcoal dark. Data-viz colours are
   fixed hexes (red = via address, blue = via home, green = healthy) so the
   chart + legend read identically in light or dark.
   ═══════════════════════════════════════════════════════════════════════ */
.stack{display:flex;flex-direction:column;gap:16px}
.stack > .card > .cb{padding:18px}
/* iOS grouped-list section label above each card */
.slab{font-size:13px;font-weight:600;color:var(--tx-2);margin:4px 4px -8px}
.crow{display:flex;align-items:center;gap:10px;margin-bottom:16px;flex-wrap:wrap}
.crow .ct{font-size:16px;font-weight:600;color:var(--tx)}
.mut{color:var(--tx-2)} .sm{font-size:12.5px;margin-top:3px}

/* connection hero */
.hrow{display:flex;justify-content:space-between;align-items:center;gap:14px}
.big{font-size:33px;font-weight:700;letter-spacing:-.02em;margin-top:5px}
.big s{text-decoration:none;font-size:15px;font-weight:600;color:var(--tx-3);margin-left:4px}
.spark{flex:1;max-width:150px;height:44px;align-self:center}
.bars{display:flex;flex-direction:column;gap:14px;margin-top:18px}
.bar{height:8px;border-radius:6px;background:var(--surf-2);overflow:hidden}
.bar i{display:block;height:100%;border-radius:6px;transition:width .8s cubic-bezier(.2,.9,.25,1)}
.bar i.g{background:var(--good)}
.bar i.r{background:var(--bad)}
.bar i.n{background:var(--surf-2)}
.barlab{display:flex;justify-content:space-between;font-size:13px;color:var(--tx-2);margin-top:8px}

/* two-stat + ring */
.two{display:flex;align-items:center}
.two .col{flex:1;min-width:0}
.two .vln{width:1px;align-self:stretch;background:var(--line);margin:4px 18px}
.two .stat{font-size:30px;font-weight:700;margin-top:6px}
.ring2{width:62px;height:62px;flex:0 0 auto;margin-left:10px}

/* traffic line chart */
.linechart{width:100%;height:150px;display:block}
.axx{display:flex;justify-content:space-between;font-size:12px;color:var(--tx-3);margin-top:8px;font-variant-numeric:tabular-nums}
.lgd{display:flex;gap:16px;flex-wrap:wrap;align-items:center;margin-top:16px;padding-top:16px;border-top:1px solid var(--line);font-size:14px;color:var(--tx-2)}
.lgd b{color:var(--tx);font-weight:600}
.lgd i{display:inline-block;width:10px;height:10px;border-radius:3px;margin-right:7px;vertical-align:middle}

/* key-value rows */
.krow{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:14px 0;border-bottom:1px solid var(--line)}
.krow:last-child{border-bottom:0}
.krow .v{font-weight:600;text-align:right;min-width:0}

/* ── native bottom tab bar: flat, iOS/Firewalla-style ──────────────────────
   The active tab is a coloured icon+label, NOT the sidebar's boxed inset-border
   pill (which boxed one tab taller than its flat neighbours and read as broken).
   Higher specificity (.side .nv.on) so it beats the base sidebar rule regardless
   of source order. */
@media (max-width:1000px){
  .side .nv.on{background:none;box-shadow:none;color:var(--ac)}
  .side .nv.on svg{stroke:var(--ac)}
  .side .nv:hover{background:none;color:var(--tx-2)}
  .side .nv.on:hover{color:var(--ac)}
}

/* ── Advanced: Test my address + Port forwarding ─────────────────────────── */
.advip{font-size:24px;font-weight:700;margin-top:4px;word-break:break-all}
.advnote{font-size:13px;color:var(--tx-2);padding:0 6px;line-height:1.5}
.advempty{font-size:14px;color:var(--tx-2);padding:6px 2px 14px;line-height:1.5}
.btn.block{display:block;width:100%}

/* test result */
.advtest{margin-top:14px;border-radius:14px;padding:15px 16px;display:flex;gap:12px;align-items:flex-start;font-size:14px}
.advtest.wait{background:var(--surf-2);color:var(--tx-2);justify-content:center;text-align:center}
.advtest.ok{background:var(--good-soft);border:1px solid var(--good-line)}
.advtest.bad{background:var(--bad-soft);border:1px solid var(--bad-line)}
.advtest b{font-size:15px;color:var(--tx)}
.advtest p{font-size:13px;color:var(--tx-2);margin-top:3px}
.advtest .tick{width:26px;height:26px;border-radius:50%;flex:0 0 auto;display:flex;align-items:center;justify-content:center}
.advtest .tick.ok{background:var(--good)} .advtest .tick.bad{background:var(--bad)}
.advtest .tick svg{width:15px;height:15px;stroke:#fff;fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round}

/* port-forward rows */
.pf{display:flex;align-items:center;gap:14px;padding:15px 0;border-top:1px solid var(--line)}
.pf:first-child{border-top:0}
.pf .txt{flex:1;min-width:0}
.pf .nm{font-size:15px;font-weight:600}
.pf .rt{font-size:13px;color:var(--tx-2);margin-top:2px}
.pf .rt b{color:var(--tx);font-weight:600}
.sw{width:50px;height:30px;border-radius:20px;background:var(--good);flex:0 0 auto;position:relative;border:0;transition:background .2s}
.sw::after{content:"";position:absolute;top:2px;left:22px;width:26px;height:26px;border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.25);transition:left .2s}
.sw.off{background:var(--surf-3)} .sw.off::after{left:2px}
.pfadd{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:15px 0;
  color:var(--ac);font-size:15px;font-weight:600;border-top:1px solid var(--line);background:none}
.pfadd svg{width:20px;height:20px;stroke:var(--ac);fill:none;stroke-width:2.2;stroke-linecap:round}
.pfform .frow{display:flex;gap:12px}

/* ── icon + chevron list rows (Firewalla-style, tappable → detail) ──────────── */
.row{display:flex;align-items:center;gap:14px;width:100%;text-align:left;padding:14px 16px;
  border-top:1px solid var(--line);background:none;color:inherit}
.row:first-child{border-top:0}
.row:active{background:var(--surf-2)}
.row .ic{width:38px;height:38px;border-radius:11px;flex:0 0 auto;background:var(--ac-soft);
  display:flex;align-items:center;justify-content:center}
.row .ic svg{width:20px;height:20px;stroke:var(--ac);fill:none;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round}
.row .tx{flex:1;min-width:0}
.row .nm{font-size:15px;font-weight:600}
.row .sub{font-size:13px;color:var(--tx-2);margin-top:2px}
.row .chev{flex:0 0 auto;display:flex}
.row .chev svg{width:18px;height:18px;stroke:var(--tx-3);fill:none;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round}

/* ── Interfaces: box port strip + per-interface port map (Firewalla-style) ──── */
.panel{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;padding:2px 4px 2px}
.port{display:flex;flex-direction:column;align-items:center;gap:7px;flex:0 0 auto}
.jack{width:44px;height:38px;border-radius:8px;border:2px solid var(--line);background:var(--surf);position:relative}
.jack::after{content:"";position:absolute;left:8px;right:8px;top:8px;bottom:14px;border-radius:2px;background:var(--surf-3)}
.jack.wan{border-color:var(--ac);background:var(--ac-soft)} .jack.wan::after{background:var(--ac)}
.jack.up{border-color:var(--good);background:var(--good-soft)} .jack.up::after{background:var(--good)}
.plab{font-size:11px;font-weight:600;color:var(--tx-2)}
.pcap{text-align:center;font-size:12.5px;color:var(--tx-3);padding:12px 12px 2px;line-height:1.45}
.mini{display:flex;gap:4px;flex:0 0 auto}
.sq{width:16px;height:13px;border-radius:3px;border:1.5px solid var(--line)}
.sq.wan{border-color:var(--ac);background:var(--ac)} .sq.up{border-color:var(--good);background:var(--good)}
.idot{width:8px;height:8px;border-radius:50%;display:inline-block;margin-right:6px;vertical-align:middle}
