/* base.css: the Wavemap design system and the app shell.
 *
 * Dark by design. A survey happens in server rooms, ceiling voids and
 * corridors at night, and a heat map's colors need a dark ground to read.
 * Palette: navy #0E2459 and blue #018EDC; Montserrat for headings (the
 * wordmark's face), the platform's own UI font for everything read quickly,
 * ui-monospace for BSSIDs and numbers.
 *
 * What lives here: the tokens (ARCHITECTURE.md section 7), every shared class
 * a view may use, and the shell builder D owns (boot, sign-in, lock, top bar,
 * tab bar, sheets, toasts, onboarding, home, settings). Map, survey and data
 * views style themselves in map.css, survey.css and data.css.
 *
 * What it refuses to do: rely on inline styles (the CSP has no
 * 'unsafe-inline'; JS sets custom properties through the CSSOM instead), or
 * animate anything for someone who asked the system for less motion.
 *
 * ⚠ iOS SAFARI: background-attachment: fixed is ignored there, so the page
 * glow lives on a fixed pseudo-element. 100vh is the LARGE viewport on iOS;
 * every full-height surface states 100vh first and 100dvh after it.
 */

/* ── Tokens ───────────────────────────────────────────────────────────── */
:root {
  --bg: #050b1f;
  --bg-2: #071233;
  --bg-3: #0a1840;
  --navy: #0e2459;
  --blue: #018edc;
  --blue-hi: #29a9f2;
  --ink: #f2f6fc;
  --ink-2: #b4c2d9;
  --ink-3: #7d8eab;
  --line: rgba(255, 255, 255, .09);
  --line-2: rgba(255, 255, 255, .16);
  --glass: rgba(255, 255, 255, .045);
  --glass-2: rgba(255, 255, 255, .08);
  --glass-blur: 18px;
  --ok: #34d399;
  --warn: #fbbf24;
  --bad: #f87171;
  --info: #60a5fa;
  --hot: #ff6b3d;
  --cold: #4f7cff;
  --crit: #ff4d6d;
  --radius: 20px;
  --radius-sm: 14px;
  --radius-xs: 10px;
  --head: 'Montserrat', system-ui, sans-serif;
  --body: -apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif;
  --mono: ui-monospace, 'SF Mono', Menlo, monospace;
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
  --topbar-h: 56px;
  --tabbar-h: 64px;
  --ease: cubic-bezier(.2, .8, .2, 1);
  --spring: cubic-bezier(.34, 1.4, .64, 1);
  /* Set by main.js from the real header height (banners change it). */
  --chrome-top: calc(var(--topbar-h) + var(--safe-t));
  --chrome-bottom: calc(var(--tabbar-h) + var(--safe-b));
  /* ⚠ THE BAR IS NOT ALL THAT FLOATS AT THE BOTTOM. The Survey button stands
     22 px over the tab bar with 6 px of rings, which --chrome-bottom leaves
     out: what a page keeps clear at its end, and when it scrolls a control
     into view (scroll-padding below), is the bar, the button and some air.
     It follows --chrome-bottom, which main.js shrinks when a view puts the
     bar away. */
  --nav-clear: calc(var(--chrome-bottom) + 36px);
  --thermal: linear-gradient(90deg, #2b3bd1, #1e9bf0, #14c7c0, #35d27a, #ffd23f, #ff9a2e, #ff4d2e);
  /* A blue fill that carries white text. The brand blue under white is 3.6:1
     and its highlight 2.6:1, short of the 4.5:1 a 15px label needs; these
     stops are 4.7:1 and 6.2:1. --blue stays for glows, borders and icons. */
  --fill-blue: linear-gradient(180deg, #0a78bd, #0565a0);
  /* Dynamic Type (see html below): exactly 1px at the default Text Size,
     more as the reader asks for more, never less than today, and capped so
     the fixed bars still hold. */
  --fs: clamp(1px, calc(1rem / 17), 1.5px);
  color-scheme: dark;
}

/* ── Reset ────────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
[hidden] { display: none !important; }
/* ⚠ DYNAMIC TYPE. A home-screen app has no Safari text zoom, so Larger Text
   (Settings, Accessibility, Display & Text Size) is the reader's only way to
   enlarge it, and px sizes ignore it. -apple-system-body sizes the root to
   that setting (17px at the default); a browser without it drops the
   declaration and keeps the 17px before it. Text people read is sized in
   var(--fs). The top and tab bars, the map's overlays and the big display
   numbers stay put, as a native app's bars do, and fields never go under
   16px (iOS zooms the page into a smaller one). */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; background: var(--bg); font-size: 17px; font: -apple-system-body; }
/* A control brought into view (a Tab to it, scrollIntoView) stops above the floating bar, never under it. */
html { scroll-padding-bottom: var(--nav-clear); }
html, body { margin: 0; min-height: 100%; }
body {
  background: var(--bg);
  color: var(--ink);
  font: calc(16 * var(--fs))/1.5 var(--body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overscroll-behavior-y: none;
  text-rendering: optimizeLegibility;
}
/* The page glow, fixed behind everything (iOS ignores background-attachment). */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(120% 55% at 50% -12%, rgba(1, 142, 220, .20), transparent 62%),
    radial-gradient(70% 45% at 105% 105%, rgba(14, 36, 89, .95), transparent 70%),
    radial-gradient(55% 35% at -10% 92%, rgba(255, 107, 61, .05), transparent 70%),
    var(--bg);
}
html.sheet-open, html.sheet-open body { overflow: hidden; }
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; border: 0; background: none; padding: 0; touch-action: manipulation; }
a { color: var(--blue-hi); text-decoration: none; touch-action: manipulation; }
img { display: block; max-width: 100%; }
svg { overflow: visible; }
::selection { background: rgba(41, 169, 242, .35); }
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--blue-hi); outline-offset: 2px; border-radius: 6px; }
/* Containers focused by script (dialogs, the view root) carry no ring. */
[tabindex="-1"]:focus, [tabindex="-1"]:focus-visible { outline: none; }
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
.noscript { padding: 40px 24px; text-align: center; color: var(--ink-2); }

/* ── Icons ────────────────────────────────────────────────────────────── */
.ico {
  width: 22px; height: 22px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round;
}
.ico-xs { width: 14px; height: 14px; }
.ico-sm { width: 18px; height: 18px; }
.ico-lg { width: 28px; height: 28px; stroke-width: 2; }
.ico-xl { width: 40px; height: 40px; stroke-width: 1.6; }

/* ── Type ─────────────────────────────────────────────────────────────── */
.eyebrow { font: 700 calc(11 * var(--fs))/1 var(--head); letter-spacing: .16em; text-transform: uppercase; color: var(--blue-hi); margin: 0 0 10px; }
.h1 { font: 800 28px/1.12 var(--head); letter-spacing: -.02em; margin: 0 0 8px; }
.h2 { font: 700 calc(20 * var(--fs))/1.2 var(--head); letter-spacing: -.01em; margin: 0 0 6px; }
.h3 { font: 600 calc(15.5 * var(--fs))/1.25 var(--head); margin: 0; display: flex; align-items: center; gap: 8px; }
.muted { color: var(--ink-2); }
.small { font-size: calc(14 * var(--fs)); }
.tiny { font-size: calc(12 * var(--fs)); line-height: 1.4; color: var(--ink-3); }
.mono { font-family: var(--mono); font-size: .92em; letter-spacing: 0; user-select: text; -webkit-user-select: text; }
.num { font-variant-numeric: tabular-nums; font-feature-settings: 'tnum' 1; }
.center { text-align: center; }
.kbd { display: inline-block; min-width: 22px; padding: 2px 7px; border-radius: 6px; border: 1px solid var(--line-2); border-bottom-width: 2px; background: var(--glass-2); font: 600 calc(12 * var(--fs))/1.4 var(--mono); color: var(--ink); text-align: center; }
.link-btn { color: var(--blue-hi); font-weight: 600; font-size: calc(14.5 * var(--fs)); min-height: 44px; padding: 0 4px; display: inline-flex; align-items: center; gap: 6px; }
.link-btn:active { opacity: .6; }
/* ⚠ 44 PX TO THE FINGER (ARCHITECTURE.md 2.7). A control drawn smaller than
   that (a link in a banner, a small or icon button, a pill in the top bar)
   keeps its look and gets an invisible box, centered on it, that reaches 44
   px; the tap lands on the control. Nothing here may clip it: overflow
   hidden on the control itself would. */
.link-btn, .btn-sm, .toast-action { position: relative; }
.link-btn::after, .btn-sm::after, .toast-action::after, .live-pill::after, .sensor-pill::after {
  content: ''; position: absolute; left: 0; right: 0; top: 50%; height: 44px; margin-top: -22px;
}
.btn-icon.btn-sm::after { left: 50%; right: auto; width: 44px; margin-left: -22px; }

/* ── Layout utilities ─────────────────────────────────────────────────── */
.row { display: flex; align-items: center; min-width: 0; }
.col { display: flex; flex-direction: column; min-width: 0; }
.gap-8 { gap: 8px; }
.gap-12 { gap: 12px; }
.gap-16 { gap: 16px; }
.grow { flex: 1; min-width: 0; }
.wrap { flex-wrap: wrap; }
.grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.scroll-x { display: flex; gap: 8px; overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x proximity; -webkit-overflow-scrolling: touch; scrollbar-width: none; padding-bottom: 2px; }
.scroll-x::-webkit-scrollbar { display: none; }
.scroll-x > * { scroll-snap-align: start; flex: none; }
/* ⚠ A ROW THAT SCROLLS SIDEWAYS SAYS SO. Its scrollbar is hidden, so the
   only hint that more lies past the edge is the edge itself: the end fades
   until the row is scrolled to it, and the start fades once it has moved.
   The row's own scroll position drives it (a scroll timeline), so a row
   that fits never fades and one scrolled to its end shows its last item
   whole. Without scroll timelines the row looks as it always did. */
