/* ============================================================================
   Voxar portal — built on Airo's design system (powercor-lco/frontend).
   Component sizes, weights and spacing are taken from Airo's app.css rather
   than approximated, so the two products read as one.

   THEME. Light is the default, matching Airo's deliberate choice: the product
   default is light for everyone rather than whatever the device is set to.
   An inline script in <head> stamps data-theme before first paint (a flash of
   the wrong theme on every load is what happens otherwise). The
   prefers-color-scheme block below is a fallback for the JS-disabled case
   only — in normal use data-theme is always present.
   ========================================================================= */

:root{
  /* ── light (default) ── page is grey, cards are white. Pure white
     everywhere makes cards invisible and glares on a ute dashboard. */
  --bg-deep:#f4f6fa; --bg-navy:#ffffff; --bg-card:#ffffff; --bg-surface:#eef1f7;
  --chrome:rgba(255,255,255,0.72);      /* translucent top bar / sidebar */
  --chrome-side:rgba(255,255,255,0.62);

  /* Gold is unreadable on white (2.0:1), so it becomes navy here and stays
     real gold only where the ground is dark — the sign-in brand panel. */
  --gold:#1d2e4f; --gold-dim:#425874;
  --gold-bg:rgba(29,46,79,0.06); --gold-border:rgba(29,46,79,0.18);

  --text-primary:#0b1220; --text-secondary:#425874; --text-muted:#7186a3;

  --green:#15803d; --green-bg:rgba(21,128,61,0.10);
  --amber:#b45309; --amber-bg:rgba(180,83,9,0.10);
  --red:#b91c1c;   --red-bg:rgba(185,28,28,0.10);
  --blue:#1d4ed8;  --blue-bg:rgba(29,78,216,0.10);
  --teal:#0f766e;  --teal-bg:rgba(15,118,110,0.10);
  --purple:#86198f;--purple-bg:rgba(134,25,143,0.10);

  --border:rgba(11,18,32,0.10); --border-mid:rgba(11,18,32,0.16);
  --shadow-card:0 1px 3px rgba(11,18,32,0.08);
  --hover:rgba(11,18,32,0.035);

  --radius-sm:6px; --radius-md:10px; --radius-lg:14px; --radius-xl:20px;
  --font:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
  --font-mono:'IBM Plex Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
  --transition:0.18s ease;
  --topbar-h:56px; --sidebar-w:200px;
}

:root[data-theme="dark"]{
  --bg-deep:#0b1220; --bg-navy:#1d2e4f; --bg-card:#162040; --bg-surface:#1a2a45;
  --chrome:rgba(29,46,79,0.72); --chrome-side:rgba(29,46,79,0.55);
  --gold:#dbc078; --gold-dim:#a8905a;
  --gold-bg:rgba(219,192,120,0.08); --gold-border:rgba(219,192,120,0.25);
  --text-primary:#f0f4ff; --text-secondary:#8aa4cc; --text-muted:#4a6080;
  --green:#4ade80; --green-bg:rgba(74,222,128,0.10);
  --amber:#f59e0b; --amber-bg:rgba(245,158,11,0.10);
  --red:#f87171;   --red-bg:rgba(248,113,113,0.10);
  --blue:#60a5fa;  --blue-bg:rgba(96,165,250,0.10);
  --teal:#2dd4bf;  --teal-bg:rgba(45,212,191,0.10);
  --purple:#b620e0;--purple-bg:rgba(182,32,224,0.12);
  --border:rgba(255,255,255,0.07); --border-mid:rgba(255,255,255,0.12);
  --shadow-card:none; --hover:rgba(255,255,255,0.04);
}
/* JS-disabled fallback only. */
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]):not([data-theme="dark"]){
    --bg-deep:#0b1220; --bg-navy:#1d2e4f; --bg-card:#162040; --bg-surface:#1a2a45;
    --chrome:rgba(29,46,79,0.72); --chrome-side:rgba(29,46,79,0.55);
    --gold:#dbc078; --gold-bg:rgba(219,192,120,0.08); --gold-border:rgba(219,192,120,0.25);
    --text-primary:#f0f4ff; --text-secondary:#8aa4cc; --text-muted:#4a6080;
    --green:#4ade80; --amber:#f59e0b; --red:#f87171; --blue:#60a5fa;
    --border:rgba(255,255,255,0.07); --border-mid:rgba(255,255,255,0.12);
    --shadow-card:none; --hover:rgba(255,255,255,0.04);
  }
}

/* ── base ───────────────────────────────────────────────────────────────── */
*{box-sizing:border-box}
html,body{height:100%}
body{margin:0;background:var(--bg-deep);color:var(--text-primary);
  font-family:var(--font);font-size:14px;line-height:1.55;-webkit-font-smoothing:antialiased}
button,input,select,textarea{font-family:inherit;font-size:inherit}
:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.num{font-variant-numeric:tabular-nums}
[hidden]{display:none!important}

/* ══ SIGN-IN ════════════════════════════════════════════════════════════════
   Split: brand on the left, form on the right — Airo's layout, Voxar's
   identity inside it. Under 860px the brand becomes a band across the top. */
.signin{display:grid;grid-template-columns:minmax(0,52%) 1fr;min-height:100dvh}

/* Always dark. It is a brand surface, not a themed one, and the only part of
   the app that never follows the toggle — which is also what lets VOXAR be
   real gold rather than the navy the light theme remaps it to. */
