/* ══════════════════════════════════════════════════════════════════════════
   NOREVYN design system
   ──────────────────────────────────────────────────────────────────────────
   One set of tokens and primitives for every surface: the public site, the
   map, Operations, the Network, Remote Operations, sign-in and onboarding.

   There used to be three palettes on top of this file — a light "imagery"
   theme for the map and the Network, a cyan "tactical" theme for Operations,
   and the dark default — and the product read as three products. The scopes
   that carried them (body.theme-light, body.theme-tactical, body.imagery-base)
   still exist as CLASS NAMES because a few hundred layout rules are keyed on
   them, but they no longer change a single colour. Everything resolves to the
   tokens below.

   ── Colour rules ──────────────────────────────────────────────────────────
     blue    actions, links, selected navigation
     cyan    genuinely live information only — a streaming feed, a live fix
     green   current, ready, completed, reviewed
     amber   advisory, expiring, attention required
     red     blocked, failed, expired, unsafe
     grey    unknown, incomplete, unavailable, not supplied

   Every status token has a -text (for text, icons, borders on the dark
   ground), a -soft (tint fill behind them) and a -line (border) variant. The
   saturated value is never a fill behind white text: white on the green is
   2.4:1 and on the amber 1.9:1.

   Contrast (2026-09-17 light system), against white:
     text 18.0 / 16.0 · text-2 10.4 / 9.2 · text-3 7.4 / 6.6
     accent 11.4 / 10.1 · cyan 9.8 · green 10.5 · amber 10.9 · red 6.5
   The accent is a LIGHT tone: near-black on it is 11.4, white 1.71, so a
   filled control carries --on-accent and never white.
   AA body text needs 4.5; large text and UI need 3.0.
   Slate-500 (#64748B) is the tactical label colour by convention and misses
   AA on every ground here (3.3-4.2), so --text-3 is the nearest value that
   holds 4.5 on the panel surfaces.
   ══════════════════════════════════════════════════════════════════════════ */

:root {
  /* ── ground ───────────────────────────────────────────────────────────── */
  /* 2026-09-17: one light product from the landing page to the platform.
     White ground, a very light blue-grey for secondary surfaces, navy text,
     NOREVYN blue for action. Contrast is measured against white. */
  --bg: #FFFFFF;                 /* application background */
  --sidebar: #F7F9FC;            /* navigation */
  --surface: #FFFFFF;            /* content surface */
  --surface-2: #F5F8FC;          /* raised / hovered surface */
  --surface-3: #F2F6FB;          /* grouped tile */
  --ribbon: #FFFFFF;
  --border: #E3E8EF;             /* divider */
  --border-strong: #CBD4E1;
  --edge: rgba(10, 22, 40, 0.05);

  /* ── text ─────────────────────────────────────────────────────────────── */
  --text: #0A1628;               /* 17.6 on white */
  --text-2: #475467;             /* 7.7 on white */
  --text-3: #5F6B7E;             /* labels and helper text. 5.5 on white, 5.2 on --surface-3 */

  /* ── blue: action, link, selected ─────────────────────────────────────── */
  --blue: #1463FF;               /* 4.9 on white: text, icons, fills */
  --blue-fill: #1463FF;
  --blue-hover: #0E52DB;
  --blue-text: #0F56E0;          /* 5.9 on white */
  --on-accent: #FFFFFF;          /* white on the blue: 4.9 */
  --action: #1463FF;
  --action-hover: #0E52DB;
  --on-action: #FFFFFF;
  --blue-soft: #EDF3FF;
  --blue-line: #BFD3FF;

  /* ── cyan: LIVE data only ─────────────────────────────────────────────── */
  --live: #0A7EA4;               /* 4.7 on white */
  --live-soft: #E7F6FB;
  --live-line: #B3E0EF;

  /* ── green: current / ready / reviewed ────────────────────────────────── */
  --ok: #12805C;                 /* 5.0 on white */
  --ok-text: #0E7552;
  --ok-soft: #E9F6F0;
  --ok-line: #B5E0CC;

  /* ── amber: advisory / expiring ───────────────────────────────────────── */
  --warn: #B54708;               /* 5.4 on white */
  --warn-text: #A33F06;
  --warn-soft: #FFF5E8;
  --warn-line: #F6D3A6;

  /* ── red: blocked / expired / unsafe ──────────────────────────────────── */
  --danger: #C0282D;             /* 5.9 on white */
  --danger-text: #B42318;
  --danger-soft: #FDEEEE;
  --danger-line: #F3C1C1;
  --danger-fill: #C0282D;        /* white on it: 5.9 */

  /* ── grey: unknown / unavailable / not supplied ───────────────────────── */
  --unknown: #667085;
  --unknown-text: #596579;
  --unknown-soft: #F2F4F7;
  --unknown-line: #D6DCE5;

  /* ── shape ────────────────────────────────────────────────────────────── */
  /* Two radii in practice: 8 for controls, 12 for surfaces. */
  --r-sm: 6px;
  --r-md: 8px;
  --r-lg: 12px;
  --r-xl: 16px;
  --control-h: 40px;
  --control-h-lg: 44px;
  /* One soft elevation for popovers and drawers; surfaces use space and a
     hairline instead. */
  --shadow: 0 16px 40px rgba(16, 24, 40, 0.14);
  --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.05);
  --shadow-card: 0 1px 2px rgba(16, 24, 40, 0.04);
  --focus: 0 0 0 3px rgba(20, 99, 255, 0.30);
  --glow: none;

  /* ── rhythm: an 8px system ────────────────────────────────────────────── */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px;  --sp-4: 16px;
  --sp-5: 24px; --sp-6: 32px; --sp-7: 48px;  --sp-8: 64px;
  --page-max: 1280px;            /* public content */
  --page-gutter: clamp(20px, 3vw, 32px);
  --app-gutter: clamp(16px, 2vw, 32px);
  --section-y: clamp(40px, 6vw, 88px);
  --measure: 34em;          /* about 76 characters in this face */

  /* ── type ─────────────────────────────────────────────────────────────── */
  --font: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-head: "Inter Tight", "Inter", system-ui, sans-serif;
  /* Data is monospaced: a coordinate, an altitude and a timestamp have to
     align in a column and stay legible at 11px. */
  --mono: "JetBrains Mono", ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
  --font-mono: var(--mono);
  --fs-display: clamp(40px, 7vw, 80px);     /* marketing headlines */
  --fs-h1: 32px;                            /* application page title */
  --fs-h2: 22px;                            /* section title */
  --fs-h3: 17px;                            /* card title */
  --fs-body: 15px;
  --fs-sm: 13px;
  --fs-label: 13px;
  --lh: 1.5;

  /* ── the shell ────────────────────────────────────────────────────────── */
  --shell-h: 56px;               /* the header / status ribbon */
  --rail-w: 232px;               /* the left navigation rail */
  --top-h: 60px;                 /* the application top bar */
  --rail-w-narrow: 68px;
  --tap: 44px;                   /* minimum touch target */

  /* ══ compatibility aliases ═══════════════════════════════════════════════
     Names the existing stylesheets reference. Each resolves to exactly one
     token above, so a colour is still decided in one place. New work should
     use the semantic names. */
  --bg-2: var(--surface);
  --panel: var(--surface);
  --panel-2: var(--surface-3);
  --panel-solid: var(--surface);
  --tile: var(--surface-3);
  --border-2: var(--border-strong);
  --hairline: rgba(10, 22, 40, 0.07);
  --hover: rgba(10, 22, 40, 0.04);
  --muted: var(--text-2);
  --faint: var(--text-3);
  --accent: var(--blue);
  --accent-hover: var(--blue-hover);
  --accent-text: var(--blue-text);
  --accent-soft: var(--blue-soft);
  --accent-line: var(--blue-line);
  --accent-dim: var(--blue);
  --accent-on: #FFFFFF;
  --ok-dim: #0B5E42;
  --auth-text: var(--warn-text);
  --auth-line: var(--warn-line);
  --auth-wash: var(--surface-3);
  --hero-wash: rgba(255, 255, 255, 0.92);
  --glass: rgba(255, 255, 255, 0.94);
  --drawer-shadow: rgba(16, 24, 40, 0.18);
  --r: var(--r-md);
  --radius: var(--r-md);
  /* The former light recipe. Every name still resolves — to the dark token
     that does the same job — so no rule that still names one renders light. */
}

/* The three former theme scopes. Kept as selectors so the layout rules keyed
   on them keep matching; they set nothing that changes colour. */
body.theme-tactical,
body.imagery-base {
  background: var(--bg);
  color: var(--text);
}
html { background: var(--bg); }
body { min-height: 100svh; }

/* ══════════════════════════ base ══════════════════════════ */
*, *::before, *::after { box-sizing: border-box; }
body {
  margin: 0;
  background: var(--bg);
  font-family: var(--font);
  font-size: var(--fs-body);
  line-height: var(--lh);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
h1, h2, h3, h4 { margin: 0; line-height: 1.25; letter-spacing: -0.01em; color: var(--text); font-family: var(--font-head); }
h1 { font-size: var(--fs-h1); font-weight: 700; }
h2 { font-size: var(--fs-h2); font-weight: 650; }
h3 { font-size: var(--fs-h3); font-weight: 600; }
p { margin: 0; }
a { color: var(--blue-text); }
code, kbd, .mono, .nv-mono { font-family: var(--mono); font-size: 0.92em; letter-spacing: 0; }
::selection { background: rgba(20, 99, 255, 0.20); color: var(--text); }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
/* Keyboard focus is visible everywhere, and only for keyboard. */
:focus-visible { outline: none; box-shadow: var(--focus); border-radius: var(--r-sm); }
:focus:not(:focus-visible) { outline: none; }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; }
}

/* ══════════════════════════ page layout ══════════════════════════ */
.nv-page { width: 100%; max-width: var(--page-max); margin: 0 auto; padding: var(--sp-5) var(--page-gutter) var(--sp-8); }
.nv-page-head {
  display: flex; align-items: flex-end; justify-content: space-between; gap: var(--sp-4);
  flex-wrap: wrap; margin-bottom: var(--sp-5);
}
.nv-page-head h1 { font-size: var(--fs-h1); font-weight: 700; letter-spacing: -0.02em; }
.nv-page-head p { margin-top: var(--sp-1); color: var(--text-2); max-width: var(--measure); }
.nv-page-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.nv-section-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3);
  margin: 0 0 var(--sp-3);
}
.nv-section-head h2 { font-size: var(--fs-h2); }
.nv-section-head small, .nv-section-head .nv-count { color: var(--text-3); font-size: var(--fs-sm); }
.nv-eyebrow {
  margin: 0 0 var(--sp-2); font-size: var(--fs-label); font-weight: 600;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-3);
}
.nv-help { color: var(--text-3); font-size: var(--fs-sm); }
.nv-stack { display: grid; gap: var(--sp-3); }
.nv-grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-4); }
.nv-grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-4); }
.nv-grid-4 { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sp-4); }
@media (max-width: 900px) { .nv-grid-3, .nv-grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 600px) { .nv-grid-2, .nv-grid-3, .nv-grid-4 { grid-template-columns: minmax(0, 1fr); } }