@property --sx-start { syntax: '<length>'; inherits: false; initial-value: 0px; }
@property --sx-end { syntax: '<length>'; inherits: false; initial-value: 0px; }
@keyframes sxEdges { 0% { --sx-start: 0px; --sx-end: 28px; } 12% { --sx-start: 28px; } 88% { --sx-end: 28px; } 100% { --sx-start: 28px; --sx-end: 0px; } }
@supports (animation-timeline: scroll(self inline)) {
  .scroll-x {
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 var(--sx-start), #000 calc(100% - var(--sx-end)), transparent);
    mask-image: linear-gradient(90deg, transparent, #000 var(--sx-start), #000 calc(100% - var(--sx-end)), transparent);
    animation: sxEdges linear both; animation-timeline: scroll(self inline);
    scroll-padding-inline-end: 28px;
  }
}
/* In a gapped column the gap is the spacing; paragraph margins would double it. */
.col.gap-8 > p, .col.gap-12 > p, .col.gap-16 > p, .list > p, .view-body > p { margin: 0; }
.divider { height: 1px; background: var(--line); border: 0; margin: 14px 0; }
.sr-only { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
/* A product's name kept on one line ("F-Droid" once broke at its hyphen). */
.nobr { white-space: nowrap; }

/* ── Buttons: a press you can feel ────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  min-height: 46px; padding: 0 18px; border-radius: 999px;
  font: 600 calc(15 * var(--fs))/1.15 var(--head); letter-spacing: .005em; text-align: center; /* wraps, not spills, under Larger Text */
  user-select: none; -webkit-user-select: none;
  transition: transform 140ms var(--ease), background 200ms var(--ease), box-shadow 200ms var(--ease), opacity 200ms, filter 140ms;
}
.btn:active { transform: scale(.96); filter: brightness(1.08); transition-duration: 60ms; }
.btn[disabled], .btn[aria-disabled="true"] { opacity: .45; pointer-events: none; }
.btn .ico { width: 20px; height: 20px; }
.btn-primary {
  color: #fff;
  background: var(--fill-blue);
  box-shadow: 0 10px 28px -10px rgba(1, 142, 220, .85), inset 0 1px 0 rgba(255, 255, 255, .28), inset 0 -1px 0 rgba(0, 0, 0, .12);
}
.btn-primary:active { box-shadow: 0 4px 14px -6px rgba(1, 142, 220, .9), inset 0 1px 0 rgba(255, 255, 255, .2); }
.btn-soft { background: rgba(1, 142, 220, .14); color: #cfe9fb; border: 1px solid rgba(41, 169, 242, .32); }
.btn-ghost { background: transparent; color: var(--ink-2); border: 1px solid var(--line-2); }
.btn-ghost:active { background: var(--glass); }
.btn-danger { background: rgba(248, 113, 113, .14); color: #fecaca; border: 1px solid rgba(248, 113, 113, .36); }
.btn-sm { min-height: 36px; padding: 0 14px; font-size: calc(13.5 * var(--fs)); gap: 7px; }
.btn-sm .ico { width: 17px; height: 17px; }
.btn-lg { min-height: 52px; font-size: calc(15.5 * var(--fs)); padding: 0 22px; }
.btn-xl { min-height: 58px; font-size: calc(16.5 * var(--fs)); padding: 0 24px; }
.btn-block { display: flex; width: 100%; }
.btn-icon { width: 44px; min-height: 44px; padding: 0; border-radius: 14px; }
.btn-icon.btn-sm { width: 36px; min-height: 36px; border-radius: 11px; }
.btn.busy { pointer-events: none; }
.btn.busy > :not(.spinner) { opacity: .4; }
.icon-btn { width: 44px; height: 44px; display: inline-grid; place-items: center; border-radius: 14px; color: var(--ink-2); position: relative; transition: background 160ms, color 160ms, transform 120ms var(--ease); }
.icon-btn:active { background: var(--glass-2); color: #fff; transform: scale(.92); }
.icon-btn.on { color: var(--blue-hi); }

/* ── Chips and segmented controls ─────────────────────────────────────── */
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
.chip {
  display: inline-flex; align-items: center; gap: 7px; min-height: 36px; padding: 0 13px;
  border-radius: 999px; border: 1px solid var(--line-2); background: transparent; color: var(--ink-2);
  font-size: calc(13.5 * var(--fs)); font-weight: 600; white-space: nowrap;
  transition: background 180ms var(--ease), border-color 180ms, color 180ms, transform 120ms var(--ease);
}
.chip:active { transform: scale(.95); }
.chip .ico { width: 16px; height: 16px; }
.chip.on, .chip[aria-pressed="true"] { background: rgba(1, 142, 220, .18); border-color: rgba(41, 169, 242, .6); color: #e0f2fe; }
.chip-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ink-3); flex: none; }
.chip-dot.ok { background: var(--ok); box-shadow: 0 0 8px rgba(52, 211, 153, .7); }
.chip-dot.warn { background: var(--warn); }
.chip-dot.bad { background: var(--bad); }
.chip-dot.info { background: var(--info); }

.seg {
  --n: 2; --i: 0;
  position: relative; display: grid; grid-template-columns: repeat(var(--n), minmax(0, 1fr));
  padding: 4px; border-radius: 14px; background: rgba(2, 6, 20, .55); border: 1px solid var(--line);
  isolation: isolate;
}
.seg-thumb {
  position: absolute; z-index: 0; top: 4px; bottom: 4px; left: 4px;
  width: calc((100% - 8px) / var(--n));
  transform: translateX(calc(var(--i) * 100%));
  border-radius: 10px; background: linear-gradient(180deg, rgba(255, 255, 255, .14), rgba(255, 255, 255, .07));
  box-shadow: inset 0 0 0 1px var(--line-2), 0 6px 16px -8px rgba(0, 0, 0, .6);
  transition: transform 240ms var(--spring);
}
.seg-opt {
  position: relative; z-index: 1; min-height: 38px; padding: 0 6px; border-radius: 10px;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  font-size: calc(13.5 * var(--fs)); font-weight: 600; color: var(--ink-3); transition: color 180ms; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.seg-opt[aria-checked="true"] { color: #fff; }
.seg-opt .ico { width: 16px; height: 16px; }
/* The label is a flex item: it, not the button, carries the ellipsis. */
.seg-opt > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* ── Toggle: a native switch, so iOS 18 adds its own haptic ───────────── */
.toggle { display: flex; align-items: center; gap: 14px; min-height: 52px; padding: 6px 0; cursor: pointer; }
.toggle-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.toggle-label { font-size: calc(15.5 * var(--fs)); font-weight: 500; }
.toggle-hint { font-size: calc(12.5 * var(--fs)); color: var(--ink-3); line-height: 1.4; }
.switch {
  -webkit-appearance: none; appearance: none; flex: none; margin: 0;
  position: relative; width: 51px; height: 31px; border-radius: 999px;
  background: rgba(255, 255, 255, .14); border: 1px solid var(--line);
  transition: background 220ms var(--ease), border-color 220ms;
  cursor: pointer;
}
.switch::before {
  content: ''; position: absolute; top: 2px; left: 2px; width: 25px; height: 25px; border-radius: 50%;
  background: #fff; box-shadow: 0 3px 8px rgba(0, 0, 0, .35), 0 1px 1px rgba(0, 0, 0, .2);
  transition: transform 240ms var(--spring);
}
.switch:checked { background: linear-gradient(180deg, var(--blue-hi), var(--blue)); border-color: rgba(41, 169, 242, .6); }
.switch:checked::before { transform: translateX(20px); }
.switch:disabled { opacity: .45; }
.switch:focus-visible { outline: 2px solid var(--blue-hi); outline-offset: 3px; }

/* ── Fields ───────────────────────────────────────────────────────────── */
.field { display: flex; flex-direction: column; gap: 7px; min-width: 0; margin: 0; }
.field-label { font: 600 calc(12 * var(--fs))/1.2 var(--head); letter-spacing: .04em; color: var(--ink-3); display: flex; align-items: center; gap: 8px; }
.field-hint { font-size: calc(12.5 * var(--fs)); color: var(--ink-3); line-height: 1.45; }
.field-row { display: flex; align-items: center; gap: 8px; }
.field-unit { color: var(--ink-3); font-size: calc(14 * var(--fs)); flex: none; }
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]), select, textarea {
  width: 100%; min-height: 48px; padding: 12px 14px; border-radius: 13px;
  background: rgba(2, 6, 20, .6); border: 1px solid var(--line-2); color: var(--ink);
  font-size: max(16px, calc(16 * var(--fs))); /* 16px: iOS zooms the page into any smaller field */
  transition: border-color 160ms, box-shadow 160ms, background 160ms;
  -webkit-appearance: none; appearance: none;
}
input::placeholder, textarea::placeholder { color: var(--ink-3); opacity: .8; }
/* The same list as the field rule above: a slider, a switch or a tick box
   tapped is not a text field, and never gets its box, fill and glow. */
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):focus, select:focus, textarea:focus { border-color: var(--blue-hi); box-shadow: 0 0 0 3px rgba(41, 169, 242, .22); background: rgba(2, 6, 20, .8); outline: none; }
select { background-image: linear-gradient(45deg, transparent 50%, var(--ink-3) 50%), linear-gradient(135deg, var(--ink-3) 50%, transparent 50%); background-position: calc(100% - 20px) 21px, calc(100% - 15px) 21px; background-size: 5px 5px; background-repeat: no-repeat; padding-right: 36px; }
textarea { min-height: 104px; resize: vertical; line-height: 1.45; }
input[type="range"] { width: 100%; accent-color: var(--blue); min-height: 32px; }
input[type="range"]:focus-visible { outline: 2px solid var(--blue-hi); outline-offset: 4px; border-radius: 999px; }
input[type="number"] { font-variant-numeric: tabular-nums; }

/* ── Cards ────────────────────────────────────────────────────────────── */
.card {
  position: relative; background: var(--glass); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 16px; box-shadow: inset 0 1px 0 rgba(255, 255, 255, .05);
}
.card + .card { margin-top: 12px; }
.card-glass {
  background: linear-gradient(180deg, rgba(255, 255, 255, .07), rgba(255, 255, 255, .03));
  -webkit-backdrop-filter: saturate(150%) blur(var(--glass-blur)); backdrop-filter: saturate(150%) blur(var(--glass-blur));
  border-color: var(--line-2);
}
.card-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 12px; min-width: 0; }
.card-title { font: 700 calc(16 * var(--fs))/1.25 var(--head); margin: 0; display: flex; align-items: center; gap: 9px; min-width: 0; }
.card-title .ico { color: var(--blue-hi); }
.card-sub { margin: 3px 0 0; font-size: calc(13.5 * var(--fs)); color: var(--ink-2); line-height: 1.45; }
.section-label { font: 700 calc(11 * var(--fs))/1 var(--head); letter-spacing: .14em; text-transform: uppercase; color: var(--ink-3); margin: 24px 4px 10px; display: flex; align-items: center; justify-content: space-between; }

/* ── Lists ────────────────────────────────────────────────────────────── */
.list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.list.list-gap { gap: 8px; }
.li {
  display: flex; align-items: center; gap: 12px; min-height: 56px; padding: 10px 2px;
  border-bottom: 1px solid var(--line); color: var(--ink); text-align: left; width: 100%;
  transition: background 160ms, transform 120ms var(--ease);
}
.list > .li:last-child, .list > li:last-child > .li { border-bottom: 0; }
button.li:active, a.li:active, .li.tappable:active { background: var(--glass); }
.li-icon { width: 38px; height: 38px; flex: none; display: grid; place-items: center; border-radius: 12px; background: var(--glass-2); color: var(--blue-hi); }
.li-icon .ico { width: 20px; height: 20px; }
.li-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.li-title { font-size: calc(15.5 * var(--fs)); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.li-sub { font-size: calc(13 * var(--fs)); color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.li-trail { flex: none; display: flex; align-items: center; gap: 8px; color: var(--ink-3); font-size: calc(13 * var(--fs)); }
.li-title.danger { color: #fca5a5; }
.li.current .li-icon { background: rgba(1, 142, 220, .2); color: #fff; box-shadow: inset 0 0 0 1px rgba(41, 169, 242, .5); }

/* ── Badges ───────────────────────────────────────────────────────────── */
.badge {
  display: inline-flex; align-items: center; gap: 5px; padding: 3px 9px; border-radius: 999px;
  font: 700 calc(11 * var(--fs))/1.35 var(--body); letter-spacing: .02em; white-space: nowrap;
  background: var(--glass-2); color: var(--ink-2); border: 1px solid var(--line);
}
.badge .ico { width: 12px; height: 12px; stroke-width: 2.2; }
.badge-ok { background: rgba(52, 211, 153, .12); color: #a7f3d0; border-color: rgba(52, 211, 153, .32); }
.badge-warn { background: rgba(251, 191, 36, .12); color: #fde68a; border-color: rgba(251, 191, 36, .32); }
.badge-bad { background: rgba(248, 113, 113, .13); color: #fecaca; border-color: rgba(248, 113, 113, .34); }
.badge-info { background: rgba(96, 165, 250, .13); color: #bfdbfe; border-color: rgba(96, 165, 250, .34); }
.badge-crit { background: rgba(255, 77, 109, .2); color: #ffe4ea; border-color: rgba(255, 77, 109, .55); box-shadow: 0 0 14px -4px rgba(255, 77, 109, .7); }

/* ── Stats ────────────────────────────────────────────────────────────── */
.stat { background: var(--glass); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 13px 12px 12px; min-width: 0; position: relative; overflow: hidden; }
.stat-value { font: 700 25px/1 var(--head); letter-spacing: -.02em; font-variant-numeric: tabular-nums; display: inline-block; }
.stat-unit { margin-left: 3px; font: 600 12.5px/1 var(--body); color: var(--ink-3); }
.stat-label { margin-top: 7px; font-size: calc(12 * var(--fs)); color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stat-hint { margin-top: 4px; font-size: calc(11.5 * var(--fs)); color: var(--ink-3); }
.stat[data-tone="ok"] .stat-value { color: #6ee7b7; }
.stat[data-tone="warn"] .stat-value { color: #fcd34d; }
.stat[data-tone="bad"] .stat-value { color: #fca5a5; }
.stat[data-tone="info"] .stat-value { color: #93c5fd; }
.stat[data-tone]::after { content: ''; position: absolute; left: 12px; right: 12px; top: 0; height: 2px; border-radius: 0 0 2px 2px; background: currentColor; opacity: .0; }
.stat[data-tone="ok"]::after { color: var(--ok); opacity: .7; }
.stat[data-tone="warn"]::after { color: var(--warn); opacity: .7; }
.stat[data-tone="bad"]::after { color: var(--bad); opacity: .7; }
.stat[data-tone="info"]::after { color: var(--info); opacity: .7; }

/* ── Signal bars ──────────────────────────────────────────────────────── */
.bars { display: inline-flex; align-items: flex-end; gap: 2px; height: 16px; flex: none; color: var(--ink-3); }
.bars i { display: block; width: 4px; border-radius: 1.5px; background: currentColor; opacity: .22; }
.bars i:nth-child(1) { height: 25%; } .bars i:nth-child(2) { height: 50%; } .bars i:nth-child(3) { height: 75%; } .bars i:nth-child(4) { height: 100%; }
.bars i.lit { opacity: 1; }
.bars-excellent { color: #34d399; } .bars-good { color: #a3e635; } .bars-fair { color: #fbbf24; } .bars-weak { color: #fb923c; } .bars-poor { color: #f87171; }

/* ── Live dot, spinner, ring, sparkline, skeleton ─────────────────────── */
.dot-live { position: relative; display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--hot); flex: none; }
.dot-live::after { content: ''; position: absolute; inset: -4px; border-radius: 50%; border: 2px solid var(--hot); opacity: 0; animation: livePulse 1.6s var(--ease) infinite; }
.dot-live.ok { background: var(--ok); } .dot-live.ok::after { border-color: var(--ok); }
@keyframes livePulse { 0% { transform: scale(.6); opacity: .9; } 100% { transform: scale(1.8); opacity: 0; } }
.spinner { --s: 18px; width: var(--s); height: var(--s); border-radius: 50%; border: 2px solid rgba(255, 255, 255, .18); border-top-color: var(--blue-hi); animation: spin .8s linear infinite; flex: none; display: inline-block; }
@keyframes spin { to { transform: rotate(360deg); } }
.ring { display: inline-block; position: relative; flex: none; }
.ring svg { display: block; transform: rotate(-90deg); }
.ring-track { stroke: rgba(255, 255, 255, .1); fill: none; }
.ring-value { stroke: var(--blue-hi); fill: none; stroke-linecap: round; transition: stroke-dashoffset 600ms var(--ease); }
.ring[data-tone="ok"] .ring-value { stroke: var(--ok); } .ring[data-tone="warn"] .ring-value { stroke: var(--warn); } .ring[data-tone="bad"] .ring-value { stroke: var(--bad); }
.ring-label { position: absolute; inset: 0; display: grid; place-items: center; font: 700 13px/1 var(--head); font-variant-numeric: tabular-nums; }
.spark { display: block; overflow: visible; color: var(--blue-hi); }
.spark-line { fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linejoin: round; stroke-linecap: round; }
.spark-area { fill: currentColor; opacity: .12; stroke: none; }
.skeleton { position: relative; overflow: hidden; background: rgba(255, 255, 255, .06); border-radius: 10px; color: transparent !important; }
.skeleton::after { content: ''; position: absolute; inset: 0; transform: translateX(-100%); background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .09), transparent); animation: shimmer 1.3s ease-in-out infinite; }
@keyframes shimmer { to { transform: translateX(100%); } }

/* ── Empty states and notices ─────────────────────────────────────────── */
.empty { text-align: center; padding: 36px 20px 28px; display: flex; flex-direction: column; align-items: center; }
.empty-icon {
  width: 72px; height: 72px; border-radius: 24px; display: grid; place-items: center; margin-bottom: 16px;
  color: var(--blue-hi); background: radial-gradient(circle at 50% 30%, rgba(41, 169, 242, .28), rgba(1, 142, 220, .08));
  border: 1px solid rgba(41, 169, 242, .3); box-shadow: 0 18px 40px -18px rgba(1, 142, 220, .8);
}
.empty-icon .ico { width: 32px; height: 32px; stroke-width: 1.7; }
.empty-title { font: 700 calc(18 * var(--fs))/1.25 var(--head); margin: 0 0 6px; }
.empty-body { color: var(--ink-2); margin: 0 auto; max-width: 34ch; font-size: calc(14.5 * var(--fs)); line-height: 1.5; }
.empty .btn { margin-top: 18px; }
.notice { border-radius: var(--radius-sm); padding: 12px 14px; font-size: calc(14 * var(--fs)); line-height: 1.45; margin: 0; display: flex; gap: 10px; align-items: flex-start; }
.notice .ico { width: 18px; height: 18px; margin-top: 1px; }
.notice-bad { background: rgba(248, 113, 113, .12); border: 1px solid rgba(248, 113, 113, .3); color: #fecaca; }
.notice-info { background: rgba(1, 142, 220, .12); border: 1px solid rgba(41, 169, 242, .3); color: #cfe9fb; }
.notice-warn { background: rgba(251, 191, 36, .1); border: 1px solid rgba(251, 191, 36, .3); color: #fde68a; }
.notice-ok { background: rgba(52, 211, 153, .1); border: 1px solid rgba(52, 211, 153, .3); color: #a7f3d0; }

/* ── Boot ─────────────────────────────────────────────────────────────── */
.boot { position: fixed; inset: 0; z-index: 100; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; background: var(--bg); transition: opacity 360ms var(--ease), visibility 0s linear 360ms; }
.boot.gone { opacity: 0; visibility: hidden; }
.boot-mark { position: relative; width: 132px; height: 132px; display: grid; place-items: center; margin-bottom: 18px; }
.boot-emblem { width: 72px; position: relative; filter: drop-shadow(0 0 22px rgba(1, 142, 220, .6)); animation: breathe 2s ease-in-out infinite; }
.boot-ring { position: absolute; inset: 0; border-radius: 50%; border: 2px solid rgba(41, 169, 242, .5); opacity: 0; animation: ripple 2.4s var(--ease) infinite; }
.boot-ring:nth-child(2) { animation-delay: .8s; border-color: rgba(255, 154, 46, .45); }
.boot-ring:nth-child(3) { animation-delay: 1.6s; border-color: rgba(20, 199, 192, .45); }
.boot-word { font: 800 24px/1 var(--head); letter-spacing: -.01em; margin: 0; }
.boot-sub { font: 600 11px/1 var(--head); letter-spacing: .18em; text-transform: uppercase; color: var(--ink-3); margin: 8px 0 0; }
@keyframes breathe { 50% { transform: scale(1.06); filter: drop-shadow(0 0 32px rgba(1, 142, 220, .85)); } }
@keyframes ripple { 0% { transform: scale(.45); opacity: .9; } 100% { transform: scale(1.25); opacity: 0; } }

/* ── Sign in ──────────────────────────────────────────────────────────── */
.signin, .lock {
  /* ⚠ clip, not hidden: overflow: hidden makes the screen a scroll container,
     which a browser scrolls to bring a tapped button into view (shown again in
     the same page it sat 24 px left). clip cuts the ripples and never scrolls;
     hidden stays first for a browser without clip. */
  position: relative; min-height: 100vh; min-height: 100dvh; overflow: hidden; overflow: clip;
  display: flex; flex-direction: column; justify-content: space-between; gap: 28px;
  padding: calc(var(--safe-t) + 44px) calc(var(--safe-r) + 24px) calc(var(--safe-b) + 22px) calc(var(--safe-l) + 24px);
}
.signin > *, .lock > * { position: relative; z-index: 1; width: 100%; max-width: 480px; margin-left: auto; margin-right: auto; }
.signin > .aurora, .lock > .aurora, .signin > .ripples { position: absolute; max-width: none; z-index: 0; }
.aurora { inset: -20% -30% auto; height: 80%; filter: blur(48px); opacity: .9; pointer-events: none; }
.aurora i { position: absolute; border-radius: 50%; mix-blend-mode: screen; animation: drift 14s ease-in-out infinite alternate; }
.aurora i:nth-child(1) { width: 60%; height: 60%; left: 8%; top: 12%; background: radial-gradient(circle, rgba(1, 142, 220, .55), transparent 65%); }
.aurora i:nth-child(2) { width: 46%; height: 50%; left: 46%; top: 4%; background: radial-gradient(circle, rgba(20, 199, 192, .35), transparent 65%); animation-duration: 17s; animation-delay: -4s; }
.aurora i:nth-child(3) { width: 40%; height: 42%; left: 30%; top: 38%; background: radial-gradient(circle, rgba(255, 154, 46, .22), transparent 65%); animation-duration: 19s; animation-delay: -9s; }
.aurora i:nth-child(4) { width: 34%; height: 36%; left: 58%; top: 34%; background: radial-gradient(circle, rgba(255, 77, 46, .16), transparent 65%); animation-duration: 23s; animation-delay: -2s; }
.aurora-calm i:nth-child(3), .aurora-calm i:nth-child(4) { opacity: .4; }
@keyframes drift { from { transform: translate3d(-6%, -3%, 0) scale(1); } to { transform: translate3d(7%, 5%, 0) scale(1.12); } }
.ripples { right: -120px; top: calc(var(--safe-t) + 60px); width: 340px; height: 340px; pointer-events: none; opacity: .5; }
.ripples i { position: absolute; inset: 0; border-radius: 50%; border: 1.5px solid rgba(41, 169, 242, .35); animation: ripple 5s var(--ease) infinite; opacity: 0; }
.ripples i:nth-child(2) { animation-delay: 1.25s; } .ripples i:nth-child(3) { animation-delay: 2.5s; } .ripples i:nth-child(4) { animation-delay: 3.75s; }
.signin-logo { display: flex; align-items: center; gap: 12px; margin-bottom: 56px; font: 800 26px/1 var(--head); letter-spacing: -.01em; color: var(--ink); }
.signin-logo img { width: 44px; height: 44px; filter: drop-shadow(0 0 18px rgba(255, 120, 60, .35)); }
.signin-title { font: 800 38px/1.06 var(--head); letter-spacing: -.028em; margin: 0 0 16px; text-wrap: balance; }
.signin-sub { color: var(--ink-2); font-size: calc(16.5 * var(--fs)); line-height: 1.55; margin: 0; max-width: 36ch; }
.signin-actions, .lock-actions { display: flex; flex-direction: column; gap: 12px; }
.signin-actions .btn-ghost { font-family: var(--body); font-weight: 500; }
.signin-code-open { align-self: center; }
.setup-form { display: flex; flex-direction: column; gap: 14px; animation: rise 260ms var(--ease) both; }
.setup-row { justify-content: flex-end; }
.setup-row .btn { flex: 1; }
.signin-foot { margin: 0; text-align: center; font-size: calc(12 * var(--fs)); color: var(--ink-3); letter-spacing: .02em; }

/* ── Lock and the app-switcher shield ─────────────────────────────────── */
.lock { position: fixed; inset: 0; z-index: 90; background: var(--bg); justify-content: center; }
.lock-body { text-align: center; display: flex; flex-direction: column; align-items: center; }
.lock-badge { width: 88px; height: 88px; border-radius: 30px; display: grid; place-items: center; margin-bottom: 26px; color: #fff; background: radial-gradient(circle at 50% 28%, var(--blue-hi), var(--navy) 78%); box-shadow: 0 0 0 8px rgba(1, 142, 220, .1), 0 24px 60px -18px rgba(1, 142, 220, .9); }
.lock-title { font: 800 34px/1.1 var(--head); letter-spacing: -.02em; margin: 0 0 10px; }
.lock-sub { color: var(--ink-2); margin: 0 0 8px; max-width: 32ch; }
.lock-actions { margin-top: 28px; }
.privacy-shield { position: fixed; inset: 0; z-index: 95; background: var(--bg); display: grid; place-items: center; }
.privacy-shield img { width: 72px; opacity: .85; }

/* ── The shell: top bar ───────────────────────────────────────────────── */
.app { min-height: 100vh; min-height: 100dvh; }
.topbar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 30;
  padding-top: var(--safe-t);
  background: linear-gradient(180deg, rgba(5, 11, 31, .9), rgba(5, 11, 31, .72));
  -webkit-backdrop-filter: saturate(170%) blur(var(--glass-blur)); backdrop-filter: saturate(170%) blur(var(--glass-blur));
  border-bottom: 1px solid var(--line);
}
.topbar-row { height: var(--topbar-h); display: flex; align-items: center; gap: 6px; padding: 0 calc(var(--safe-r) + 8px) 0 calc(var(--safe-l) + 10px); max-width: 1100px; margin: 0 auto; }
.place { flex: 1; min-width: 0; display: flex; align-items: center; gap: 10px; height: 44px; padding: 0 10px 0 4px; border-radius: 14px; text-align: left; transition: background 160ms, transform 120ms var(--ease); }
.place:active { background: var(--glass-2); transform: scale(.98); }
.place-mark { width: 34px; height: 34px; flex: none; display: grid; place-items: center; border-radius: 11px; color: #fff; background: linear-gradient(140deg, var(--blue), var(--navy)); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .22), 0 6px 16px -8px rgba(1, 142, 220, .9); }
.place-text { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.place-site { font: 600 10.5px/1.1 var(--head); letter-spacing: .12em; text-transform: uppercase; color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.place-floor { font: 700 15px/1.15 var(--head); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.place-chev { color: var(--ink-3); }
.tb-actions { display: flex; align-items: center; gap: 2px; flex: none; }
.live-pill, .sensor-pill {
  position: relative; display: inline-flex; align-items: center; gap: 7px; height: 32px; padding: 0 11px; border-radius: 999px;
  font: 700 12px/1 var(--head); letter-spacing: .02em; white-space: nowrap; max-width: 116px;
  transition: transform 120ms var(--ease), background 200ms;
}
.live-pill { color: #ffd9cc; background: rgba(255, 107, 61, .16); border: 1px solid rgba(255, 107, 61, .45); margin-right: 4px; }
.live-pill.paused { color: #fde68a; background: rgba(251, 191, 36, .12); border-color: rgba(251, 191, 36, .4); }
.live-pill.paused .dot-live { background: var(--warn); } .live-pill.paused .dot-live::after { animation: none; }
.sensor-pill { color: var(--ink-2); background: var(--glass); border: 1px solid var(--line); }
.sensor-pill:active, .live-pill:active { transform: scale(.95); }
.sensor-pill.current { box-shadow: inset 0 0 0 1px rgba(41, 169, 242, .7); color: #fff; }
#tb-sensor-label, #tb-live-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.pill-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ink-3); flex: none; transition: background 300ms, box-shadow 300ms; }
.sensor-pill.live { color: #c7f9e4; border-color: rgba(52, 211, 153, .4); background: rgba(52, 211, 153, .1); }
.sensor-pill.live .pill-dot { background: var(--ok); box-shadow: 0 0 10px rgba(52, 211, 153, .9); }
/* Heard from, but not lately: a ring, so stale differs from live by shape too. */
.sensor-pill.recent .pill-dot { background: transparent; box-shadow: inset 0 0 0 2px var(--warn); }
.sensor-pill.probe .pill-dot { background: var(--blue-hi); }
/* On the Survey screen the Live pill would only point at the screen itself:
   it is the way back from the other tabs, so it shows there and not here. */
.app:has(#tab-survey[aria-current="page"]) #tb-live { display: none; }
/* ⚠ THE PLACE KEEPS ITS NAME. With the Live pill showing, a phone has no
   room for two labelled pills and the site and floor ("DEM... / Lev...").
   The pulsing Survey button already says a survey is live, so the sensor
   pill gives up its word and keeps its dot (its aria-label still names the
   source), and still reaches 44 px both ways. */
@media (max-width: 430px) {
  .app:not(:has(#tab-survey[aria-current="page"])) .topbar-row:has(#tb-live:not([hidden])) #tb-sensor-label { display: none; }
  .app:not(:has(#tab-survey[aria-current="page"])) .topbar-row:has(#tb-live:not([hidden])) .sensor-pill { padding: 0 12px; }
  .app:not(:has(#tab-survey[aria-current="page"])) .topbar-row:has(#tb-live:not([hidden])) .sensor-pill::after { left: 50%; right: auto; width: 44px; margin-left: -22px; }
}
.sync-dot { position: absolute; right: 8px; bottom: 9px; width: 9px; height: 9px; border-radius: 50%; background: var(--ok); border: 2px solid var(--bg); transition: background 300ms; }
.sync-btn[data-state="syncing"] .sync-dot { background: var(--blue-hi); animation: blink 1s ease-in-out infinite; }
.sync-btn[data-state="offline"] .sync-dot { background: var(--ink-3); }
.sync-btn[data-state="error"] .sync-dot { background: var(--bad); }
.sync-btn[data-state="conflict"] .sync-dot { background: var(--warn); }
.sync-btn[data-state="pending"] .sync-dot { background: var(--warn); }
/* ⚠ COLOR IS NEVER THE ONLY SIGNAL (ARCHITECTURE.md 2.7). Green and red are
   one color to a red-green colorblind owner, and "waiting" and "a floor
   needs your decision" share amber. So the shape says it too: a solid dot is
   up to date (blinking while it syncs), a ring is waiting, "!" is paused by
   an error, "?" is a decision to make, and offline swaps the icon itself. */
.sync-btn[data-state="pending"] .sync-dot { background: var(--bg); border-color: var(--warn); box-shadow: 0 0 0 2px var(--bg); }
.sync-btn[data-state="error"] .sync-dot, .sync-btn[data-state="conflict"] .sync-dot {
  right: 4px; bottom: 5px; width: 15px; height: 15px; display: grid; place-items: center;
  font: 800 10px/1 var(--body); color: var(--bg);
}
.sync-btn[data-state="error"] .sync-dot::after { content: '!'; }
.sync-btn[data-state="conflict"] .sync-dot::after { content: '?'; }
@keyframes blink { 50% { opacity: .3; } }

.banners { display: flex; flex-direction: column; }
.banner { display: flex; align-items: center; gap: 10px; padding: 9px calc(var(--safe-r) + 16px) 9px calc(var(--safe-l) + 16px); font-size: calc(13.5 * var(--fs)); line-height: 1.35; border-top: 1px solid var(--line); animation: drop 240ms var(--ease) both; }
.banner > span { flex: 1; min-width: 0; }
.banner .link-btn { min-height: 32px; font-size: calc(13.5 * var(--fs)); }
.banner-warn { background: rgba(251, 191, 36, .1); color: #fde68a; }
.banner-info { background: rgba(1, 142, 220, .14); color: #cfe9fb; }

/* ── Views ────────────────────────────────────────────────────────────── */
/* Scrolled to its end, a page's last control sits above the bar and its raised Survey button (--nav-clear). */
.view-root { display: block; padding-top: var(--chrome-top); padding-bottom: calc(var(--nav-clear) + 12px); min-height: 100vh; min-height: 100dvh; outline: none; }
.view { display: block; max-width: 760px; margin: 0 auto; padding: 18px calc(var(--safe-r) + 16px) 8px calc(var(--safe-l) + 16px); }
.view-wide { max-width: 1100px; }
/* A view that owns the whole space between the bars (the map, the editor). */
.view-fill { position: fixed; left: 0; right: 0; top: var(--chrome-top); bottom: var(--chrome-bottom); max-width: none; padding: 0; overflow: hidden; }
.view-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; margin: 4px 0 16px; }
.view-title { font: 800 27px/1.1 var(--head); letter-spacing: -.02em; margin: 0; }
.view-sub { margin: 6px 0 0; color: var(--ink-2); font-size: calc(14.5 * var(--fs)); line-height: 1.5; }
.view-body { display: flex; flex-direction: column; gap: 12px; }
.view.entering { animation: viewIn 260ms var(--ease) both; }
.view.entering.from-left { animation-name: viewInLeft; }
.view.leaving { animation: viewOut 150ms ease-in both; pointer-events: none; }
@keyframes viewIn { from { opacity: 0; transform: translate3d(14px, 0, 0); } }
@keyframes viewInLeft { from { opacity: 0; transform: translate3d(-14px, 0, 0); } }
@keyframes viewOut { to { opacity: 0; } }
@keyframes rise { from { opacity: 0; transform: translateY(10px); } }
@keyframes drop { from { opacity: 0; transform: translateY(-8px); } }
.view-loading { display: grid; gap: 12px; padding-top: 6px; }
.view-loading .skeleton:nth-child(1) { height: 30px; width: 46%; }
.view-loading .skeleton:nth-child(2) { height: 180px; border-radius: var(--radius); }
.view-loading .skeleton:nth-child(3) { height: 64px; border-radius: var(--radius-sm); }
.view-error { margin-top: 24px; }
.view-error .row { justify-content: center; margin-top: 18px; }
.view-error .empty .btn { margin-top: 0; }

/* ── Tab bar with a raised Survey button ──────────────────────────────── */
.tabbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
  height: calc(var(--tabbar-h) + var(--safe-b)); padding: 0 calc(var(--safe-r) + 6px) var(--safe-b) calc(var(--safe-l) + 6px);
  display: grid; grid-template-columns: repeat(5, 1fr); align-items: stretch;
  background: linear-gradient(0deg, rgba(5, 11, 31, .96), rgba(5, 11, 31, .78));
  -webkit-backdrop-filter: saturate(170%) blur(var(--glass-blur)); backdrop-filter: saturate(170%) blur(var(--glass-blur));
  border-top: 1px solid var(--line);
  transition: transform 260ms var(--ease), visibility 0s;
}
/* Put away, it is gone for a Tab key too once it has slid off (visibility waits for the slide). */
.tabbar.away { transform: translateY(calc(100% + 32px)); visibility: hidden; transition: transform 260ms var(--ease), visibility 0s 260ms; }
/* All five labels share one baseline, 13 px off the bar's floor: the four
   icon tabs stack up from it, and so does Survey under its raised button. */
.tab {
  position: relative; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; gap: 4px; padding-bottom: 13px;
  color: var(--ink-3); font: 600 10.5px/1 var(--head); letter-spacing: .02em; min-width: 0;
  transition: color 200ms var(--ease), transform 120ms var(--ease);
}
.tab .ico { width: 24px; height: 24px; transition: transform 260ms var(--spring); }
.tab:active { transform: scale(.92); }
.tab[aria-current="page"] { color: #fff; }
.tab[aria-current="page"] .ico { color: var(--blue-hi); transform: translateY(-1px); }
.tab::before { content: ''; position: absolute; top: 0; left: 50%; width: 22px; height: 3px; margin-left: -11px; border-radius: 0 0 3px 3px; background: var(--blue-hi); box-shadow: 0 0 12px rgba(41, 169, 242, .9); transform: scaleX(0); transition: transform 260ms var(--spring); }
.tab[aria-current="page"]::before { transform: scaleX(1); }
.tab-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.tab-survey::before { display: none; }
/* 56 px from 22 px above the bar: its rings end above the shared label line. */
.tab-fab {
  position: absolute; top: -22px; left: 50%; width: 56px; height: 56px; margin-left: -28px; border-radius: 50%;
  display: grid; place-items: center; color: #fff;
  background: radial-gradient(circle at 50% 28%, #4cc0ff, var(--blue) 62%, #0168a8);
  box-shadow: 0 0 0 5px var(--bg), 0 0 0 6px rgba(41, 169, 242, .28), 0 16px 34px -10px rgba(1, 142, 220, .95), inset 0 1px 0 rgba(255, 255, 255, .35);
  transition: transform 180ms var(--spring), box-shadow 300ms, background 300ms;
}
.tab-fab .ico { color: #fff !important; transform: none !important; }
.tab-survey:active { transform: none; }
.tab-survey:active .tab-fab { transform: scale(.9); }
.tab-survey[aria-current="page"] { color: #fff; }
.tab-survey.live .tab-fab { background: radial-gradient(circle at 50% 28%, #ff9a6b, var(--hot) 60%, #d9381e); box-shadow: 0 0 0 5px var(--bg), 0 0 0 6px rgba(255, 107, 61, .45), 0 16px 34px -10px rgba(255, 107, 61, .9); }
.tab-survey.live .tab-fab::after { content: ''; position: absolute; inset: -6px; border-radius: 50%; border: 2px solid var(--hot); animation: livePulse 1.8s var(--ease) infinite; }
/* ⚠ ON THE SURVEY SCREEN THE BUTTON SETTLES INTO THE BAR. Raised there, it
   could only reopen the screen it is on, it sat over the HUD's "Sample
   here" (tapped every few metres), and its pulse took the taps meant for
   it. So on that screen it is a tab like the others (orange while a survey
   runs), the HUD owns the space above the bar, and the other tabs stay one
   tap away for a walker who wants the map or the networks mid-survey. */
.tab-survey[aria-current="page"]::before { display: block; }
.tab-survey[aria-current="page"] .tab-fab { position: static; width: 24px; height: 24px; margin: 0; background: none; box-shadow: none; }
.tab-survey[aria-current="page"] .tab-fab::after { content: none; }
.tab-survey[aria-current="page"] .tab-fab .ico { width: 24px; height: 24px; stroke-width: 1.8; color: var(--blue-hi) !important; }
.tab-survey.live[aria-current="page"] .tab-fab .ico { color: var(--hot) !important; }
html.kb-open .tabbar { transform: translateY(110%); }

/* ── Sheets ───────────────────────────────────────────────────────────── */
.sheet-layer { position: fixed; inset: 0; z-index: 60; display: flex; align-items: flex-end; justify-content: center; }
.sheet-scrim { position: absolute; inset: 0; background: rgba(2, 6, 18, .58); -webkit-backdrop-filter: blur(5px); backdrop-filter: blur(5px); animation: fadeIn 220ms var(--ease) both; }
.sheet {
  position: relative; width: 100%; max-width: 560px; max-height: 88vh; max-height: 88dvh;
  display: flex; flex-direction: column;
  border-radius: 28px 28px 0 0; border: 1px solid var(--line-2); border-bottom: 0;
  background: linear-gradient(180deg, #0d1d4c 0%, #081538 55%, #071233 100%);
  box-shadow: 0 -24px 70px rgba(0, 0, 0, .55), inset 0 1px 0 rgba(255, 255, 255, .08);
  animation: sheetUp 320ms var(--ease) both;
  will-change: transform;
  outline: none;
}
/* Tertiary text on the sheet's lighter navy, and on a card's glass over it,
   is 4.3:1 at the page's --ink-3; inside a sheet it is lifted to 5.1:1. */
.sheet { --ink-3: #8a9bb8; }
.sheet-tall { max-height: 94vh; max-height: 94dvh; height: 94vh; height: 94dvh; }
.sheet.dragging { transition: none !important; animation: none; }
.sheet.settling { transition: transform 260ms var(--ease); }
.sheet-layer.closing .sheet { animation: sheetDown 220ms ease-in both; }
.sheet-layer.closing .sheet-scrim { animation: fadeOut 220ms ease-in both; }
@keyframes sheetUp { from { transform: translateY(100%); } }
@keyframes sheetDown { to { transform: translateY(100%); } }
@keyframes fadeIn { from { opacity: 0; } }
@keyframes fadeOut { to { opacity: 0; } }
.sheet-top { flex: none; padding: 10px 20px 4px; touch-action: none; cursor: grab; user-select: none; -webkit-user-select: none; }
.sheet-grab { width: 40px; height: 5px; border-radius: 3px; background: rgba(255, 255, 255, .22); margin: 0 auto 12px; }
.sheet-head { display: flex; align-items: flex-start; gap: 12px; }
.sheet-head-text { flex: 1; min-width: 0; padding-top: 4px; }
.sheet-title { font: 700 calc(20 * var(--fs))/1.2 var(--head); letter-spacing: -.01em; margin: 0; }
.sheet-sub { margin: 5px 0 0; color: var(--ink-2); font-size: calc(14 * var(--fs)); line-height: 1.45; }
.sheet-close { margin: -2px -8px 0 0; }
/* A group heading in a sheet is a .section-label, as on every screen; a
   sheet's body is a gapped column, so it drops the page's 24 px above. */
.sheet .section-label { margin: 6px 2px 0; }
.sheet-body { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; padding: 12px 20px 8px; }
/* ⚠ A SHEET THAT SCROLLS SAYS SO AT BOTH ENDS. Content that has slid under
   the title gets an edge there, and content that runs on past the actions
   fades out over 18 px instead of being sliced mid-line. Both follow the
   body's own scroll position (a scroll timeline shared with the title), so
   a sheet that fits shows neither, and one scrolled to its end shows its
   last line whole. Without scroll timelines the sheet looks as it did. */
.sheet-top { border-bottom: 1px solid transparent; }
@keyframes sheetEdge { to { border-bottom-color: var(--line-2); } }
@keyframes sheetFade { 0% { --sx-end: 18px; } 92% { --sx-end: 18px; } 100% { --sx-end: 0px; } }
@supports (animation-timeline: scroll(self block)) {
  .sheet { timeline-scope: --sheet-body; }
  .sheet-body {
    scroll-timeline: --sheet-body block;
    -webkit-mask-image: linear-gradient(180deg, #000 calc(100% - var(--sx-end)), transparent);
    mask-image: linear-gradient(180deg, #000 calc(100% - var(--sx-end)), transparent);
    animation: sheetFade linear both; animation-timeline: --sheet-body;
  }
  .sheet-top { animation: sheetEdge linear both; animation-timeline: --sheet-body; animation-range: 0px 16px; }
}
.sheet-actions { flex: none; display: flex; flex-direction: column; gap: 10px; padding: 10px 20px calc(var(--safe-b) + 18px); }
.sheet-actions.two { display: grid; grid-template-columns: 1fr 1fr; }
.sheet-actions:empty { padding: 0 0 calc(var(--safe-b) + 14px); }
.menu-list { display: flex; flex-direction: column; }
.menu-item { display: flex; align-items: center; gap: 14px; min-height: 54px; padding: 0 4px; border-bottom: 1px solid var(--line); font-size: calc(15.5 * var(--fs)); font-weight: 500; text-align: left; width: 100%; transition: background 140ms; border-radius: 10px; }
.menu-item:last-child { border-bottom: 0; }
.menu-item:active { background: var(--glass); }
.menu-item .ico { color: var(--blue-hi); }
.menu-item.danger, .menu-item.danger .ico { color: #fca5a5; }
.menu-item .li-sub { display: block; font-weight: 400; }

/* ── Toasts ───────────────────────────────────────────────────────────── */
/* ⚠ OVER THE TOP BAR, NOT UNDER IT. Below the bar a toast lands on what
   every screen keeps at its top (a sheet's title and Close, the editor's
   Back, Undo and Add, the survey's notice, the map's controls), for up to 9
   s when it carries an Undo. Over the bar it hides only the place name and
   the pills, and a sheet (88% of the screen at most) starts below it. */
.toast-root { position: fixed; z-index: 80; left: 0; right: 0; top: calc(var(--safe-t) + 4px); display: flex; flex-direction: column; align-items: center; gap: 8px; pointer-events: none; padding: 0 16px; }
.toast {
  pointer-events: auto; max-width: min(520px, 100%); display: flex; align-items: center; gap: 10px;
  padding: 11px 12px 11px 14px; border-radius: 18px;
  background: rgba(12, 26, 66, .94); border: 1px solid var(--line-2);
  -webkit-backdrop-filter: blur(16px); backdrop-filter: blur(16px);
  box-shadow: 0 18px 44px -12px rgba(0, 0, 0, .6);
  font-size: calc(14.5 * var(--fs)); font-weight: 600; line-height: 1.35;
  animation: toastIn 300ms var(--spring) both;
}
.toast.out { animation: toastOut 200ms ease-in both; }
.toast .ico { width: 20px; height: 20px; }
.toast-msg { flex: 1; min-width: 0; }
.toast-ok .ico { color: var(--ok); } .toast-warn .ico { color: var(--warn); } .toast-bad .ico { color: var(--bad); } .toast-info .ico { color: var(--blue-hi); }
.toast-bad { border-color: rgba(248, 113, 113, .4); }
.toast-action { flex: none; min-height: 34px; padding: 0 12px; border-radius: 999px; background: rgba(41, 169, 242, .18); color: #e0f2fe; font-size: calc(13.5 * var(--fs)); font-weight: 700; }
.toast-action:active { transform: scale(.95); }
@keyframes toastIn { from { opacity: 0; transform: translateY(-14px) scale(.96); } }
@keyframes toastOut { to { opacity: 0; transform: translateY(-10px) scale(.97); } }

/* ── Onboarding ───────────────────────────────────────────────────────── */
/* Under the sheets (60 is theirs, 55 the tour's): a sheet the tour opens (the
   email step's browser hint, "end the survey first?") or one sync raises
   during it (a conflict) shows over the tour, and components.js makes the
   tour inert beneath it. Over the bars; under the toasts and the lock. */
.onb { position: fixed; inset: 0; z-index: 55; display: flex; flex-direction: column; background: var(--bg); animation: fadeIn 300ms var(--ease) both; }
.onb.closing { animation: fadeOut 240ms ease-in both; }
.onb.inline { position: relative; inset: auto; z-index: auto; min-height: calc(100dvh - var(--chrome-top) - var(--chrome-bottom)); background: transparent; }
.onb-top { flex: none; display: flex; align-items: center; justify-content: space-between; padding: calc(var(--safe-t) + 10px) calc(var(--safe-r) + 12px) 0 calc(var(--safe-l) + 20px); }
.onb.inline .onb-top { padding-top: 10px; }
.onb-brand { display: flex; align-items: center; gap: 10px; font: 700 14px/1 var(--head); }
.onb-brand img { width: 26px; }
.onb-track { flex: 1; min-height: 0; display: flex; overflow-x: auto; overflow-y: hidden; scroll-snap-type: x mandatory; scrollbar-width: none; -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain; }
.onb-track::-webkit-scrollbar { display: none; }
.onb-slide { flex: 0 0 100%; scroll-snap-align: center; scroll-snap-stop: always; overflow-y: auto; padding: 8px calc(var(--safe-r) + 24px) 16px calc(var(--safe-l) + 24px); display: flex; flex-direction: column; }
/* A slide cut off at the bottom fades out, so it reads as one that scrolls. */
.onb-slide { -webkit-mask-image: linear-gradient(#000 calc(100% - 28px), transparent); mask-image: linear-gradient(#000 calc(100% - 28px), transparent); }
.onb-slide > * { width: 100%; max-width: 480px; margin-left: auto; margin-right: auto; }
.onb-art { position: relative; height: clamp(132px, 26vh, 230px); margin: 10px auto 22px; flex: none; }
.onb-title { font: 800 29px/1.1 var(--head); letter-spacing: -.02em; margin: 0 0 12px; text-wrap: balance; }
.onb-text { color: var(--ink-2); font-size: calc(16 * var(--fs)); line-height: 1.55; margin: 0 0 14px; }
.onb-points { list-style: none; padding: 0; margin: 8px auto 0; display: flex; flex-direction: column; gap: 10px; }
.onb-points li { display: flex; align-items: center; gap: 12px; font-size: calc(15 * var(--fs)); font-weight: 500; }
.onb-points .li-icon { width: 36px; height: 36px; }
.onb-foot { flex: none; padding: 12px calc(var(--safe-r) + 20px) calc(var(--safe-b) + 18px) calc(var(--safe-l) + 20px); display: flex; align-items: center; gap: 12px; max-width: 520px; width: 100%; margin: 0 auto; }
.onb-dots { flex: 1; display: flex; gap: 7px; justify-content: center; }
.onb-dot { width: 8px; height: 8px; border-radius: 999px; background: rgba(255, 255, 255, .22); transition: width 280ms var(--spring), background 280ms; min-width: 8px; min-height: 8px; }
.onb-dot[aria-current="step"] { width: 26px; background: var(--blue-hi); }
/* 44 px tall and out to the middle of each gap (Back, Next and a swipe do the same job). */
.onb-dot { position: relative; }
.onb-dot::after { content: ''; position: absolute; left: -3.5px; right: -3.5px; top: 50%; height: 44px; margin-top: -22px; }
.onb-foot .btn { min-width: 104px; }

/* Slide 1: a living heat map in miniature. */
.heat-art { border-radius: 28px; overflow: hidden; background: #0a1440; border: 1px solid var(--line-2); box-shadow: 0 30px 70px -30px rgba(1, 142, 220, .8), inset 0 1px 0 rgba(255, 255, 255, .08); }
.heat-art .blob { position: absolute; border-radius: 50%; filter: blur(22px); mix-blend-mode: screen; animation: drift 9s ease-in-out infinite alternate; }
.heat-art .b1 { width: 64%; height: 70%; left: -6%; top: 12%; background: radial-gradient(circle, rgba(255, 210, 166, .95), rgba(255, 77, 46, .85) 24%, rgba(255, 154, 46, .6) 44%, rgba(53, 210, 122, .4) 64%, transparent 75%); }
.heat-art .b2 { width: 52%; height: 60%; left: 52%; top: 30%; background: radial-gradient(circle, rgba(255, 210, 63, .85), rgba(53, 210, 122, .6) 40%, rgba(20, 199, 192, .4) 60%, transparent 75%); animation-delay: -3s; }
.heat-art .b3 { width: 46%; height: 44%; left: 30%; top: -10%; background: radial-gradient(circle, rgba(30, 155, 240, .7), rgba(43, 59, 209, .4) 55%, transparent 75%); animation-delay: -6s; }
.heat-art .walls { position: absolute; inset: 14px; border: 2px solid rgba(242, 246, 252, .55); border-radius: 6px; }
.heat-art .walls::before { content: ''; position: absolute; left: 44%; top: 0; bottom: 38%; border-left: 2px solid rgba(242, 246, 252, .55); }
.heat-art .walls::after { content: ''; position: absolute; left: 44%; right: 0; top: 50%; border-top: 2px solid rgba(242, 246, 252, .55); }
.heat-art .ap { position: absolute; width: 14px; height: 14px; border-radius: 50%; background: #fff; box-shadow: 0 0 0 4px rgba(255, 255, 255, .25), 0 0 18px #fff; }
.heat-art .ap.a1 { left: 22%; top: 42%; } .heat-art .ap.a2 { left: 72%; top: 58%; }
.heat-art .puck { position: absolute; left: 50%; top: 70%; width: 16px; height: 16px; border-radius: 50%; background: var(--blue-hi); border: 3px solid #fff; box-shadow: 0 0 0 10px rgba(41, 169, 242, .25); animation: walkPath 8s ease-in-out infinite; }
@keyframes walkPath { 0% { left: 12%; top: 76%; } 30% { left: 38%; top: 64%; } 55% { left: 62%; top: 30%; } 80% { left: 84%; top: 70%; } 100% { left: 12%; top: 76%; } }
/* Slide 2: the phone is the brain, sensors are the eyes. */
.sensor-art { display: grid; place-items: center; }
.sensor-art .hub { width: 92px; height: 150px; border-radius: 22px; border: 2px solid rgba(242, 246, 252, .7); background: linear-gradient(180deg, #102866, #0a1840); display: grid; place-items: center; box-shadow: 0 24px 60px -20px rgba(1, 142, 220, .9); position: relative; z-index: 2; }
.sensor-art .hub img { width: 44px; }
.sensor-art .node { position: absolute; width: 54px; height: 54px; border-radius: 18px; display: grid; place-items: center; background: var(--glass-2); border: 1px solid var(--line-2); color: var(--blue-hi); z-index: 2; }
.sensor-art .node.n1 { left: 4%; top: 10%; } .sensor-art .node.n2 { right: 4%; top: 10%; } .sensor-art .node.n3 { left: 6%; bottom: 6%; } .sensor-art .node.n4 { right: 6%; bottom: 6%; }
.sensor-art .wire { position: absolute; left: 50%; top: 50%; width: 42%; height: 2px; transform-origin: 0 50%; background: repeating-linear-gradient(90deg, rgba(41, 169, 242, .8) 0 6px, transparent 6px 12px); background-size: 24px 2px; animation: flow 1.2s linear infinite; z-index: 1; opacity: .7; }
.sensor-art .wire.w1 { transform: rotate(-145deg); } .sensor-art .wire.w2 { transform: rotate(-35deg); } .sensor-art .wire.w3 { transform: rotate(150deg); } .sensor-art .wire.w4 { transform: rotate(30deg); }
@keyframes flow { to { background-position: -24px 0; } }
.honest { display: flex; gap: 12px; align-items: flex-start; padding: 14px; border-radius: var(--radius-sm); background: rgba(251, 191, 36, .08); border: 1px solid rgba(251, 191, 36, .28); color: #fde68a; font-size: calc(14.5 * var(--fs)); line-height: 1.5; margin: 0 0 14px; }
.honest .ico { color: var(--warn); margin-top: 2px; }
/* Slide 3: two paths. */
.path-pick { display: flex; flex-direction: column; gap: 12px; margin-top: 4px; }
.path-card { display: flex; gap: 14px; align-items: flex-start; text-align: left; padding: 16px; border-radius: var(--radius); background: var(--glass); border: 1.5px solid var(--line); transition: border-color 200ms, background 200ms, transform 120ms var(--ease); width: 100%; }
.path-card:active { transform: scale(.98); }
.path-card[aria-checked="true"] { border-color: rgba(41, 169, 242, .75); background: rgba(1, 142, 220, .12); box-shadow: 0 16px 40px -22px rgba(1, 142, 220, .9); }
.path-card .li-icon { width: 46px; height: 46px; border-radius: 15px; }
.path-card h3 { font: 700 calc(16 * var(--fs))/1.25 var(--head); margin: 2px 0 5px; }
.path-card p { margin: 0; font-size: calc(14 * var(--fs)); color: var(--ink-2); line-height: 1.45; }
.path-card .badge { margin-top: 10px; }
.path-check { margin-left: auto; width: 24px; height: 24px; flex: none; border-radius: 50%; border: 2px solid var(--line-2); display: grid; place-items: center; color: transparent; transition: all 200ms; }
.path-card[aria-checked="true"] .path-check { background: var(--blue); border-color: var(--blue); color: #fff; }
.path-check .ico { width: 14px; height: 14px; stroke-width: 3; }
/* Slide 4: finishing touches. */
.ready-art { display: grid; place-items: center; height: 150px; }
.ready-art .badge-big { width: 108px; height: 108px; border-radius: 34px; display: grid; place-items: center; background: radial-gradient(circle at 50% 28%, #4cc0ff, var(--blue) 60%, var(--navy)); box-shadow: 0 0 0 10px rgba(1, 142, 220, .1), 0 30px 70px -20px rgba(1, 142, 220, .9); color: #fff; }
.ready-art .badge-big .ico { width: 52px; height: 52px; stroke-width: 1.6; }
.istep { display: flex; align-items: center; gap: 12px; padding: 12px; border-radius: var(--radius-sm); background: rgba(2, 6, 20, .5); border: 1px solid var(--line); font-size: calc(14.5 * var(--fs)); line-height: 1.4; }
.istep + .istep { margin-top: 8px; }
.istep b { color: #fff; font-weight: 650; }
.istep-n { font: 700 12px/1 var(--head); color: var(--ink-3); width: 14px; flex: none; text-align: center; }
.iglyph { width: 36px; height: 36px; border-radius: 11px; flex: none; display: grid; place-items: center; background: var(--glass-2); color: var(--blue-hi); }
.iglyph .ico { width: 20px; height: 20px; }
.onb-cta { display: flex; flex-direction: column; gap: 10px; margin-top: 18px; }

/* ── Home ─────────────────────────────────────────────────────────────── */
.home-greet { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; margin: 2px 2px 14px; }
.home-greet .view-title { font-size: 25px; }
.hero { padding: 0; overflow: hidden; border-radius: 24px; border-color: var(--line-2); background: linear-gradient(180deg, rgba(255, 255, 255, .06), rgba(255, 255, 255, .025)); animation: rise 360ms var(--ease) both; }
.hero-media { position: relative; aspect-ratio: 16 / 10; background: #081236; overflow: hidden; display: block; width: 100%; text-align: left; }
.hero-media canvas, .hero-media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; animation: fadeIn 500ms var(--ease) both; }
/* The picture is swapped as a survey adds samples: taps belong to the button, never to a canvas about to go. */
.hero-media canvas, .hero-media img, .hero-placeholder { pointer-events: none; }
.hero-media::after { content: ''; position: absolute; inset: 0; background: linear-gradient(180deg, rgba(5, 11, 31, 0) 40%, rgba(5, 11, 31, .55) 72%, rgba(5, 11, 31, .92)); pointer-events: none; }
.hero-placeholder { position: absolute; inset: 0; overflow: hidden; }
.hero-placeholder .blob { position: absolute; border-radius: 50%; filter: blur(28px); opacity: .55; animation: drift 12s ease-in-out infinite alternate; }
.hero-placeholder .b1 { width: 60%; height: 80%; left: 4%; top: 6%; background: radial-gradient(circle, rgba(30, 155, 240, .9), transparent 70%); }
.hero-placeholder .b2 { width: 50%; height: 70%; left: 46%; top: 20%; background: radial-gradient(circle, rgba(20, 199, 192, .7), transparent 70%); animation-delay: -4s; }
.hero-placeholder .b3 { width: 34%; height: 50%; left: 30%; top: 40%; background: radial-gradient(circle, rgba(255, 154, 46, .45), transparent 70%); animation-delay: -8s; }
.hero-placeholder .grid-lines { position: absolute; inset: 0; background-image: linear-gradient(rgba(255, 255, 255, .05) 1px, transparent 1px), linear-gradient(90deg, rgba(255, 255, 255, .05) 1px, transparent 1px); background-size: 24px 24px; }
.hero-cap { position: absolute; left: 16px; right: 16px; bottom: 14px; z-index: 2; display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; }
.hero-cap .eyebrow { margin-bottom: 6px; color: #9fd8ff; text-shadow: 0 1px 8px rgba(0, 0, 0, .6); }
.hero-name { font: 800 23px/1.1 var(--head); letter-spacing: -.015em; margin: 0; text-shadow: 0 2px 14px rgba(0, 0, 0, .7); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hero-badge { flex: none; }
/* ⚠ A FLOOR'S PICTURE IS ALL HEAT MAP. The header right above already names
   the site and floor, so the floor's card (the button) drops the caption,
   keeps the Simulated badge in the top-left corner, and fades only its
   last 18% into the numbers below. The welcome card keeps its caption. */
button.hero-media::after { background: linear-gradient(180deg, rgba(5, 11, 31, 0) 82%, rgba(5, 11, 31, .6)); }
button.hero-media .hero-cap { top: 10px; left: 10px; right: auto; bottom: auto; }
button.hero-media .hero-cap > .grow { display: none; }
/* The scale says what its ends mean (hot is strong, whatever the metric:
   colormap.js turns the ramp for latency and jitter). The bar is a
   background layer placed after the fixed-width "Weak". */
.hero-legend {
  position: absolute; top: 10px; right: 10px; z-index: 2;
  display: flex; align-items: center; gap: 52px; height: 22px; border-radius: 999px;
  font: 700 10px/1 var(--head); letter-spacing: .02em; color: var(--ink-2);
  background: var(--thermal) 43px 50% / 40px 6px no-repeat, rgba(5, 11, 31, .74);
  box-shadow: inset 0 0 0 1px var(--line-2);
}
.hero-legend::before { content: 'Weak'; width: 28px; margin-left: 9px; text-align: right; }
.hero-legend::after { content: 'Strong'; margin-right: 9px; }
.hero-stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0; border-top: 1px solid var(--line); }
.hero-stats .stat { border: 0; border-radius: 0; background: transparent; padding: 14px 14px 13px; }
.hero-stats .stat + .stat { border-left: 1px solid var(--line); }
.hero-stats .stat-value { font-size: 22px; }
.quick { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin-top: 12px; }
.quick-tile { display: flex; flex-direction: column; align-items: flex-start; gap: 12px; padding: 14px; border-radius: var(--radius); background: var(--glass); border: 1px solid var(--line); text-align: left; min-height: 104px; transition: transform 140ms var(--ease), background 200ms, border-color 200ms; animation: rise 360ms var(--ease) both; }
.quick-tile:nth-child(2) { animation-delay: 40ms; } .quick-tile:nth-child(3) { animation-delay: 80ms; } .quick-tile:nth-child(4) { animation-delay: 120ms; }
.quick-tile:active { transform: scale(.96); background: var(--glass-2); }
.quick-tile[aria-disabled="true"] { opacity: .5; }
.quick-icon { width: 40px; height: 40px; border-radius: 13px; display: grid; place-items: center; color: #fff; }
.quick-icon.q-survey { background: linear-gradient(140deg, #ff9a6b, var(--hot)); box-shadow: 0 10px 24px -12px rgba(255, 107, 61, .9); }
.quick-icon.q-map { background: linear-gradient(140deg, #4cc0ff, var(--blue)); box-shadow: 0 10px 24px -12px rgba(1, 142, 220, .9); }
.quick-icon.q-edit { background: linear-gradient(140deg, #8b9cff, #4f5fd9); box-shadow: 0 10px 24px -12px rgba(79, 124, 255, .9); }
.quick-icon.q-net { background: linear-gradient(140deg, #4ee0c2, #0e9f84); box-shadow: 0 10px 24px -12px rgba(20, 199, 192, .9); }
.quick-title { display: block; font: 700 calc(15 * var(--fs))/1.2 var(--head); }
.quick-sub { display: block; font-size: calc(12.5 * var(--fs)); color: var(--ink-3); margin-top: 3px; line-height: 1.35; }
.li > button.grow { text-align: left; min-height: 44px; min-width: 0; }
.strip { margin-top: 12px; }
.strip .chip { min-height: 38px; background: var(--glass); border-color: var(--line); color: var(--ink-2); font-weight: 600; }
.demo-card { display: flex; gap: 14px; align-items: center; padding: 16px; background: linear-gradient(120deg, rgba(1, 142, 220, .16), rgba(255, 107, 61, .08)); border-color: rgba(41, 169, 242, .3); }
.demo-card .li-icon { width: 48px; height: 48px; border-radius: 16px; background: var(--thermal); color: #fff; }
.site-card { padding: 6px 14px 4px; animation: rise 360ms var(--ease) both; }
.site-head { display: flex; align-items: center; gap: 12px; padding: 10px 0 8px; }
.site-head .li-icon { background: rgba(14, 36, 89, .9); color: #9fd8ff; }
.site-name { font: 700 calc(16 * var(--fs))/1.25 var(--head); margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.site-addr { font-size: calc(12.5 * var(--fs)); color: var(--ink-3); margin: 2px 0 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.site-card .li { min-height: 52px; padding-left: 4px; }
.site-card .li .li-icon { width: 34px; height: 34px; border-radius: 10px; }
.floor-thumb { width: 34px; height: 34px; border-radius: 10px; overflow: hidden; flex: none; background: linear-gradient(135deg, #12306e, var(--navy)); display: grid; place-items: center; color: var(--blue-hi); }
.add-floor { color: var(--blue-hi); font-weight: 600; font-size: calc(14 * var(--fs)); min-height: 46px; display: flex; align-items: center; gap: 8px; padding: 0 4px; }

/* ── Settings ─────────────────────────────────────────────────────────── */
.set-card { padding: 4px 16px; }
.set-row { display: flex; align-items: center; gap: 12px; min-height: 56px; padding: 10px 0; border-bottom: 1px solid var(--line); }
.set-row:last-child { border-bottom: 0; }
.set-row.stack { flex-direction: column; align-items: stretch; gap: 10px; }
.set-row .toggle { flex: 1; padding: 0; min-height: 0; }
.set-label { flex: 1; min-width: 0; }
.set-label b { display: block; font-weight: 600; font-size: calc(15.5 * var(--fs)); }
.set-label span { display: block; font-size: calc(12.5 * var(--fs)); color: var(--ink-3); line-height: 1.4; margin-top: 2px; }
.set-value { color: var(--ink-2); font-size: calc(14.5 * var(--fs)); text-align: right; overflow-wrap: anywhere; }
.account { display: flex; align-items: center; gap: 14px; padding: 16px; }
.avatar { width: 52px; height: 52px; flex: none; border-radius: 50%; display: grid; place-items: center; font: 800 18px/1 var(--head); color: #fff; background: linear-gradient(140deg, var(--blue), var(--navy)); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 10px 26px -12px rgba(1, 142, 220, .9); }
.account-main { min-width: 0; flex: 1; }
.account-email { font: 700 calc(16 * var(--fs))/1.25 var(--head); margin: 0; overflow-wrap: anywhere; }
.account-sub { font-size: calc(13 * var(--fs)); color: var(--ink-2); margin: 4px 0 0; display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.pk-item .li-icon { color: #9fd8ff; }
.act-card .h3 { display: flex; align-items: center; gap: 8px; margin: 4px 0 0; color: var(--ink-2); }
.act-card .h3 .ico { color: var(--blue-hi); }
.act-card > p { margin: 0; }
.act-row .li-icon { color: #9fd8ff; }
.act-row.act-warn .li-icon { color: var(--warn); }
.act-row > .btn, .act-row > .badge { flex: none; }
/* A device and when it signed in are worth reading whole: they wrap. */
.act-row .li-title, .act-row .li-sub { white-space: normal; overflow-wrap: anywhere; }
.act-card > .btn { align-self: flex-start; }
.palette-swatch { display: block; height: 10px; border-radius: 5px; margin-top: 6px; box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .15); }
.palette-pick { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.palette-opt { padding: 10px; border-radius: 13px; border: 1.5px solid var(--line); background: rgba(2, 6, 20, .4); text-align: left; font-size: calc(13 * var(--fs)); font-weight: 600; color: var(--ink-2); transition: border-color 200ms, transform 120ms var(--ease); }
.palette-opt:active { transform: scale(.96); }
.palette-opt[aria-checked="true"] { border-color: rgba(41, 169, 242, .75); color: #fff; }
.cap-table { width: 100%; border-collapse: collapse; font-size: calc(13 * var(--fs)); }
.cap-table th, .cap-table td { padding: 9px 6px; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; }
.cap-table th { font: 700 calc(11 * var(--fs))/1.2 var(--head); letter-spacing: .08em; text-transform: uppercase; color: var(--ink-3); }
.cap-table td:first-child { font-weight: 600; white-space: nowrap; }
.cap-yes { color: #6ee7b7; } .cap-no { color: var(--ink-3); } .cap-part { color: #fcd34d; }
.calib-count { font: 800 64px/1 var(--head); text-align: center; margin: 12px 0 4px; font-variant-numeric: tabular-nums; }
.calib-cap { text-align: center; color: var(--ink-3); font-size: calc(13 * var(--fs)); margin: 0 0 12px; }
.version { text-align: center; color: var(--ink-3); font-size: calc(12 * var(--fs)); margin: 22px 0 6px; }
.version img { width: 34px; margin: 0 auto 10px; opacity: .8; }

/* ── Wider screens ────────────────────────────────────────────────────── */
@media (min-width: 720px) {
  .view { padding-top: 26px; }
  .quick { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .signin { justify-content: center; gap: 40px; }
  .signin-logo { margin-bottom: 40px; }
  .sheet-layer { align-items: center; padding: 24px; }
  .sheet { border-radius: 26px; border-bottom: 1px solid var(--line-2); max-height: 84vh; animation-name: sheetPop; }
  .sheet-tall { height: 84vh; }
  .sheet-actions { padding-bottom: 18px; }
  .sheet-layer.closing .sheet { animation-name: sheetPopOut; }
  @keyframes sheetPop { from { opacity: 0; transform: translateY(18px) scale(.97); } }
  @keyframes sheetPopOut { to { opacity: 0; transform: translateY(12px) scale(.98); } }
  .tabbar { left: 50%; right: auto; width: min(560px, 100%); transform: translateX(-50%); bottom: 14px; height: var(--tabbar-h); padding: 0 6px; border-radius: 22px; border: 1px solid var(--line-2); box-shadow: 0 20px 50px -20px rgba(0, 0, 0, .7); }
  /* ⚠ Clear of the raised Survey button too: 140% of the bar left its top showing under a page with the bar away. */
  .tabbar.away { transform: translate(-50%, calc(100% + 48px)); }
  :root { --chrome-bottom: calc(var(--tabbar-h) + 28px); }
}
@media (max-width: 360px) {
  .sensor-pill { max-width: 84px; padding: 0 9px; }
  .signin-title { font-size: 33px; }
  .hero-stats .stat-value { font-size: 19px; }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; scroll-behavior: auto !important; }
  .aurora i, .ripples, .heat-art .puck { animation: none !important; }
}