.signin-brand{
  position:relative;overflow:hidden;
  background:radial-gradient(120% 100% at 0% 0%,#1b2540,#0d1526 62%);
  display:flex;flex-direction:column;justify-content:space-between;
  padding:clamp(40px,4.2vw,84px);
}
/* The radar sits centred in the panel with the copy pushed below it, so the
   two never overlap — the old watermark ran underneath the wording. */
.signin-radar{position:absolute;left:50%;top:47%;transform:translate(-50%,-50%);
  width:min(84%,420px);aspect-ratio:1;pointer-events:none}
.signin-radar img{width:100%;height:100%;display:block}
.signin-logo{position:relative;display:block;margin:0 auto;
  width:clamp(215px,24vw,430px);max-width:100%;height:auto}
.signin-copy{position:relative}
.signin-mark{
  font-size:clamp(34px,2.7vw,50px);font-weight:800;letter-spacing:.15em;
  line-height:1;margin:0 0 clamp(18px,1.6vw,26px);
  background:linear-gradient(100deg,#b98947,#dbc078 45%,#faf0a1);
  -webkit-background-clip:text;background-clip:text;color:transparent;
}
.signin-copy h2{
  font-size:clamp(15px,1.25vw,20px);font-weight:400;line-height:1.45;margin:0;
  color:rgba(234,240,250,.78);
}
.signin-verbs{
  display:flex;flex-wrap:wrap;align-items:center;gap:clamp(10px,1.1vw,20px);
  margin-top:clamp(20px,2vw,30px);font-size:clamp(11px,.85vw,14px);font-weight:700;
  letter-spacing:.22em;text-transform:uppercase;color:#dbc078;
}
.signin-verbs i{font-style:normal;opacity:.5;letter-spacing:0}

/* A street map in blue — the dark basemap inverted. Legible as roads rather
   than a texture; a white glow behind the card keeps the form readable. */
.signin-form{display:flex;align-items:center;justify-content:center;position:relative;
  padding:40px clamp(24px,4.2vw,84px);color:#0f1b2d;
  background:radial-gradient(120% 90% at 100% 0%,#eaf0f9 0%,#ffffff 62%)}
.signin-form::before{content:'';position:absolute;inset:0;pointer-events:none;
  background:url('streets.svg') center/cover no-repeat;opacity:.18}
.signin-form::after{content:'';position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(58% 52% at 50% 50%,rgba(255,255,255,.95) 0%,rgba(255,255,255,0) 72%)}
.signin-inner{position:relative;z-index:1;width:100%;max-width:clamp(340px,27vw,440px)}

/* A dark card on the white. Carries the dark tokens explicitly rather than
   relying on the page theme — a light-mode account would otherwise get white
   controls inside a dark card. */
.signin-card{
  border-radius:18px;padding:clamp(26px,2.2vw,38px);
  background:linear-gradient(160deg,#16203a,#0d1526 70%);
  border:1px solid rgba(159,178,208,.14);
  box-shadow:0 2px 6px rgba(15,27,45,.10),0 22px 60px rgba(15,27,45,.22);
  --bg-surface:rgba(255,255,255,.05);
  --text-primary:#eaf0fa; --text-secondary:#9fb2d0; --text-muted:#8fa3c0;
  --border:rgba(159,178,208,.16); --border-mid:rgba(159,178,208,.22);
  --gold:#dbc078; --red:#f87171; --red-bg:rgba(248,113,113,.10);
  color:var(--text-primary);
}
.login-title{font-size:clamp(21px,1.5vw,28px);font-weight:700;margin:0 0 4px}
.login-sub{font-size:13.5px;color:var(--text-muted);margin:0 0 24px}
.field{margin-bottom:12px}
.field input{
  width:100%;padding:0 14px;height:clamp(48px,3.2vw,54px);font-size:15px;
  background:var(--bg-surface);color:var(--text-primary);
  border:1px solid var(--border-mid);border-radius:var(--radius-md);
  transition:border-color var(--transition);
}
.field input::placeholder{color:#7c8fac}
.field input:focus{outline:none;border-color:#dbc078}
.code-input{text-align:center;font-size:24px;letter-spacing:.4em;
  font-family:var(--font-mono);height:56px!important}
.btn-primary{
  width:100%;height:48px;margin-top:6px;cursor:pointer;font-weight:600;
  background:linear-gradient(100deg,#b98947,#dbc078 45%,#faf0a1);
  border:1px solid #dbc078;border-radius:var(--radius-md);color:#20180a;
  transition:background var(--transition);
}
.btn-primary:hover{background:#e8d08a}
.btn-primary:disabled{opacity:.55;cursor:not-allowed}
.remember-row{display:flex;align-items:flex-start;gap:10px;cursor:pointer;
  margin:10px 0 2px;font-size:13.5px;color:var(--text-secondary)}
.remember-row input{margin-top:2px;flex:none;width:16px;height:16px;accent-color:#dbc078}
.login-footer{display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap;
  margin-top:18px;font-size:12px;color:var(--text-muted)}
.link-btn{background:none;border:none;padding:0;cursor:pointer;
  color:var(--text-muted);font-size:12px}
.link-btn:hover{color:var(--text-secondary);text-decoration:underline}
.msg{border-radius:var(--radius-md);padding:10px 14px;font-size:13px;margin-bottom:14px}
.msg.err{background:var(--red-bg);border:1px solid rgba(248,113,113,.3);color:var(--red)}
.msg.ok{background:rgba(74,222,128,.10);border:1px solid rgba(74,222,128,.3);color:#4ade80}
.step{display:none}
.step.on{display:block}

@media (max-width:860px){
  .signin{grid-template-columns:1fr;grid-template-rows:auto 1fr}
  .signin-brand{padding:28px 26px 30px;justify-content:flex-start;gap:16px}
  .signin-copy h2{font-size:19px}
  .signin-mark{font-size:32px;margin-bottom:18px}
  .signin-verbs{gap:9px;margin-top:18px;font-size:11px}
  .signin-logo{width:min(280px,72%)}
  .signin-radar{width:min(58%,240px);top:50%}
  .signin-form{justify-content:center;padding:34px 26px 40px}
  .signin-inner{max-width:420px}
  .signin-card{padding:22px}
}

/* ══ APP ═══════════════════════════════════════════════════════════════════ */
#app{display:none;height:100dvh;grid-template-rows:var(--topbar-h) 1fr}
#app.on{display:grid}

.topbar{
  background:var(--chrome);
  backdrop-filter:blur(16px) saturate(160%);
  -webkit-backdrop-filter:blur(16px) saturate(160%);
  border-bottom:1px solid var(--border-mid);
  padding:0 1.5rem;height:var(--topbar-h);
  display:flex;align-items:center;gap:16px;
}
.topbar-logo{font-size:16px;font-weight:700;color:var(--gold);letter-spacing:.22em;
  white-space:nowrap;display:flex;align-items:center}
.topbar-divider{width:1px;height:20px;background:var(--border-mid);flex:none}
.topbar-title{font-size:13px;color:var(--text-secondary);white-space:nowrap}
.topbar-spacer{flex:1}

.theme-toggle{display:inline-flex;align-items:center;padding:3px;
  background:var(--hover);border:1px solid var(--border);border-radius:999px}
.theme-toggle button{
  border:0;background:transparent;cursor:pointer;color:var(--text-secondary);
  width:30px;height:26px;border-radius:999px;display:inline-flex;
  align-items:center;justify-content:center;transition:background var(--transition),color var(--transition);
}
.theme-toggle button:hover{color:var(--text-primary)}
.theme-toggle button.on{background:var(--gold);color:var(--bg-navy)}
:root[data-theme="dark"] .theme-toggle button.on{background:#fff;color:#16233f}

.avatar{width:28px;height:28px;border-radius:50%;display:grid;place-items:center;
  background:var(--gold-bg);color:var(--gold);border:1px solid var(--gold-border);
  font-size:11px;font-weight:600;flex:none}
.badge{display:inline-flex;align-items:center;gap:4px;padding:3px 10px;
  border-radius:20px;font-size:11px;font-weight:500;white-space:nowrap}
.badge.green{background:var(--green-bg);color:var(--green)}
.badge.amber{background:var(--amber-bg);color:var(--amber)}
.badge.red{background:var(--red-bg);color:var(--red)}
.badge.blue{background:var(--blue-bg);color:var(--blue)}
.badge.purple{background:var(--purple-bg);color:var(--purple)}
.badge.grey{background:var(--bg-surface);color:var(--text-muted)}
.badge.gold{background:var(--gold-bg);color:var(--gold);border:1px solid var(--gold-border)}
.badge.muted{background:var(--hover);color:var(--text-muted)}
.btn-ghost{background:none;border:none;cursor:pointer;color:var(--text-muted);
  font-size:12.5px;padding:6px 8px;border-radius:var(--radius-sm)}
.btn-ghost:hover{color:var(--red);background:var(--hover)}

.body{display:grid;grid-template-columns:var(--sidebar-w) 1fr;min-height:0}

.sidebar{
  background:var(--chrome-side);
  backdrop-filter:blur(16px) saturate(150%);
  -webkit-backdrop-filter:blur(16px) saturate(150%);
  border-right:1px solid var(--border-mid);
  padding:1rem 0;overflow-y:auto;display:flex;flex-direction:column;
}
.sidebar-section{font-size:10px;font-weight:500;text-transform:uppercase;
  letter-spacing:.08em;color:var(--text-muted);padding:12px 16px 4px}
.sidebar-section:first-child{padding-top:0}
.nav-item{
  display:flex;align-items:center;gap:8px;width:100%;padding:9px 16px;
  font-size:13px;color:var(--text-secondary);cursor:pointer;text-align:left;
  background:none;border:none;border-left:2px solid transparent;
  transition:all var(--transition);
}
.nav-item svg{flex:none;opacity:.85}
.nav-item:hover{color:var(--text-primary);background:var(--hover);
  border-left-color:var(--gold-border)}
.nav-item.active{color:var(--gold);border-left-color:var(--gold);background:var(--gold-bg)}
.nav-badge{margin-left:auto;background:var(--gold-bg);color:var(--gold);
  border:1px solid var(--gold-border);font-size:10px;font-weight:600;line-height:1;
  padding:2px 7px;border-radius:10px}
.sidebar-foot{margin-top:auto;padding:12px 16px 0;border-top:1px solid var(--border);
  font-size:11.5px;color:var(--text-muted)}
.sidebar-foot .row{display:flex;justify-content:space-between;padding:2.5px 0}

.content{overflow-y:auto;min-width:0;padding:1.5rem;position:relative}
.view{display:none}
.view.on{display:block}
.page-title{font-size:22px;font-weight:600;margin:0 0 2px;letter-spacing:-.01em}
.page-sub{color:var(--text-secondary);font-size:13.5px;margin:0 0 1.25rem}

.stat-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));
  gap:12px;margin-bottom:1.25rem}
.stat-card{background:var(--bg-card);border:1px solid var(--border);
  border-radius:var(--radius-lg);padding:1rem 1.25rem;box-shadow:var(--shadow-card)}
.stat-label{font-size:10px;font-weight:500;text-transform:uppercase;
  letter-spacing:.08em;color:var(--text-muted);margin-bottom:8px}
.stat-num{font-size:2rem;font-weight:500;line-height:1;color:var(--text-primary);
  font-variant-numeric:tabular-nums}
.stat-sub{font-size:12px;color:var(--text-muted);margin-top:4px}

.card{background:var(--bg-card);border:1px solid var(--border);
  border-radius:var(--radius-lg);padding:1.25rem;box-shadow:var(--shadow-card)}
.card-header{display:flex;align-items:center;gap:8px;margin-bottom:1rem;
  padding-bottom:.75rem;border-bottom:1px solid var(--border)}
.card-title{font-size:12px;font-weight:500;color:var(--text-secondary);
  text-transform:uppercase;letter-spacing:.05em;flex:1}

.placeholder{border:1px dashed var(--border-mid);border-radius:var(--radius-lg);
  padding:44px 24px;text-align:center;color:var(--text-muted)}
.placeholder .k{font-size:10px;font-weight:500;letter-spacing:.08em;
  text-transform:uppercase;color:var(--gold);margin-bottom:8px}
.placeholder p{margin:0;font-size:13.5px}

/* Collapsed to a rail. The same treatment the narrow breakpoint already used,
   promoted to something you can ask for — on a wall display or beside a map
   the nav is a list you read once and then want out of the way.

   Grouped with the breakpoint rules so the two can never drift apart. */
:root.rail{--sidebar-w:60px}
:root.rail .nav-item{justify-content:center;padding:11px 0;gap:0}
:root.rail .nav-item span,
:root.rail .sidebar-section,
:root.rail .sidebar-foot,
:root.rail .nav-badge{display:none}
:root.rail .rail-toggle{color:var(--gold)}

/* Lives in the top bar beside the wordmark, which is where anybody looks for
   it. It spent one iteration at the bottom of the sidebar in muted grey and
   was, reasonably, invisible. */
.rail-toggle{display:inline-flex;align-items:center;justify-content:center;
  width:30px;height:30px;flex:none;margin-right:2px;padding:0;background:none;
  border:none;border-radius:var(--radius-sm);color:var(--text-muted);cursor:pointer}
.rail-toggle:hover{color:var(--text-primary);background:var(--hover)}
.rail-toggle svg{flex:none;transition:opacity var(--transition)}

@media (max-width:900px){
  :root{--sidebar-w:56px}
  .nav-item{justify-content:center;padding:11px 0;gap:0}
  .nav-item span{display:none}
  .sidebar-section,.sidebar-foot,.nav-badge{display:none}
  .topbar-title,.topbar-divider{display:none}
  .content{padding:1rem}
}
@media (prefers-reduced-motion:reduce){*{transition-duration:.01ms!important}}

/* ══ TABLES ═══════════════════════════════════════════════════════════════ */
.table-wrap{overflow-x:auto;border:1px solid var(--border);border-radius:var(--radius-lg);
  background:var(--bg-card);box-shadow:var(--shadow-card)}
table{width:100%;border-collapse:collapse;font-size:13.5px}
thead th{font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:.08em;
  color:var(--text-muted);text-align:left;padding:12px 14px;white-space:nowrap;
  border-bottom:1px solid var(--border)}
tbody td{padding:13px 14px;border-bottom:1px solid var(--border);color:var(--text-secondary);
  vertical-align:middle}
tbody tr:last-child td{border-bottom:none}
tbody tr{cursor:pointer;transition:background var(--transition)}
tbody tr:hover{background:var(--hover)}
td.name{color:var(--text-primary);font-weight:500;white-space:nowrap}
td .unit{display:block;font-size:11px;color:var(--text-muted);font-family:var(--font-mono);
  font-weight:400;margin-top:1px}
.spark{display:flex;align-items:flex-end;gap:1.5px;height:20px}
.spark i{flex:1;min-width:2px;border-radius:1px;background:var(--green);opacity:.85}
.spark i.gap{background:var(--red)}

/* ══ SITE PANEL ═══════════════════════════════════════════════════════════ */
.panel-scrim{position:fixed;inset:0;background:rgba(11,18,32,.45);
  opacity:0;pointer-events:none;transition:opacity var(--transition);z-index:1100}
.panel-scrim.on{opacity:1;pointer-events:auto}
.panel{position:fixed;top:0;right:0;bottom:0;width:min(420px,92vw);z-index:1101;
  background:var(--bg-navy);border-left:1px solid var(--border-mid);
  transform:translateX(100%);transition:transform var(--transition);
  display:flex;flex-direction:column;box-shadow:-8px 0 40px rgba(11,18,32,.18)}
.panel.on{transform:none}
.panel-head{display:flex;align-items:flex-start;gap:12px;padding:18px 20px;
  border-bottom:1px solid var(--border)}
.panel-head h2{font-size:17px;font-weight:600;margin:0}
.panel-head .unit{font-size:11.5px;color:var(--text-muted);font-family:var(--font-mono)}
.panel-x{margin-left:auto;background:none;border:none;cursor:pointer;font-size:18px;
  color:var(--text-muted);line-height:1;padding:2px 4px}
.panel-x:hover{color:var(--text-primary)}
.panel-body{overflow-y:auto;padding:16px 20px 24px;flex:1}
.panel-foot{padding:14px 20px;border-top:1px solid var(--border)}
.sec-label{font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:.08em;
  color:var(--text-muted);margin:20px 0 8px}
.sec-label:first-child{margin-top:0}
.kv{display:flex;justify-content:space-between;align-items:baseline;gap:12px;
  padding:7px 0;border-bottom:1px solid var(--border);font-size:13px}
.kv:last-child{border-bottom:none}
.kv .k{color:var(--text-muted)}
.kv .v{color:var(--text-primary);font-variant-numeric:tabular-nums;text-align:right}
.btn-enter{width:100%;height:42px;cursor:pointer;font-weight:600;font-size:13px;
  background:var(--gold-bg);color:var(--gold);border:1px solid var(--gold-border);
  border-radius:var(--radius-md);transition:background var(--transition)}
.btn-enter:hover:not(:disabled){background:var(--gold-border)}
.btn-enter:disabled{opacity:.45;cursor:not-allowed}

/* battery */
.batt-head{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:6px}
.batt-pct{font-size:22px;font-weight:500;font-variant-numeric:tabular-nums;line-height:1}
.batt-basis{font-size:11px;color:var(--text-muted);margin-left:6px}
.batt-track{height:7px;border-radius:4px;background:var(--hover);overflow:hidden}
.batt-fill{height:100%;border-radius:4px;transition:width var(--transition)}
/* The SVG stretches to fill the width (preserveAspectRatio="none"), which is
   right for the line and wrong for text — labels live in HTML instead. */
.chart{height:70px;margin-top:10px;position:relative}
.chart svg{width:100%;height:100%;display:block}
.chart-hi,.chart-lo{position:absolute;left:0;font-size:10px;color:var(--text-muted);
  font-variant-numeric:tabular-nums;background:var(--bg-navy);padding-right:4px;line-height:1}
.chart-hi{top:0}
.chart-lo{bottom:0}
.chart-empty{height:70px;display:grid;place-items:center;font-size:12px;color:var(--text-muted)}
.range-pills{display:flex;gap:4px}
.range-pills button{font-size:10px;padding:2px 7px;cursor:pointer;border-radius:999px;
  background:none;border:1px solid var(--border);color:var(--text-muted)}
.range-pills button.on{color:var(--gold);border-color:var(--gold-border);background:var(--gold-bg)}
.empty{padding:36px;text-align:center;color:var(--text-muted);font-size:13.5px}

/* ══ HEAD ROW ═════════════════════════════════════════════════════════════ */
.head-row{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap}
.head-row .page-sub{margin-bottom:1rem}

/* ══ EVENT LIST ═══════════════════════════════════════════════════════════ */
.event{display:grid;grid-template-columns:auto 1fr auto;gap:12px;align-items:start;
  padding:12px 14px;border:1px solid var(--border);border-radius:var(--radius-md);
  background:var(--bg-card);box-shadow:var(--shadow-card);margin-bottom:8px}
.event-dot{width:8px;height:8px;border-radius:50%;margin-top:6px}
.event-title{font-weight:500;color:var(--text-primary);font-size:13.5px}
.event-detail{font-size:12.5px;color:var(--text-secondary);margin-top:1px}
.event-when{font-size:11.5px;color:var(--text-muted);white-space:nowrap;
  font-variant-numeric:tabular-nums;text-align:right}
.event-when small{display:block;font-size:10.5px;opacity:.75}

/* ══ FILTERS ══════════════════════════════════════════════════════════════ */
.filters{display:flex;flex-wrap:wrap;align-items:center;gap:10px;justify-content:flex-end}
.date-range{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text-muted)}
.date-range input{padding:4px 8px;border-radius:var(--radius-sm);font-size:12px;
  background:var(--bg-card);color:var(--text-primary);border:1px solid var(--border);
  color-scheme:light dark}
.date-range input:focus{outline:none;border-color:var(--gold-border)}
.result-count{font-size:12px;color:var(--text-muted);margin:0 0 10px}

/* ══ ACTIVITY ═════════════════════════════════════════════════════════════ */
.act-layout{display:grid;grid-template-columns:238px 1fr;gap:16px;align-items:start}
.act-side{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);
  padding:14px;box-shadow:var(--shadow-card);position:sticky;top:0}
.act-side .sec-label{margin:18px 0 6px}

.cal-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}
.cal-head span{font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
  color:var(--text-secondary)}
.cal-head button{background:none;border:none;cursor:pointer;color:var(--text-muted);
  font-size:16px;line-height:1;padding:2px 7px;border-radius:var(--radius-sm)}
.cal-head button:hover{color:var(--text-primary);background:var(--hover)}
.cal-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:2px}
.cal-hdr{font-size:9px;color:var(--text-muted);text-align:center;padding-bottom:4px}
.cal-day{aspect-ratio:1;display:grid;place-items:center;font-size:11px;border-radius:5px;
  color:var(--text-secondary);cursor:default;font-variant-numeric:tabular-nums;
  border:1px solid transparent}
/* Volume, not presence: the API returns a count per day and shading it turns
   the month into something readable at a glance. */
.cal-day.has{cursor:pointer;color:var(--text-primary)}
.cal-day.l1{background:color-mix(in srgb,var(--gold) 14%,transparent)}
.cal-day.l2{background:color-mix(in srgb,var(--gold) 30%,transparent)}
.cal-day.l3{background:color-mix(in srgb,var(--gold) 52%,transparent)}
.cal-day.l4{background:color-mix(in srgb,var(--gold) 78%,transparent);color:var(--bg-navy)}
:root[data-theme="dark"] .cal-day.l4{color:#0b1220}
.cal-day.has:hover{border-color:var(--gold)}
.cal-day.today{outline:1px dashed var(--border-mid);outline-offset:-2px}
.cal-day.sel{border-color:var(--gold);box-shadow:inset 0 0 0 1px var(--gold)}
.cal-key{display:flex;align-items:center;gap:3px;margin-top:9px;font-size:9px;color:var(--text-muted)}
.cal-key i{width:11px;height:11px;border-radius:3px;display:inline-block}
.cal-key .l1{background:color-mix(in srgb,var(--gold) 14%,transparent)}
.cal-key .l2{background:color-mix(in srgb,var(--gold) 30%,transparent)}
.cal-key .l3{background:color-mix(in srgb,var(--gold) 52%,transparent)}
.cal-key .l4{background:color-mix(in srgb,var(--gold) 78%,transparent)}

.filter-btn{display:flex;align-items:center;gap:8px;width:100%;padding:6px 8px;cursor:pointer;
  background:none;border:none;border-radius:var(--radius-sm);color:var(--text-secondary);
  font-size:12.5px;text-align:left}
.filter-btn:hover{background:var(--hover);color:var(--text-primary)}
.filter-btn.on{background:var(--gold-bg);color:var(--gold);font-weight:500}
.filter-btn .dot{width:7px;height:7px;border-radius:50%;flex:none}
.filter-btn .n{margin-left:auto;font-size:11px;color:var(--text-muted);font-variant-numeric:tabular-nums}

.day-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;
  flex-wrap:wrap;margin-bottom:12px}
.day-head h2{font-size:16px;font-weight:600;margin:0}
.day-summary{display:flex;gap:10px;flex-wrap:wrap;font-size:12px;color:var(--text-secondary)}
.day-summary b{color:var(--text-primary);font-weight:600}

/* Events as a timeline: the clock time is the spine, so a burst of activity
   reads as a cluster rather than as a wall of identical rows. */
.tl{position:relative;padding-left:104px}
.tl::before{content:'';position:absolute;left:95px;top:4px;bottom:4px;width:1px;background:var(--border)}
.tl-row{position:relative;padding:9px 0 9px 14px}
.tl-time{position:absolute;left:-104px;top:9px;width:90px;text-align:right;font-size:11.5px;
  color:var(--text-muted);font-variant-numeric:tabular-nums}
.tl-row::before{content:'';position:absolute;left:-4px;top:14px;width:9px;height:9px;
  border-radius:50%;background:var(--bg-deep);border:2px solid var(--text-muted)}
.tl-row.fault::before{border-color:var(--red)}
.tl-row.recovery::before{border-color:var(--green)}
.tl-row.info::before{border-color:var(--blue)}
.tl-row.warning::before{border-color:var(--amber)}
.tl-title{font-size:13.5px;font-weight:500;color:var(--text-primary)}
.tl-site{font-size:11px;color:var(--text-muted);margin-left:7px;font-weight:400}
.tl-detail{font-size:12.5px;color:var(--text-secondary);margin-top:1px}

@media (max-width:820px){
  .act-layout{grid-template-columns:1fr}
  .act-side{position:static}
}

/* grouped events */
.tl-row.major .tl-title{font-weight:600}
.tl-row.major::before{width:11px;height:11px;left:-5px;border-width:3px}
.tl-time{white-space:nowrap}
.tl-more{margin-top:5px;background:none;border:none;padding:0;cursor:pointer;
  font-size:11.5px;color:var(--text-muted)}
.tl-more:hover{color:var(--gold)}
.tl-sub{margin-top:6px;border-left:1px solid var(--border);padding-left:10px}
.tl-sub div{display:flex;gap:9px;font-size:12px;color:var(--text-secondary);padding:2px 0}
.tl-sub span{color:var(--text-muted);font-variant-numeric:tabular-nums;flex:none}
.muted-note{color:var(--text-muted)}

/* ══ TABS ═════════════════════════════════════════════════════════════════ */
.tabs{display:flex;gap:2px;border-bottom:1px solid var(--border);margin:0 0 16px}
.tabs button{background:none;border:none;cursor:pointer;padding:8px 14px;font-size:13px;
  color:var(--text-secondary);border-bottom:2px solid transparent;margin-bottom:-1px}
.tabs button:hover{color:var(--text-primary)}
.tabs button.on{color:var(--gold);border-bottom-color:var(--gold);font-weight:500}

/* ══ SESSIONS ═════════════════════════════════════════════════════════════ */
.sess-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(110px,1fr));gap:10px;margin-bottom:14px}
.sess-stat{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-md);
  padding:10px 12px;box-shadow:var(--shadow-card)}
.sess-stat .v{font-size:20px;font-weight:500;line-height:1.1;font-variant-numeric:tabular-nums}
.sess-stat .l{font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--text-muted);margin-top:3px}

.sess-card{border:1px solid var(--border);border-radius:var(--radius-md);background:var(--bg-card);
  box-shadow:var(--shadow-card);margin-bottom:8px;overflow:hidden}
.sess-hdr{display:flex;align-items:center;gap:14px;padding:12px 14px;cursor:pointer;width:100%;
  background:none;border:none;text-align:left;color:inherit}
.sess-hdr:hover{background:var(--hover)}
.sess-who{flex:1;min-width:0}
.sess-name{font-size:13.5px;font-weight:500;color:var(--text-primary)}
.sess-meta{font-size:12px;color:var(--text-muted);margin-top:1px}
.sess-nums{display:flex;gap:16px;flex:none}
.sess-num{text-align:right}
.sess-num b{display:block;font-size:13px;font-weight:500;font-variant-numeric:tabular-nums}
.sess-num span{font-size:9.5px;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted)}
.sess-body{padding:0 14px 14px;border-top:1px solid var(--border)}