/* ══════════════════════════ buttons ══════════════════════════
   primary (the one action), secondary (an equal alternative), tertiary
   (incidental, text-like), danger (destroys something). One primary per
   screen. All are 40px tall — 44px on touch — and carry the same focus ring. */
.nv-btn, .btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  min-height: var(--control-h-lg); padding: 0 18px;
  border-radius: var(--r-md); border: 1px solid transparent;
  background: transparent; color: var(--text);
  font: 600 var(--fs-body)/1.2 var(--font);
  cursor: pointer; white-space: nowrap; text-decoration: none;
  transition: background 0.16s, border-color 0.16s, color 0.16s, box-shadow 0.16s;
}
.nv-btn:focus-visible, .btn:focus-visible { box-shadow: var(--focus); }
.nv-btn:disabled, .nv-btn[aria-disabled="true"], .btn:disabled { opacity: 0.5; cursor: not-allowed; }
.nv-btn-primary, .btn-primary { background: var(--blue-fill); color: var(--on-accent); border-color: var(--blue-fill); }
.nv-btn-primary:hover:not(:disabled), .btn-primary:hover:not(:disabled) { background: var(--blue-hover); border-color: var(--blue-hover); }
.nv-btn-primary:active:not(:disabled) { background: var(--blue); }
.nv-btn-secondary, .btn-secondary, .btn-ghost { border-color: var(--border-strong); background: var(--surface); color: var(--text); box-shadow: var(--shadow-sm); }
.nv-btn-secondary:hover:not(:disabled), .btn-ghost:hover:not(:disabled) { border-color: var(--blue-line); background: var(--surface-2); }
.nv-btn-ghost, .nv-btn-tertiary, .btn-tertiary { color: var(--text-2); padding: 0 10px; }
.nv-btn-ghost:hover:not(:disabled), .nv-btn-tertiary:hover:not(:disabled) { color: var(--text); background: var(--hover); }
.nv-btn-danger, .btn-danger { border-color: var(--danger-line); color: var(--danger-text); }
.nv-btn-danger:hover:not(:disabled) { background: var(--danger-soft); }
.nv-btn-danger-solid { background: var(--danger-fill); color: #fff; }
.nv-btn-sm { min-height: 36px; padding: 0 14px; font-size: var(--fs-sm); }
.nv-btn-lg, .btn-lg { min-height: var(--control-h-lg); padding: 0 22px; font-size: 15px; }
.nv-btn-block { width: 100%; }
.nv-btn.is-loading { color: transparent; position: relative; pointer-events: none; }
.nv-btn.is-loading::after {
  content: ""; position: absolute; width: 16px; height: 16px; border-radius: 50%;
  border: 2px solid rgba(255,255,255,0.35); border-top-color: #fff; animation: nv-spin 0.8s linear infinite;
}
.nv-btn svg, .btn svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
@media (pointer: coarse) { .nv-btn, .btn { min-height: var(--tap); } .nv-btn-sm { min-height: 40px; } .nv-ws a { min-height: 40px; } .nv-subnav a, .nv-subnav button { min-height: var(--tap); } }
.nv-link { color: var(--blue-text); text-decoration: none; font-weight: 600; }
.nv-link:hover { text-decoration: underline; }
/* An action that runs script is a button, not a link: it goes nowhere, it
   should not offer "open in new tab", and it must not put "#" in the URL. */
.nv-linkbtn { border: 0; background: none; padding: 0; font: inherit; cursor: pointer; }

/* ══════════════════════════ inputs ══════════════════════════ */
.nv-field { display: block; }
.nv-field > span, .nv-label {
  display: block; margin-bottom: 6px;
  font-size: var(--fs-sm); font-weight: 600; color: var(--text-2);
}
.nv-field .nv-opt, .nv-label .nv-opt { font-weight: 400; color: var(--text-3); }
.nv-input, .nv-select, .nv-textarea,
.nv-field input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
.nv-field select, .nv-field textarea {
  width: 100%; min-height: var(--control-h-lg); padding: 0 14px;
  border: 1px solid var(--border-strong); border-radius: var(--r-md);
  background: var(--surface); color: var(--text);
  font: 400 var(--fs-body)/1.4 var(--font);
  transition: border-color 0.16s, box-shadow 0.16s;
}
.nv-textarea, .nv-field textarea { padding: 10px 12px; min-height: 96px; resize: vertical; }
.nv-select, .nv-field select {
  appearance: none; -webkit-appearance: none; padding-right: 34px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235F6B7E' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 10px center; background-size: 16px;
}
.nv-input:hover, .nv-field input:hover, .nv-field select:hover { border-color: rgba(10, 22, 40, 0.168); }
.nv-input:focus, .nv-select:focus, .nv-textarea:focus,
.nv-field input:focus, .nv-field select:focus, .nv-field textarea:focus {
  outline: none; border-color: var(--blue); box-shadow: var(--focus);
}
.nv-input:disabled, .nv-field input:disabled, .nv-field select:disabled { opacity: 0.55; cursor: not-allowed; }
.nv-input[aria-invalid="true"], .nv-field.is-error input, .nv-field.is-error select { border-color: var(--danger); }
.nv-field-err { margin-top: 6px; font-size: var(--fs-sm); color: var(--danger-text); }
.nv-field-help { margin-top: 6px; font-size: var(--fs-sm); color: var(--text-3); }
input::placeholder, textarea::placeholder { color: var(--text-3); opacity: 1; }
.nv-check { display: flex; align-items: flex-start; gap: 10px; font-size: var(--fs-body); color: var(--text-2); cursor: pointer; }
.nv-check input { width: 18px; height: 18px; margin: 2px 0 0; accent-color: var(--blue); flex: none; }
.nv-search { position: relative; }
.nv-search input { padding-left: 36px; }
.nv-search svg { position: absolute; left: 12px; top: 50%; width: 16px; height: 16px; transform: translateY(-50%); fill: none; stroke: var(--text-3); stroke-width: 2; pointer-events: none; }
@media (max-width: 600px) {
  /* 16px or iOS zooms the page on focus. */
  .nv-input, .nv-select, .nv-field input, .nv-field select, .nv-field textarea { font-size: 16px; min-height: var(--tap); }
}

/* ══════════════════════════ status badges ══════════════════════════
   Colour carries meaning and a glyph carries it again, so the state survives
   colour-blindness, greyscale and print. Vocabulary:
     ok      current · ready · completed · reviewed
     warn    advisory · expiring · attention
     danger  blocked · expired · failed
     live    a value that is streaming right now
     info    neutral fact
     (none)  unknown · unavailable · not supplied · attested only */
.nv-badge, .pc-badge, .lp-tag {
  display: inline-flex; align-items: center; gap: 5px;
  min-height: 22px; padding: 2px 8px; border: 1px solid var(--unknown-line);
  background: var(--unknown-soft); color: var(--text-2); white-space: nowrap;
}
.nv-badge::before { content: ""; border-radius: 50%; background: var(--unknown); flex: none; }
.nv-badge-ok, .nv-badge.ok, .pc-badge.ok, .lp-tag.ok { color: var(--ok-text); border-color: var(--ok-line); background: var(--ok-soft); }
.nv-badge-ok::before, .nv-badge.ok::before { background: var(--ok); }
.nv-badge-warn, .nv-badge.warn, .pc-badge.warn, .lp-tag.warn { color: var(--warn-text); border-color: var(--warn-line); background: var(--warn-soft); }
.nv-badge-warn::before, .nv-badge.warn::before { background: var(--warn); border-radius: 1px; transform: rotate(45deg); }
.nv-badge-danger, .nv-badge.danger, .nv-badge.bad, .pc-badge.bad, .lp-tag.bad { color: var(--danger-text); border-color: var(--danger-line); background: var(--danger-soft); }
.nv-badge-danger::before, .nv-badge.danger::before, .nv-badge.bad::before { background: var(--danger); border-radius: 1px; }
.nv-badge-info, .nv-badge.info, .pc-badge.info, .lp-tag.info { color: var(--blue-text); border-color: var(--blue-line); background: var(--blue-soft); }
.nv-badge-info::before, .nv-badge.info::before { background: var(--blue-text); }
.nv-badge-live, .nv-badge.live { color: var(--live); border-color: var(--live-line); background: var(--live-soft); }
.nv-badge-live::before, .nv-badge.live::before { background: var(--live); }
.nv-badge-plain::before { display: none; }

/* Credential and readiness state words, so the same state is always the same
   label. Use with .nv-badge; the class only sets the glyph/colour. */
.nv-state-current { color: var(--ok-text); }
.nv-state-expiring { color: var(--warn-text); }
.nv-state-expired, .nv-state-blocked { color: var(--danger-text); }
.nv-state-unknown, .nv-state-none, .nv-state-attested, .nv-state-submitted { color: var(--text-2); }

/* Status dot for lists — a glyph, never colour alone. */
.nv-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--unknown); flex: none; }
.nv-dot.ok { background: var(--ok); }
.nv-dot.warn { background: var(--warn); border-radius: 1px; transform: rotate(45deg); }
.nv-dot.bad, .nv-dot.danger { background: var(--danger); border-radius: 1px; }
.nv-dot.live { background: var(--live); }

/* ══════════════════════════ cards ══════════════════════════ */
.nv-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  padding: var(--sp-5);
}
.nv-card-sm { padding: var(--sp-4); }
.nv-card-hover { transition: border-color 0.16s; }
.nv-card-hover:hover { border-color: var(--border-strong); }
.nv-card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-3); margin-bottom: var(--sp-3); }
.nv-card-head h3 { font-size: var(--fs-h3); }
.nv-card-head small { color: var(--text-3); font-size: var(--fs-sm); }
.nv-card-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); margin-top: var(--sp-4); padding-top: var(--sp-3); border-top: 1px solid var(--hairline); }

/* Metric card: a number with its meaning and its source. A dash is not a
   number — when the value is unknown the card says why, in words. */
.nv-metric { min-width: 0; }
.nv-metric b { font-size: 24px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.1; font-variant-numeric: tabular-nums; }
.nv-metric span { font-size: var(--fs-sm); color: var(--text-2); }
.nv-metric small { font-size: var(--fs-label); color: var(--text-3); }
.nv-metric.is-unknown b { font-size: var(--fs-body); font-weight: 600; color: var(--text-2); }
.nv-metric.is-warn b { color: var(--warn-text); }
.nv-metric.is-bad b { color: var(--danger-text); }
.nv-metric.is-live b { color: var(--live); }

/* Key/value rows inside a card. */
.nv-kv { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px var(--sp-3); font-size: var(--fs-body); }
.nv-kv dt { color: var(--text-2); }
.nv-kv dd { margin: 0; text-align: right; color: var(--text); font-variant-numeric: tabular-nums; min-width: 0; overflow-wrap: anywhere; }

/* ══════════════════════════ tables ══════════════════════════
   Desktop: a table. Under 720px each row becomes a card with the header
   text repeated as a label (data-th on each cell). */
/* position: relative keeps absolutely positioned cell content (sr-only labels)
   inside the scroll box; without it they escaped and widened the page. */
