/* data.css: the Networks, Security and Sensors screens, and the charts they draw.
 *
 * Everything here rides on the tokens and shared classes in base.css
 * (ARCHITECTURE.md section 7): no new colors except the five signal grades
 * schema.js already names, so a network that reads "good" is the same green
 * on a bar, a sparkline, a history band and a dot on a plan.
 *
 * Selectors are plain classes, not scoped under a view: the detail sheet, the
 * radio picker and the one-time token sheet render into #sheet-root, which
 * sits outside every view. Names are prefixed (net-, nd-, chan-, hist-,
 * score-, sev-, sf- for a security finding, gauge-, src-, guide-, tok-,
 * bl-, data-) so nothing collides.
 *
 * ⚠ NEVER A BARE GENERIC NAME (.finding, .rec, .kv, .cmd): map.css styles
 * .finding unscoped for the heat map's diagnosis cards, and data.css loads
 * after it, so a shared name restyles the other screen without either
 * file looking wrong on its own.
 *
 * What it refuses to do:
 *   - color an SVG network by CSS: charts.js puts each SSID's hue in the fill
 *     and stroke attributes, and a CSS fill would override every one of them
 *   - use color as the only signal (bars light by count, findings carry a
 *     labelled badge, hidden names are italic and say "Hidden")
 *   - animate for someone who asked the system not to
 *
 * ⚠ iOS SAFARI: inputs under 16px zoom the page (base.css sets 16px; do not
 * shrink .net-search-input or .tok-input). position: sticky needs a
 * background or rows show through under the glass.
 */