/* When they actually transmitted, across the session. A count of 40 PTTs
   tells you nothing about whether it was steady work or one busy minute. */
.tx-bar{position:relative;height:26px;margin:12px 0 6px;background:var(--bg-surface);
  border-radius:4px;overflow:hidden}
.tx-bar i{position:absolute;top:0;bottom:0;background:var(--gold);opacity:.75;min-width:1.5px;border-radius:1px}
.tx-scale{display:flex;justify-content:space-between;font-size:10px;color:var(--text-muted);
  font-variant-numeric:tabular-nums;margin-bottom:10px}

.ptt-table{width:100%;font-size:12.5px;border-collapse:collapse}
.ptt-table th{font-size:9.5px;text-transform:uppercase;letter-spacing:.07em;color:var(--text-muted);
  text-align:left;padding:6px 8px 6px 0;font-weight:500;border-bottom:1px solid var(--border)}
.ptt-table td{padding:6px 8px 6px 0;border-bottom:1px solid var(--border);color:var(--text-secondary);
  font-variant-numeric:tabular-nums}
.ptt-table td:last-child,.ptt-table th:last-child{text-align:right}

/* ══ ROC COMPLIANCE ═══════════════════════════════════════════════════════ */
/* No calendar here: breaches are rare and spread over months, so a month
   grid would be almost entirely empty. The list is the whole story.        */
.chip-row{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 14px}
.chip{background:var(--bg-card);border:1px solid var(--border);border-radius:999px;
  padding:5px 12px;font-size:12px;color:var(--text-secondary);cursor:pointer;
  display:inline-flex;align-items:center;gap:7px}
.chip:hover{background:var(--hover);color:var(--text-primary)}
.chip.on{border-color:var(--gold);color:var(--text-primary);font-weight:500}
.chip .n{font-variant-numeric:tabular-nums;color:var(--text-muted);font-size:11px}

.br-card{border:1px solid var(--border);border-left:3px solid var(--red);
  border-radius:var(--radius-md);background:var(--bg-card);box-shadow:var(--shadow-card);
  padding:12px 14px;margin-bottom:8px;display:flex;gap:14px;align-items:flex-start}
.br-card.cleared{border-left-color:var(--border)}
.br-main{flex:1;min-width:0}
.br-top{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}
.br-who{font-size:13.5px;font-weight:500;color:var(--text-primary)}
.br-when{font-size:12px;color:var(--text-muted)}
.br-type{font-size:13px;color:var(--text-primary);margin-top:3px}
.br-note{font-size:12px;color:var(--text-muted);margin-top:2px}
.br-gps{font-size:11.5px;color:var(--text-muted);margin-top:4px;font-variant-numeric:tabular-nums}
.br-gps a{color:var(--gold);text-decoration:none}
.br-gps a:hover{text-decoration:underline}
.br-acts{display:flex;flex-direction:column;gap:6px;flex:none;align-items:flex-end}
.br-btn{background:var(--bg-surface);border:1px solid var(--border);border-radius:var(--radius-sm);
  padding:5px 10px;font-size:11.5px;color:var(--text-secondary);cursor:pointer;white-space:nowrap}
.br-btn:hover:not(:disabled){background:var(--hover);color:var(--text-primary)}
.br-btn:disabled{opacity:.5;cursor:default}
.br-cleared{font-size:11px;color:var(--text-muted);text-align:right;line-height:1.45}

.panel-acts{display:flex;gap:8px}
.panel-acts .br-btn{flex:1;text-align:center;height:36px}

/* Site-access toggles in the user panel */
.acc-row{display:flex;align-items:center;gap:10px;padding:8px 0;
  border-bottom:1px solid var(--border);cursor:pointer}
.acc-row:last-of-type{border-bottom:none}
.acc-row.off{cursor:default;opacity:.55}
.acc-row input{width:15px;height:15px;accent-color:var(--gold);cursor:inherit;margin:0;flex:none}
.acc-name{flex:1;font-size:13px;color:var(--text-primary)}
.acc-state{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted)}

/* ══ SETTINGS ═════════════════════════════════════════════════════════════ */
.set-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(330px,1fr));gap:14px;align-items:start}
.s-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);
  padding:18px 20px;box-shadow:var(--shadow-card)}
.s-card.wide{grid-column:1/-1}
.s-card.danger{border-color:var(--red-bg)}
.s-head{margin-bottom:14px}
.s-head h3{font-size:14px;font-weight:600;margin:0}
.s-head p{font-size:12px;color:var(--text-muted);margin:3px 0 0;line-height:1.5}

.fld{display:block;margin-bottom:12px}
.fld > span{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.06em;
  color:var(--text-muted);margin-bottom:5px}
.fld input,.fld select{width:100%;box-sizing:border-box;height:36px;padding:0 11px;font:inherit;
  font-size:13px;color:var(--text-primary);background:var(--bg-surface);
  border:1px solid var(--border);border-radius:var(--radius-sm)}
.fld input:focus,.fld select:focus{outline:none;border-color:var(--gold)}
.fld.sub{margin:-4px 0 12px 0}
/* Needs to out-specify `.fld > span`, which would otherwise force display:block
   (killing the gap) and pass its uppercase down to the unit. */
.fld > span.fld-unit{display:flex;align-items:center;gap:9px;margin-bottom:0}
.fld-unit input{width:88px;flex:none}
.fld-unit i{font-style:normal;font-size:12px;color:var(--text-muted);
  text-transform:none;letter-spacing:normal}
.fld-row{display:flex;gap:10px}
.fld-row .fld{flex:1;min-width:0}

.tog-row{display:flex;align-items:center;gap:14px;padding:11px 0;border-bottom:1px solid var(--border)}
.tog-row div{flex:1;min-width:0}
.tog-row b{display:block;font-size:13px;font-weight:500}
.tog-row span{display:block;font-size:11.5px;color:var(--text-muted);margin-top:1px}
.tog{flex:none;width:36px;height:20px;border-radius:10px;border:none;cursor:pointer;position:relative;
  background:var(--border);transition:background var(--transition)}
.tog::after{content:"";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;
  background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.25);transition:transform var(--transition)}
.tog.on{background:var(--gold)}
.tog.on::after{transform:translateX(16px)}

.s-foot{display:flex;align-items:center;gap:10px;margin-top:14px}
.btn-p{height:34px;padding:0 16px;font:inherit;font-size:12.5px;font-weight:500;cursor:pointer;
  color:var(--bg-card);background:var(--text-primary);border:none;border-radius:var(--radius-sm)}
.btn-p:hover:not(:disabled){opacity:.88}
.btn-p:disabled{opacity:.5;cursor:default}
.s-msg{font-size:12px;color:var(--text-muted)}
.s-msg.ok{color:var(--green)}
.s-msg.err{color:var(--red)}

.callout{background:var(--amber-bg);border:1px solid var(--amber-bg);border-left:3px solid var(--amber);
  border-radius:var(--radius-sm);padding:10px 13px;font-size:12px;color:var(--text-secondary);
  line-height:1.55;margin-bottom:14px}

/* Add forms stay folded away — the list is what you come here to read. */
.adder{margin-top:14px;border-top:1px solid var(--border);padding-top:12px}
.adder > summary{cursor:pointer;font-size:12.5px;color:var(--gold);list-style:none;
  display:inline-flex;align-items:center;gap:6px}
.adder > summary::-webkit-details-marker{display:none}
.adder > summary::before{content:"+";font-size:14px;line-height:1}
.adder[open] > summary::before{content:"−"}
.adder-body{padding-top:14px}

.typeahead{position:relative;display:block}
.ta-drop{position:absolute;top:calc(100% + 2px);left:0;right:0;z-index:20;max-height:190px;
  overflow-y:auto;background:var(--bg-card);border:1px solid var(--border);
  border-radius:var(--radius-sm);box-shadow:var(--shadow-card)}
.ta-drop button{display:block;width:100%;text-align:left;background:none;border:none;cursor:pointer;
  padding:8px 11px;font:inherit;font-size:12.5px;color:var(--text-secondary);line-height:1.4}
.ta-drop button:hover{background:var(--hover);color:var(--text-primary)}

.roc-row{display:flex;align-items:flex-start;gap:14px;padding:12px 0;border-bottom:1px solid var(--border)}
.roc-row:last-child{border-bottom:none}
.roc-main{flex:1;min-width:0}
.roc-name{font-size:13.5px;font-weight:500}
.roc-meta{font-size:12px;color:var(--text-muted);margin-top:2px}
.roc-ops{margin-top:7px;display:flex;flex-wrap:wrap;gap:5px}

.dz-row{display:flex;flex-wrap:wrap;gap:8px}
.btn-d{height:32px;padding:0 13px;font:inherit;font-size:12px;cursor:pointer;color:var(--red);
  background:var(--red-bg);border:1px solid var(--red-bg);border-radius:var(--radius-sm)}
.btn-d:hover:not(:disabled){border-color:var(--red)}
.btn-d.hard{font-weight:600;border-color:var(--red)}
.btn-d:disabled{opacity:.5;cursor:default}

.totp-state{display:flex;align-items:center;gap:8px;font-size:12.5px;margin-bottom:12px}
#totpQr{background:#fff;display:inline-block;padding:8px;border-radius:6px;margin-bottom:10px}
.totp-secret{display:block;font-family:var(--font-mono);font-size:12px;letter-spacing:1.5px;
  color:var(--gold);background:var(--bg-surface);padding:8px 10px;border-radius:var(--radius-sm);
  margin-bottom:12px;word-break:break-all}
.totp-code{width:130px;letter-spacing:5px;font-family:var(--font-mono);text-align:center}

/* 29 registered drones would otherwise push the danger zone a full screen
   below the fold. Cap it and let the table scroll on its own. */
.reg-scroll{max-height:340px;overflow-y:auto;border:1px solid var(--border);
  border-radius:var(--radius-md)}
.reg-scroll table{margin:0}
.reg-scroll thead th{position:sticky;top:0;background:var(--bg-card);z-index:1;
  box-shadow:inset 0 -1px 0 var(--border)}
.s-count{font-size:11px;color:var(--text-muted);font-variant-numeric:tabular-nums}