.nv-table-wrap { position: relative; overflow-x: auto; -webkit-overflow-scrolling: touch; border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--surface); }
.nv-table { width: 100%; border-collapse: collapse; font-size: var(--fs-body); }
.nv-table th { text-align: left; padding: 10px 14px; font-size: var(--fs-label); font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-3); border-bottom: 1px solid var(--border); white-space: nowrap; }
.nv-table td { padding: 12px 14px; border-bottom: 1px solid var(--hairline); vertical-align: top; overflow-wrap: anywhere; }
.nv-table tr:last-child td { border-bottom: 0; }
.nv-table tbody tr:hover { background: var(--hover); }
.nv-table .num { text-align: right; font-variant-numeric: tabular-nums; }
@media (max-width: 720px) {
  .nv-table-wrap.nv-table-cards { overflow: visible; border: 0; background: transparent; }
  .nv-table-cards .nv-table thead { display: none; }
  .nv-table-cards .nv-table, .nv-table-cards .nv-table tbody, .nv-table-cards .nv-table tr, .nv-table-cards .nv-table td { display: block; }
  .nv-table-cards .nv-table tr { margin-bottom: var(--sp-3); border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--surface); padding: 6px 0; }
  .nv-table-cards .nv-table td { display: grid; grid-template-columns: 110px minmax(0, 1fr); gap: var(--sp-3); padding: 8px 14px; border: 0; }
  .nv-table-cards .nv-table td::before { content: attr(data-th); font-size: var(--fs-label); text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-3); padding-top: 2px; }
  .nv-table-cards .nv-table td.num { text-align: left; }
}

/* ══════════════════════════ action queue ══════════════════════════
   A row is one thing to do: what is wrong, what it affects, and the one
   action that resolves it. Sorted by whoever built the list, not by CSS. */
.nv-queue { display: grid; gap: var(--sp-2); }
.nv-q {
  display: grid; grid-template-columns: 10px minmax(0, 1fr) auto; gap: var(--sp-3); align-items: center;
  padding: 12px 14px; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface);
  text-decoration: none; color: inherit; transition: border-color 0.16s;
}
.nv-q:hover { border-color: var(--border-strong); }
.nv-q b { display: block; font-size: var(--fs-body); font-weight: 600; }
.nv-q small { display: block; margin-top: 2px; font-size: var(--fs-sm); color: var(--text-2); overflow-wrap: anywhere; }
.nv-q .nv-q-when { font-size: var(--fs-sm); color: var(--text-3); white-space: nowrap; font-variant-numeric: tabular-nums; }
.nv-q.is-block { border-left: 3px solid var(--danger); }
.nv-q.is-warn { border-left: 3px solid var(--warn); }
.nv-q.is-ok { border-left: 3px solid var(--ok); }
.nv-q.is-unknown { border-left: 3px solid var(--unknown); }
@media (max-width: 600px) { .nv-q { grid-template-columns: 10px minmax(0, 1fr); } .nv-q .nv-q-when { grid-column: 2; } }

/* ══════════════════════════ states ══════════════════════════
   Empty, loading, error and unavailable each look like what they are. An
   unknown value is never rendered as a good one. */
.nv-empty {
  display: flex; flex-direction: column; align-items: center; text-align: center; gap: var(--sp-3);
  padding: var(--sp-6) var(--sp-4); color: var(--text-2);
}
.nv-empty svg { width: 32px; height: 32px; fill: none; stroke: var(--text-3); stroke-width: 1.5; }
.nv-empty h3 { margin: 0; font-size: var(--fs-h3); color: var(--text); }
.nv-empty p { margin: 0; font-size: var(--fs-body); }

.nv-notice {
  display: grid; grid-template-columns: 18px minmax(0, 1fr); gap: 10px; align-items: start;
  padding: 12px 14px; border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--surface-3); color: var(--text-2); font-size: var(--fs-sm); line-height: 1.5;
}
.nv-notice svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; margin-top: 1px; }
.nv-notice b { color: var(--text); font-weight: 600; }
.nv-notice a { color: var(--blue-text); }
.nv-notice.is-warn { border-color: var(--warn-line); background: var(--warn-soft); color: var(--text); }
.nv-notice.is-warn svg { stroke: var(--warn-text); }
.nv-notice.is-danger { border-color: var(--danger-line); background: var(--danger-soft); color: var(--text); }
.nv-notice.is-danger svg { stroke: var(--danger-text); }
.nv-notice.is-ok { border-color: var(--ok-line); background: var(--ok-soft); color: var(--text); }
.nv-notice.is-ok svg { stroke: var(--ok-text); }
.nv-notice.is-unavailable { border-style: dashed; }

/* Loading: skeletons shaped like what is coming, plus one line saying what
   stage the wait is at. */
@keyframes nv-shimmer { 0% { background-position: -420px 0; } 100% { background-position: 420px 0; } }
.nv-skel {
  border-radius: var(--r-sm);
  background: linear-gradient(90deg, rgba(10, 22, 40, 0.030) 25%, rgba(10, 22, 40, 0.063) 50%, rgba(10, 22, 40, 0.030) 75%);
  background-size: 840px 100%; animation: nv-shimmer 1.4s linear infinite;
}
@media (prefers-reduced-motion: reduce) { .nv-skel { animation: none; background: rgba(10, 22, 40, 0.042); } }
.nv-skel-line { height: 11px; margin-bottom: 8px; }
.nv-skel-line:last-child { margin-bottom: 0; width: 62%; }
.nv-loading { display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--sp-4); }
.nv-loading-say { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-sm); color: var(--text-2); }
.nv-sweep { width: 15px; height: 15px; border-radius: 50%; border: 1.5px solid var(--blue-line); position: relative; flex: none; }
.nv-sweep::after { content: ""; position: absolute; inset: -1.5px; border-radius: 50%; border: 1.5px solid transparent; border-top-color: var(--blue-text); animation: nv-spin 1.05s linear infinite; }
@keyframes nv-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .nv-sweep::after { animation: none; border-top-color: var(--blue-line); } }
.nv-skel-card { display: grid; grid-template-columns: 46px minmax(0, 1fr) 84px; gap: var(--sp-3); align-items: center; padding: var(--sp-3); border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--surface); }
.nv-skel-avatar { width: 46px; height: 46px; border-radius: 10px; }
.nv-skel-card .nv-skel-line:nth-child(1) { width: 45%; }

/* The map is dark before its first tile, which is also what a failed map
   looks like. This names the stage so the two are never confused. */
.nv-map-loading {
  position: absolute; inset: 0; display: grid; place-items: center; z-index: 400; pointer-events: none;
  background: var(--bg); transition: opacity 0.3s;
}
.nv-map-loading.gone { opacity: 0; }
.nv-map-loading .nv-loading-say { padding: 9px 15px; border-radius: 999px; border: 1px solid var(--border); background: var(--glass); backdrop-filter: blur(8px); color: var(--text); }
.nv-map-loading.is-failed { pointer-events: auto; }
.nv-map-loading.is-failed .nv-loading-say { border-color: var(--danger-line); }

/* ══════════════════════════ tabs ══════════════════════════ */
.nv-tabs { display: flex; gap: 2px; padding: 3px; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface-3); overflow-x: auto; scrollbar-width: none; }
.nv-tabs::-webkit-scrollbar { display: none; }
.nv-tabs button, .nv-tabs a {
  display: inline-flex; align-items: center; gap: 6px; min-height: 32px; padding: 0 12px;
  border: 0; border-radius: 6px; background: transparent; color: var(--text-2);
  font: 600 var(--fs-sm)/1 var(--font); white-space: nowrap; cursor: pointer; text-decoration: none; flex: none;
  transition: color 0.16s, background 0.16s;
}
.nv-tabs button:hover, .nv-tabs a:hover { color: var(--text); background: var(--hover); }
.nv-tabs button.on, .nv-tabs a.on, .nv-tabs [aria-selected="true"], .nv-tabs [aria-current="page"] { color: var(--text); background: var(--surface); box-shadow: inset 0 0 0 1px var(--border-strong), 0 1px 2px rgba(16, 24, 40, 0.16); }
.nv-tabs .nv-tab-n { font-size: var(--fs-label); color: var(--text-3); }

/* ══════════════════════════ dialogs & drawers ══════════════════════════ */
.nv-scrim { display: grid; place-items: center; padding: var(--sp-4); }
.nv-dialog {
  width: 100%; max-width: 480px; max-height: calc(100svh - 32px); overflow: auto;
  background: var(--surface-2); border: 1px solid var(--border-strong); border-radius: var(--r-lg);
  box-shadow: var(--shadow); padding: var(--sp-5);
}
.nv-dialog h2 { font-size: var(--fs-h2); margin-bottom: var(--sp-2); }
.nv-dialog > p { color: var(--text-2); }
.nv-dialog-actions { display: flex; justify-content: flex-end; gap: var(--sp-2); margin-top: var(--sp-5); flex-wrap: wrap; }
/* NV.confirm(): the product's confirmation, a native <dialog> (focus trap and
   Escape come with it). Centered on a desktop, a bottom sheet on a phone. */
dialog.nv-confirm { color: var(--text); margin: auto; }
dialog.nv-confirm::backdrop { background: rgba(10, 22, 40, 0.42); }
dialog.nv-confirm h2 { margin: 0 0 var(--sp-2); }
dialog.nv-confirm p { margin: 0; color: var(--text-2); line-height: 1.55; }
.nv-confirm-list { margin: var(--sp-3) 0 0; padding-left: 1.2em; color: var(--text-2); line-height: 1.6; }
.nv-confirm-field { margin-top: var(--sp-4); }
dialog.nv-confirm .nv-btn { min-height: 44px; }
@media (max-width: 600px) {
  dialog.nv-confirm { margin: auto 0 0; width: 100%; max-width: none; border-radius: var(--r-lg) var(--r-lg) 0 0; padding-bottom: calc(var(--sp-5) + env(safe-area-inset-bottom)); }
  dialog.nv-confirm .nv-dialog-actions .nv-btn { flex: 1 1 auto; }
}
.nv-drawer {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 910; width: min(480px, 100vw);
  background: var(--surface); border-left: 1px solid var(--border); box-shadow: -12px 0 40px var(--drawer-shadow);
  display: flex; flex-direction: column; transform: translateX(0); transition: transform 0.18s ease-out;
}
.nv-drawer[hidden] { display: none; }
.nv-drawer-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); padding: var(--sp-4) var(--sp-5); border-bottom: 1px solid var(--border); }
.nv-drawer-body { flex: 1 1 auto; overflow: auto; padding: var(--sp-4) var(--sp-5); }
.nv-drawer-foot { padding: var(--sp-3) var(--sp-5); border-top: 1px solid var(--border); display: flex; gap: var(--sp-2); justify-content: flex-end; }
@media (max-width: 600px) {
  .nv-drawer { top: auto; left: 0; width: auto; max-height: 86svh; border-left: 0; border-top: 1px solid var(--border-strong); border-radius: var(--r-lg) var(--r-lg) 0 0; }
  .nv-scrim { align-items: end; padding: 0; }
  .nv-dialog { max-width: none; border-radius: var(--r-lg) var(--r-lg) 0 0; max-height: 92svh; }
}
.nv-x { display: inline-grid; place-items: center; width: 36px; height: 36px; border: 0; border-radius: var(--r-md); background: transparent; color: var(--text-2); font-size: 20px; line-height: 1; cursor: pointer; }
.nv-x:hover { background: var(--hover); color: var(--text); }