/* ── Grades, shared with schema.js GRADES ─────────────────────────────── */
:root {
  --g-excellent: #34d399;
  --g-good: #a3e635;
  --g-fair: #fbbf24;
  --g-weak: #fb923c;
  --g-poor: #f87171;
  --sev-critical: var(--crit, #ff4d6d);
  --sev-high: var(--bad);
  --sev-medium: var(--warn);
  --sev-low: var(--info);
  --sev-info: var(--ink-3);
}

/* ── Small shared helpers ─────────────────────────────────────────────── */
.bad-text { color: #fca5a5; }
.warn-text { color: #fcd34d; }
.ok-text { color: #6ee7b7; }
.hidden-name { font-style: italic; color: var(--ink-2); }
.danger-link { color: #fca5a5; font-size: calc(13.5 * var(--fs)); align-self: flex-start; }
.field-hint.ok { color: #6ee7b7; }
.field-hint.bad { color: #fca5a5; }

/* Three views, one rhythm: the flex gap spaces cards, so base.css's
   card-after-card margin would double it. */
.networks-view .view-body > .card + .card,
.security-view .view-body > .card + .card,
.sensors-view .view-body > .card + .card,
.src-grid > .card + .card,
.findings .card + .card { margin-top: 0; }
.networks-view .view-body > .section-label,
.security-view .view-body > .section-label,
.sensors-view .view-body > .section-label { margin: 14px 4px 0; }

/* A banner that says, in words, that what follows is simulated. */
.demo-banner { align-items: center; padding: 8px 8px 8px 14px; animation: dataDrop 260ms var(--ease) both; }
.demo-banner > .ico { color: var(--warn); margin-top: 0; }
.demo-banner b { font-weight: 700; }
.demo-banner .btn { flex: none; color: #fde68a; border-color: rgba(251, 191, 36, .4); }

/* Rings that ripple out of a sensor icon: waiting for a radio to speak. */
.data-radar { position: relative; width: 64px; height: 64px; display: grid; place-items: center; flex: none; color: var(--blue-hi); }
.data-radar > i { position: absolute; inset: 0; border-radius: 50%; border: 2px solid rgba(41, 169, 242, .55); opacity: 0; animation: dataRipple 2.4s var(--ease) infinite; }
.data-radar > i:nth-child(2) { animation-delay: .8s; border-color: rgba(20, 199, 192, .5); }
.data-radar > i:nth-child(3) { animation-delay: 1.6s; border-color: rgba(255, 154, 46, .45); }
.data-radar > .ico { position: relative; width: 26px; height: 26px; filter: drop-shadow(0 0 10px rgba(1, 142, 220, .8)); }
.data-radar.big { width: 120px; height: 120px; margin: 4px auto 10px; }
.data-radar.big > .ico { width: 40px; height: 40px; stroke-width: 1.6; }
.data-radar.big::before { content: ''; position: absolute; inset: 22%; border-radius: 50%; background: radial-gradient(circle, rgba(1, 142, 220, .32), transparent 70%); }

/* A definition list as label / value rows (detail sheet, evidence). */
.nd-kv { margin: 0; display: flex; flex-direction: column; }
.nd-kv-row { display: grid; grid-template-columns: minmax(8.5em, 38%) minmax(0, 1fr); gap: 12px; padding: 9px 2px; border-bottom: 1px solid var(--line); align-items: baseline; }
.nd-kv-row:last-child { border-bottom: 0; }
.nd-kv-row dt { font-size: calc(12.5 * var(--fs)); color: var(--ink-3); }
.nd-kv-row dd { margin: 0; font-size: calc(14 * var(--fs)); color: var(--ink); overflow-wrap: anywhere; user-select: text; -webkit-user-select: text; }

/* ── Networks: header, controls, stats ────────────────────────────────── */
.net-status-row { margin-top: 6px; min-height: 22px; }
.net-status { margin: 0; font-size: calc(13.5 * var(--fs)); }
.net-status-row .dot-live { margin: 0 2px; }
/* ⚠ THE LIST IS THE SCREEN. Everything above the first row costs a row, so
   the two switches share one row at every width (on a phone as words only:
   Live | This floor, Networks | Channels), and on a phone the three numbers
   are one strip, not three tiles. */
.net-controls { display: grid; grid-template-columns: minmax(0, 1fr); gap: 8px; }
.net-controls:has(> .seg:not([hidden]) + .seg:not([hidden])) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.net-stats .stat-value { font-size: 23px; }
.net-stats .stat-hint { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.net-pane { display: flex; flex-direction: column; min-width: 0; }

/* ⚠ STICKY UNDER THE TOP BAR. The search and filters stay in reach while
   the list scrolls; the glass needs its own background or rows show
   through, and it bleeds to the view's gutters so nothing peeks at the
   sides. ⚠ ONLY THEY STAY: Sort is set once and then read past, so it sits
   first in the toolbar and the toolbar sticks 56 px higher (the sort row's
   46, drawn from 44 px options in a bordered segment, and the 9 px gap
   after it). Scrolled, Sort tucks under the top bar and its room goes to
   the rows; the search sits where it always did. */
.net-sortrow { order: -1; }
.net-toolbar {
  position: sticky; top: calc(var(--chrome-top) - 56px); z-index: 6;
  display: flex; flex-direction: column; gap: 9px;
  margin: 0 -16px; padding: 10px 16px 10px;
  background: linear-gradient(180deg, rgba(5, 11, 31, .94), rgba(5, 11, 31, .86));
  -webkit-backdrop-filter: saturate(150%) blur(var(--glass-blur)); backdrop-filter: saturate(150%) blur(var(--glass-blur));
  border-bottom: 1px solid var(--line);
}
.net-search { position: relative; display: block; }
.net-search > .ico { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); color: var(--ink-3); pointer-events: none; }
/* ⚠ base.css styles inputs with input:not(...)x4, specificity (0,4,1):
   this has to outrank it or the icon sits on top of the placeholder. */
.net-toolbar .net-search input[type="search"].net-search-input { padding-left: 44px; min-height: 46px; border-radius: 999px; }
.net-search-input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.net-filters { padding: 1px 0 2px; }
.net-filters .chip { min-height: 38px; }
.chip-count { min-width: 20px; padding: 1px 6px; border-radius: 999px; background: rgba(255, 255, 255, .08); color: var(--ink-3); font: 700 calc(11 * var(--fs))/1.4 var(--body); text-align: center; font-variant-numeric: tabular-nums; }
.chip.on .chip-count { background: rgba(255, 255, 255, .18); color: #fff; }
.net-sortrow { display: flex; align-items: center; gap: 10px; }
.net-sortrow .tiny { flex: none; text-transform: uppercase; letter-spacing: .12em; font: 700 calc(10.5 * var(--fs))/1 var(--head); }
.net-sortrow .seg { flex: 1; }
/* 44 px to the finger (ARCHITECTURE.md 2.7): each option reaches over the
   segment's 4 px padding, so the control is drawn 46 px tall and taps 44. */
.net-sortrow .seg-opt { min-height: 44px; margin: -4px 0; font-size: calc(12.5 * var(--fs)); }
.net-count { margin: 10px 4px 2px; }

/* ── Networks: the rows ───────────────────────────────────────────────── */
.net-list { margin-top: 2px; }
.net-row { min-height: 70px; padding: 10px 6px 10px 4px; gap: 12px; position: relative; border-radius: 12px; }
.net-row.enter { animation: dataRise 280ms var(--ease) both; }
.net-row:active { background: var(--glass); transform: scale(.995); }
.net-row.connected { background: linear-gradient(90deg, rgba(1, 142, 220, .13), rgba(1, 142, 220, 0) 72%); }
.net-row.connected::before { content: ''; position: absolute; left: -6px; top: 12px; bottom: 12px; width: 3px; border-radius: 3px; background: var(--blue-hi); box-shadow: 0 0 12px rgba(41, 169, 242, .8); }
.net-sim { padding: 2px 8px; font-size: calc(10.5 * var(--fs)); }

.net-bars { height: 22px; gap: 3px; width: 25px; }
.net-bars i { width: 4.5px; border-radius: 2px; transform-origin: bottom; transition: opacity 320ms var(--ease), transform 420ms var(--spring), background-color 320ms; }
.net-bars i:not(.lit) { transform: scaleY(.82); }

.net-l1, .net-l2, .net-l3 { display: flex; align-items: center; gap: 6px; min-width: 0; }
.net-l1 { gap: 7px; }
.net-ssid { font-size: calc(15.5 * var(--fs)); font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.net-mine { color: var(--blue-hi); display: inline-flex; flex: none; }
.net-conn { flex: none; padding: 1px 7px; }
.net-l2 { font-size: calc(12.5 * var(--fs)); color: var(--ink-3); }
.net-bssid { flex: none; color: var(--ink-2); font-size: calc(12 * var(--fs)); }
.net-vendor { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.net-l2 > * + *::before { content: '·'; margin-right: 6px; color: var(--ink-3); }
.net-l3 { margin-top: 3px; flex-wrap: wrap; gap: 5px; }
.net-band, .net-sec { padding: 2px 8px; font-size: calc(10.5 * var(--fs)); }
.net-band { font-variant-numeric: tabular-nums; }

.net-side { flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 4px; min-width: 64px; }
.net-sig { display: inline-flex; align-items: baseline; gap: 3px; }
.net-dbm { font: 700 19px/1 var(--head); letter-spacing: -.02em; display: inline-block; }
.net-dbm.tick { animation: dataTick 520ms var(--ease); }
.net-unit { font-size: 10.5px; color: var(--ink-3); font-weight: 600; }
.net-spark { display: block; height: 22px; width: 64px; color: var(--ink-3); }
.net-spark .spark { color: inherit; }
.net-spark[data-tone="ok"] { color: var(--ok); }
.net-spark[data-tone="warn"] { color: var(--warn); }
.net-spark[data-tone="bad"] { color: var(--bad); }
.spark-none { display: block; height: 1px; width: 100%; margin-top: 11px; background: repeating-linear-gradient(90deg, var(--line-2) 0 4px, transparent 4px 8px); }

.net-more { margin-top: 8px; }
.net-none { min-height: 0; }
.net-none-inner { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 26px 10px 10px; text-align: center; }
.net-none-inner p { margin: 0; }

/* ── Networks: nothing to show ────────────────────────────────────────── */
.net-explainer { text-align: center; padding: 22px 18px 18px; overflow: hidden;
  background: radial-gradient(120% 70% at 50% 0%, rgba(1, 142, 220, .16), transparent 70%), var(--glass); }
.net-explainer .h2 { margin-top: 2px; }
.net-explainer > p { margin: 0 auto; max-width: 38ch; font-size: calc(14.5 * var(--fs)); line-height: 1.55; }
.net-explainer-actions { max-width: 340px; margin: 18px auto 12px; }
.net-explainer > .tiny { margin-top: 2px; }
.quiet-card { display: flex; flex-direction: column; gap: 10px; }
.quiet-card p { margin: 0; }

/* ── Networks: channels ───────────────────────────────────────────────── */
.chan-card { padding: 14px 14px 12px; }
.chan-card .card-head { margin-bottom: 6px; }
.chan-chart { margin: 0 -6px; }
/* ⚠ 5 and 6 GHz span hundreds of MHz: squeezed to a phone's width the hills
   would be a few pixels tall. They scroll sideways instead, at a size you
   can read, with the snap that iOS users expect. */
.chan-scroll { overflow-x: auto; overscroll-behavior-x: contain; -webkit-overflow-scrolling: touch; scrollbar-width: thin; padding-bottom: 6px; }
.chan-scroll .chan-svg { min-width: 640px; }
.chan-legend { display: flex; flex-wrap: wrap; gap: 4px 14px; margin: 6px 2px 0; }
.chan-lg { display: inline-flex; align-items: center; gap: 6px; }
.chan-lg::before { content: ''; flex: none; }
.chan-lg-net::before { width: 18px; height: 9px; border-radius: 9px 9px 0 0; border: 1.5px solid var(--blue-hi); border-bottom: 0; background: rgba(41, 169, 242, .18); }
.chan-lg-load::before { width: 6px; height: 10px; border-radius: 2px; background: linear-gradient(180deg, var(--warn), var(--ok)); }
.chan-rec { display: flex; align-items: flex-start; gap: 12px; margin-top: 12px; padding: 12px; border-radius: var(--radius-sm);
  background: linear-gradient(135deg, rgba(52, 211, 153, .12), rgba(1, 142, 220, .08)); border: 1px solid rgba(52, 211, 153, .28); }
.chan-rec .li-icon { background: rgba(52, 211, 153, .16); color: #6ee7b7; }
.chan-rec p { margin: 0; }
.chan-rec-title { font: 700 calc(15.5 * var(--fs))/1.3 var(--head); margin: 2px 0 3px !important; }
/* One line per radio of yours (never one channel for the whole network). */
.chan-plan .chan-rec-title { display: flex; align-items: center; gap: 8px; }
.chan-plan .chan-rec-title .ico { flex: none; color: #6ee7b7; }
.chan-plan-list { list-style: none; margin: 6px 0 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.chan-plan-row { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.chan-plan-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; min-width: 0; }
.chan-plan-name { font-weight: 650; min-width: 0; overflow-wrap: anywhere; }
.chan-plan-row .small { line-height: 1.45; }
.chan-clash { display: flex; gap: 6px; align-items: flex-start; margin-top: 10px !important; color: var(--ink-2); }
.chan-clash .ico { flex: none; margin-top: 2px; color: var(--sev-medium); }
.chan-busy, .chan-mine { margin: 10px 2px 0; color: var(--ink-2); }
.chan-busy b, .chan-mine b { color: var(--ink); font-weight: 650; }
.chan-foot { text-align: center; margin: 2px 0 0; }

/* ── Charts: channel spectrum (SVG, viewBox units) ────────────────────── */
.chan-svg { display: block; width: 100%; height: auto; overflow: visible; font-family: var(--body); }
.chan-grid .grid-line { stroke: rgba(255, 255, 255, .06); stroke-width: 1; }
.chan-grid .grid-line.thr { stroke: rgba(41, 169, 242, .35); stroke-dasharray: 3 3; }
.chan-grid .grid-label { fill: var(--ink-3); font-size: 8px; font-variant-numeric: tabular-nums; }
/* The target line's label, tinted like the dashed line and the history chart's target label. */
.chan-grid .grid-label.thr-label { fill: #7dd3fc; font-weight: 700; }
.chan-axis .axis-line { stroke: var(--line-2); stroke-width: 1.2; }
.chan-axis .axis-break { stroke: var(--ink-3); stroke-width: 1.2; fill: none; }
.chan-axis .axis-tick { stroke: var(--line-2); }
.chan-axis .tick-label { fill: var(--ink-3); font-size: 9px; font-weight: 600; font-variant-numeric: tabular-nums; }
.chan-axis .tick-label.best { fill: #6ee7b7; font-weight: 800; }
.chan-load .load-bar { fill: rgba(255, 255, 255, .16); transition: height 300ms var(--ease), y 300ms var(--ease); }
.chan-load .load-low { fill: rgba(52, 211, 153, .55); }
.chan-load .load-mid { fill: rgba(251, 191, 36, .7); }
.chan-load .load-high { fill: rgba(248, 113, 113, .8); }
.chan-load .load-bar.best { stroke: #6ee7b7; stroke-width: 1; }
.chan-best .best-line { stroke: rgba(110, 231, 183, .55); stroke-width: 1.2; stroke-dasharray: 2 3; }
.chan-best .best-pill { fill: rgba(52, 211, 153, .2); stroke: rgba(110, 231, 183, .7); stroke-width: 1; }
.chan-best .best-text { fill: #a7f3d0; font-size: 8px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; }
/* fill and stroke come from the attributes (one hue per SSID). */
.chan-net { stroke-width: 1.3; stroke-opacity: .75; stroke-linejoin: round; transition: fill-opacity 240ms; }
.chan-net.hl { stroke-width: 2.4; stroke-opacity: 1; filter: drop-shadow(0 0 5px rgba(255, 255, 255, .25)); }
.chan-net.hidden-net { stroke-dasharray: 3 2.5; }
.chan-label { font-size: 9px; font-weight: 700; paint-order: stroke; stroke: rgba(5, 11, 31, .9); stroke-width: 2.6px; stroke-linejoin: round; }
.chan-label.hl { font-size: 10px; font-weight: 800; }
.chan-label.hidden-net { font-style: italic; }
.chan-empty { fill: var(--ink-3); font-size: 10px; }

/* ── Charts: signal history ───────────────────────────────────────────── */
/* ⚠ height: auto, never a fixed height: the SVG says preserveAspectRatio
   none (so its bands fill edge to edge), and a fixed height on a wide sheet
   would stretch every label sideways. Auto keeps the viewBox ratio. */
.hist-chart { display: block; width: 100%; height: auto; color: var(--blue-hi); font-family: var(--body); border-radius: var(--radius-xs); background: rgba(2, 6, 20, .45); border: 1px solid var(--line); }
.hband { opacity: .075; }
.hband-excellent { fill: var(--g-excellent); } .hband-good { fill: var(--g-good); } .hband-fair { fill: var(--g-fair); }
.hband-weak { fill: var(--g-weak); } .hband-poor { fill: var(--g-poor); }
.hband-label { fill: var(--ink-3); font-size: 7.5px; font-variant-numeric: tabular-nums; }
.hist-thr { stroke: rgba(41, 169, 242, .6); stroke-width: 1; stroke-dasharray: 4 3; vector-effect: non-scaling-stroke; }
.hist-thr-label { fill: #7dd3fc; font-size: 7.5px; font-weight: 700; }
.hist-line { fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.hist-area { stroke: none; }
.hist-point { fill: currentColor; }
.hist-halo { fill: currentColor; opacity: .22; animation: dataHalo 1.8s var(--ease) infinite; transform-box: fill-box; transform-origin: center; }
.hist-dot { stroke: #fff; stroke-width: 1.2; }
.hist-x { fill: var(--ink-3); font-size: 8px; }
.hist-empty { fill: var(--ink-3); font-size: 11px; }
.g-excellent { fill: var(--g-excellent); } .g-good { fill: var(--g-good); } .g-fair { fill: var(--g-fair); }
.g-weak { fill: var(--g-weak); } .g-poor { fill: var(--g-poor); } .g-none { fill: var(--ink-3); }

/* ── Charts: score ring ───────────────────────────────────────────────── */
.score-ring { --tone: var(--ok); display: block; width: 100%; height: auto; overflow: visible; font-family: var(--head); }
.score-ring.tone-ok { --tone: var(--ok); }
.score-ring.tone-good { --tone: var(--g-good); }
.score-ring.tone-warn { --tone: var(--warn); }
.score-ring.tone-orange { --tone: var(--g-weak); }
.score-ring.tone-bad { --tone: var(--sev-critical); }
.score-ticks line { stroke: rgba(255, 255, 255, .13); stroke-width: 1.2; stroke-linecap: round; }
.score-track { fill: none; stroke: rgba(255, 255, 255, .07); stroke-width: 12; }
/* The arc fills from empty on first paint (charts.js sets --len and --off),
   then glides between later scores. */
.score-arc {
  fill: none; stroke: var(--tone); stroke-width: 12; stroke-linecap: round;
  filter: drop-shadow(0 0 8px var(--tone));
  transition: stroke-dashoffset 800ms var(--ease), stroke 400ms;
  animation: dataRingFill 1100ms var(--ease) 120ms backwards;
}
.score-grade { fill: var(--tone); font-size: 52px; font-weight: 800; letter-spacing: -.03em; transition: fill 400ms; }
.score-num { fill: var(--ink-3); font-size: 12px; font-weight: 700; font-family: var(--body); font-variant-numeric: tabular-nums; letter-spacing: .02em; }

/* ── Charts: gauge ────────────────────────────────────────────────────── */
.gauge { --tone: var(--blue-hi); display: block; width: 100%; height: auto; overflow: visible; font-family: var(--head); }
.gauge.tone-ok { --tone: var(--ok); }
.gauge.tone-warn { --tone: var(--warn); }
.gauge.tone-bad { --tone: var(--bad); }
.gauge.tone-none { --tone: var(--ink-3); }
.gauge-track { fill: none; stroke: rgba(255, 255, 255, .08); stroke-linecap: round; }
.gauge-value { fill: none; stroke: var(--tone); stroke-linecap: round; filter: drop-shadow(0 0 6px var(--tone)); transition: stroke-dashoffset 900ms var(--ease), stroke 300ms; }
.gauge.tone-none .gauge-value { filter: none; }
.gauge-num { fill: var(--ink); font-size: 27px; font-weight: 800; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.gauge.tone-none .gauge-num { fill: var(--ink-3); font-size: 20px; }
.gauge-unit { fill: var(--ink-3); font-size: 11px; font-weight: 600; font-family: var(--body); }
.gauge-end { fill: var(--ink-3); font-size: 9px; font-family: var(--body); font-variant-numeric: tabular-nums; }
.gauge-label { fill: var(--ink-2); font-size: 12.5px; font-weight: 700; }

/* ── Charts: a floor at a glance ──────────────────────────────────────── */
.mini-plan { display: block; width: 100%; height: auto; margin-top: 8px; }
.mp-bg { fill: rgba(2, 6, 20, .55); stroke: var(--line); }
.mp-outline { fill: rgba(1, 142, 220, .07); stroke: rgba(41, 169, 242, .45); stroke-width: 1.2; stroke-linejoin: round; }
.mp-room { fill: rgba(255, 255, 255, .025); stroke: rgba(255, 255, 255, .1); stroke-width: .8; }
.mp-wall { stroke-width: 2; stroke-linecap: round; opacity: .8; }
.mp-pt { stroke: rgba(5, 11, 31, .8); stroke-width: .8; }
.mp-ap-ring { fill: rgba(255, 107, 61, .08); stroke: var(--hot); stroke-width: 1.2; stroke-dasharray: 4 3; }
.mp-ap-glow { fill: rgba(255, 107, 61, .35); animation: dataHalo 1.8s var(--ease) infinite; transform-box: fill-box; transform-origin: center; }
.mp-ap { fill: var(--hot); stroke: #fff; stroke-width: 1.6; }

/* ── Networks: the detail sheet ───────────────────────────────────────── */
.net-detail .h3 { margin-top: 6px; color: var(--ink-2); }
.net-detail .h3 .ico { color: var(--blue-hi); }
.nd-gone { font-size: calc(13 * var(--fs)); }
.nd-hero { display: flex; align-items: center; gap: 14px; padding: 14px; border-radius: var(--radius-sm);
  background: linear-gradient(135deg, rgba(1, 142, 220, .14), rgba(14, 36, 89, .35)); border: 1px solid rgba(41, 169, 242, .22); flex-wrap: wrap; }
.nd-bars { display: inline-flex; flex: none; }
.nd-hero .bars { height: 30px; gap: 4px; }
.nd-hero .bars i { width: 6px; border-radius: 2px; }
.nd-name { margin: 0; font: 700 calc(19 * var(--fs))/1.25 var(--head); letter-spacing: -.01em; overflow-wrap: anywhere; }
.nd-sub { margin: 3px 0 0; color: var(--ink-2); font-size: calc(12.5 * var(--fs)); }
.nd-badges { display: flex; flex-wrap: wrap; gap: 6px; width: 100%; }
.nd-hero .grow + .nd-badges { margin-top: 2px; }
.nd-stats .stat-value { font-size: 22px; }
.nd-hist { min-height: 132px; }
.nd-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.nd-actions:empty { display: none; }
.nd-loc { display: flex; flex-direction: column; gap: 8px; }
.nd-loc:empty { display: none; }
.nd-loc p { margin: 0; }

/* ── Security: score ──────────────────────────────────────────────────── */
.score-card { display: flex; align-items: center; gap: 18px; padding: 18px 16px; overflow: hidden;
  background: radial-gradient(90% 120% at 0% 50%, rgba(1, 142, 220, .14), transparent 65%), var(--glass); }
.score-dial { width: 136px; flex: none; }
.score-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.score-head { margin: 0; font: 800 calc(19 * var(--fs))/1.2 var(--head); letter-spacing: -.01em; }
.score-text > p { margin: 0; }
.sev-pills { display: flex; flex-wrap: wrap; gap: 12px 6px; margin-top: 4px; }
.sev-pill {
  --c: var(--ink-3);
  display: inline-flex; align-items: center; gap: 6px; min-height: 32px; padding: 0 10px 0 8px; border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--c) 40%, transparent); background: color-mix(in srgb, var(--c) 12%, transparent);
  font-size: calc(12 * var(--fs)); font-weight: 650; color: var(--ink-2); transition: transform 120ms var(--ease);
}
.sev-pill:active { transform: scale(.95); }
/* Drawn 32 px tall, tapped 44: the row gap is 12 px so two rows never share a finger. */
.sev-pill { position: relative; }
.sev-pill::after { content: ''; position: absolute; left: 0; right: 0; top: 50%; height: 44px; margin-top: -22px; }
.sev-pill .sev-n { min-width: 18px; height: 18px; display: inline-grid; place-items: center; border-radius: 999px; padding: 0 5px; background: var(--c); color: #0b1020; font: 800 11px/1 var(--head); }
.sev-pill.zero { opacity: .42; }
.sev-pill.sev-critical { --c: var(--sev-critical); }
.sev-pill.sev-high { --c: var(--sev-high); }
.sev-pill.sev-medium { --c: var(--sev-medium); }
.sev-pill.sev-low { --c: var(--sev-low); }
.sev-pill.sev-info { --c: var(--sev-info); }
.sev-pill.sev-critical:not(.zero) { box-shadow: 0 0 16px -6px var(--sev-critical); }

.nudge-card { display: flex; flex-direction: column; gap: 14px;
  background: linear-gradient(135deg, rgba(1, 142, 220, .16), rgba(14, 36, 89, .4)); border-color: rgba(41, 169, 242, .35); }
.nudge-card .row { align-items: flex-start; }
.nudge-card p { margin: 0; }
.all-clear { display: flex; align-items: center; gap: 12px; border-color: rgba(52, 211, 153, .3); background: rgba(52, 211, 153, .07); }
.all-clear .li-icon { background: rgba(52, 211, 153, .16); color: #6ee7b7; }
.all-clear p { margin: 0; }
.empty-alt { justify-content: center; margin-top: -10px; padding-bottom: 8px; }

/* ── Security: findings ───────────────────────────────────────────────── */
.findings { display: flex; flex-direction: column; gap: 12px; }
.sev-group { display: flex; flex-direction: column; gap: 8px; }
.sev-group > .section-label { margin: 10px 4px 2px; }
.sev-group > .section-label .num { min-width: 22px; padding: 2px 7px; border-radius: 999px; background: var(--glass-2); color: var(--ink-2); text-align: center; letter-spacing: 0; }
.sev-box { display: flex; flex-direction: column; gap: 8px; }
.sf {
  --c: var(--ink-3);
  position: relative; border-radius: var(--radius-sm); background: var(--glass); border: 1px solid var(--line);
  overflow: hidden; transition: border-color 200ms, background 200ms;
  animation: dataRise 260ms var(--ease) both;
}
.sf::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--c); opacity: .9; }
.sf.sev-critical { --c: var(--sev-critical); border-color: rgba(255, 77, 109, .38); background: linear-gradient(90deg, rgba(255, 77, 109, .1), var(--glass) 60%); }
.sf.sev-high { --c: var(--sev-high); }
.sf.sev-medium { --c: var(--sev-medium); }
.sf.sev-low { --c: var(--sev-low); }
.sf.sev-info { --c: var(--sev-info); }
.sf.open { border-color: var(--line-2); background: var(--glass-2); }
/* ⚠ THE TITLE GETS THE WIDTH. The stripe and the group heading already say
   the severity, so the badge (still a word, never color alone) rides small
   on the category line under the title instead of taking a column before
   it: sf-main's lines join the head's grid as title / badge + category /
   the "unconfirmed" note, with the chevron down the right. */
.sf-head {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas: "title title chev" "badge cat chev" "unc unc chev";
  align-items: center; column-gap: 8px; width: 100%; min-height: 60px; padding: 12px 12px 12px 15px; text-align: left;
}
.sf-head:active { background: var(--glass); }
.sf-head > .sf-main { display: contents; }
.sf-head .sf-title { grid-area: title; margin-bottom: 4px; }
.sf-head .sev-badge { grid-area: badge; justify-self: start; padding: 1px 7px 1px 5px; font-size: calc(9.5 * var(--fs)); }
.sf-head .sev-badge .ico { width: 11px; height: 11px; }
.sf-head .sf-cat { grid-area: cat; min-width: 0; }
.sf-head .sf-notes { grid-area: unc; display: flex; flex-wrap: wrap; column-gap: 10px; min-width: 0; }
.sf-notes > * { margin-top: 2px; }
.sf-single { color: var(--sev-medium); }
.health-group > .health-sub { margin: -6px 4px 2px; }
.sf-head > .sf-chev { grid-area: chev; margin-left: 4px; }
.sev-badge {
  flex: none; display: inline-flex; align-items: center; gap: 4px; padding: 3px 8px 3px 6px; border-radius: 999px;
  font: 800 calc(10.5 * var(--fs))/1.3 var(--head); letter-spacing: .04em; text-transform: uppercase; color: var(--c);
  background: color-mix(in srgb, var(--c) 14%, transparent); border: 1px solid color-mix(in srgb, var(--c) 38%, transparent);
}
.sev-badge .ico { width: 13px; height: 13px; stroke-width: 2.2; }
.sev-badge.sev-critical { --c: var(--sev-critical); } .sev-badge.sev-high { --c: var(--sev-high); }
.sev-badge.sev-medium { --c: var(--sev-medium); } .sev-badge.sev-low { --c: var(--sev-low); } .sev-badge.sev-info { --c: var(--sev-info); }
.sf-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.sf-title { font-size: calc(15 * var(--fs)); font-weight: 650; line-height: 1.3; overflow-wrap: anywhere; }
.sf-cat { font-size: calc(12 * var(--fs)); color: var(--ink-3); }
.sf-chev { flex: none; color: var(--ink-3); display: inline-flex; transition: transform 240ms var(--ease); }
.sf.open .sf-chev { transform: rotate(180deg); color: var(--ink-2); }
.sf-body { padding: 0 14px 14px 15px; display: flex; flex-direction: column; gap: 12px; animation: dataDrop 220ms var(--ease) both; }
.sf-detail { margin: 0; font-size: calc(14 * var(--fs)); color: var(--ink-2); line-height: 1.55; }
.sf-ev-box:empty { display: none; }
.sf-evidence { margin: 0; border-radius: var(--radius-xs); background: rgba(2, 6, 20, .5); border: 1px solid var(--line); padding: 2px 12px; }
.sf-ev-row { display: grid; grid-template-columns: minmax(7.5em, 36%) minmax(0, 1fr); gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--line); align-items: baseline; }
.sf-ev-row:last-child { border-bottom: 0; }
.sf-ev-row dt { font-size: calc(12 * var(--fs)); color: var(--ink-3); }
.sf-ev-row dd { margin: 0; font-size: calc(13.5 * var(--fs)); overflow-wrap: anywhere; user-select: text; -webkit-user-select: text; }
.sf-rec { display: flex; gap: 12px; align-items: flex-start; padding: 12px; border-radius: var(--radius-xs); background: rgba(1, 142, 220, .09); border: 1px solid rgba(41, 169, 242, .22); }
.sf-rec-icon { flex: none; width: 32px; height: 32px; border-radius: 10px; display: grid; place-items: center; background: rgba(1, 142, 220, .18); color: #7dd3fc; }
.sf-rec-icon .ico { width: 18px; height: 18px; }
.sf-rec-title { margin: 0 0 2px; font: 700 calc(12 * var(--fs))/1.2 var(--head); letter-spacing: .08em; text-transform: uppercase; color: #7dd3fc; }
.sf-rec-text { margin: 0; font-size: calc(14 * var(--fs)); line-height: 1.5; }
.sf-actions { display: flex; flex-wrap: wrap; gap: 8px; }

/* ── Security: your networks ──────────────────────────────────────────── */
.baseline-card { display: flex; flex-direction: column; gap: 10px; }
.baseline-card > .card-head { margin-bottom: 0; }
.bl-list .li { min-height: 60px; }
.bl-list > p { padding: 4px 2px; }
.bl-row .li-sub .mono { color: var(--ink-2); }
.bl-row .li-trail .badge { padding: 2px 7px; }
.bl-seen { display: inline-flex; align-items: center; }
.bl-sub { display: flex; align-items: center; gap: 8px; margin: 10px 0 0; font: 700 calc(13 * var(--fs))/1.2 var(--head); color: var(--ink-2); }
.bl-sub .ico { color: var(--blue-hi); }

/* The picker (a sheet): tick your radios. */
.bl-picker > p { margin: 0; }
.pick-group { display: flex; flex-direction: column; border-radius: var(--radius-sm); background: var(--glass); border: 1px solid var(--line); padding: 4px 12px 6px; }
.pick-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; min-height: 44px; }
.pick-name { font: 700 calc(15 * var(--fs))/1.25 var(--head); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.pick-group > .notice { margin: 2px 0 6px; padding: 8px 10px; }
.pick-row { display: flex; align-items: center; gap: 12px; min-height: 56px; padding: 6px 0; border-top: 1px solid var(--line); cursor: pointer; }
.pick-row.sus .li-title { color: #fca5a5; }
.pick-box {
  -webkit-appearance: none; appearance: none; flex: none; margin: 0; width: 24px; height: 24px; border-radius: 8px;
  border: 1.5px solid var(--line-2); background: rgba(2, 6, 20, .6); display: grid; place-items: center; cursor: pointer;
  transition: background 160ms, border-color 160ms, transform 120ms var(--ease);
}
.pick-box::after { content: ''; width: 6px; height: 11px; border: solid #fff; border-width: 0 2.4px 2.4px 0; transform: translateY(-1px) rotate(45deg) scale(0); transition: transform 180ms var(--spring); }
.pick-box:checked { background: linear-gradient(180deg, var(--blue-hi), var(--blue)); border-color: rgba(41, 169, 242, .7); }
.pick-box:checked::after { transform: translateY(-1px) rotate(45deg) scale(1); }
.pick-box:disabled { opacity: .55; cursor: default; }
.pick-box:active { transform: scale(.9); }
.pick-box:focus-visible { outline: 2px solid var(--blue-hi); outline-offset: 2px; }

/* ── Security: how each check works ───────────────────────────────────── */
.how-card > .card-head { margin-bottom: 4px; }
.how-list { display: flex; flex-direction: column; margin-top: 8px; }
.how-item { border-top: 1px solid var(--line); }
.how-item summary { list-style: none; display: flex; align-items: center; justify-content: space-between; gap: 10px; min-height: 48px; cursor: pointer; font-size: calc(14.5 * var(--fs)); font-weight: 600; }
.how-item summary::-webkit-details-marker { display: none; }
.how-item summary .ico { color: var(--ink-3); transition: transform 220ms var(--ease); }
.how-item[open] summary .ico { transform: rotate(180deg); }
.how-item p { margin: 0 0 12px; line-height: 1.55; }

/* ── Sensors: the status cards ────────────────────────────────────────── */
.src-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; }
.src-card { display: flex; flex-direction: column; gap: 12px; }
.src-card > .card-head { margin-bottom: 0; }
.src-card > p { margin: 0; }
.gauge-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px 14px; padding: 4px 6px 0; }
.probe-line { line-height: 1.5; }
.probe-result { min-height: 1.4em; text-align: center; }
.probing .gauge-value { animation: dataSweep 1.3s ease-in-out infinite; }
.probing .gauge-track { animation: dataBreathe 1.3s ease-in-out infinite; }
.src-state { margin: 0; font-size: calc(14 * var(--fs)); color: var(--ink-2); }
.src-stats .stat-value { font-size: 21px; white-space: nowrap; }
.src-stats .stat-hint { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dev-list:empty { display: none; }
.dev-list > p { margin: 0; }
.dev-row { min-height: 54px; }

/* ── Sensors: test connection ─────────────────────────────────────────── */
.test-card { display: flex; flex-direction: column; }
.test-card > .card-head { margin-bottom: 4px; }
.test-wait { display: flex; align-items: center; gap: 16px; padding: 10px 2px; }
.test-wait p { margin: 0; }
.test-feed { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.test-row { display: flex; align-items: center; gap: 12px; min-height: 58px; padding: 8px 2px; border-bottom: 1px solid var(--line); }
.test-row:last-child { border-bottom: 0; }
.test-row.fresh { animation: dataArrive 900ms var(--ease) both; }
.test-row .li-trail { font-variant-numeric: tabular-nums; }

/* ── Sensors: setup guides ────────────────────────────────────────────── */
.guide-card { display: flex; flex-direction: column; gap: 14px; }
.tok-field .row { align-items: stretch; }
.tok-field .tok-input { flex: 1; min-width: 0; letter-spacing: .01em; }
/* Masked while it sits on screen: a token is a password for your sensors. */
.tok-input.masked { -webkit-text-security: disc; }
.tok-field .btn-icon { min-height: 48px; width: 48px; flex: none; }
.guide-pick { gap: 8px; margin: 0 -16px; padding: 2px 16px 4px; scroll-padding-inline: 16px; }
.guide-tab {
  display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  min-width: 82px; min-height: 72px; padding: 10px 12px; border-radius: var(--radius-sm);
  border: 1px solid var(--line); background: var(--glass); color: var(--ink-2);
  font: 600 calc(12.5 * var(--fs))/1.1 var(--head); transition: background 180ms, border-color 180ms, color 180ms, transform 120ms var(--ease);
}
.guide-tab .ico { width: 24px; height: 24px; }
.guide-tab:active { transform: scale(.96); }
.guide-tab[aria-selected="true"] { color: #fff; border-color: rgba(41, 169, 242, .6); background: linear-gradient(180deg, rgba(1, 142, 220, .26), rgba(1, 142, 220, .1)); box-shadow: 0 8px 22px -12px rgba(1, 142, 220, .9); }
.guide-tab[aria-selected="true"] .ico { color: var(--blue-hi); }
.guide-body { display: flex; flex-direction: column; gap: 14px; animation: dataFade 220ms var(--ease) both; }
.guide-sees { display: flex; gap: 10px; align-items: flex-start; margin: 0; font-size: calc(14 * var(--fs)); color: var(--ink-2); line-height: 1.5; }
.guide-sees .ico { color: var(--blue-hi); margin-top: 2px; }
.guide-get p { margin: 0; }
.guide-get .guide-or { margin-top: 6px; color: var(--ink-2); }
.guide-steps { list-style: none; margin: 0; padding: 0; counter-reset: step; display: flex; flex-direction: column; }
.guide-steps .step { counter-increment: step; position: relative; padding: 0 0 16px 42px; display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.guide-steps .step::before {
  content: counter(step); position: absolute; left: 0; top: -2px; width: 28px; height: 28px; border-radius: 50%;
  display: grid; place-items: center; font: 800 13px/1 var(--head); color: #fff;
  background: var(--fill-blue); box-shadow: 0 0 0 4px rgba(1, 142, 220, .14);
}
.guide-steps .step::after { content: ''; position: absolute; left: 13.5px; top: 30px; bottom: 2px; width: 1px; background: linear-gradient(180deg, rgba(41, 169, 242, .5), var(--line)); }
.guide-steps .step:last-child { padding-bottom: 0; }
.guide-steps .step:last-child::after { display: none; }
.guide-steps .step > p { margin: 0; font-size: calc(14.5 * var(--fs)); line-height: 1.55; }
.guide-cmd { position: relative; display: flex; align-items: flex-start; gap: 6px; padding: 10px 6px 10px 12px; border-radius: var(--radius-xs); background: rgba(2, 6, 20, .72); border: 1px solid var(--line-2); min-width: 0; }
.guide-cmd code { flex: 1; min-width: 0; font-size: calc(12.5 * var(--fs)); line-height: 1.55; color: #cfe9fb; white-space: pre-wrap; overflow-wrap: anywhere; user-select: all; -webkit-user-select: all; padding-top: 6px; }
.guide-cmd-copy { flex: none; color: var(--ink-2); }
.script-box { display: flex; flex-direction: column; gap: 10px; padding: 12px; border-radius: var(--radius-sm); border: 1px dashed var(--line-2); background: rgba(255, 255, 255, .025); }
.script-head { display: flex; align-items: center; gap: 12px; min-width: 0; }
.script-status { margin: 0; }
.script-status.bad { color: #fca5a5; }
.script-box > .notice { font-size: calc(13 * var(--fs)); padding: 10px 12px; }

/* ── Sensors: tokens ──────────────────────────────────────────────────── */
.tok-card { display: flex; flex-direction: column; gap: 12px; }
.tok-card > .card-head { margin-bottom: 0; }
.tok-card > p { margin: 0; }
.tok-create { display: flex; align-items: flex-end; gap: 10px; }
.tok-create .btn { flex: none; min-height: 48px; }
.tok-list .li { min-height: 60px; }
.tok-list > p { margin: 0; }
.tok-row.revoked { opacity: .5; }
.tok-row.revoked .li-title { text-decoration: line-through; text-decoration-color: var(--ink-3); }
.tok-row .badge .dot-live { width: 6px; height: 6px; }
.tok-skel { height: 56px; margin: 4px 0; }
/* The one time a token is ever shown: big, selectable in one tap, glowing. */
.tok-secret {
  display: block; padding: 16px; border-radius: var(--radius-sm); text-align: center;
  font-size: calc(15 * var(--fs)); line-height: 1.55; letter-spacing: .02em; color: #e0f2fe; overflow-wrap: anywhere; word-break: break-all;
  background: rgba(2, 6, 20, .8); border: 1px solid rgba(41, 169, 242, .5);
  box-shadow: 0 0 0 4px rgba(1, 142, 220, .12), 0 0 32px -10px rgba(1, 142, 220, .8);
  user-select: all; -webkit-user-select: all;
}

/* ── Sensors: more sources ────────────────────────────────────────────── */
.import-row { margin-top: 12px; }
.import-out:empty { display: none; }
.import-out { margin-top: 12px; }
.import-out p { margin: 0; }

/* ── Motion ───────────────────────────────────────────────────────────── */
@keyframes dataRise { from { opacity: 0; transform: translateY(8px); } }
@keyframes dataDrop { from { opacity: 0; transform: translateY(-6px); } }
@keyframes dataFade { from { opacity: 0; } }
@keyframes dataRipple { 0% { transform: scale(.35); opacity: .95; } 100% { transform: scale(1.15); opacity: 0; } }
@keyframes dataTick { 0% { transform: scale(1); } 35% { transform: scale(1.14); color: #fff; text-shadow: 0 0 12px rgba(41, 169, 242, .9); } 100% { transform: scale(1); } }
@keyframes dataHalo { 0%, 100% { transform: scale(.8); opacity: .32; } 50% { transform: scale(1.35); opacity: .08; } }
@keyframes dataRingFill { from { stroke-dashoffset: var(--len); } to { stroke-dashoffset: var(--off); } }
@keyframes dataSweep { 0%, 100% { opacity: .35; } 50% { opacity: 1; } }
@keyframes dataBreathe { 0%, 100% { stroke: rgba(255, 255, 255, .08); } 50% { stroke: rgba(41, 169, 242, .22); } }
@keyframes dataArrive { 0% { opacity: 0; transform: translateX(-10px); background: rgba(52, 211, 153, .18); } 40% { opacity: 1; transform: none; background: rgba(52, 211, 153, .14); } 100% { background: transparent; } }

/* ── Wider screens ────────────────────────────────────────────────────── */
@media (max-width: 519px) {
  .net-controls .seg-opt .ico { display: none; }
  .net-stats { gap: 0; background: var(--glass); border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden; }
  .net-stats .stat { background: transparent; border: 0; border-radius: 0; padding: 10px 12px 9px; }
  .net-stats .stat + .stat { border-left: 1px solid var(--line); }
  .net-stats .stat-value { font-size: 19px; }
  .net-stats .stat-label { margin-top: 5px; }
  .net-stats .stat-hint { display: none; }
}
@media (min-width: 520px) {
  .score-dial { width: 156px; }
  .score-head { font-size: calc(21 * var(--fs)); }
}
@media (min-width: 720px) {
  .net-toolbar { margin: 0 -8px; padding: 10px 8px; border-radius: 0 0 var(--radius-sm) var(--radius-sm); }
  .src-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .src-grid > .src-card:first-child { grid-row: span 2; }
  .gauge-grid { gap: 10px 22px; }
  .chan-scroll .chan-svg { min-width: 0; }
  .net-row { padding-left: 10px; padding-right: 10px; }
  .net-row.connected::before { left: 0; }
}
@media (max-width: 360px) {
  .net-side { min-width: 54px; }
  .net-spark { width: 54px; }
  .net-bssid { display: none; }
  .net-l2 > * + *::before { content: none; }
  .score-card { flex-direction: column; text-align: center; }
  .sev-pills { justify-content: center; }
  .score-dial { width: 148px; }
  .nd-kv-row, .sf-ev-row { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  .guide-tab { min-width: 72px; }
}

/* ⚠ Reduced motion: base.css shortens every animation, but a ring that
   ripples or a halo that breathes forever is still motion. They stop and
   settle at a visible frame instead. */
@media (prefers-reduced-motion: reduce) {
  .data-radar > i, .hist-halo, .mp-ap-glow, .probing .gauge-value, .probing .gauge-track { animation: none !important; }
  .data-radar > i { opacity: .28; transform: scale(.8); }
  .data-radar > i:nth-child(2) { transform: scale(.95); }
  .data-radar > i:nth-child(3) { transform: scale(1.1); }
  .net-bars i, .sf-chev, .gauge-value, .score-arc { transition: none; }
}