/* ══ ANALYTICS ════════════════════════════════════════════════════════════ */
.page-bar{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap}
.pills{display:flex;gap:2px;background:var(--bg-surface);border:1px solid var(--border);
  border-radius:999px;padding:2px}
.pills button{background:none;border:none;cursor:pointer;font:inherit;font-size:12px;
  padding:5px 13px;border-radius:999px;color:var(--text-secondary)}
.pills button:hover{color:var(--text-primary)}
.pills button.on{background:var(--bg-card);color:var(--text-primary);font-weight:500;
  box-shadow:var(--shadow-card)}

.kpi-strip{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;margin:16px 0 14px}
.kpi{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);
  padding:14px 16px;box-shadow:var(--shadow-card)}
.kpi .v{font-size:26px;font-weight:500;line-height:1.05;font-variant-numeric:tabular-nums}
.kpi .l{font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--text-muted);margin-top:5px}
.kpi .d{font-size:11.5px;margin-top:6px;color:var(--text-muted)}
.kpi .d.up{color:var(--green)} .kpi .d.down{color:var(--red)}

.an-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:14px;align-items:start}
.a-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);
  padding:16px 18px;box-shadow:var(--shadow-card)}
.a-card.wide{grid-column:1/-1}
.a-title{display:flex;align-items:baseline;justify-content:space-between;gap:10px;
  font-size:13px;font-weight:600;margin-bottom:12px}
.a-title span{font-size:10.5px;font-weight:400;text-transform:uppercase;letter-spacing:.06em;
  color:var(--text-muted)}

/* uptime + generic labelled bar rows */
.br{display:grid;grid-template-columns:1fr 96px 46px;align-items:center;gap:10px;padding:7px 0}
.br-name{font-size:12.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.br-track{height:6px;border-radius:3px;background:var(--bg-surface);overflow:hidden}
.br-fill{height:100%;border-radius:3px}
.br-val{font-size:12px;text-align:right;font-variant-numeric:tabular-nums;color:var(--text-secondary)}

/* daily column chart */
.cols{display:flex;align-items:flex-end;gap:3px;height:110px}
.cols i{flex:1;min-width:2px;background:var(--gold);border-radius:2px 2px 0 0;opacity:.85;
  display:block;position:relative}
.cols i:hover{opacity:1}
.cols i.zero{background:var(--border);opacity:1}
.col-x{display:flex;gap:3px;margin-top:6px}
.col-x span{flex:1;min-width:0;font-size:9.5px;color:var(--text-muted);text-align:center;
  overflow:hidden;white-space:nowrap}

/* 24h heartbeat sparkline per site */
.spark-row{padding:9px 0;border-bottom:1px solid var(--border)}
.spark-row:last-child{border-bottom:none}
.spark-head{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:5px}
.spark-head b{font-size:12.5px;font-weight:500}
.spark-head span{font-size:11.5px;color:var(--text-muted);font-variant-numeric:tabular-nums}
.spark{display:flex;gap:1.5px;height:26px;align-items:flex-end}
.spark i{flex:1;border-radius:1px;display:block}
.spark i.up{background:var(--green);opacity:.55;height:100%}
.spark i.down{background:var(--red);opacity:.4;height:26%}

/* peak-hours heatmap */
.hm{display:grid;grid-template-columns:32px repeat(24,1fr);gap:2px;align-items:center;max-width:450px}
.hm-lbl{font-size:10px;color:var(--text-muted);text-align:right;padding-right:4px}
/* Fixed square cells: aspect-ratio made them wide rectangles that read as
   vertical stripes rather than a day-by-hour grid. */
.hm-cell{height:14px;border-radius:2px;background:var(--bg-surface)}
.hm-hours{display:grid;grid-template-columns:32px repeat(24,1fr);gap:2px;margin-top:4px;max-width:450px}
.hm-hours span{font-size:9px;color:var(--text-muted);text-align:center}
.hm-key{display:flex;align-items:center;gap:6px;margin-top:10px;font-size:10.5px;color:var(--text-muted)}
.hm-key i{width:13px;height:13px;border-radius:2px;display:block}

/* ══ NOTAMS ═══════════════════════════════════════════════════════════════ */
.adder-card{padding:0;margin:16px 0 14px}
.adder-card > summary{cursor:pointer;list-style:none;padding:14px 18px;font-size:13px;
  font-weight:600;display:flex;align-items:center;gap:7px}
.adder-card > summary::-webkit-details-marker{display:none}
.adder-card > summary::before{content:"+";font-size:15px;line-height:1;color:var(--gold)}
.adder-card[open] > summary::before{content:"−"}
.adder-card .adder-body{padding:0 18px 16px;border-top:1px solid var(--border);padding-top:14px}
.adder-card textarea{width:100%;box-sizing:border-box;font-family:var(--font-mono);font-size:12px;
  line-height:1.5;padding:10px 12px;color:var(--text-primary);background:var(--bg-surface);
  border:1px solid var(--border);border-radius:var(--radius-sm);resize:vertical}
.adder-card textarea:focus{outline:none;border-color:var(--gold)}

.nt-counts{display:flex;gap:8px;flex-wrap:wrap}
.nt-bar{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 0 10px}
.nt-all{display:inline-flex;align-items:center;gap:8px;cursor:pointer;font-size:12.5px;
  color:var(--text-secondary)}
.nt-all input{width:15px;height:15px;accent-color:var(--gold);margin:0;cursor:pointer}

.nt-card{border:1px solid var(--border);border-left:3px solid var(--border);
  border-radius:var(--radius-md);background:var(--bg-card);box-shadow:var(--shadow-card);
  margin-bottom:8px;overflow:hidden}
.nt-card.active{border-left-color:var(--green)}
.nt-card.upcoming{border-left-color:var(--amber)}
.nt-card.expired{opacity:.62}
.nt-head{display:flex;align-items:flex-start;gap:12px;padding:11px 14px}
.nt-main{flex:1;min-width:0}
.nt-id{font-family:var(--font-mono);font-size:13px;font-weight:600}
.nt-when{font-size:11.5px;color:var(--text-muted);margin-top:2px;font-variant-numeric:tabular-nums}
.nt-where{font-size:11.5px;color:var(--text-muted);margin-top:3px}
.nt-acts{display:flex;align-items:center;gap:10px;flex:none}
.nt-show{display:inline-flex;align-items:center;gap:6px;cursor:pointer;font-size:11px;
  text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted)}
.nt-show input{width:14px;height:14px;accent-color:var(--gold);margin:0;cursor:pointer}
.nt-body{padding:0 14px 12px}
.nt-body pre{margin:0;font-family:var(--font-mono);font-size:11.5px;line-height:1.55;
  white-space:pre-wrap;word-break:break-word;color:var(--text-secondary);
  background:var(--bg-surface);padding:10px 12px;border-radius:var(--radius-sm);
  max-height:260px;overflow:auto}
.nt-toggle{background:none;border:none;padding:0;cursor:pointer;font-size:11.5px;color:var(--gold)}

/* ══ ACTIVE DRONES ════════════════════════════════════════════════════════ */
.dr-live{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--text-muted)}
.dr-live .dot{width:7px;height:7px;border-radius:50%;background:var(--green);
  box-shadow:0 0 0 3px var(--green-bg)}
.dr-live .dot.idle{background:var(--text-muted);box-shadow:none}

.dr-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(330px,1fr));gap:14px;align-items:start}
.dr-card{background:var(--bg-card);border:1px solid var(--border);border-left:3px solid var(--green);
  border-radius:var(--radius-lg);padding:16px 18px;box-shadow:var(--shadow-card)}
.dr-card.stale{border-left-color:var(--amber)}
.dr-head{display:flex;align-items:flex-start;gap:10px;margin-bottom:12px}
.dr-name{flex:1;min-width:0}
.dr-name b{display:block;font-size:14px;font-weight:600}
.dr-name span{display:block;font-size:11.5px;color:var(--text-muted);margin-top:2px;
  font-family:var(--font-mono)}
.dr-badges{display:flex;flex-wrap:wrap;gap:5px;margin-bottom:12px}
.dr-tel{display:grid;grid-template-columns:repeat(auto-fit,minmax(96px,1fr));gap:10px 12px}
.dr-t b{display:block;font-size:14px;font-weight:500;font-variant-numeric:tabular-nums;line-height:1.2}
.dr-t span{display:block;font-size:10px;text-transform:uppercase;letter-spacing:.06em;
  color:var(--text-muted);margin-top:3px}
.dr-t.dim b{color:var(--text-muted);font-weight:400}
.dr-pos{margin-top:12px;padding-top:11px;border-top:1px solid var(--border);
  font-size:12px;color:var(--text-muted);font-variant-numeric:tabular-nums}
.dr-pos a{color:var(--gold);text-decoration:none}
.dr-pos a:hover{text-decoration:underline}

/* The aircraft a dock holds, drawn inside the dock's card — they are one unit. */
.dr-sub{margin-top:14px;padding:12px 14px;border-radius:var(--radius-md);
  background:var(--bg-surface);border:1px solid var(--border)}
.dr-sub-head{display:flex;align-items:center;justify-content:space-between;gap:10px}
.dr-sub-name{font-size:13px;font-weight:600}
.dr-sub-meta{font-size:11px;color:var(--text-muted);margin-top:2px;font-family:var(--font-mono)}

/* ══ MAINTENANCE ══════════════════════════════════════════════════════════ */
.panel.wide{width:min(600px,96vw)}

.mt-row{display:flex;align-items:flex-start;gap:14px;padding:13px 15px;background:var(--bg-card);
  border:1px solid var(--border);border-left:3px solid var(--border);
  border-radius:var(--radius-md);box-shadow:var(--shadow-card);margin-bottom:8px;
  cursor:pointer;transition:background var(--transition)}
.mt-row:hover{background:var(--hover)}
.mt-row.overdue{border-left-color:var(--red)}
.mt-row.soon{border-left-color:var(--amber)}
.mt-row.done{border-left-color:var(--green);cursor:pointer}
.mt-main{flex:1;min-width:0}
.mt-title{font-size:13.5px;font-weight:500}
.mt-meta{font-size:12px;color:var(--text-muted);margin-top:2px}
.mt-right{flex:none;text-align:right}
.mt-when{font-size:12.5px;font-variant-numeric:tabular-nums}
.mt-when b{font-weight:600}
.mt-sub{font-size:11px;color:var(--text-muted);margin-top:2px}

.mt-group{font-size:11px;text-transform:uppercase;letter-spacing:.07em;color:var(--text-muted);
  margin:16px 0 8px}
.mt-group:first-child{margin-top:0}

/* checklist inside the visit panel */
.ck-cat{font-size:11px;text-transform:uppercase;letter-spacing:.07em;color:var(--gold);
  margin:16px 0 6px;padding-bottom:5px;border-bottom:1px solid var(--border)}
.ck-item{padding:9px 0;border-bottom:1px solid var(--border)}
.ck-item:last-child{border-bottom:none}
.ck-label{font-size:13px;margin-bottom:6px}
.ck-opts{display:flex;gap:6px}
.ck-opts button{flex:1;height:30px;font:inherit;font-size:12px;cursor:pointer;
  background:var(--bg-surface);border:1px solid var(--border);border-radius:var(--radius-sm);
  color:var(--text-secondary)}
.ck-opts button:hover{color:var(--text-primary)}
.ck-opts button.on[data-r="pass"]{background:var(--green-bg);border-color:var(--green);color:var(--green);font-weight:600}
.ck-opts button.on[data-r="fail"]{background:var(--red-bg);border-color:var(--red);color:var(--red);font-weight:600}
.ck-opts button.on[data-r="na"]{background:var(--bg-surface);border-color:var(--text-muted);color:var(--text-primary);font-weight:600}
.ck-note{width:100%;box-sizing:border-box;margin-top:7px;padding:7px 9px;font:inherit;font-size:12.5px;
  color:var(--text-primary);background:var(--bg-surface);border:1px solid var(--border);
  border-radius:var(--radius-sm);resize:vertical}
.ck-note:focus{outline:none;border-color:var(--gold)}

.ck-res{display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:600}
.ck-res.pass{color:var(--green)} .ck-res.fail{color:var(--red)} .ck-res.na{color:var(--text-muted)}
.ck-read{display:flex;align-items:flex-start;gap:12px;padding:8px 0;border-bottom:1px solid var(--border)}
.ck-read:last-child{border-bottom:none}
.ck-read div{flex:1;min-width:0}
.ck-read .n{font-size:11.5px;color:var(--text-muted);margin-top:3px}