/* ══════════════════════════ tooltip ══════════════════════════ */
[data-tip] { position: relative; }
[data-tip]:hover::after, [data-tip]:focus-visible::after {
  content: attr(data-tip); position: absolute; left: 50%; bottom: calc(100% + 6px); transform: translateX(-50%);
  padding: 6px 9px; border-radius: var(--r-sm); background: var(--surface-2); border: 1px solid var(--border-strong);
  color: var(--text); font-size: var(--fs-label); font-weight: 500; white-space: nowrap; z-index: 50; pointer-events: none;
  box-shadow: var(--shadow-sm);
}

/* ══════════════════════════ toast ══════════════════════════
   One live region (NV.toast in nv-core.js), present from load and empty
   until there is something to say. It sits above every drawer, sheet, scrim
   and modal form (900 to 950) and clears the home indicator on a phone. */
.nv-toast {
  position: fixed; left: 50%; bottom: calc(20px + env(safe-area-inset-bottom)); transform: translateX(-50%); z-index: 960;
  display: grid; justify-items: center; width: max-content; max-width: min(520px, calc(100vw - 32px)); pointer-events: none;
}
.nv-toast-msg {
  padding: 10px 14px; pointer-events: auto;
  border: 1px solid var(--border-strong); border-radius: var(--r-md); background: var(--surface-2); color: var(--text);
  font-size: var(--fs-sm); box-shadow: var(--shadow);
}
.nv-toast-msg.bad { border-color: var(--danger-line); }

/* ══════════════════════════ the application shell ══════════════════════════
   One header on every authenticated surface: brand, the three workspaces,
   the account menu. Same height, spacing and active treatment everywhere. */
.nv-shell {
  display: flex; align-items: center; gap: var(--sp-4);
  height: var(--shell-h); padding: 0 var(--sp-4);
  background: var(--surface); border-bottom: 1px solid var(--border);
  position: relative; z-index: 60;
}
.nv-shell-brand { display: inline-flex; align-items: center; gap: var(--sp-2); color: var(--text); text-decoration: none; font-weight: 700; font-size: 15px; letter-spacing: 0.02em; flex: none; min-height: var(--tap); }
.nv-shell-brand svg { width: 22px; height: 22px; flex: none; }
.nv-shell-spacer { flex: 1 1 auto; min-width: 0; }
.nv-shell-right { display: flex; align-items: center; gap: var(--sp-2); flex: none; }

/* Workspace switcher. Active = filled AND underlined-weight, so it does not
   depend on colour alone. */
.nv-ws { display: inline-flex; align-items: center; gap: 2px; padding: 3px; border-radius: var(--r-md); background: var(--surface-3); border: 1px solid var(--border); flex: none; }
.nv-ws a {
  display: inline-flex; align-items: center; min-height: 34px; padding: 0 13px; border-radius: 6px;
  color: var(--text-2); font-size: var(--fs-sm); font-weight: 600; text-decoration: none; white-space: nowrap;
  transition: color 0.16s, background 0.16s;
}
.nv-ws a:hover { color: var(--text); background: var(--hover); }
.nv-ws a[aria-current="page"] { color: var(--on-accent); background: var(--blue); font-weight: 700; }
.nv-ws a:focus-visible { box-shadow: var(--focus); }

/* Secondary row: where you are WITHIN a workspace. Active has a bar, not
   only a colour. */
.nv-subnav {
  display: flex; align-items: center; gap: 2px; height: 44px; padding: 0 var(--sp-4);
  background: var(--bg); border-bottom: 1px solid var(--border); overflow-x: auto; scrollbar-width: none;
}
.nv-subnav::-webkit-scrollbar { display: none; }
.nv-subnav a, .nv-subnav button {
  position: relative; display: inline-flex; align-items: center; height: 44px; padding: 0 12px;
  border: 0; background: transparent; color: var(--text-2);
  font: 600 var(--fs-sm)/1 var(--font); text-decoration: none; white-space: nowrap; cursor: pointer; flex: 0 0 auto;
  transition: color 0.16s;
}
.nv-subnav a:hover, .nv-subnav button:hover { color: var(--text); }
.nv-subnav a.on, .nv-subnav button.on, .nv-subnav a[aria-current="page"], .nv-subnav [aria-selected="true"] { color: var(--text); }
.nv-subnav a.on::after, .nv-subnav button.on::after, .nv-subnav a[aria-current="page"]::after, .nv-subnav [aria-selected="true"]::after {
  content: ""; position: absolute; left: 12px; right: 12px; bottom: -1px; height: 2px; background: var(--blue-text); border-radius: 2px 2px 0 0;
}
.nv-subnav .nv-tab-n { margin-left: 6px; font-size: var(--fs-label); color: var(--text-3); }

.nv-who { display: inline-flex; align-items: center; height: 30px; padding: 0 11px; border-radius: var(--r-sm); background: var(--surface-3); border: 1px solid var(--border); color: var(--text-2); font-size: 12px; max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Account menu — the one place for identity and sign out. */
.acct { position: relative; flex: none; }
.acct-btn {
  display: inline-flex; align-items: center; gap: 5px; min-height: 36px; padding: 0 6px 0 4px;
  border: 1px solid transparent; border-radius: 999px; background: transparent; cursor: pointer; color: var(--text-2);
  transition: background 0.16s, border-color 0.16s;
}
.acct-btn:hover, .acct.is-open .acct-btn { background: var(--hover); border-color: var(--border); }
.acct-av { display: grid; place-items: center; width: 28px; height: 28px; border-radius: 50%; background: var(--blue-soft); border: 1px solid var(--blue-line); color: var(--text); font: 700 10.5px/1 var(--font); letter-spacing: 0.04em; }
.acct-av.lg { width: 38px; height: 38px; font-size: 13px; }
.acct-chev { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; transition: transform 0.16s; }
.acct.is-open .acct-chev { transform: rotate(180deg); }
.acct-menu {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 70; min-width: 248px; padding: 6px;
  border: 1px solid var(--border-strong); border-radius: var(--r-lg); background: var(--surface-2); box-shadow: var(--shadow);
}
.acct-head { display: flex; align-items: center; gap: 10px; padding: 10px 10px 12px; border-bottom: 1px solid var(--hairline); margin-bottom: 6px; }
.acct-id { min-width: 0; }
.acct-id b { display: block; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.acct-id small { display: block; margin-top: 2px; font-size: 12px; color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.acct-item { display: flex; align-items: center; gap: 9px; width: 100%; min-height: 38px; padding: 8px 10px; border: 0; border-radius: var(--r-md); background: transparent; color: var(--text); font: 500 13px/1 var(--font); text-align: left; text-decoration: none; cursor: pointer; }
.acct-item svg { width: 15px; height: 15px; flex: none; fill: none; stroke: var(--text-2); stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.acct-item:hover { background: var(--hover); }
.acct-out { color: var(--danger-text); }
.acct-out:hover { background: var(--danger-soft); }
.acct-out svg { stroke: var(--danger-text); }

@media (max-width: 1000px) { .nv-shell { gap: var(--sp-2); } .nv-who { max-width: 22vw; } }
@media (max-width: 860px) { .nv-who { display: none; } }
@media (max-width: 760px) {
  .nv-shell { padding: 0 var(--sp-3); gap: var(--sp-2); height: auto; min-height: var(--shell-h); flex-wrap: wrap; row-gap: var(--sp-2); padding-top: var(--sp-2); padding-bottom: var(--sp-2); }
  .nv-shell > * { flex: 0 0 auto; }
  .nv-shell-spacer { flex: 1 1 auto; min-width: 0; }
  .nv-shell-brand { order: -2; }
  .nv-shell-brand span { display: none; }
  .nv-ws { order: -1; }
  .nv-ws a { padding: 0 10px; font-size: 12px; }
  .nv-shell-right { margin-left: auto; }
  .nv-subnav { padding: 0 var(--sp-3); }
}

/* ══════════════════════════ public site navigation ══════════════════════════
   The marketing pages and sign-in share this header. Fixed, one row, the
   same brand mark as the application shell. */
.site-nav {
  position: sticky; top: 0; z-index: 100;
  display: flex; align-items: center; gap: var(--sp-4);
  min-height: 64px; padding: 0 var(--page-gutter);
  background: rgba(255, 255, 255, 0.94); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--border);
}
.site-nav-brand { display: inline-flex; align-items: center; gap: 9px; color: var(--text); text-decoration: none; font-weight: 700; font-size: 16px; letter-spacing: 0.04em; min-height: var(--tap); flex: none; }
.site-nav-brand svg { width: 24px; height: 24px; }
.site-nav-links { display: flex; align-items: center; gap: 4px; margin-left: var(--sp-3); }
.site-nav-links a, .site-nav-links button {
  display: inline-flex; align-items: center; min-height: 40px; padding: 0 12px; border: 0; border-radius: var(--r-md);
  background: transparent; color: var(--text-2); font: 500 var(--fs-body)/1 var(--font); text-decoration: none; cursor: pointer;
  transition: color 0.16s, background 0.16s;
}
.site-nav-links a:hover, .site-nav-links button:hover { color: var(--text); background: var(--hover); }
.site-nav-links a[aria-current="page"] { color: var(--text); box-shadow: inset 0 -2px 0 var(--blue-text); border-radius: 0; }
.site-nav-spacer { flex: 1 1 auto; }
.site-nav-right { display: flex; align-items: center; gap: var(--sp-2); }
.site-nav-toggle { display: none; }
.site-nav-links .site-nav-mobile-only { display: none; }
@media (max-width: 860px) {
  .site-nav-links { display: none; }
  .site-nav.is-open .site-nav-links {
    display: flex; flex-direction: column; align-items: stretch; gap: 2px;
    position: absolute; left: 0; right: 0; top: 100%; padding: var(--sp-2) var(--page-gutter) var(--sp-4);
    background: var(--surface); border-bottom: 1px solid var(--border); margin: 0;
  }
  .site-nav.is-open .site-nav-links a { min-height: var(--tap); }
  .site-nav-toggle { display: inline-grid; place-items: center; width: var(--tap); height: var(--tap); border: 1px solid var(--border); border-radius: var(--r-md); background: transparent; color: var(--text); cursor: pointer; }
  .site-nav-toggle svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
  .site-nav-right .nv-btn-ghost { display: none; }
  .site-nav.is-open .site-nav-links .site-nav-mobile-only { display: flex; }
}

/* ══════════════════════════ site footer ══════════════════════════ */
.site-foot { border-top: 1px solid var(--border); background: var(--bg); }
.site-foot-in { max-width: var(--page-max); margin: 0 auto; padding: var(--sp-6) var(--page-gutter); display: grid; grid-template-columns: minmax(0, 1.4fr) repeat(3, minmax(0, 1fr)); gap: var(--sp-5); }
.site-foot h3 { font-size: var(--fs-label); text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-3); margin-bottom: var(--sp-2); }
.site-foot a { display: block; padding: 4px 0; color: var(--text-2); text-decoration: none; font-size: var(--fs-sm); }
.site-foot a:hover { color: var(--text); }
.site-foot p { color: var(--text-3); font-size: var(--fs-sm); max-width: 40ch; }
.site-foot-bar { max-width: var(--page-max); margin: 0 auto; padding: var(--sp-3) var(--page-gutter) var(--sp-5); border-top: 1px solid var(--hairline); color: var(--text-3); font-size: var(--fs-label); display: flex; gap: var(--sp-3); flex-wrap: wrap; justify-content: space-between; }
@media (max-width: 760px) { .site-foot-in { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* ══════════════════════════ application rail ═══════════════════════════
   The logged-in navigation: one left rail on every application page.
   Dashboard · Operations · Network · Resources · Insights, and at the foot
   Notifications · Help · Settings · the account. Narrow screens get a
   68px icon rail; phones get a bottom bar. Mounted by /js/nv-shell.js on any
   page whose <body> carries data-nav. */
body.has-rail { display: grid; grid-template-columns: var(--rail-w) minmax(0, 1fr); min-height: 100svh; }.nv-rail {
  grid-column: 1; grid-row: 1 / span 999;
  position: sticky; top: 0; height: 100svh;
  display: flex; flex-direction: column; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-2);
  background: var(--sidebar); border-right: 1px solid var(--border);
  z-index: 80;
}
.nv-rail-brand { display: flex; align-items: center; gap: 10px; height: 44px; padding: 0 10px; color: var(--text); text-decoration: none; font: 800 15px/1 var(--font-head); letter-spacing: 0.06em; }
.nv-rail-brand svg { width: 26px; height: 26px; flex: none; }
.nv-rail-group { display: grid; gap: 2px; }
.nv-rail-group.is-foot { margin-top: auto; padding-top: var(--sp-2); border-top: 1px solid var(--hairline); }
.nv-rail-item {
  position: relative; display: flex; align-items: center; gap: 11px;
  min-height: 40px; padding: 0 10px; border-radius: var(--r-md);
  color: var(--text-2); font: 600 var(--fs-body)/1 var(--font); text-decoration: none; cursor: pointer;
  border: 0; background: transparent; width: 100%; text-align: left;
  transition: background 0.14s, color 0.14s;
}
.nv-rail-item svg { width: 18px; height: 18px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.nv-rail-item:hover { color: var(--text); background: var(--hover); }
.nv-rail-item[aria-current="page"] { color: var(--blue-text); background: var(--blue-soft); }
.nv-rail-item[aria-current="page"]::before { content: ""; position: absolute; left: -8px; top: 9px; bottom: 9px; width: 3px; border-radius: 2px; background: var(--blue-text); }
.nv-rail-item .nv-rail-n { margin-left: auto; min-width: 20px; height: 20px; padding: 0 6px; border-radius: 999px; display: grid; place-items: center; font-size: 11px; font-weight: 700; background: var(--danger-fill); color: #fff; }
.nv-rail-item .nv-rail-n.is-info { background: var(--blue); }
.nv-rail-me { display: flex; align-items: center; gap: 10px; width: 100%; min-height: 52px; padding: 6px 8px; border: 0; border-radius: var(--r-md); background: transparent; color: var(--text); cursor: pointer; text-align: left; transition: background 0.14s; }
.nv-rail-me:hover { background: var(--hover); }
.nv-rail-me .acct-av { width: 34px; height: 34px; font-size: 12px; }
.nv-rail-me b { display: block; font-size: var(--fs-sm); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nv-rail-me small { display: block; font-size: var(--fs-label); color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nv-rail .acct-menu { position: fixed; left: calc(var(--rail-w) + 8px); bottom: 12px; top: auto; right: auto; }/* Narrow: icon rail */
@media (max-width: 1100px) {
  body.has-rail { grid-template-columns: var(--rail-w-narrow) minmax(0, 1fr); }
  .nv-rail { padding: var(--sp-3) 8px; }
  .nv-rail-brand span, .nv-rail-item span, .nv-rail-me b, .nv-rail-me small { display: none; }
  .nv-rail-brand { justify-content: center; padding: 0; }
  .nv-rail-item { justify-content: center; padding: 0; min-height: 44px; }
  .nv-rail-item .nv-rail-n { position: absolute; top: 4px; right: 4px; margin: 0; min-width: 16px; height: 16px; font-size: 10px; padding: 0 4px; }
  .nv-rail-me { justify-content: center; padding: 6px 0; }
  .nv-rail .acct-menu { left: calc(var(--rail-w-narrow) + 8px); }
}
/* Phone: bottom bar, rail hidden */
/* The phone layout lives with the drawer, further down: the rail slides in
   rather than collapsing into a five-slot bottom bar that could only ever
   show four of the six sections. */
/* The "More" sheet on phones and the notifications drawer everywhere */
.nv-sheet { position: fixed; inset: 0; z-index: 920; background: rgba(15, 23, 42, 0.30); display: flex; align-items: flex-end; }
.nv-sheet[hidden] { display: none; }
.nv-sheet-body { width: 100%; max-height: 80svh; overflow: auto; background: var(--surface-2); border-top: 1px solid var(--border-strong); border-radius: var(--r-lg) var(--r-lg) 0 0; padding: var(--sp-3) var(--sp-3) calc(var(--sp-4) + env(safe-area-inset-bottom)); }
.nv-sheet-grab { width: 36px; height: 4px; border-radius: 2px; background: var(--border-strong); margin: 0 auto var(--sp-3); }
.nv-sheet .nv-rail-item { min-height: 48px; }
/* Notification centre */
.nv-notif { display: grid; gap: var(--sp-2); }
.nv-notif-row { display: grid; grid-template-columns: 10px minmax(0, 1fr) auto; gap: var(--sp-3); align-items: start; padding: 12px 14px; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface); color: inherit; text-decoration: none; }
.nv-notif-row:hover { border-color: var(--border-strong); }
.nv-notif-row b { display: block; font-size: var(--fs-body); font-weight: 600; }
.nv-notif-row small { display: block; margin-top: 2px; color: var(--text-2); font-size: var(--fs-sm); }
.nv-notif-row time { font-size: var(--fs-label); color: var(--text-3); white-space: nowrap; }
.nv-notif-row .nv-dot { margin-top: 6px; }

/* ══════════════════════════ the map component ═══════════════════════════
   One map everywhere (/js/nv-map.js?v=b2b6717423): controls top-right, stage line
   top-left, legend bottom-left, layers popover under the Layers button.
   Marker glyphs carry a state colour AND a letter, so state never rides on
   colour alone. */
.nvm { position: relative; background: var(--bg); border-radius: var(--r-lg); overflow: hidden; isolation: isolate; }
.nvm.leaflet-container { font-family: var(--font); }
.nvm .leaflet-control-attribution { background: rgba(255, 255, 255, 0.94); color: var(--text-3); font-size: 10px; padding: 2px 6px; }
.nvm .leaflet-control-attribution a { color: var(--text-2); }
.nvm-ctl { position: absolute; top: 10px; right: 10px; z-index: 700; display: flex; flex-direction: column; gap: 6px; align-items: flex-end; }
.nvm-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-width: 36px; height: 36px; padding: 0 8px;
  border: 1px solid var(--border-strong); border-radius: var(--r-md); background: var(--glass); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  color: var(--text); font: 600 var(--fs-sm)/1 var(--font); cursor: pointer; box-shadow: var(--shadow-sm); transition: background 0.14s, border-color 0.14s;
}
.nvm-btn svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.nvm-btn:hover { background: var(--surface-2); border-color: var(--blue-line); }
.nvm-btn.is-on { color: var(--blue-text); border-color: var(--blue-line); }
.nvm-btn-group { display: flex; flex-direction: column; border-radius: var(--r-md); overflow: hidden; border: 1px solid var(--border-strong); box-shadow: var(--shadow-sm); }
.nvm-btn-group .nvm-btn { border: 0; border-radius: 0; box-shadow: none; }
.nvm-btn-group .nvm-btn + .nvm-btn { border-top: 1px solid var(--border); }
@media (pointer: coarse) { .nvm-btn { min-width: var(--tap); height: var(--tap); } }
.nvm-stage { position: absolute; top: 10px; left: 10px; z-index: 700; padding: 7px 11px; border-radius: 999px; border: 1px solid var(--border); background: var(--glass); color: var(--text-2); font-size: var(--fs-label); font-weight: 600; }
.nvm-stage.is-bad { border-color: var(--danger-line); color: var(--danger-text); max-width: calc(100% - 120px); border-radius: 14px; line-height: 1.35; }
/* The legend is a grid, and a display rule outranks the hidden attribute:
   a closed legend drew as an empty box in the corner of every map. */
.nvm-stage[hidden], .nvm-legend[hidden], .nvm-pop[hidden] { display: none; }
.nvm-legend { position: absolute; left: 10px; bottom: 26px; z-index: 700; display: grid; gap: 5px; max-width: min(300px, 70%); padding: 10px 12px; border-radius: var(--r-md); border: 1px solid var(--border); background: var(--glass); backdrop-filter: blur(8px); font-size: var(--fs-label); color: var(--text-2); }
.nvm-lg-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.nvm-lg-row i, .nvm-pop-row i, .nvm-lg-states i { display: inline-block; width: 10px; height: 10px; border-radius: 3px; background: var(--c); flex: none; }
.nvm-lg-row.is-bad { color: var(--danger-text); }
.nvm-lg-row.is-meta { color: var(--text-3); }
.nvm-lg-states { display: inline-flex; gap: 6px; align-items: center; margin-left: 4px; color: var(--text-3); }
.nvm-lg-states i { width: 8px; height: 8px; margin-left: 2px; }
.nvm-pop { position: absolute; top: 52px; right: 10px; z-index: 720; width: 260px; max-height: calc(100% - 78px); overflow: auto; padding: 8px; border-radius: var(--r-lg); border: 1px solid var(--border-strong); background: var(--surface-2); box-shadow: var(--shadow); }
.nvm-pop-h { padding: 6px 8px 4px; font-size: var(--fs-label); color: var(--text-3); }
.nvm-pop-row { display: flex; align-items: center; gap: 9px; min-height: 34px; padding: 0 8px; border-radius: var(--r-sm); font-size: var(--fs-sm); color: var(--text); cursor: pointer; }
.nvm-pop-row:hover { background: var(--hover); }
.nvm-pop-row input { accent-color: var(--blue); width: 15px; height: 15px; margin: 0; }
.nvm-seg { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 2px; padding: 3px; margin: 0 4px 6px; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface-3); }
.nvm-seg button { min-height: 30px; border: 0; border-radius: 6px; background: transparent; color: var(--text-2); font: 600 12px/1 var(--font); cursor: pointer; }
.nvm-seg button.on { background: var(--surface); color: var(--text); box-shadow: var(--shadow-sm); }
.nvm-pin-wrap { background: transparent; border: 0; }
.nvm-pin { position: relative; display: grid; place-items: center; width: 28px; height: 28px; border-radius: 50%; background: var(--surface); border: 2px solid var(--c); box-shadow: 0 0 0 3px rgba(16, 24, 40, 0.16), 0 2px 8px rgba(16, 24, 40, 0.16); transition: transform 0.14s, box-shadow 0.14s; }
.nvm-pin i { font: 700 12px/1 var(--font); font-style: normal; color: var(--c); }
.nvm-pin-operations::after { content: ""; position: absolute; inset: 5px; border-radius: 50%; background: var(--c); }
.nvm-pin-operations i { display: none; }
.nvm-pin.is-sel, .is-hover .nvm-pin { transform: scale(1.22); box-shadow: 0 0 0 4px var(--blue-line), 0 4px 14px rgba(16, 24, 40, 0.16); }
.nvm-cluster-wrap { background: transparent; border: 0; }
.nvm-cluster { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 50%; background: var(--surface); border: 2px solid var(--blue); color: var(--text); font: 700 12px/1 var(--font); box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.25); }
.nvm-cluster-pilots { border-color: var(--ok); box-shadow: 0 0 0 4px rgba(34, 197, 94, 0.22); }
.nvm-cluster-sites { border-color: var(--unknown); box-shadow: 0 0 0 4px rgba(156, 163, 175, 0.2); }
.nvm-pt { display: grid; place-items: center; width: 22px; height: 22px; border-radius: 6px; background: var(--surface); border: 2px solid var(--blue-text); color: var(--blue-text); font: 700 10px/1 var(--font); }
.nvm-pt i { font-style: normal; }
.nvm-pt-launch { border-color: var(--ok); color: var(--ok-text); }
.nvm-pt-recovery { border-color: var(--warn); color: var(--warn-text); }
.nvm-route { stroke-dasharray: 8 6; animation: nvm-dash 1.2s linear infinite; }
@keyframes nvm-dash { to { stroke-dashoffset: -28; } }
@media (prefers-reduced-motion: reduce) { .nvm-route { animation: none; } }
.nvm-tip.leaflet-tooltip { background: var(--surface-2); border: 1px solid var(--border-strong); color: var(--text); font-size: var(--fs-label); border-radius: var(--r-sm); box-shadow: var(--shadow-sm); padding: 6px 9px; }
.nvm-tip.leaflet-tooltip::before { display: none; }
.nvm:fullscreen { border-radius: 0; }
/* Marker preview card that floats over a map */
.nvm-preview { position: absolute; left: 10px; bottom: 26px; z-index: 710; width: min(340px, calc(100% - 20px)); padding: var(--sp-4); border-radius: var(--r-lg); border: 1px solid var(--border-strong); background: var(--surface-2); box-shadow: var(--shadow); }
.nvm-preview .nv-x { position: absolute; top: 6px; right: 6px; }
.nvm-preview h3 { padding-right: 36px; font-size: var(--fs-h3); }
.nvm-preview .nv-kv { margin-top: var(--sp-3); font-size: var(--fs-sm); }
.nvm-preview .nvm-preview-foot { display: flex; gap: var(--sp-2); margin-top: var(--sp-3); }
@media (max-width: 600px) { .nvm-legend { display: none; } .nvm-preview { left: 8px; right: 8px; width: auto; bottom: 8px; } }

/* Pages that still carry the old top header keep its ids for their scripts,
   but the rail is the navigation now. The live map keeps its search bar and
   only loses the workspace switcher. */
body.has-rail header.nv-shell:not(#topbar) { display: none; }
body.has-rail #topbar .nv-ws { display: none; }
body.has-rail .nv-subnav { border-top: 0; }

/* ══════════════════════════ section chrome (all application pages) ═══════════════════════════ */
.sec-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; flex-wrap: wrap; padding: 24px clamp(12px, 2.4vw, 26px) 0; max-width: 1680px; margin: 0 auto; }
.sec-head h1 { margin: 0; font-size: 28px; letter-spacing: -0.02em; }
.sec-head p { margin: 4px 0 0; color: var(--text-2); }
.sec-bar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; padding: 12px clamp(12px, 2.4vw, 26px) 0; max-width: 1680px; margin: 0 auto; }
.sec-tabs { display: flex; gap: 2px; padding: 3px; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface-3); overflow-x: auto; scrollbar-width: none; }
.sec-tabs::-webkit-scrollbar { display: none; }
.sec-tabs button, .sec-tabs a { display: inline-flex; align-items: center; gap: 6px; min-height: 34px; padding: 0 14px; border: 0; border-radius: 6px; background: transparent; color: var(--text-2); font: 600 13px/1 var(--font); white-space: nowrap; cursor: pointer; text-decoration: none; }
.sec-tabs button:hover, .sec-tabs a:hover { color: var(--text); background: var(--hover); }
.sec-tabs button.on, .sec-tabs a.on, .sec-tabs [aria-current="page"] { color: var(--text); background: var(--surface); box-shadow: var(--shadow-sm); }
.sec-tabs .nv-tab-n { font-size: 12px; color: var(--text-3); }
.sec-view { display: inline-flex; gap: 2px; padding: 3px; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface-3); }
.sec-view button { min-height: 32px; padding: 0 12px; border: 0; border-radius: 6px; background: transparent; color: var(--text-2); font: 600 13px/1 var(--font); cursor: pointer; display: inline-flex; align-items: center; gap: 6px; }
.sec-view button.on { background: var(--surface); color: var(--text); box-shadow: var(--shadow-sm); }
.sec-view svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 2; }
.sec-right { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
@media (pointer: coarse) { .sec-tabs button, .sec-view button { min-height: 40px; } }



/* ═══════════════════════ tactical layer ═══════════════════════════════
   The instrument vocabulary shared by every surface: data keys, data
   values, hairline dividers, the grid ground, live telemetry pings and
   the status chip. Colour still comes from the tokens above; this layer
   only decides how a fact is TYPESET.
   ══════════════════════════════════════════════════════════════════════ */

/* A data key: the label above or beside a value. Small, monospaced,
   uppercase, wide-tracked — never competing with the value it names. */
/* A field label in the interface face at a readable size. The monospaced,
   wide-tracked capitals this used to be are reserved for --font-mono data:
   identifiers, coordinates, registrations, timestamps. */
.nv-key {
  display: block;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-3);
}
.nv-key-mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* A data value: monospaced and tabular so columns of numbers line up and
   a changing digit does not shift the ones beside it. */