.mt-photos{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
.mt-photos img{width:96px;height:96px;object-fit:cover;border-radius:var(--radius-sm);
  border:1px solid var(--border)}

/* template editor */
.tp-item{display:flex;align-items:center;gap:8px;padding:6px 0}
.tp-item input{flex:1;height:32px;padding:0 10px;font:inherit;font-size:12.5px;color:var(--text-primary);
  background:var(--bg-surface);border:1px solid var(--border);border-radius:var(--radius-sm)}
.tp-item input:focus{outline:none;border-color:var(--gold)}
.tp-x{background:none;border:none;cursor:pointer;color:var(--text-muted);font-size:15px;
  line-height:1;padding:4px 6px;flex:none}
.tp-x:hover{color:var(--red)}
.tp-sec{margin-top:14px;padding-top:12px;border-top:1px solid var(--border)}
.tp-sec-head{display:flex;align-items:center;gap:8px;margin-bottom:4px}
.tp-sec-head input{flex:1;height:32px;padding:0 10px;font:inherit;font-size:12px;font-weight:600;
  text-transform:uppercase;letter-spacing:.06em;color:var(--gold);background:transparent;
  border:none;border-bottom:1px solid var(--border);border-radius:0}
.tp-sec-head input:focus{outline:none;border-bottom-color:var(--gold)}
.link-add{background:none;border:none;padding:0;cursor:pointer;font:inherit;font-size:12px;color:var(--gold)}
.link-add:hover{text-decoration:underline}

/* ══ NETWORK MAP ══════════════════════════════════════════════════════════ */
/* The map is the landing view and wants the whole area, so this one view
   escapes the content padding rather than sitting in a card. It is absolute
   within .content — which is why .content carries position:relative — so it
   fills the pane without covering the sidebar or the top bar. */
.view.map-view.on{display:flex;flex-direction:column;position:absolute;inset:0;padding:0;
  overflow:hidden}
#mapWrap{position:relative;flex:1;min-height:0}
#netMap{position:absolute;inset:0;background:var(--bg-surface)}
.leaflet-container{font-family:var(--font);background:var(--bg-surface)}
.leaflet-control-attribution{font-size:9px!important;background:var(--chrome)!important;
  color:var(--text-muted)!important}
.leaflet-control-attribution a{color:var(--text-secondary)!important}
.leaflet-bar a{background:var(--bg-card)!important;color:var(--text-secondary)!important;
  border-color:var(--border)!important}
.leaflet-bar a:hover{background:var(--hover)!important;color:var(--text-primary)!important}

/* Site pin.
   --green in this palette is #15803d, chosen so text passes contrast on white.
   As a map pin over imagery or a dark basemap it reads as a dull olive, so the
   pins use a vivid green of their own and glow with it. A site that is up
   should be findable at a glance on a map covering half a state.

   Only online pulses. Offline is not alive and should not animate — a blinking
   red dot reads as "alerting", which is a different claim from "not there". */
/* display:block matters — this is a <span>, and inline elements ignore width
   and height, so the pin rendered 0x0: all that showed was its 2.5px border,
   with the glow cast from a box with no size. */
.site-pin{position:relative;display:block;width:18px;height:18px;overflow:visible}
.site-pin i{position:absolute;inset:0;border-radius:50%;display:block;z-index:2;
  border:2.5px solid #fff}
/* A site that hosts a dock carries a square inside the dot. Without it a
   healthy dock site and a healthy radio-only site are the same green circle,
   and you cannot tell from the map which places have aircraft. */
.site-pin.has-dock i::after{content:"";position:absolute;top:50%;left:50%;width:6px;height:6px;
  transform:translate(-50%,-50%);background:#fff;border-radius:1.5px;opacity:.95}
.lg-dot.site-dock-key{background:#22c55e;position:relative;box-shadow:0 0 6px 1px rgba(34,197,94,.7)}
.lg-dot.site-dock-key::after{content:"";position:absolute;top:50%;left:50%;width:4px;height:4px;
  transform:translate(-50%,-50%);background:#fff;border-radius:1px}
.site-pin.online i{background:#22c55e;
  box-shadow:0 0 0 1px rgba(11,18,32,.25),0 0 10px 2px rgba(34,197,94,.85),0 0 22px 6px rgba(34,197,94,.45)}
.site-pin.warn i{background:#f59e0b;
  box-shadow:0 0 0 1px rgba(11,18,32,.25),0 0 9px 2px rgba(245,158,11,.7)}
.site-pin.offline i{background:#ef4444;
  box-shadow:0 0 0 1px rgba(11,18,32,.25),0 0 8px 1px rgba(239,68,68,.6)}
/* Filled halo rather than a hairline ring — a 1px ring disappears against
   street detail at low zoom. */
.site-pin.online::after{content:"";position:absolute;inset:0;border-radius:50%;z-index:1;
  background:rgba(34,197,94,.45);animation:pinPulse 2.2s ease-out infinite}
@keyframes pinPulse{0%{transform:scale(1);opacity:.75}100%{transform:scale(3.6);opacity:0}}
.site-label{font-size:11px;font-weight:600;color:var(--text-primary);white-space:nowrap;
  background:var(--chrome);backdrop-filter:blur(8px);border:1px solid var(--border);
  padding:2px 7px;border-radius:999px;box-shadow:var(--shadow-card);transform:translate(12px,-9px)}

.map-legend{position:absolute;left:14px;bottom:22px;z-index:500;background:var(--chrome);
  backdrop-filter:blur(10px);border:1px solid var(--border);border-radius:var(--radius-md);
  padding:10px 13px;box-shadow:var(--shadow-card);font-size:11.5px;min-width:150px}
.map-legend .lg-row{display:flex;align-items:center;gap:8px;padding:2.5px 0;color:var(--text-secondary)}
.map-legend .lg-dot{width:9px;height:9px;border-radius:50%;flex:none}
.map-legend .lg-n{margin-left:auto;font-variant-numeric:tabular-nums;color:var(--text-primary);
  font-weight:600}
.map-legend h4{font-size:10px;text-transform:uppercase;letter-spacing:.07em;color:var(--text-muted);
  margin:0 0 6px;font-weight:600}

.map-basemap{position:absolute;right:14px;top:14px;z-index:500;background:var(--chrome);
  backdrop-filter:blur(10px);box-shadow:var(--shadow-card)}
/* On imagery the translucent chrome loses against a bright roof or a dark
   paddock, so labels get a solid ground instead of a blurred one. */
.map-sat .site-label{background:var(--bg-card);border-color:var(--border-mid)}
.map-sat .map-legend,.map-sat .map-basemap{background:var(--bg-card)}

/* Leaflet stacks its own panes from 400 and its controls up to 1000, so a
   detail panel at z-index 41 opened *behind* the map — visible only as a strip
   above it. Everything of ours that overlays the page has to clear 1000. */
.leaflet-pane,.leaflet-top,.leaflet-bottom{z-index:auto}
.leaflet-container .leaflet-control{z-index:600}

/* ── map: drones, docks, ROC ─────────────────────────────────────────────── */
.dr-mark{position:relative;display:block;width:30px;height:30px}
.dr-mark svg{position:absolute;inset:0;width:30px;height:30px;overflow:visible;
  filter:drop-shadow(0 1px 3px rgba(11,18,32,.5))}
.dr-mark.stale{opacity:.45}
/* Site labels sit to the right of their pin. A dock is often metres from its
   site, so its tag goes BELOW the marker instead — otherwise the two land on
   the same pixel and overprint each other. */
.dr-tag{font-size:10.5px;font-weight:600;white-space:nowrap;color:var(--text-primary);
  background:var(--bg-card);border:1px solid var(--border-mid);padding:2px 7px;
  border-radius:999px;box-shadow:var(--shadow-card);display:inline-block;
  transform:translate(-50%,17px)}
.roc-tag{font-size:10.5px;font-weight:600;white-space:nowrap;color:#8a6d1f;
  background:var(--bg-card);border:1px solid rgba(180,131,9,.45);padding:2px 8px;
  border-radius:999px;box-shadow:var(--shadow-card);transform:translate(10px,-8px)}
.map-sat .dr-tag,.map-sat .roc-tag{background:var(--bg-card)}

.nt-pin{display:flex;align-items:center;justify-content:center;width:30px;height:30px;
  border-radius:50%;border:2px solid currentColor;background:var(--bg-card);
  font-size:15px;font-weight:700;box-shadow:var(--shadow-card)}

/* ── map: ADS-B traffic ──────────────────────────────────────────────────── */
/* The silhouette comes from the shared icon set, so it is 32px like the live
   portal's and the label sits beside it rather than under it — at 33fps a tag
   centred under a moving marker reads as a second thing sliding about. */
.ac-mark{position:relative;width:32px;height:32px;overflow:visible}
/* The silhouettes are gold, which was chosen against the dark map the live
   portal always uses. On the light basemap gold on near-white is barely there,
   so the icon carries its own dark outline — a tight halo that reads as an
   edge, not a shadow. It costs nothing on dark and rescues it on light. */
/* Light is the default here, so light is the base case: the gold is knocked
   back to a bronze that holds against a near-white basemap, plus a tight dark
   edge. Untouched on dark, where the colour was chosen. */
.ac-mark svg{width:32px;height:32px;overflow:visible;
  filter:brightness(.68) saturate(1.35)
         drop-shadow(0 0 1px rgba(255,255,255,.9))
         drop-shadow(0 1px 3px rgba(11,18,32,.45))}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .ac-mark svg{
    filter:drop-shadow(0 0 1px rgba(11,18,32,.9)) drop-shadow(0 1px 3px rgba(11,18,32,.55))}
}
:root[data-theme="dark"] .ac-mark svg{
  filter:drop-shadow(0 0 1px rgba(11,18,32,.9)) drop-shadow(0 1px 3px rgba(11,18,32,.55))}
/* Beside the aircraft, clear of it — close enough to belong, far enough not to
   sit on the wing. */
.ac-label{position:absolute;top:50%;left:27px;transform:translateY(-50%);
  white-space:nowrap;pointer-events:none;line-height:1.35;
  font-family:var(--font-mono);font-size:9px;padding:1px 5px;border-radius:3px;
  color:var(--text-primary);background:var(--chrome);border:1px solid var(--border);
  font-variant-numeric:tabular-nums}
.ac-label b{display:block;font-weight:700}
.ac-label span{display:block;color:var(--text-secondary)}
.map-sat .ac-label{background:var(--bg-card)}
/* Helicopters are drawn to fill the same box as an airliner, which makes a
   light single look larger than a 737. Scaled to something honest, with the
   label brought in to match. */
.ac-mark.heli svg{width:21px;height:21px}
.ac-mark.heli{width:21px;height:21px}
.ac-mark.heli .ac-label{left:19px}

/* Rotors turn, matching the live portal and the device UIs. */
.heli-rotor{transform-box:fill-box;transform-origin:center;
  animation:heliSpin .8s linear infinite}
@keyframes heliSpin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}

.map-traffic{position:absolute;right:14px;bottom:22px;z-index:500;max-width:250px;
  background:var(--chrome);backdrop-filter:blur(10px);border:1px solid var(--border);
  border-radius:var(--radius-md);padding:10px 12px;box-shadow:var(--shadow-card);font-size:11.5px}
.map-sat .map-traffic{background:var(--bg-card)}
.map-traffic h4{font-size:10px;text-transform:uppercase;letter-spacing:.07em;
  color:var(--text-muted);margin:0 0 6px;font-weight:600}

/* ══ RADIO MODE ═══════════════════════════════════════════════════════════
   Entering a site is a mode of the map, not another page. The nav goes away,
   the map keeps only what an operator of THIS radio needs, and the controls
   arrive as panels over it that can be moved and resized. */
:root.radio .sidebar,
:root.radio .topbar-title,
:root.radio .topbar-divider{display:none}
/* The sidebar is display:none, so it stops being a grid item — and .content
   slides into the first column, which --sidebar-w had set to 0. Collapse the
   grid to a single column instead of trying to zero the first one. */
:root.radio .body{grid-template-columns:1fr}
/* Both are network-wide readouts — other sites' docks, ROCs and NOTAM counts.
   An operator of this radio has no use for them, and the traffic list is
   already a panel they can move. */
:root.radio .map-legend,
:root.radio .map-traffic,
:root.radio .map-basemap{display:none}
:root.radio .rail-toggle{display:none}

/* wraps rather than crushes: on a portrait monitor the health cells need a
   second and third row, and squeezing them onto one made the values unreadable. */
.radio-bar{position:absolute;top:16px;left:16px;right:16px;z-index:520;display:flex;
  align-items:center;flex-wrap:wrap;gap:12px 12px;background:var(--chrome);backdrop-filter:blur(12px);
  border:1px solid var(--border);border-radius:var(--radius-lg);padding:9px 12px;
  box-shadow:var(--shadow-card)}
.rb-exit{height:30px;flex:none;display:inline-flex;align-items:center;gap:5px;
  padding:0 10px 0 6px;background:none;border:1px solid var(--border);
  border-radius:999px;color:var(--text-secondary);cursor:pointer;font:inherit;font-size:12px}
.rb-exit span{font-weight:500}
.rb-exit:hover{background:var(--hover);color:var(--text-primary)}
.rb-name{min-width:0}
.rb-name b{display:block;font-size:14px;font-weight:600;line-height:1.15}
.rb-name span{display:block;font-size:11px;color:var(--text-muted)}
/* Site health belongs in the bar, not a card you have to go and open — it is
   glanced at, never interacted with. */
.rb-health{display:flex;gap:18px;flex-wrap:wrap;margin-left:14px;padding-left:16px;
  border-left:1px solid var(--border)}
.rb-h{min-width:0}
.rb-h b{display:block;font-size:13px;font-weight:600;line-height:1.15;
  font-variant-numeric:tabular-nums}
.rb-h span{display:block;font-size:9.5px;text-transform:uppercase;letter-spacing:.07em;
  color:var(--text-muted);margin-top:1px}
.rb-panels{margin-left:auto;display:flex;gap:4px;flex-wrap:wrap}
.rb-panels button{background:var(--bg-surface);border:1px solid var(--border);cursor:pointer;
  font:inherit;font-size:11.5px;color:var(--text-secondary);padding:4px 10px;
  border-radius:999px}
.rb-panels button:hover{color:var(--text-primary)}
.rb-panels button.on{border-color:var(--gold);color:var(--text-primary);font-weight:500}
/* Not a toggle like the ones beside it, so it does not look like one. */
.rb-panels .rb-reset{background:none;border-style:dashed;margin-left:6px}

/* Portrait and other narrow windows: the bar becomes rows rather than columns,
   and the panel toggles get their own full-width line instead of being pushed
   off the end. */
@media (max-width: 900px){
  .radio-bar{gap:8px 10px}
  .rb-health{margin-left:0;padding-left:0;border-left:none;gap:14px;
    flex-basis:100%;order:3}
  .rb-panels{margin-left:auto;order:2}
}

/* floating panels */
.fp{position:absolute;z-index:530;min-width:210px;min-height:120px;display:flex;
  flex-direction:column;background:var(--chrome);backdrop-filter:blur(12px);
  border:1px solid var(--border);border-radius:var(--radius-lg);
  box-shadow:0 6px 24px rgba(11,18,32,.18);overflow:hidden}
.map-sat .fp,.map-sat .radio-bar{background:var(--bg-card)}
.fp-hdr{display:flex;align-items:center;gap:8px;padding:7px 10px;cursor:grab;
  border-bottom:1px solid var(--border);user-select:none;flex:none}
.fp-hdr:active{cursor:grabbing}
.fp-hdr h4{flex:1;margin:0;font-size:11px;font-weight:600;text-transform:uppercase;
  letter-spacing:.07em;color:var(--text-muted)}
.fp-x{background:none;border:none;cursor:pointer;color:var(--text-muted);font-size:14px;
  line-height:1;padding:2px 4px}
.fp-x:hover{color:var(--text-primary)}
.fp-body{flex:1;min-height:0;overflow:auto;padding:10px 11px}
/* Both bottom corners resize. A panel pushed against the right-hand edge could
   only ever be made narrower from the right grip; the left one grows it back
   into the space it has. */
.fp-grip{position:absolute;right:0;bottom:0;width:16px;height:16px;cursor:nwse-resize}
.fp-grip-l{position:absolute;left:0;bottom:0;width:16px;height:16px;cursor:nesw-resize}
.fp-grip-l::after{content:"";position:absolute;left:3px;bottom:3px;width:7px;height:7px;
  border-left:1.5px solid var(--border-mid);border-bottom:1.5px solid var(--border-mid)}
.fp-grip::after{content:"";position:absolute;right:3px;bottom:3px;width:7px;height:7px;
  border-right:2px solid var(--text-muted);border-bottom:2px solid var(--text-muted);
  opacity:.6}

/* radio controls */
.rc-freq{font-size:30px;font-weight:600;letter-spacing:.02em;text-align:center;
  font-variant-numeric:tabular-nums;margin:2px 0 10px}
.rc-row{display:flex;align-items:center;gap:8px;margin-bottom:9px}
.rc-row select{flex:1;height:32px;padding:0 9px;font:inherit;font-size:12.5px;
  color:var(--text-primary);background:var(--bg-surface);border:1px solid var(--border);
  border-radius:var(--radius-sm)}
.rc-ptt{width:100%;height:58px;border-radius:var(--radius-md);border:2px solid var(--green);
  background:var(--green-bg);color:var(--green);font:inherit;font-size:13px;font-weight:700;
  letter-spacing:.08em;text-transform:uppercase;cursor:pointer}
.rc-ptt:disabled{border-color:var(--border);background:var(--bg-surface);
  color:var(--text-muted);cursor:not-allowed}
.rc-ptt.live{background:var(--red);border-color:var(--red);color:#fff}
.rc-owner{font-size:11px;color:var(--text-muted);margin-top:5px;text-align:center}

/* ── radio controls ─────────────────────────────────────────────────────── */
.rc-power{display:flex;align-items:center;gap:9px;width:100%;padding:8px 11px;
  margin-bottom:9px;border-radius:var(--radius-md);cursor:pointer;font:inherit;
  border:1px solid var(--border);background:var(--bg-surface);color:var(--text-secondary)}
.rc-power:hover{border-color:var(--border-mid)}
.rc-power.on{border-color:var(--green);background:var(--green-bg);color:var(--green)}
.rc-power .dot{width:9px;height:9px;border-radius:50%;background:var(--text-muted);flex:none}
.rc-power.on .dot{background:var(--green);box-shadow:0 0 8px 1px rgba(34,197,94,.8)}
.rc-power b{font-size:13px;font-weight:600}
.rc-power i{font-style:normal;margin-left:auto;font-size:11px;opacity:.75}

.rc-freq-row{display:flex;align-items:baseline;justify-content:center;gap:6px;margin:0 0 2px}
.rc-freq-in{width:136px;padding:1px 4px;font:inherit;font-family:var(--font-mono);
  font-size:24px;font-weight:600;text-align:center;font-variant-numeric:tabular-nums;
  color:var(--text-primary);background:transparent;border:none;
  border-bottom:1px dashed var(--border-mid);border-radius:0}
.rc-freq-in:focus{outline:none;border-bottom-color:var(--gold)}
.rc-mhz{font-size:11px;color:var(--text-muted)}
.rc-hint{font-size:10.5px;color:var(--text-muted);line-height:1.35;margin:3px 0 6px}
.rc-lbl{display:block;font-size:10px;text-transform:uppercase;letter-spacing:.07em;
  color:var(--text-muted);margin-bottom:4px}
.rc-select{width:100%;height:32px;padding:0 9px;margin-bottom:10px;font:inherit;font-size:12.5px;
  color:var(--text-primary);background:var(--bg-surface);border:1px solid var(--border);
  border-radius:var(--radius-sm)}
.rc-select:focus{outline:none;border-color:var(--gold)}
.rc-range{width:100%;margin:1px 0 0;accent-color:var(--gold);display:block}
.rc-sql{margin-bottom:10px}
.rc-sql-top{display:flex;align-items:center;gap:8px;margin-bottom:6px}
.rc-sql-top label{flex:1;font-size:10px;text-transform:uppercase;letter-spacing:.07em;
  color:var(--text-muted)}
.rc-sql-top{margin-bottom:2px}
.rc-sql-top b{font-size:13px;font-variant-numeric:tabular-nums}
.rc-step{width:24px;height:24px;flex:none;border-radius:var(--radius-sm);cursor:pointer;
  border:1px solid var(--border);background:var(--bg-surface);color:var(--text-secondary);
  font:inherit;font-size:14px;line-height:1}
.rc-step:hover:not(:disabled){color:var(--text-primary);border-color:var(--border-mid)}
.rc-step:disabled{opacity:.4;cursor:not-allowed}
.rc-sql-bar{display:flex;gap:2px;height:8px}
.rc-sql-bar i{flex:1;border-radius:1px;background:var(--border)}
.rc-sql-bar i.lit{background:var(--gold)}
.rc-sql-bar i.hot{background:var(--amber)}

/* Panels live in their own layer that starts below the bar, so one can never
   be dragged over the health readout and hide it. */
/* Starts below the radio bar and fills the rest of the map. It needs an
   explicit top: with only left/right/bottom the layer collapsed to zero height
   and every panel inside it rendered off-screen. */
/* top is set from the bar's measured height, not a constant: the health
   readout wraps to two or three rows on a narrow or portrait window, and a
   fixed 76px left the panels sitting underneath it. */
.fp-layer{position:absolute;left:0;right:0;top:var(--rb-h,76px);bottom:0;z-index:530;
  pointer-events:none;overflow:hidden}
.fp-layer .fp{pointer-events:auto}
:root.radio .leaflet-control-zoom{display:none}

/* Other sites stay on the map while operating one radio — you should be able
   to see the network — but dimmed, because they are not what you are on. */
.site-pin.dimmed{opacity:.42;transform:scale(.72)}
.site-pin.dimmed::after{display:none}
.site-label.dimmed{opacity:.5;font-weight:500}

/* RX and TX rings on the site being operated. Receiving is the radio hearing
   something; transmitting is this operator keying it. They are different
   events and must not look the same. */
/* Ring direction carries the meaning: receiving pulls inward because the
   signal is arriving, transmitting pushes outward because it is leaving.
   Green in, red out — and never both, since they cannot happen at once on a
   half-duplex radio. */
/* Thin. The ring is a movement cue, not a highlight — weight makes it read as
   a halo stuck to the pin and buries the site underneath it.

   The size is animated, not transform: scale(). A transform scales the border
   with everything else, so a 1px ring drawn at scale(9) renders nine pixels
   thick. That is why RX kept reading heavy however thin the border was set —
   it sweeps inward, so it is at its widest exactly when it first appears and
   the opacity ramp brings it up. TX never showed the problem because it starts
   small and has faded out by the time it is large.

   Animating inset costs a little layout per frame, confined to two spans on
   the one pin being operated, and buys a stroke that is the same weight at
   every size. */
.site-pin .ring,.site-pin .ring2{position:absolute;inset:-8px;border-radius:50%;
  pointer-events:none;border:1px solid transparent}
.site-pin.rx .ring{border-color:#22c55e;animation:ringIn 2s cubic-bezier(.3,0,.5,1) infinite}
.site-pin.tx .ring{border-width:1.5px;border-color:#ef4444;animation:ringOut 1.1s cubic-bezier(.3,0,.5,1) infinite}
/* A second ring, offset in time, so the movement reads as a sequence rather
   than a single pulse. */
.site-pin.rx .ring2{border-color:#22c55e;animation:ringIn 2s cubic-bezier(.3,0,.5,1) infinite;
  animation-delay:1s}
.site-pin.tx .ring2{border-width:1.5px;border-color:#ef4444;animation:ringOut 1.1s cubic-bezier(.3,0,.5,1) infinite;
  animation-delay:.55s}
/* Travel, not thickness — the ring has to sweep far enough from the site to
   read as movement across the map rather than a halo on the pin. The pin is
   18px, so inset -136px is a ring about 290px across. */
@keyframes ringIn{0%{inset:-136px;opacity:0}
  18%{opacity:.55}100%{inset:-4px;opacity:0}}
@keyframes ringOut{0%{inset:-4px;opacity:.95}
  100%{inset:-136px;opacity:0}}

/* Transmitting is its own state on the power button — not a louder version of
   receiving. Red, because it is the one state where you are on air. */
.rc-power.transmitting{border-color:#ef4444;background:rgba(239,68,68,.14);color:#dc2626;
  animation:txGlow .8s ease-in-out infinite}
.rc-power.transmitting .dot{background:#ef4444;box-shadow:0 0 10px 2px rgba(239,68,68,.9)}
@keyframes txGlow{0%,100%{box-shadow:0 0 0 0 rgba(239,68,68,.35)}
  50%{box-shadow:0 0 16px 4px rgba(239,68,68,.5)}}

/* Radio power button: off / listening / receiving. Receiving glows because it
   is the one state that means something is happening right now. */
.rc-power.listening{border-color:var(--gold);background:var(--gold-bg);color:var(--text-primary)}
.rc-power.receiving{border-color:#22c55e;background:rgba(34,197,94,.14);color:#16a34a;
  animation:rxGlow 1.1s ease-in-out infinite}
.rc-power.receiving .dot{background:#22c55e;box-shadow:0 0 10px 2px rgba(34,197,94,.9)}
@keyframes rxGlow{0%,100%{box-shadow:0 0 0 0 rgba(34,197,94,.35)}
  50%{box-shadow:0 0 14px 3px rgba(34,197,94,.45)}}

/* CASA 15-minute broadcast timer */
.tx-timer{display:flex;align-items:baseline;gap:7px;padding:6px 9px;margin-bottom:9px;
  border-radius:var(--radius-sm);background:var(--bg-surface);border:1px solid var(--border)}
.tx-timer b{font-family:var(--font-mono);font-size:15px;font-variant-numeric:tabular-nums}
.tx-timer span{font-size:10px;text-transform:uppercase;letter-spacing:.07em;color:var(--text-muted)}
.tx-timer-x{margin-left:auto;background:none;border:none;cursor:pointer;color:var(--text-muted);
  font-size:12px;line-height:1;padding:2px 3px}
.tx-timer-x:hover{color:var(--text-primary)}
.tx-timer.due{border-color:var(--amber);background:var(--amber-bg)}
.tx-timer.due b{color:var(--amber)}
.tx-timer.over{border-color:var(--red);background:var(--red-bg)}
.tx-timer.over b{color:var(--red)}
.ctaf{margin-bottom:10px;padding:10px 12px;border-radius:var(--radius-sm);
  background:var(--gold-bg);border:1px solid var(--gold-border);font-size:12.5px;line-height:1.5}
.ctaf-act{display:flex;gap:8px;margin-top:8px}

/* ── confirm dialog ─────────────────────────────────────────────────────────
   The browser's own confirm() is chrome, not the product: it lands top left
   against the address bar, in the browser's colours, headed with the domain
   name — which reads as the page having thrown an error rather than as the
   page asking a question. This is the same question in the portal's own
   surface, centred. */
.cf-scrim{position:fixed;inset:0;z-index:900;display:grid;place-items:center;
  background:rgba(11,18,32,.5);backdrop-filter:blur(2px);
  opacity:0;pointer-events:none;transition:opacity var(--transition)}
.cf-scrim.on{opacity:1;pointer-events:auto}
.cf{width:min(400px,calc(100vw - 32px));background:var(--bg-card);
  border:1px solid var(--border);border-radius:var(--radius-lg);
  box-shadow:0 18px 48px rgba(11,18,32,.22);padding:22px 22px 18px;
  transform:translateY(6px) scale(.98);transition:transform var(--transition)}
.cf-scrim.on .cf{transform:none}
.cf h3{margin:0 0 8px;font-size:16px;font-weight:600;color:var(--text-primary);
  line-height:1.3}
.cf p{margin:0;font-size:13px;line-height:1.5;color:var(--text-secondary)}
.cf-row{display:flex;gap:8px;justify-content:flex-end;margin-top:20px}
.cf-row button{height:34px;padding:0 16px;font:inherit;font-size:12.5px;
  font-weight:500;cursor:pointer;border-radius:var(--radius-sm)}
.cf-no{background:none;border:1px solid var(--border);color:var(--text-secondary)}
.cf-no:hover{color:var(--text-primary);background:var(--hover)}
.cf-yes{border:none;color:var(--bg-card);background:var(--text-primary)}
.cf-yes:hover{opacity:.88}
.cf-yes.danger{background:var(--red);color:#fff}

/* TX arbitration — who holds the transmitter, and what you can do about it. */
.tx-ask{margin-top:8px;display:flex;flex-direction:column;gap:7px;
  padding:9px 10px;border:1px solid var(--border);border-radius:var(--radius-md);
  background:var(--bg-surface)}
.tx-ask span{font-size:11.5px;line-height:1.45;color:var(--text-secondary)}
.tx-ask button{align-self:stretch;justify-content:center;display:inline-flex;
  align-items:center}
.tx-acts{display:flex;gap:6px}
.tx-acts button{flex:1}
.tx-ask.warn{border-color:var(--amber)}
.tx-ask.danger{border-color:var(--red);background:rgba(239,68,68,.07)}
.tx-ask.danger span{color:var(--red);font-weight:500}
.tx-note{font-size:11px;color:var(--text-muted);margin-top:7px;text-align:center}
.tx-here{margin-top:8px;font-size:10.5px;color:var(--text-muted);line-height:1.4}

/* ── Radio Wall ─────────────────────────────────────────────────────────────
   Every radio on one screen, for an admin listening across the network rather
   than operating one site. The map sits behind because a radio is a place —
   which site is talking matters as much as that something is. */

/* Full bleed inside .content, the same trick the network map uses — absolute
   against the pane so it fills it without covering the sidebar or top bar. */
.view.wall-view.on{display:block;position:absolute;inset:0;padding:0;overflow:hidden;
  background:var(--bg-page)}
/* The map is context, not content: legible enough to place a site, quiet
   enough that twenty cards on top of it still read. */
.wall-map{position:absolute;inset:0;filter:saturate(.5);opacity:.5}
.wall-map .leaflet-control-container{display:none}

.wall-bar{position:absolute;top:16px;left:16px;right:16px;z-index:20;display:flex;
  align-items:center;flex-wrap:wrap;gap:12px;padding:10px 14px;background:var(--chrome);
  backdrop-filter:blur(12px);border:1px solid var(--border);
  border-radius:var(--radius-lg);box-shadow:var(--shadow-card)}
.wall-title{display:flex;align-items:baseline;gap:10px;min-width:0}
.wall-title h1{margin:0;font-size:16px;font-weight:600;color:var(--text-primary)}
.wall-title span{font-size:11.5px;color:var(--text-muted)}
.wall-tools{margin-left:auto;display:flex;align-items:center;gap:14px}
.wall-chip{height:28px;padding:0 12px;border-radius:999px;border:1px solid var(--border);
  background:var(--bg-surface);color:var(--text-secondary);font:inherit;font-size:11.5px;
  cursor:pointer}
.wall-chip:hover{color:var(--text-primary)}
.wall-chip.on{border-color:var(--red);color:var(--red)}
.wall-master{display:flex;align-items:center;gap:8px}
.wall-master span{font-size:9.5px;text-transform:uppercase;letter-spacing:.07em;
  color:var(--text-muted)}
.wall-master .rw-vol{width:110px}

.wall-grid{position:absolute;inset:0;z-index:10;overflow:auto;
  padding:calc(16px + 52px + 16px) 16px 16px;
  display:grid;gap:14px;align-content:start;
  grid-template-columns:repeat(auto-fill,minmax(272px,1fr))}

/* ── a radio ─────────────────────────────────────────────────────────────── */
.rw{position:relative;background:var(--bg-card);border:1px solid var(--border);
  border-radius:var(--radius-lg);padding:14px;box-shadow:var(--shadow-card);
  display:flex;flex-direction:column;gap:10px;
  transition:border-color var(--transition),box-shadow var(--transition)}

/* Receiving lights the whole card, not a dot on it. Across twenty cards the
   question is which one is talking, and that has to be answerable from the
   other side of a room. */
.rw.rx{border-color:#22c55e;
  box-shadow:0 0 0 1px #22c55e, 0 0 26px 3px rgba(34,197,94,.42);
  animation:rwPulse 1.5s ease-in-out infinite}
@keyframes rwPulse{
  0%,100%{box-shadow:0 0 0 1px #22c55e, 0 0 20px 2px rgba(34,197,94,.32)}
  50%    {box-shadow:0 0 0 1px #22c55e, 0 0 34px 6px rgba(34,197,94,.55)}}
.rw.rx .rw-name{color:#15803d}
/* Transmitting is the other exceptional state and must not look like RX. */
.rw.tx{border-color:#ef4444;box-shadow:0 0 0 1px #ef4444, 0 0 26px 3px rgba(239,68,68,.4)}
.rw.tx .rw-name{color:#dc2626}
.rw.down{opacity:.55}
.rw.down .rw-body{filter:grayscale(1)}

.rw-head{display:flex;align-items:flex-start;gap:8px}
/* The site name is the single most important thing on the card. */
.rw-name{margin:0;font-size:17px;font-weight:650;line-height:1.15;letter-spacing:-.01em;
  color:var(--text-primary);min-width:0;overflow-wrap:anywhere}
.rw-dot{width:9px;height:9px;border-radius:50%;flex:none;margin-top:5px;
  background:var(--text-muted)}
.rw-dot.up{background:var(--green);box-shadow:0 0 8px 1px rgba(34,197,94,.8)}
.rw-dot.bad{background:var(--red)}

.rw-sub{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;margin-top:-4px}
.rw-state{font-size:11.5px;font-weight:500;color:var(--text-secondary)}
.rw.rx .rw-state{color:#15803d;font-weight:600}
.rw.tx .rw-state{color:#dc2626;font-weight:600}
.rw-freq{margin-left:auto;font-size:14px;font-weight:600;font-variant-numeric:tabular-nums;
  color:var(--text-primary)}
.rw-freq i{font-style:normal;font-size:9.5px;color:var(--text-muted);margin-left:3px}

.rw-body{display:flex;flex-direction:column;gap:9px}

/* Listen controls: mute is a state you can see at a glance, volume is its own
   slider because the point of the wall is balancing several radios at once. */
.rw-listen{display:flex;align-items:center;gap:9px}
.rw-mute{width:34px;height:34px;flex:none;display:grid;place-items:center;cursor:pointer;
  border-radius:50%;border:1px solid var(--border);background:var(--bg-surface);
  color:var(--text-secondary)}
.rw-mute:hover{color:var(--text-primary)}
.rw-mute.on{border-color:var(--green);color:var(--green);background:rgba(34,197,94,.1)}
.rw-mute:disabled{opacity:.4;cursor:not-allowed}

.rw-vol{-webkit-appearance:none;appearance:none;flex:1;height:4px;border-radius:999px;
  background:var(--border);outline:none;cursor:pointer}
.rw-vol::-webkit-slider-thumb{-webkit-appearance:none;width:14px;height:14px;
  border-radius:50%;background:var(--text-primary);cursor:pointer;border:none}
.rw-vol::-moz-range-thumb{width:14px;height:14px;border-radius:50%;
  background:var(--text-primary);cursor:pointer;border:none}
.rw-vol:disabled{opacity:.4;cursor:not-allowed}
.rw-volnum{width:26px;text-align:right;font-size:10.5px;color:var(--text-muted);
  font-variant-numeric:tabular-nums}

/* A live level, so a card that is receiving shows how much rather than just
   that it is. */
.rw-meter{height:3px;border-radius:999px;background:var(--border);overflow:hidden}
.rw-meter i{display:block;height:100%;width:0;background:var(--green);
  transition:width .08s linear}

.rw-tx{display:flex;align-items:center;gap:7px}
.rw-tx button{height:30px;padding:0 12px;font:inherit;font-size:11.5px;font-weight:500;
  cursor:pointer;border-radius:var(--radius-sm);flex:1}
.rw-claim{border:none;color:var(--bg-card);background:var(--text-primary)}
.rw-claim:hover{opacity:.88}
.rw-rel{border:1px solid var(--border);background:none;color:var(--red)}
.rw-rel:hover{border-color:var(--red)}
.rw-owner{font-size:10.5px;color:var(--text-muted);line-height:1.35}
.rw-owner b{color:var(--text-secondary);font-weight:600}

.wall-empty{grid-column:1/-1;padding:40px;text-align:center;color:var(--text-muted);
  font-size:13px}

/* Tuning: the same two ways in as the radio card — a named channel from the
   list, or six typed digits. */
.rw-tune{display:flex;gap:7px}
.rw-sel{flex:1;height:30px;padding:0 8px;font:inherit;font-size:11.5px;cursor:pointer;
  color:var(--text-primary);background:var(--bg-surface);
  border:1px solid var(--border);border-radius:var(--radius-sm)}
.rw-sel:disabled{opacity:.4;cursor:not-allowed}
.rw-freq-in{width:70px;padding:0;border:none;background:none;text-align:right;
  font:inherit;font-size:14px;font-weight:600;font-variant-numeric:tabular-nums;
  color:var(--text-primary);outline:none}
.rw-freq-in:focus{color:var(--gold)}
.rw-freq-in:disabled{opacity:.5}
.rw-sql{display:flex;align-items:center;gap:9px}
.rw-sql label{font-size:9.5px;text-transform:uppercase;letter-spacing:.07em;
  color:var(--text-muted);width:26px;flex:none}

/* ── Phones ─────────────────────────────────────────────────────────────────
   Below this the map is the whole screen, and the cards floating over it were
   sized for a desktop — the legend alone covered the sites it was describing. */
.lg-det > summary{list-style:none;cursor:pointer;font-weight:600;
  color:var(--text-primary);display:none}
.lg-det > summary::-webkit-details-marker{display:none}

/* ══ PHONE — the portal chrome ════════════════════════════════════════════
   A 56px icon rail is fifteen percent of a 375px screen spent on nine icons
   nobody can name, down the side, where a thumb does not reach. The same nav
   goes along the bottom instead: labelled, evenly divided, and where the hand
   already is. Role gating is untouched — an operator sees their own items and
   the bar simply has fewer of them. */
@media (max-width:640px){
  .body{grid-template-columns:1fr}
  .sidebar{position:fixed;left:0;right:0;bottom:0;top:auto;z-index:520;
    flex-direction:row;padding:0;overflow-x:auto;overflow-y:hidden;
    border-right:none;border-top:1px solid var(--border-mid);
    padding-bottom:env(safe-area-inset-bottom);scrollbar-width:none}
  .sidebar::-webkit-scrollbar{display:none}
  /* Headings and the online/offline tally are desk furniture; the bar is the
     nav and nothing else. */
  .sidebar-section,.sidebar-foot{display:none}
  /* width:auto matters: the side rail sets width:100%, which in a row makes
     every item as wide as the bar and pushes the rest off the end. */
  .nav-item{width:auto;flex:1 0 auto;min-width:62px;max-width:96px;
    flex-direction:column;gap:3px;padding:7px 6px 6px;justify-content:center;
    border-radius:0;border-left:none;border-bottom:2px solid transparent}
  /* The label comes back — this is the room the side rail did not have. */
  .nav-item span{display:block;font-size:9.5px;line-height:1;letter-spacing:.01em}
  .nav-item.on{border-bottom-color:var(--gold);background:none}

  /* Content clears the bar, and the collapse control has nothing to collapse. */
  .content{padding:14px 12px calc(66px + env(safe-area-inset-bottom))}
  .rail-toggle{display:none}
  .topbar{padding-left:14px}
  .topbar-logo{font-size:15px;letter-spacing:.14em}
  /* Sign out is a word on a bar with no room; the avatar is the way in. */
  #btnSignout{display:none}

  /* The map is the one view that owns the whole screen — the radio lives on
     it and the sheet already sits above the nav bar. */
  .map-view{inset:0}
  .view[data-view="map"]{margin:0 -12px -12px;height:calc(100dvh - var(--topbar-h))}
}

/* ══ PHONE — tables become cards ══════════════════════════════════════════
   A six-column table on a 375px screen is a column and a half, with the rest
   behind a sideways scroll nobody discovers. The same rows read as cards: the
   name on its own line, the readings wrapped underneath it.

   Done in CSS against the markup that already ships, so every table follows
   without its renderer knowing — and the desk layout keeps its columns. The
   header row goes, because a card carries its own meaning: a status badge says
   "Online", a frequency says MHz, and a bare "126.700" under a heading you
   cannot see says nothing. */
@media (max-width:640px){
  .table-wrap{border:none;background:none;box-shadow:none;overflow:visible}
  .table-wrap::after{display:none}
  table,tbody,tr,td{display:block;width:auto}
  thead{display:none}
  tr{background:var(--bg-card);border:1px solid var(--border);
    border-radius:var(--radius-lg);box-shadow:var(--shadow-card);
    padding:11px 13px;margin-bottom:9px}
  tbody td,td{border:none!important;padding:0;font-size:12.5px}
  /* First cell is the name — its own line, at the size a heading would be. */
  tr td:first-child{font-size:14px;font-weight:600;margin-bottom:7px}
  /* The rest wrap as a row of readings under it. */
  tr td:not(:first-child){display:inline-block;margin:0 14px 3px 0;
    color:var(--text-secondary)}
  /* An empty-state row is a message, not a card. */
  tr td.empty{display:block;margin:0;font-size:13px;text-align:center}
  tr:has(td.empty){background:none;border:none;box-shadow:none;padding:0}

  /* Stat rows go two-up rather than four-across. */
  .stat-card{padding:11px 12px}
  .stat-num{font-size:1.5rem}
  .page-title{font-size:19px}
  .page-bar{flex-wrap:wrap;gap:8px}

  /* Side panels are full-screen sheets — a 420px drawer on a 375px screen is
     the whole screen anyway, minus a sliver that only makes it look broken. */
  .panel{width:100vw!important;max-width:100vw;border-radius:0}
}

/* ══ PHONE — the radio as a sheet ══════════════════════════════════════════
   Everything below is the second shape of the radio screen, not a shrunken
   first one. See the note beside isPhone() in app.js. */
@media (max-width:640px){
  /* The map keeps the whole screen; the sheet sits over it and can be put
     away, because seeing where the traffic is matters as much as the list. */
  /* The sheet stops where the bar starts. --rb-h is measured, not assumed:
     the bar carries however many health cells the site has, and a Victron site
     has three more than an ICR one. Guessing a height here buries the PTT
     button behind the switcher on some sites and not others. */
  .fp{position:fixed;left:0;right:0;bottom:var(--rb-h,116px);top:auto;
    width:auto!important;height:auto!important;max-height:46dvh;
    min-width:0;min-height:0;transform:none!important;
    border-radius:16px 16px 0 0;border-left:none;border-right:none;border-bottom:none;
    box-shadow:0 -8px 28px rgba(11,18,32,.22);z-index:639;display:none}
  .fp.on{display:flex}
  /* Raised, for reading a long de-confliction list or working the camera. */
  .fp.tall{max-height:calc(100dvh - var(--rb-h,116px) - var(--topbar-h) - 8px)}

  /* The header is the grab area: a whole bar is a thumb target, a 20px handle
     is not. The bar above it says which sheet this is, so the title is a
     handle rather than a label. */
  .fp-hdr{cursor:pointer;padding:10px 14px 8px;position:relative;justify-content:center}
  .fp-hdr::before{content:"";position:absolute;top:5px;left:50%;transform:translateX(-50%);
    width:34px;height:4px;border-radius:2px;background:var(--border-mid)}
  .fp-hdr h4{font-size:12px}
  .fp-x{position:absolute;right:10px;top:50%;transform:translateY(-40%);
    width:30px;height:30px;font-size:15px}
  /* Dragging and resizing belong to the desk layout. */
  .fp-grip,.fp-grip-l{display:none}
  .fp-body{padding:10px 14px 14px;-webkit-overflow-scrolling:touch}

  /* The switcher rides above the sheet, always reachable. */
  .radio-bar{top:auto!important;bottom:0;left:0;right:0;position:fixed;
    padding-bottom:calc(6px + env(safe-area-inset-bottom));
    border-radius:0;padding:6px 8px;gap:6px;z-index:641;
    border-left:none;border-right:none;border-bottom:none}
  .rb-panels{margin-left:0;order:1;flex-basis:100%;display:flex;gap:5px;
    flex-wrap:nowrap}
  .rb-panels button{flex:1 1 0;min-width:0;padding:9px 4px;font-size:11px;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  /* Health moves above the switcher and stays one line — it is the thing an
     operator glances at, not something to scroll for. */
  .rb-health{order:0;flex-basis:100%;gap:0;flex-wrap:nowrap;
    overflow-x:auto;scrollbar-width:none}
  .rb-health::-webkit-scrollbar{display:none}
  .rb-h{flex:0 0 auto;padding:0 7px}
  /* Back button and site name lead, on their own line above the readouts. */
  .rb-exit{order:0;flex:0 0 auto}
  .rb-name{order:0;flex:1 1 auto;min-width:0}
  .rb-name b{font-size:13px}

  /* A sheet is up most of the time, so the map furniture moves out from under
     it and up to the top edge. */
  .map-legend,.map-traffic{bottom:auto;top:56px;max-height:34vh}
  .map-basemap{top:56px}

  /* Push to talk is the one control that must never be missed or mis-hit. */
  .rc-ptt{height:74px;font-size:15px;letter-spacing:.06em}
  .rc-power{padding:12px 11px}
  /* Squelch is dragged with a thumb, so the rail and its handle grow. */
  .rc-sql input[type=range]{height:26px}
  .dc-sliders input[type=range]{height:26px}
  .dc-sliders label{grid-template-columns:70px 1fr 56px}
  /* Preset and PTZ buttons become touch targets rather than mouse targets. */
  .cam-presets button{height:34px;font-size:11.5px}
  .cam-pad button,.cam-zoom button{width:38px;height:34px}
}

@media (max-width:640px){
  /* Legend and traffic share the bottom edge, each collapsed to a single line
     until asked for. */
  .lg-det > summary{display:block}
  .lg-det[open] > summary{margin-bottom:6px;padding-bottom:6px;
    border-bottom:1px solid var(--border)}
  .map-legend{left:8px;right:auto;min-width:0;max-width:calc(50vw - 12px);
    padding:8px 10px;font-size:11px;overflow:auto}
  .map-traffic{right:8px;max-width:calc(50vw - 12px);padding:8px 10px;overflow:auto}
  .map-basemap{right:8px}
  .map-basemap button{padding:5px 10px;font-size:11px}

  /* Cell type only — where the bar sits and how it stacks is set with the
     sheet above, which has to agree with it. */
  .rb-h b{font-size:12px}
  .rb-h span{font-size:8.5px;letter-spacing:.05em}

  /* Tables already scroll inside their wrapper — make that discoverable
     rather than looking like the page is simply cut off. */
  .table-wrap{position:relative}
  .table-wrap::after{content:'';position:absolute;top:0;right:0;bottom:0;width:22px;
    pointer-events:none;background:linear-gradient(90deg,transparent,var(--bg-card))}
}

/* Which site's receiver supplied a fix, when it was not this one. */

/* A microphone that will not open is stated where the operator is looking —
   they pressed transmit and nothing will be heard. */
.rc-micerr{margin-top:7px;padding:7px 9px;border-radius:var(--radius-sm);
  border:1px solid var(--red);background:rgba(239,68,68,.08);
  font-size:11px;line-height:1.4;color:var(--red)}

/* ── Site camera ────────────────────────────────────────────────────────── */
.cam-wrap{position:relative;flex:1;min-height:120px;border-radius:var(--radius-md);
  overflow:hidden;background:#0b1220;display:flex;align-items:center;justify-content:center}
.cam-wrap img{width:100%;height:100%;object-fit:contain;display:none}
.cam-wrap img.on{display:block}
.cam-msg{position:absolute;font-size:11.5px;color:var(--text-muted);text-align:center;
  padding:0 12px;line-height:1.45}
.cam-foot{display:flex;align-items:center;gap:8px;margin-top:8px}
.cam-age{font-size:10px;color:var(--text-muted);font-variant-numeric:tabular-nums}
.cam-foot .btn-p{margin-left:auto}

.cam-wrap video{width:100%;height:100%;object-fit:contain;display:block;background:#0b1220}
.cam-wrap video[hidden],.cam-wrap img[hidden]{display:none}
.cam-presets{display:flex;gap:4px;margin-top:8px;flex-wrap:wrap}
.cam-presets button{flex:1 1 0;min-width:54px;height:26px;padding:0 6px;cursor:pointer;
  font:inherit;font-size:10.5px;color:var(--text-secondary);background:var(--bg-surface);
  border:1px solid var(--border);border-radius:var(--radius-sm);white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis}
.cam-presets button:hover{color:var(--text-primary)}
.cam-presets button.on{border-color:var(--gold);color:var(--text-primary)}
/* Labelled, because two round buttons with a plus and a minus on them could be
   anything — and a magnifier says zoom in a way a bare + does not. */
.cam-ptz{display:flex;align-items:flex-end;gap:12px;margin-top:8px}
.cam-group{display:flex;flex-direction:column;gap:3px}
.cam-lbl{font-size:8.5px;text-transform:uppercase;letter-spacing:.08em;
  color:var(--text-muted);line-height:1}
.cam-pop{width:24px;height:24px;display:grid;place-items:center;cursor:pointer;
  color:var(--text-secondary);background:var(--bg-surface);border:1px solid var(--border);
  border-radius:var(--radius-sm);padding:0}
.cam-pop:hover{color:var(--text-primary);border-color:var(--border-mid)}
.cam-pad{display:grid;grid-template-columns:repeat(3,22px);grid-template-rows:repeat(2,22px);
  gap:2px}
.cam-pad button:nth-child(1){grid-column:2;grid-row:1}
.cam-pad button:nth-child(2){grid-column:1;grid-row:2}
.cam-pad button:nth-child(3){grid-column:3;grid-row:2}
.cam-pad button:nth-child(4){grid-column:2;grid-row:2}
.cam-pad button,.cam-zoom button{height:22px;cursor:pointer;font:inherit;font-size:10px;
  color:var(--text-secondary);background:var(--bg-surface);border:1px solid var(--border);
  border-radius:var(--radius-sm);display:grid;place-items:center;padding:0}
.cam-pad button:active,.cam-zoom button:active{border-color:var(--gold);color:var(--gold)}
.cam-zoom{display:flex;gap:2px}
.cam-zoom button{width:24px;font-size:13px;line-height:1}
.cam-ptz .cam-age{margin-left:auto}

/* ── De-Conflict ────────────────────────────────────────────────────────────
   The verdict first and largest: the panel is glanced at, and what it has to
   answer is whether anything is close, not which aircraft. */
.dc-head{padding:9px 10px;border-radius:var(--radius-md);border:1px solid var(--border);
  background:var(--bg-surface)}
.dc-head b{display:block;font-size:14px;font-weight:650;line-height:1.2;color:var(--text-primary)}
.dc-head span{display:block;font-size:10.5px;color:var(--text-muted);margin-top:2px}
.dc-head.red{border-color:var(--red);background:rgba(239,68,68,.1)}
.dc-head.red b{color:var(--red)}
.dc-head.orange{border-color:var(--amber);background:rgba(245,158,11,.1)}
.dc-head.orange b{color:var(--amber)}
.dc-head.green{border-color:var(--green)}
.dc-head.green b{color:var(--green)}

.dc-list{margin-top:8px;display:flex;flex-direction:column;gap:3px}
/* Three stacked lines, not three columns. This was a grid of 1fr auto auto back
   when a row was three spans; the readings and the callout that followed were
   dropped straight into those columns and each was squeezed into a strip a few
   characters wide. A row is a block of lines now, and the column count went
   with the layout it belonged to. */
.dc-row{position:relative;padding:4px 24px 5px 7px;border-radius:var(--radius-sm);
  border-left:3px solid transparent;background:var(--bg-surface);font-size:11px}
.dc-row.red{border-left-color:var(--red)}
.dc-row.orange{border-left-color:var(--amber)}
.dc-row.green{border-left-color:var(--green)}
.dc-row.unknown{border-left-color:var(--border-mid)}
/* The callsign is the thing you say and the thing you look up, so it never
   gets clipped — it was coming out "JST…" when the row was still a grid and the
   column starved it. It holds its width and the readings beside it give way. */
.dc-call{font-family:var(--font-mono);font-size:12px;font-weight:700;
  letter-spacing:.01em;color:var(--text-primary);white-space:nowrap}
.dc-row.red .dc-call{color:var(--red)}
/* Vertical separation is the number that decides it, so it reads as the number. */
.dc-sep{font-variant-numeric:tabular-nums;font-weight:600;color:var(--text-primary);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
.dc-row.red .dc-sep{color:var(--red)}
.dc-row.orange .dc-sep{color:var(--amber)}
.dc-dist{font-variant-numeric:tabular-nums;color:var(--text-muted);white-space:nowrap}
.dc-note{margin-top:7px;font-size:10.5px;color:var(--text-muted);line-height:1.4}

/* Thresholds, folded away until wanted. */
.dc-set{margin-top:9px;border-top:1px solid var(--border);padding-top:7px}
.dc-set > summary{list-style:none;cursor:pointer;font-size:10px;text-transform:uppercase;
  letter-spacing:.07em;color:var(--text-muted);display:flex;align-items:center;gap:5px}
.dc-set > summary::-webkit-details-marker{display:none}
.dc-set > summary::before{content:"▸";font-size:9px;transition:transform var(--transition)}
.dc-set[open] > summary::before{transform:rotate(90deg)}
.dc-set > summary:hover{color:var(--text-primary)}
.dc-sliders{margin-top:8px;display:flex;flex-direction:column;gap:8px}
.dc-sliders label{display:grid;grid-template-columns:74px 1fr 58px;gap:8px;align-items:center}
.dc-sliders label > span{font-size:10.5px;color:var(--text-secondary)}
.dc-sliders label > b{font-size:10.5px;font-weight:600;text-align:right;
  font-variant-numeric:tabular-nums;color:var(--text-primary)}
.dc-sliders input[type=range]{-webkit-appearance:none;appearance:none;height:4px;
  border-radius:999px;background:var(--border);outline:none;cursor:pointer}
.dc-sliders input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:13px;
  height:13px;border-radius:50%;background:var(--text-primary);border:none;cursor:pointer}
.dc-sliders input[type=range]:disabled{opacity:.45;cursor:not-allowed}
.dc-save{display:flex;align-items:center;gap:8px;margin-top:2px}
.dc-save .btn-p{margin-left:auto}

/* De-Conflict rows: verdict on the first line, what you would say on the second. */
/* Line one: who, how far above, how far away. */
.dc-main{display:flex;align-items:baseline;gap:8px}
.dc-call{flex:0 0 auto}
.dc-dist{margin-left:auto;font-variant-numeric:tabular-nums;white-space:nowrap}

/* Line two: the two bearings, kept on one line at a size that fits the panel
   in its default column — 268px, which is what set the type size here. */
.dc-brg{font-family:var(--font-mono);font-size:9.5px;letter-spacing:-.01em;
  color:var(--text-muted);margin-top:2px;white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis}
.dc-row.red .dc-brg,.dc-row.orange .dc-brg{color:inherit;opacity:.75}

/* Line three: the words to say. Set in quotes and indented so it reads as
   speech rather than as another reading to interpret. */
.dc-say{margin-top:2px;font-size:10px;line-height:1.3;color:var(--text-secondary)}
.dc-row.red .dc-say{color:var(--red);opacity:.95}

/* Silencing one aircraft. Parked in the row's top-right corner, out of the
   text flow so it cannot push the readings around, and clear of the panel's
   own ✕ above it. */
.dc-ack{position:absolute;top:3px;right:3px;width:17px;height:17px;padding:0;
  display:flex;align-items:center;justify-content:center;
  border:1px solid var(--border);background:var(--bg-card);color:var(--text-muted);
  line-height:1;border-radius:4px;cursor:pointer}
.dc-ack:hover{color:var(--text-primary);border-color:var(--text-muted)}
.dc-ack.off{color:var(--text-muted);opacity:.75}
.dc-row.red .dc-ack:not(.off){color:var(--red);border-color:var(--red);opacity:.8}
/* An acknowledged conflict is quieter, not gone. */
.dc-row.acked{opacity:.72}

/* ── conflict lines on the map ──────────────────────────────────────────────
   Red moves: the dashes march towards the aircraft and the whole line breathes.
   A line drawn on a map full of lines is missed; one that moves is not. Amber
   holds still — it is there to be noticed, not to pull the eye off a red. */
@keyframes dcMarch{ to{ stroke-dashoffset:-32 } }
@keyframes dcPulse{ 0%,100%{ stroke-opacity:1 } 50%{ stroke-opacity:.35 } }
@keyframes dcRing{
  0%   { stroke-opacity:.95; stroke-width:2.2 }
  70%  { stroke-opacity:0;   stroke-width:9 }
  100% { stroke-opacity:0;   stroke-width:9 } }
.dc-line-red{ animation:dcMarch 1s linear infinite, dcPulse 1.1s ease-in-out infinite }
.dc-halo-red{ animation:dcPulse 1.1s ease-in-out infinite }
.dc-ring-red{ animation:dcRing 1.4s ease-out infinite }
.dc-line-amber{ stroke-opacity:.95 }

/* Motion is the alert here, so it is not decoration that can simply be dropped
   for reduced-motion: the line stays fully drawn and solid instead, which
   remains the most prominent thing on the map without moving. */
@media (prefers-reduced-motion: reduce){
  .dc-line-red,.dc-halo-red,.dc-ring-red{ animation:none }
  .dc-line-red{ stroke-dasharray:none; stroke-opacity:1 }
  .dc-ring-red{ stroke-opacity:.9 }
}

.rb-reset.ok{color:var(--green);border-color:var(--green)}
.rb-reset.warn{color:var(--amber);border-color:var(--amber)}

/* Role sits with the account actions rather than among the read-only facts
   above them: it is something you change, not something you look up. */
.up-role{display:flex;align-items:center;gap:9px;flex-wrap:wrap;
  padding:10px 0 12px;border-bottom:1px solid var(--border);margin-bottom:12px}
.up-role label{font-size:11.5px;color:var(--text-secondary)}
.up-role select{font:inherit;font-size:12px;padding:5px 8px;border-radius:var(--radius-sm);
  border:1px solid var(--border);background:var(--bg-card);color:var(--text-primary)}
.up-role select:disabled{opacity:.55}
.up-role .s-msg{margin-left:auto;font-size:10.5px}