.nv-val, .nv-mono {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

/* A vertical hairline between metrics in a ribbon. */
.nv-rule { display: inline-block; width: 1px; height: 16px; background: var(--border); flex: none; }

/* The grid ground. Two 24px gradients, faint enough to read as paper
   rather than as content. */
.nv-grid-bg {
  background-image:
    linear-gradient(to right, rgba(10, 22, 40, 0.06) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(10, 22, 40, 0.06) 1px, transparent 1px);
  background-size: 24px 24px;
}

/* A live telemetry ping: a solid dot with one expanding ring behind it.
   Only for something genuinely live — a streaming feed, an active flight.
   It stops moving when the viewer asks for less motion. */
.nv-ping { position: relative; display: inline-flex; width: 8px; height: 8px; flex: none; }
.nv-ping::before, .nv-ping::after { content: ""; position: absolute; inset: 0; border-radius: 50%; background: var(--ok); }
.nv-ping::before { animation: nv-ping 1.8s cubic-bezier(0, 0, 0.2, 1) infinite; opacity: 0.75; }
.nv-ping.is-live::before, .nv-ping.is-live::after { background: var(--live); }
.nv-ping.is-warn::before, .nv-ping.is-warn::after { background: var(--warn); }
.nv-ping.is-bad::before, .nv-ping.is-bad::after { background: var(--danger); }
.nv-ping.is-off::before { animation: none; opacity: 0; }
.nv-ping.is-off::after { background: var(--unknown); }
@keyframes nv-ping { 75%, 100% { transform: scale(2.4); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .nv-ping::before { animation: none; opacity: 0.35; } }

/* Status chips read as instrument annunciators: monospaced, uppercase,
   tinted ground, hairline border in the status colour. */
.nv-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; border-radius: 999px;
  font-family: var(--font); font-size: 12px; font-weight: 600;
  letter-spacing: 0; text-transform: none; white-space: nowrap;
  background: var(--unknown-soft); color: var(--unknown-text); border: 1px solid var(--unknown-line);
}
.nv-chip-ok { background: var(--ok-soft); color: var(--ok-text); border-color: var(--ok-line); }
.nv-chip-warn { background: var(--warn-soft); color: var(--warn-text); border-color: var(--warn-line); }
.nv-chip-bad { background: var(--danger-soft); color: var(--danger-text); border-color: var(--danger-line); }
.nv-chip-live { background: var(--live-soft); color: var(--live); border-color: var(--live-line); }
.nv-chip-blue { background: var(--blue-soft); color: var(--blue-text); border-color: var(--blue-line); }
.nv-chip .nv-ping { width: 6px; height: 6px; }

/* A panel lit from above: hairline all round, one brighter top edge. */
.nv-panel {
  background: var(--surface); border: 1px solid var(--border);
  border-top-color: var(--edge); border-radius: var(--r-md);
}
.nv-glass {
  background: var(--glass); border: 1px solid var(--border);
  border-top-color: var(--edge); border-radius: var(--r-md);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
}

/* A ribbon: the fixed strip at the top of a workspace. */
.nv-ribbon {
  display: flex; align-items: center; gap: var(--sp-3);
  height: var(--shell-h); padding: 0 var(--sp-4);
  background: var(--ribbon); border-bottom: 1px solid var(--border);
}
.nv-ribbon .nv-key { display: inline-block; }


/* ═══════════════════ the canvas workspace ═════════════════════════════
   A canvas page is a full-bleed instrument: the viewport is the work
   surface, nothing scrolls behind it, and the map fills whatever the
   ribbon and rail leave.

   This also fixes a real defect. The rail turned the body into a grid
   whose height was only a MINIMUM, so #app — a grid item asking for
   height:100% — resolved to its content: the 48px header. Its absolutely
   positioned map, insetting from top and bottom of that, came out zero
   pixels tall. Tiles loaded into a box with no height, which is why the
   flight view rendered as a black rectangle.
   ══════════════════════════════════════════════════════════════════════ */
body.is-canvas { height: 100svh; overflow: hidden; }
body.is-canvas > #app,
body.is-canvas > main { height: 100svh; min-height: 0; position: relative; }
@media (max-width: 760px) {
  /* The bottom tab bar takes the last 60px; the canvas stops above it. */
  body.is-canvas { height: 100svh; padding-bottom: 0; }
  body.is-canvas > #app,
  body.is-canvas > main { height: calc(100svh - 60px - env(safe-area-inset-bottom)); }
}

.nvm .leaflet-container, .nvm { background: var(--bg); }

/* Every badge in the product is an annunciator: monospaced, sharp, its
   ground tinted by its state. The shape marker beside it keeps the state
   readable without colour. */
/* A status badge says "Ready", not "READY". Sentence case in the interface
   face, with the status carried by a coloured dot and a tinted ground —
   capitals everywhere was making a page of shouting labels. Monospace is
   kept for identifiers, coordinates, registrations and timestamps, which is
   what it is for. */
.nv-badge, .pc-badge, .lp-tag {
  border-radius: 999px;
  font-family: var(--font);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
}
.nv-badge::before { width: 6px; height: 6px; }
.nv-badge-mono, .nv-chip-mono { font-family: var(--mono); font-variant-numeric: tabular-nums; letter-spacing: 0; }

/* Empty states: a calm light plate, one sentence and one action. */
.nv-empty {
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--surface-2);
}
.nv-empty h3 { font-family: var(--font-head); }


/* ═══════════════════ reading: prose, everywhere ═══════════════════════
   Two settings decide whether text is comfortable to read: how far apart
   the lines sit, and how far the eye has to travel before it comes back.
   Across the product these were left at the interface defaults — 1.5
   leading, and no measure at all on the documentation page, where lines
   ran past a hundred characters.

   Dense operational surfaces are deliberately excluded: a table row, a
   status chip and a data key are not prose and are set to be scanned.
   ══════════════════════════════════════════════════════════════════════ */
:root {
  --lh-prose: 1.65;
  /* Measured in em, not ch. Inter's zero is 0.63em wide while its average
     character is 0.44em, so a 72ch rule renders as 102 real characters —
     half again the comfortable line. One em holds about 2.25 characters
     here, so 32em is roughly 72 characters and 27em about 60. */
  --measure-prose: 32em;
  --measure-note: 27em;
}

/* Bare paragraphs and list items — the ones nothing else styles, which is
   exactly the text that was left at the interface default. */
p { line-height: var(--lh-prose); }
li { line-height: 1.6; }

/* Named prose surfaces, each given room and a measure. */
.nv-empty p { line-height: var(--lh-prose); max-width: 26em; }
.nv-notice p { line-height: var(--lh-prose); max-width: var(--measure-note); }
.nv-blueprint p { line-height: var(--lh-prose); }
.nv-field small, .nv-hint, .mw-hint, .disclaimer, .page-sub, .s-small {
  line-height: var(--lh-prose); max-width: var(--measure-note);
}

/* A documentation page is a reading document. Its paragraphs, list items
   and table prose were running 94 to 113 characters a line. */
.doc p, .doc li, .doc .purpose, .doc dd, .doc td {
  line-height: var(--lh-prose);
  max-width: var(--measure-prose);
}

/* ═══════════ product layer: the pieces every page is built from ═══════
   One definition per component, with variants — not a copy per page.
   ══════════════════════════════════════════════════════════════════════ */

/* A form control on a phone is 16px or the browser zooms the page when it
   takes focus, which throws the layout sideways mid-interaction. */
@media (max-width: 760px) {
  .nv-input, .nv-select, .nv-textarea,
  .nv-field input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
  .nv-field select, .nv-field textarea { font-size: 16px; min-height: var(--control-h-lg); }
  .nv-btn { min-height: var(--control-h-lg); }
}

/* Page header: one title, optional meaning, actions on the right. */
.nv-ph { display: flex; align-items: flex-start; gap: var(--sp-4); flex-wrap: wrap; margin-bottom: var(--sp-5); }
.nv-ph-main { min-width: 0; flex: 1 1 auto; }
.nv-ph h1 { font-size: 24px; letter-spacing: -0.02em; }
.nv-ph p { margin-top: 4px; color: var(--text-2); font-size: var(--fs-sm); max-width: 68ch; }
.nv-ph-actions { display: flex; align-items: center; gap: var(--sp-2); flex: none; }

/* Metric card: a number that means something, with its own trend or note. */
.nv-metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: var(--sp-3); }
.nv-metric {
  display: flex; flex-direction: column; gap: 4px; padding: var(--sp-4);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  text-decoration: none; box-shadow: var(--shadow-card);
  transition: border-color 0.16s, transform 0.16s;
}
a.nv-metric:hover { border-color: var(--blue-line); transform: translateY(-1px); }
.nv-metric-k { font-size: 12px; color: var(--text-3); }
.nv-metric-v { font: 700 26px/1.1 var(--font-head); letter-spacing: -0.02em; font-variant-numeric: tabular-nums; color: var(--text); }
.nv-metric-n { font-size: 12px; color: var(--text-3); }
.nv-metric.is-ok .nv-metric-v { color: var(--ok-text); }
.nv-metric.is-warn .nv-metric-v { color: var(--warn-text); }
.nv-metric.is-bad .nv-metric-v { color: var(--danger-text); }
.nv-metric.is-unknown .nv-metric-v { font-size: 15px; font-weight: 600; color: var(--text-3); }

/* Filter chips: what is narrowing the results, each removable. */
.nv-chips { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.nv-chip-x {
  display: inline-flex; align-items: center; gap: 6px; padding: 4px 6px 4px 10px;
  border-radius: 999px; border: 1px solid var(--blue-line); background: var(--blue-soft);
  color: var(--blue-text); font-size: 12px; font-weight: 600;
}
.nv-chip-x button {
  display: inline-grid; place-items: center; width: 18px; height: 18px; padding: 0;
  border: 0; border-radius: 50%; background: transparent; color: inherit; cursor: pointer; font-size: 14px; line-height: 1;
}
.nv-chip-x button:hover { background: rgba(10, 22, 40, 0.084); }

/* Toolbar: search, filters and view switches on one line. */
.nv-toolbar { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; margin-bottom: var(--sp-4); }
.nv-toolbar .nv-search { flex: 1 1 260px; min-width: 0; max-width: 460px; position: relative; }
.nv-toolbar .nv-search input { padding-left: 36px; }
.nv-toolbar .nv-search svg { position: absolute; left: 11px; top: 50%; transform: translateY(-50%); width: 16px; height: 16px; fill: none; stroke: var(--text-3); stroke-width: 2; pointer-events: none; }
.nv-seg { display: inline-flex; padding: 3px; gap: 2px; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface-2); }
.nv-seg button {
  min-height: 30px; padding: 0 12px; border: 0; border-radius: 7px; background: transparent;
  color: var(--text-2); font: 600 var(--fs-sm)/1 var(--font); cursor: pointer;
}
.nv-seg button[aria-pressed="true"], .nv-seg button.on { background: var(--surface); color: var(--text); box-shadow: var(--shadow-sm); }

/* A sheet: the drawer and the modal are the same object, docked differently. */
.nv-scrim { position: fixed; inset: 0; z-index: 900; background: rgba(15, 23, 42, 0.30); backdrop-filter: blur(2px); }
.nv-scrim[hidden] { display: none; }
.nv-sheet-x { display: inline-grid; place-items: center; width: 32px; height: 32px; border: 0; border-radius: var(--r-md); background: transparent; color: var(--text-2); cursor: pointer; font-size: 20px; line-height: 1; }
.nv-sheet-x:hover { background: var(--hover); color: var(--text); }
@media (max-width: 760px) {
  /* A drawer on a phone is the screen. */
  .nv-drawer { width: 100vw; border-left: 0; }
}

/* ═════════════ the shell on a phone: one navigation, slid in ══════════
   A bottom tab strip could show four of the six sections and hid the rest
   behind a "More" sheet, so the product had two navigations that disagreed.
   The rail itself slides in instead, behind a compact top bar.
   ══════════════════════════════════════════════════════════════════════ */
.nv-appbar { display: none; }
.nv-rail-scrim { display: none; }

@media (max-width: 860px) {
  body.has-rail { display: block; padding-top: var(--shell-h); padding-bottom: 0; }
  .nv-appbar {
    position: fixed; top: 0; left: 0; right: 0; z-index: 95;
    display: flex; align-items: center; gap: var(--sp-2);
    height: var(--shell-h); padding: 0 var(--sp-2) 0 4px;
    background: var(--ribbon); border-bottom: 1px solid var(--border);
    backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  }
  .nv-appbar-menu {
    position: relative; display: inline-grid; place-items: center;
    width: var(--control-h-lg); height: var(--control-h-lg);
    border: 0; border-radius: var(--r-md); background: transparent; color: var(--text); cursor: pointer;
  }
  .nv-appbar-menu svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
  .nv-appbar-dot { position: absolute; top: 9px; right: 9px; width: 7px; height: 7px; border-radius: 50%; background: var(--warn); }
  .nv-appbar-title { font: 700 15px/1 var(--font-head); letter-spacing: -0.01em; }
  .nv-appbar-me { margin-left: auto; display: grid; place-items: center; width: var(--control-h-lg); height: var(--control-h-lg); text-decoration: none; }

  .nv-rail {
    position: fixed; top: 0; bottom: 0; left: 0; width: min(300px, 86vw); height: 100svh;
    transform: translateX(-102%); transition: transform 0.18s ease-out;
    box-shadow: var(--shadow); padding-top: var(--sp-3); z-index: 100;
  }
  body.rail-open .nv-rail { transform: translateX(0); }
  .nv-rail-brand span, .nv-rail-item span, .nv-rail-me b, .nv-rail-me small { display: inline; }
  .nv-rail-brand { justify-content: flex-start; padding: 0 10px; }
  .nv-rail-item { min-height: var(--control-h-lg); }
  .nv-rail-scrim {
    display: block; position: fixed; inset: 0; z-index: 96;
    background: rgba(15, 23, 42, 0.30); backdrop-filter: blur(2px);
  }
  .nv-rail-scrim[hidden] { display: none; }  @media (prefers-reduced-motion: reduce) { .nv-rail { transition: none; } }
}

/* Touch targets: a segmented control and the small buttons inside toolbars
   are comfortable with a mouse and too small for a thumb. */
@media (pointer: coarse) {
  .nv-seg button, .nv-tabs button, .nv-tabs a, .nv-btn-sm, .nv-sheet-x, .nv-chip-x button {
    min-height: var(--control-h-lg);
  }
  .nv-chip-x button { width: var(--control-h-lg); }
  .nv-seg { padding: 4px; }
}

/* The dot inside a status chip carries the tone; the word carries the
   meaning. Both are always present, so colour is never the only signal. */
.nv-chip-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; opacity: 0.85; }

/* ═══════ a step down from the action, not a wall of identical cards ═══
   Everything sat on the same surface with the same border, so a primary
   action, a card and a helper line all read at one level. Supporting
   surfaces recede; the thing you act on stays lit.
   ══════════════════════════════════════════════════════════════════════ */
.nv-btn-primary, .btn-primary { font-weight: 650; }
.nv-btn-secondary { color: var(--text); }
/* Helper text is quieter than body text, but still readable at 6:1. */
.nv-field-help, .nv-help, .nv-metric-n, .ap-row small, .ap-row-when { color: var(--text-3); }
/* A supporting panel sits back from a primary one. */
.ap-side .nv-card { background: var(--surface-2); box-shadow: none; }
.ap-side .ap-row { background: var(--surface); }


/* ═══════════════ application shell — approved dark board ═══════════════
   Rail (home, operations, map, aircraft, people, network, records,
   insights / resources, settings, help, a photographic card) and a top bar
   (search, organization, notifications, quick links, account). Rendered by
   /js/nv-shell.js. Everything below overrides the earlier rail rules.
   ══════════════════════════════════════════════════════════════════════ */
body.has-rail.has-top { grid-template-rows: var(--top-h) auto; }
body.has-rail > :not(.nv-rail):not(.nv-appbar):not(.nv-rail-scrim) { grid-column: 2; }
.nv-rail { padding: 14px 12px 14px; gap: 6px; overflow-y: auto; scrollbar-width: thin; }
.nv-rail-brand { height: 48px; margin-bottom: 8px; padding: 0 8px; gap: 12px; font: 600 18px/1 var(--font); letter-spacing: 0.2em; }
.nv-rail-brand svg { width: 28px; height: 28px; }
.nv-rail-group { gap: 2px; }
.nv-rail-group.is-second { margin-top: 10px; padding-top: 12px; border-top: 1px solid var(--hairline); }
.nv-rail-item { min-height: 42px; padding: 0 12px; gap: 13px; font: 500 15px/1 var(--font); color: var(--text-2); }
.nv-rail-item svg { width: 20px; height: 20px; }
.nv-rail-item[aria-current="page"] { color: var(--blue-text); background: var(--blue-soft); }
.nv-rail-item[aria-current="page"] svg { color: var(--blue-text); }
.nv-rail-item[aria-current="page"]::before { left: -12px; top: 8px; bottom: 8px; background: var(--action); }
/* The rail's one primary action (Book a pilot), above the sections. It is a
   button, not a nav item: it never takes aria-current and never looks
   "selected", because it starts something rather than going somewhere. */
.nv-rail-action {
  display: flex; align-items: center; gap: 11px; justify-content: flex-start;
  min-height: 42px; margin: 0 0 var(--sp-3); padding: 0 12px;
  border-radius: var(--r-md); border: 1px solid transparent;
  background: var(--action); color: #fff;
  font: 600 15px/1 var(--font); text-decoration: none;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.16);
  transition: filter 0.14s;
}
.nv-rail-action:hover { filter: brightness(1.06); }
.nv-rail-action:focus-visible { outline: none; box-shadow: var(--focus); }
.nv-rail-action svg { width: 20px; height: 20px; color: currentColor; }
.nv-rail-trail { margin-left: auto; display: inline-flex; font-style: normal; }
.nv-rail-trail svg { width: 16px; height: 16px; }
.nv-rail-promo { position: relative; display: flex; flex-direction: column; justify-content: flex-end; gap: 12px; min-height: 250px; margin-top: auto; padding: 16px; overflow: hidden; border-radius: var(--r-lg); color: var(--text); text-decoration: none; isolation: isolate; }
.nv-rail-promo img { position: absolute; inset: 0; z-index: -2; width: 100%; height: 100%; object-fit: cover; object-position: 40% 50%; }
.nv-rail-promo::before { content: ""; position: absolute; inset: 0; z-index: -1; background: linear-gradient(180deg, rgba(8,13,20,.05) 20%, rgba(8,13,20,.82) 78%); }
.nv-rail-promo-t { font: 700 14px/1.3 var(--font); letter-spacing: 0.04em; text-transform: uppercase; }
.nv-rail-promo-b { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 40px; border-radius: var(--r-md); background: var(--action); color: var(--on-action); font: 600 14px/1 var(--font); }
.nv-rail-promo-b svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.nv-rail-promo:hover .nv-rail-promo-b { background: var(--action-hover); }
.nv-rail-foot { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 12px 0 2px; color: var(--text-2); font: 600 13px/1 var(--font); letter-spacing: 0.18em; }
.nv-rail-foot svg { width: 18px; height: 18px; }
@media (max-height: 900px) { .nv-rail-promo { min-height: 190px; } }
@media (max-height: 780px) { .nv-rail-promo, .nv-rail-foot { display: none; } }

.nv-top { position: sticky; top: 0; z-index: 70; display: flex; align-items: center; gap: 16px; height: var(--top-h); padding: 0 24px; background: rgba(255, 255, 255, 0.94); border-bottom: 1px solid var(--border); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); }
.nv-top svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; flex: none; }
.nv-top-search { display: flex; align-items: center; gap: 10px; width: min(460px, 42vw); min-height: 40px; padding: 0 10px 0 12px; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface); color: var(--text-3); font: 400 15px/1 var(--font); cursor: text; text-align: left; }
.nv-top-search span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nv-top-search kbd { padding: 3px 6px; border: 1px solid var(--border-strong); border-radius: 4px; font: 500 12px/1 var(--font); color: var(--text-2); }
.nv-top-search:hover { border-color: var(--border-strong); }
.nv-top-right { margin-left: auto; display: flex; align-items: center; gap: 6px; }
.nv-top-ctx { display: inline-flex; align-items: center; gap: 8px; max-width: 260px; margin-right: 8px; color: var(--text-2); font: 500 14px/1.2 var(--font); }
.nv-top-ctx span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nv-top-btn { position: relative; display: inline-grid; place-items: center; width: 44px; height: 44px; border: 0; border-radius: var(--r-md); background: transparent; color: var(--text-2); cursor: pointer; }
.nv-top-btn:hover, .nv-top-btn[aria-expanded="true"] { background: var(--hover); color: var(--text); }
.nv-top-dot { position: absolute; top: 10px; right: 11px; width: 8px; height: 8px; border-radius: 50%; background: #E5484D; box-shadow: 0 0 0 2px var(--bg); }
.nv-top-menu { position: relative; }
.nv-top .acct { position: relative; }
.nv-top-me { display: flex; align-items: center; gap: 10px; min-height: 44px; padding: 4px 8px 4px 4px; border: 0; border-radius: var(--r-md); background: transparent; color: var(--text); cursor: pointer; text-align: left; }
.nv-top-me:hover, .nv-top-me[aria-expanded="true"] { background: var(--hover); }
.nv-top-me .acct-av { width: 36px; height: 36px; font-size: 13px; background: #1F5BD8; color: #fff; }
.nv-top-me-id b { display: block; max-width: 170px; font: 600 14px/1.25 var(--font); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nv-top-me-id small { display: block; max-width: 170px; font-size: 12px; color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nv-top .acct-menu { position: absolute; top: calc(100% + 6px); right: 0; left: auto; bottom: auto; z-index: 90; min-width: 240px; }
.nv-top .nv-apps-menu { right: 0; }

/* search dialog */
.nv-scrim.is-search { align-items: flex-start; justify-content: center; }
.nv-scrim.is-search .nv-drawer { position: relative; margin-top: 10vh; width: min(640px, calc(100vw - 32px)); height: auto; max-height: 70vh; border-radius: var(--r-lg); border: 1px solid var(--border-strong); }
.nv-search { display: grid; gap: 10px; }
.nv-search-field { display: flex; align-items: center; gap: 10px; min-height: 48px; padding: 0 14px; border: 1px solid var(--border-strong); border-radius: var(--r-md); background: var(--surface); color: var(--text-2); }
.nv-search-field svg { position: static; flex: none; transform: none; width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }
.nv-search-field input { flex: 1; min-width: 0; height: 46px; padding-left: 0; border: 0; background: transparent; color: var(--text); font: 400 16px/1 var(--font); outline: none; }
.nv-search-list { display: grid; gap: 2px; max-height: 50vh; overflow: auto; }
.nv-search-g { padding: 10px 10px 4px; font: 600 13px/1 var(--font); color: var(--text-3); }
.nv-search-r { display: grid; gap: 2px; padding: 10px; border-radius: var(--r-md); color: var(--text); text-decoration: none; }
.nv-search-r b { font: 600 15px/1.3 var(--font); }
.nv-search-r small { font-size: 13px; color: var(--text-3); }
.nv-search-r:hover, .nv-search-r.is-sel { background: var(--blue-soft); }
.nv-search-empty { padding: 12px 10px; color: var(--text-3); }

/* canvas pages share the column with the top bar */
body.is-canvas.has-top > #app,
body.is-canvas.has-top > main { height: calc(100svh - var(--top-h)); }

@media (max-width: 1100px) {
  .nv-rail-promo, .nv-rail-foot, .nv-rail-trail { display: none; }
  .nv-top-ctx { display: none; }
  .nv-top-me-id { display: none; }
  .nv-rail-group.is-second { padding-top: 8px; }
}
@media (max-width: 860px) {
  body.has-rail.has-top { grid-template-rows: none; }
  .nv-top { display: none; }
  .nv-appbar-btn { position: relative; display: inline-grid; place-items: center; width: 44px; height: 44px; border: 0; border-radius: var(--r-md); background: transparent; color: var(--text); cursor: pointer; }
  .nv-appbar-btn svg { width: 21px; height: 21px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
  #nv-search-btn-m { margin-left: auto; }
  .nv-appbar-me { margin-left: 0; }
  .nv-appbar .nv-top-dot { top: 10px; right: 10px; }
  .nv-rail-brand span, .nv-rail-item span { display: inline; }
  .nv-rail-item { min-height: 48px; justify-content: flex-start; padding: 0 12px; }
  body.is-canvas.has-top > #app, body.is-canvas.has-top > main { height: calc(100svh - var(--shell-h)); }
}

/* A map is its own stacking context. Leaflet gives its panes and controls
   z-indexes in the hundreds; without isolation they paint over the sticky
   top bar and the phone app bar as the page scrolls beneath them. */
.nvm { isolation: isolate; }

/* The map page carries its own toolbar for search, feed status and counts.
   Under the application shell its brand and account button duplicated the
   top bar (and, on a phone, stacked a second header under the app bar). */
body.has-rail #topbar .nv-shell-brand,
body.has-rail #topbar #acct { display: none; }

/* Notification center (one place for attention items and updates). */
.nv-notif-h { margin: 4px 0 8px; font-size: var(--fs-sm); font-weight: 600; color: var(--text-3); }
.nv-notif-h-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 18px; }
.nv-notif-h-row .nv-notif-h { margin: 0; }
.nv-notif + .nv-notif-h, .nv-notif + .nv-notif-h-row { margin-top: 20px; }
.nv-notif-row time { margin-left: auto; flex: none; font-size: 12px; color: var(--text-3); white-space: nowrap; }
.nv-notif-row.is-unread b { color: var(--text); }

/* Skip link for the application shell, the same treatment as the public
   site's .b-skip but in this sheet's own tokens: invisible until focused,
   then the first thing on the page. */
.nv-skip { position: absolute; left: 12px; top: -60px; z-index: 100; padding: 10px 14px; background: var(--text); color: var(--bg) !important; border-radius: var(--r-md); text-decoration: none; font-weight: 600; }
.nv-skip:focus { top: 12px; }
/* The skip link lands focus on the page region itself; a ring around the
   whole page says nothing, and the next Tab shows a real one. */
main[tabindex="-1"]:focus, [role="main"][tabindex="-1"]:focus { outline: none; }

/* ── print ───────────────────────────────────────────────────────────────
   A page printed from the application is the thing the person is looking
   at, not the shell around it: no rail, top bar, scrim or toast, white
   paper, no shadows, no page-width scrolling. When a drawer is open (a
   mission's readiness verdict, a registry record) the drawer is what prints,
   full width and unclipped, and the page behind it does not. */
@media print {
  @page { margin: 16mm; }
  .nv-rail, .nv-top, .nv-rail-scrim, .nv-shell, .nv-scrim, .nv-toast, .b-skip, .nv-skip,
  .nv-sheet-x, .nv-drawer-foot, [data-print-hide] { display: none !important; }
  html, body { background: #fff !important; color: #000; height: auto !important; overflow: visible !important; }
  * { box-shadow: none !important; text-shadow: none !important; }
  main, [role="main"] { padding: 0 !important; margin: 0 !important; max-width: none !important; }
  /* An open drawer is the document. Everything beside it stays on screen. */
  body:has(.nv-drawer:not([hidden])) main > *:not(:has(.nv-drawer:not([hidden]))) { display: none !important; }
  .nv-drawer:not([hidden]) {
    position: static !important; inset: auto !important; width: auto !important; max-width: none !important;
    height: auto !important; max-height: none !important; transform: none !important; border: 0 !important;
    overflow: visible !important; box-shadow: none !important;
  }
  .nv-drawer:not([hidden]) .nv-drawer-body { overflow: visible !important; height: auto !important; max-height: none !important; }
  /* Links keep their words; the paper does not need their addresses. */
  a { color: inherit; text-decoration: none; }
  a[href]::after { content: none !important; }
}
