/* ============================================================================
   dronesight.cc — design tokens + shared component layer
   SINGLE SOURCE OF TRUTH. Linked from BOTH the Dash index template
   (dash_app.INDEX_STRING) and every raw-HTML page (submit_form, admin,
   listen). Never redefine a colour, radius or spacing value anywhere else.
   ========================================================================= */

/* ---------------------------------------------------------------- tokens */
:root {
  /* Surface elevation ladder — dark UI signals elevation with LIGHTER
     surfaces, never by switching to white. 4 steps, used in order. */
  --surface-0: #0b0e13;   /* page ground                                   */
  --surface-1: #11151c;   /* card / panel                                  */
  --surface-2: #171c24;   /* raised: inset strips, table head, hover       */
  --surface-3: #1f2530;   /* highest: menus, active row, input focus       */

  --border:        #242b36;
  --border-strong: #333c4a;

  /* Text — never #fff, never #000 */
  --text:       #e6e9ef;
  --text-muted: #9aa4b2;  /* secondary sentences, still AA on surface-0/1  */
  --text-dim:   #8a93a3;  /* micro-labels only, never body copy (AA on s3) */

  /* ONE primary accent. Chrome, links, focus, single CTA. */
  --accent:        #ff6b35;
  --accent-hover:  #ff8a5c;
  --accent-ink:    #170b04;              /* text ON solid accent           */
  --accent-soft:   rgba(255, 107, 53, 0.11);
  --accent-line:   rgba(255, 107, 53, 0.38);

  /* Semantic severity — desaturated siblings of one family, AA-verified
     against surface-0/1/2. Used as tinted pills, never as solid slabs. */
  --sev-airport:  #f2777f;
  --sev-military: #ad9ce4;
  --sev-incident: #e9a05c;
  --sev-sighting: #6fc28d;
  --sev-unknown:  #9aa4b2;

  --sev-airport-bg:  rgba(242, 119, 127, 0.13);
  --sev-military-bg: rgba(173, 156, 228, 0.13);
  --sev-incident-bg: rgba(233, 160, 92, 0.13);
  --sev-sighting-bg: rgba(111, 194, 141, 0.13);
  --sev-unknown-bg:  rgba(154, 164, 178, 0.13);

  --sev-airport-line:  rgba(242, 119, 127, 0.32);
  --sev-military-line: rgba(173, 156, 228, 0.32);
  --sev-incident-line: rgba(233, 160, 92, 0.32);
  --sev-sighting-line: rgba(111, 194, 141, 0.32);
  --sev-unknown-line:  rgba(154, 164, 178, 0.32);

  /* Spacing scale — one ladder, no ad-hoc rem values */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;
  --sp-7: 48px;
  --sp-8: 64px;

  /* Radius — 8 / 12 / 16 / pill. Nothing else. */
  --r-sm:   8px;
  --r-md:   12px;
  --r-lg:   16px;
  --r-pill: 999px;

  /* Type */
  --font-body: "IBM Plex Sans", "Inter", system-ui, -apple-system, sans-serif;
  --font-display: "IBM Plex Sans Condensed", "IBM Plex Sans", system-ui, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, "JetBrains Mono", monospace;

  --fs-body:    1.0625rem;  /* 17px desktop — WCAG-comfortable body        */
  --fs-lead:    1.1875rem;  /* 19px intro paragraph                        */
  --fs-small:   0.9375rem;  /* 15px dense table cells / secondary          */
  --fs-micro:   0.8125rem;  /* 13px micro-labels ONLY                      */
  --fs-h1:      2.25rem;
  --fs-h2:      1.5rem;
  --fs-h3:      1.1875rem;

  /* Landing display size. The old hero ran its h1 at --fs-h1 (36px) against a
     19px lead — a 1.9x step that let the page title read as just another
     heading. A landing page gets one moment of scale; this is it. */
  --fs-display: clamp(2.75rem, 5.4vw, 4.25rem);

  --lh-body: 1.6;

  /* Elevation — subtle, never both border AND heavy shadow */
  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-2: 0 8px 24px rgba(0, 0, 0, 0.45);

  --dur-fast: 150ms;
  --dur:      250ms;
  --ease:     cubic-bezier(0.22, 0.61, 0.36, 1);

  --measure: 62ch;
  --shell:   1140px;
}

/* ------------------------------------------------------------------ base */
*, *::before, *::after { box-sizing: border-box; }

html { font-size: 16px; -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--surface-0);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, h5 {
  font-family: var(--font-display);
  color: var(--text);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.2;
  margin: 0 0 var(--sp-3) 0;
}
h1 { font-size: var(--fs-h1); letter-spacing: -0.02em; }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); }
h4 { font-size: 1.0625rem; }

p { margin: 0 0 var(--sp-4) 0; color: var(--text); max-width: var(--measure); }
p:last-child { margin-bottom: 0; }

ul, ol { margin: 0 0 var(--sp-4) 0; padding-left: var(--sp-5); max-width: var(--measure); }
li { margin-bottom: var(--sp-2); }
li:last-child { margin-bottom: 0; }

a {
  color: var(--text);
  text-decoration: underline;
  text-decoration-color: var(--border-strong);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: color var(--dur-fast) var(--ease),
              text-decoration-color var(--dur-fast) var(--ease);
}
a:hover { color: var(--accent); text-decoration-color: var(--accent-line); }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

strong, b { font-weight: 600; color: var(--text); }
hr { border: 0; border-top: 1px solid var(--border); margin: var(--sp-6) 0; }

code, .code, kbd {
  font-family: var(--font-mono);
  font-size: 0.875em;
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  padding: 0.1em 0.4em;
  border-radius: var(--r-sm);
}
pre code { border: 0; background: none; padding: 0; }

::selection { background: var(--accent-line); color: var(--text); }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--surface-0); }
::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: var(--r-pill); }
::-webkit-scrollbar-thumb:hover { background: #43506180; }

/* --------------------------------------------------------------- helpers */
.muted   { color: var(--text-muted); }
.micro   { color: var(--text-dim); font-size: var(--fs-micro); }
.mono    { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.lead    { font-size: var(--fs-lead); color: var(--text-muted); max-width: var(--measure); }
.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 var(--sp-2) 0;
}
.stack-4 > * + * { margin-top: var(--sp-4); }
.stack-5 > * + * { margin-top: var(--sp-5); }
.scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ----------------------------------------------------------------- shell */
.ds-shell {
  max-width: var(--shell);
  margin: 0 auto;
  padding: var(--sp-5) var(--sp-5) var(--sp-8) var(--sp-5);
}

/* ---------------------------------------------------------------- header
   Was three stacked rows — brand+actions, nav, then a tagline that repeated
   what the page's own H1 and lead already said — roughly 200px of chrome above
   every page. Brand, nav and actions now share one row on desktop. */
.ds-header {
  border-bottom: 1px solid var(--border);
  padding-bottom: var(--sp-3);
  margin-bottom: var(--sp-5);
}
.ds-header {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
}
/* Phone/tablet: brand and the two actions share row one, nav wraps to row two. */
.ds-header > .ds-brand   { order: 1; flex: 0 0 auto; }
.ds-header > .ds-actions { order: 2; margin-left: auto; flex: 0 0 auto; }
.ds-header > .ds-nav     { order: 3; flex: 1 1 100%; margin-top: 0; margin-left: calc(-1 * var(--sp-3)); }
.ds-header > .ds-tagline { order: 4; flex: 1 1 100%; }

/* Two rows, not three. Cramming brand + seven nav items + the action onto one
   line overflows at 1440px and wraps the action onto a ragged third row, so the
   nav keeps its own line; what goes is the tagline, which repeated what the
   page's own H1 and lead already say. */
.ds-header > .ds-tagline { display: none; }
.ds-brand {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  gap: var(--sp-3);
  text-decoration: none;
  color: var(--text);
}
.ds-brand:hover { color: var(--text); }
.ds-brand-mark { width: 30px; height: 30px; flex-shrink: 0; display: block; }
.ds-brand-name {
  font-family: var(--font-display);
  font-size: 1.375rem;
  font-weight: 600;
  letter-spacing: -0.015em;
  margin: 0;
  line-height: 1;
}
.ds-brand-name em {
  font-style: normal;
  color: var(--accent);
}

.ds-actions { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.ds-nav {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  flex-wrap: wrap;
  margin-top: var(--sp-3);
  margin-left: calc(-1 * var(--sp-3));
}
.ds-nav-link {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 44px;
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-sm);
  color: var(--text-muted);
  font-size: var(--fs-small);
  font-weight: 500;
  text-decoration: none;
  border: 1px solid transparent;
  transition: color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.ds-nav-link:hover { color: var(--text); background: var(--surface-2); text-decoration: none; }
.ds-nav-link.is-current,
.ds-nav-link[aria-current="page"] {
  color: var(--text);
  background: var(--surface-2);
  border-color: var(--border);
}
/* Secondary action: reads as a button, but does not compete for the accent. */
.ds-nav-link.is-quiet {
  color: var(--text);
  background: var(--surface-2);
  border-color: var(--border-strong);
  font-weight: 600;
}
.ds-nav-link.is-quiet:hover { background: var(--surface-3); color: var(--text); }
.ds-nav-link.is-quiet.is-current,
.ds-nav-link.is-quiet[aria-current="page"] {
  background: var(--surface-3);
  border-color: var(--text-dim);
  color: var(--text);
}

.ds-nav-link.is-cta {
  color: var(--accent);
  background: var(--accent-soft);
  border-color: var(--accent-line);
}
.ds-nav-link.is-cta:hover { color: var(--accent-hover); background: rgba(255, 107, 53, 0.17); }
/* An active CTA must still read as "you are here" without losing its accent. */
.ds-nav-link.is-cta.is-current,
.ds-nav-link.is-cta[aria-current="page"] {
  background: rgba(255, 107, 53, 0.22);
  border-color: var(--accent);
  color: var(--accent-hover);
}

.ds-tagline {
  margin: var(--sp-3) 0 0 0;
  color: var(--text-muted);
  font-size: var(--fs-small);
  max-width: var(--measure);
}

/* ---------------------------------------------------------------- footer */
.ds-footer {
  margin-top: var(--sp-8);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
  color: var(--text-muted);
  font-size: var(--fs-small);
}
.ds-footer a { color: var(--text-muted); }
.ds-footer a:hover { color: var(--accent); }
.ds-footer-links {
  display: flex;
  gap: var(--sp-4);
  flex-wrap: wrap;
  align-items: center;
  margin-top: var(--sp-2);
}
/* Standalone links need a real activation area (WCAG 2.5.5), unlike inline
   links inside a sentence, which are exempt. */
.ds-footer-links > a {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 44px;
}

/* ----------------------------------------------------------------- icons
   CSS-mask icons (Lucide geometry). Inherit currentColor, so they sit in
   text exactly like a glyph — but they are a real icon system, not emoji. */
.i {
  display: inline-block;
  width: 1em;
  height: 1em;
  flex-shrink: 0;
  background-color: currentColor;
  vertical-align: -0.135em;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
}
.i-lg { width: 1.25em; height: 1.25em; }
.i-accent { color: var(--accent); }

.i-list          { -webkit-mask-image: var(--ico-list);      mask-image: var(--ico-list); }
.i-trend         { -webkit-mask-image: var(--ico-trend);     mask-image: var(--ico-trend); }
.i-news          { -webkit-mask-image: var(--ico-news);      mask-image: var(--ico-news); }
.i-scan          { -webkit-mask-image: var(--ico-scan);      mask-image: var(--ico-scan); }
.i-flag          { -webkit-mask-image: var(--ico-flag);      mask-image: var(--ico-flag); }
.i-chart         { -webkit-mask-image: var(--ico-chart);     mask-image: var(--ico-chart); }
.i-mic           { -webkit-mask-image: var(--ico-mic);       mask-image: var(--ico-mic); }
.i-upload        { -webkit-mask-image: var(--ico-upload);    mask-image: var(--ico-upload); }
.i-info          { -webkit-mask-image: var(--ico-info);      mask-image: var(--ico-info); }
.i-code          { -webkit-mask-image: var(--ico-code);      mask-image: var(--ico-code); }
.i-siren         { -webkit-mask-image: var(--ico-siren);     mask-image: var(--ico-siren); }
.i-check         { -webkit-mask-image: var(--ico-check);     mask-image: var(--ico-check); }
.i-arrow-right   { -webkit-mask-image: var(--ico-arrow-r);   mask-image: var(--ico-arrow-r); }
.i-arrow-left    { -webkit-mask-image: var(--ico-arrow-l);   mask-image: var(--ico-arrow-l); }
.i-pin           { -webkit-mask-image: var(--ico-pin);       mask-image: var(--ico-pin); }
.i-plane         { -webkit-mask-image: var(--ico-plane);     mask-image: var(--ico-plane); }
.i-ship          { -webkit-mask-image: var(--ico-ship);      mask-image: var(--ico-ship); }
.i-cloud         { -webkit-mask-image: var(--ico-cloud);     mask-image: var(--ico-cloud); }
.i-users         { -webkit-mask-image: var(--ico-users);     mask-image: var(--ico-users); }
.i-camera        { -webkit-mask-image: var(--ico-camera);    mask-image: var(--ico-camera); }
.i-wave          { -webkit-mask-image: var(--ico-wave);      mask-image: var(--ico-wave); }
.i-clock         { -webkit-mask-image: var(--ico-clock);     mask-image: var(--ico-clock); }
.i-mail          { -webkit-mask-image: var(--ico-mail);      mask-image: var(--ico-mail); }
.i-file          { -webkit-mask-image: var(--ico-file);      mask-image: var(--ico-file); }
.i-warn          { -webkit-mask-image: var(--ico-warn);      mask-image: var(--ico-warn); }
.i-database      { -webkit-mask-image: var(--ico-database);  mask-image: var(--ico-database); }
.i-book          { -webkit-mask-image: var(--ico-book);      mask-image: var(--ico-book); }
.i-rss           { -webkit-mask-image: var(--ico-rss);       mask-image: var(--ico-rss); }
.i-external      { -webkit-mask-image: var(--ico-external);  mask-image: var(--ico-external); }
.i-radio         { -webkit-mask-image: var(--ico-radio);     mask-image: var(--ico-radio); }
.i-search        { -webkit-mask-image: var(--ico-search);    mask-image: var(--ico-search); }
.i-filter        { -webkit-mask-image: var(--ico-filter);    mask-image: var(--ico-filter); }
.i-shield        { -webkit-mask-image: var(--ico-shield);    mask-image: var(--ico-shield); }
.i-inbox         { -webkit-mask-image: var(--ico-inbox);     mask-image: var(--ico-inbox); }
.i-map           { -webkit-mask-image: var(--ico-map);       mask-image: var(--ico-map); }

/* ----------------------------------------------------------------- cards */
.panel {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-5);
  margin-bottom: var(--sp-5);
}
.panel > :last-child { margin-bottom: 0; }
.panel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  flex-wrap: wrap;
  margin-bottom: var(--sp-4);
}
.panel-head h2,
.panel-head h3,
.section-card-head h2,
.section-card-head h3 {
  font-size: var(--fs-h3);
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
}
.panel-head .btn { flex-shrink: 0; }
.panel-flush { padding: var(--sp-5); }
.panel-flush .js-plotly-plot { border-radius: var(--r-sm); overflow: hidden; }

/* Inset strip — raised surface used for meta/summary rows inside a page.
   This replaces every former light-grey (#f5f5f5) band. */
.strip {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-3) var(--sp-4);
  margin-bottom: var(--sp-5);
  display: flex;
  gap: var(--sp-5);
  flex-wrap: wrap;
  align-items: center;
  font-size: var(--fs-small);
  color: var(--text-muted);
}
.strip-item { display: inline-flex; align-items: center; gap: var(--sp-2); min-height: 32px; }
.strip-item > a { display: inline-flex; align-items: center; min-height: 44px; }
.strip-item .i { color: var(--text-dim); }
.strip-item strong { color: var(--text); font-family: var(--font-mono); font-weight: 500; }
/* A feed that has gone quiet says so, rather than blending in with the live ones. */
.strip-item.is-stale strong { color: var(--sev-incident); }
.strip-item.is-stale .i { color: var(--sev-incident); opacity: 0.8; }

/* Callout */
.callout {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--r-md);
  padding: var(--sp-4) var(--sp-5);
  margin: var(--sp-5) 0;
  color: var(--text-muted);
}
.callout strong { color: var(--text); }
.callout .i { color: var(--accent); }

/* ------------------------------------------------------------- stat grid */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: var(--sp-3);
}
/* Six tiles read best as 3x2 rather than 5 + 1 orphan. */
@media (min-width: 880px) {
  .stat-grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.stat-card {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-4);
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  transition: border-color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);
}
.stat-card:hover { border-color: var(--border-strong); background: var(--surface-2); }
.stat-card .label {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  line-height: 1.3;
}
.stat-card .label .i { color: var(--text-dim); }
.stat-card .value {
  color: var(--text);
  font-family: var(--font-display);
  font-size: 2rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  line-height: 1.05;
  letter-spacing: -0.02em;
}
.stat-card .sub {
  color: var(--text-muted);
  font-size: var(--fs-micro);
  line-height: 1.4;
  margin-top: auto;
  padding-top: var(--sp-1);
}
.stat-card.accent  .value { color: var(--accent); }
.stat-card.success .value { color: var(--sev-sighting); }
.stat-card.warning .value { color: var(--sev-incident); }
.stat-card.danger  .value { color: var(--sev-airport); }
.stat-card.danger  { border-color: var(--sev-airport-line); }

/* ------------------------------------------------------------------ hero */
.hero {
  position: relative;
  background:
    radial-gradient(120% 130% at 12% -10%, rgba(255, 107, 53, 0.10) 0%, transparent 55%),
    linear-gradient(180deg, var(--surface-1) 0%, var(--surface-0) 100%);
  border-bottom: 1px solid var(--border);
  padding: var(--sp-7) 0 var(--sp-6) 0;
  margin: calc(-1 * var(--sp-5)) calc(-1 * var(--sp-5)) var(--sp-6) calc(-1 * var(--sp-5));
  overflow: hidden;
}
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(var(--border) 1px, transparent 1px),
    linear-gradient(90deg, var(--border) 1px, transparent 1px);
  background-size: 44px 44px;
  opacity: 0.28;
  mask-image: radial-gradient(80% 70% at 70% 0%, #000 0%, transparent 70%);
  -webkit-mask-image: radial-gradient(80% 70% at 70% 0%, #000 0%, transparent 70%);
}
.hero-inner {
  position: relative;
  z-index: 1;
  max-width: var(--shell);
  margin: 0 auto;
  padding: 0 var(--sp-5);
}
.hero h1 { margin: 0 0 var(--sp-3) 0; max-width: 18ch; }
.hero .tagline {
  color: var(--text-muted);
  font-size: var(--fs-lead);
  margin: 0 0 var(--sp-6) 0;
  max-width: 60ch;
}
.hero-titlerow {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  flex-wrap: wrap;
}

/* The plain-language opening. Wider type than body copy, because it is the
   one paragraph every first-time visitor is expected to actually read. */
.hero-lead {
  font-size: 1.3125rem;
  line-height: 1.5;
  color: var(--text);
  margin: 0 0 var(--sp-4) 0;
  max-width: 54ch;
}
.hero-why {
  color: var(--text-muted);
  font-size: var(--fs-small);
  margin: 0 0 var(--sp-6) 0;
  max-width: 58ch;
}

/* ------------------------------------------------- landing hero (split)
   The old landing opened with ~450px of Danish prose and put the map — the
   thing the site actually is — entirely below the fold, with 500px of empty
   column beside the text on a 1440px screen. The visitor met a pamphlet
   instead of an instrument. The split puts the live map in the first
   viewport, next to the copy, and gives the empty column a job. */
.hero-split {
  display: grid;
  grid-template-columns: minmax(0, 1.02fr) minmax(0, 0.98fr);
  gap: var(--sp-7);
  align-items: center;
}
.hero-copy { min-width: 0; }
.hero-display {
  font-family: var(--font-display);
  font-size: var(--fs-display);
  font-weight: 700;
  line-height: 1.02;
  letter-spacing: -0.035em;
  margin: 0 0 var(--sp-4) 0;
  max-width: 11ch;
}
/* The second word carries the accent — one word, not a gradient wash. */
.hero-display em { font-style: normal; color: var(--accent); }

.hero-eyebrow {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
  margin: 0 0 var(--sp-5) 0;
}

/* Map plate in the hero. Squarer than the old full-width panel, which
   letterboxed Denmark into the middle third and left grey bands either side. */
.hero-map {
  position: relative;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface-1);
  padding: var(--sp-3);
  box-shadow: var(--shadow-2);
  min-width: 0;
}
.hero-map .js-plotly-plot { border-radius: var(--r-md); overflow: hidden; }
/* Stacked, not a two-up row: four severity pills plus a caption never fit on
   one line at this width, and the wrap left the foot ragged. */
.hero-map-foot {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-2) var(--sp-1) var(--sp-2);
  border-top: 1px solid var(--border);
  margin-top: var(--sp-3);
}
/* Four pills at full size overflow the plate and drop one onto a second line. */
.hero-map-foot .sev-legend { margin: 0; gap: var(--sp-1); }
.hero-map-foot .sev { font-size: 0.75rem; padding: 2px 7px; }
.hero-map-cap {
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
}

@media (max-width: 1040px) {
  .hero-split { grid-template-columns: 1fr; gap: var(--sp-5); }
  .hero-display { max-width: none; }
}

/* ----------------------------------------------------- primary call cards
   Two doors into the site, stated as full sentences rather than nav labels.
   Sized as cards, not buttons, so they cannot be mistaken for chrome. */
.cta-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--sp-3);
  max-width: 760px;
  margin: 0 0 var(--sp-6) 0;
}
.cta-card {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  column-gap: var(--sp-4);
  row-gap: var(--sp-1);
  padding: var(--sp-4) var(--sp-5);
  min-height: 88px;
  border-radius: var(--r-md);
  border: 1px solid var(--border-strong);
  background: var(--surface-3);
  color: var(--text);
  box-shadow: var(--shadow-1);
  text-decoration: none;
  transition: background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.cta-card:hover {
  background: var(--surface-3);
  border-color: var(--accent-line);
  color: var(--text);
  text-decoration: none;
  transform: translateY(-1px);
}
.cta-card:active { transform: translateY(0); }
.cta-card .cta-icon {
  grid-column: 1;
  grid-row: 1 / span 2;
  font-size: 1.5rem;
  color: var(--accent);
}
.cta-card .cta-title {
  grid-column: 2;
  grid-row: 1;
  align-self: end;
  font-family: var(--font-display);
  font-size: 1.1875rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.2;
}
.cta-card .cta-sub {
  grid-column: 2;
  grid-row: 2;
  align-self: start;
  font-size: var(--fs-small);
  line-height: 1.4;
  color: var(--text-muted);
}
.cta-card .cta-go {
  grid-column: 3;
  grid-row: 1 / span 2;
  color: var(--text-dim);
  transition: transform var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.cta-card:hover .cta-go { color: var(--accent); transform: translateX(3px); }

/* Exactly one solid-accent surface on the page — the action we most want a
   worried citizen to find. */
.cta-card.is-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
  box-shadow: var(--shadow-2);
}
.cta-card.is-primary:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
  color: var(--accent-ink);
}
.cta-card.is-primary .cta-icon,
.cta-card.is-primary .cta-go { color: var(--accent-ink); }
.cta-card.is-primary .cta-sub { color: rgba(23, 11, 4, 0.86); }
.cta-card.is-primary:hover .cta-go { color: var(--accent-ink); }

/* Credibility line: who runs this, on what data, and where the method is. */
.hero-trust {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-5);
  align-items: center;
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
  font-size: var(--fs-small);
  color: var(--text-muted);
}
.hero-trust > * {
  display: inline-flex;
  align-items: flex-start;
  gap: var(--sp-2);
}
/* Icon rides the first text line, not the middle of a wrapped block. */
.hero-trust .i { color: var(--text-dim); flex-shrink: 0; margin-top: 0.25em; }
.hero-trust a {
  color: var(--accent);
  min-height: 44px;
  text-decoration-color: var(--accent-line);
}
.hero-trust a .i { color: var(--accent); }
.hero-trust strong { font-family: var(--font-mono); font-weight: 500; }

/* Anchor targets must not land under the top edge of the viewport. */
[id] { scroll-margin-top: var(--sp-5); }

/* One plain sentence under a panel heading, decoding what the panel shows. */
.panel-intro {
  color: var(--text-muted);
  font-size: var(--fs-small);
  margin: calc(-1 * var(--sp-2)) 0 var(--sp-4) 0;
  max-width: var(--measure);
}

.live-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--r-pill);
  background: var(--sev-sighting-bg);
  border: 1px solid var(--sev-sighting-line);
  color: var(--sev-sighting);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: 0.1em;
}
.live-dot {
  width: 7px;
  height: 7px;
  border-radius: var(--r-pill);
  background: var(--sev-sighting);
  animation: ds-pulse 2.4s ease-in-out infinite;
}
@keyframes ds-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }

/* ------------------------------------------------------- pills / badges */
.sev {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 2px var(--sp-2);
  border-radius: var(--r-pill);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  font-weight: 500;
  line-height: 1.5;
  letter-spacing: 0.02em;
  white-space: nowrap;
  border: 1px solid transparent;
}
.sev::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: var(--r-pill);
  background: currentColor;
  flex-shrink: 0;
}
.sev-airport_closure { color: var(--sev-airport);  background: var(--sev-airport-bg);  border-color: var(--sev-airport-line); }
.sev-military        { color: var(--sev-military); background: var(--sev-military-bg); border-color: var(--sev-military-line); }
.sev-incident        { color: var(--sev-incident); background: var(--sev-incident-bg); border-color: var(--sev-incident-line); }
.sev-sighting        { color: var(--sev-sighting); background: var(--sev-sighting-bg); border-color: var(--sev-sighting-line); }
.sev-unknown         { color: var(--sev-unknown);  background: var(--sev-unknown-bg);  border-color: var(--sev-unknown-line); }

/* Evidence-tier chip (unconfirmed → reported → verified → official).
   Same colour family as severity, but a SQUARE corner and a leading icon
   instead of a dot: a claim about how well a thing is documented must not
   read as the same kind of statement as how bad it was. No new colours —
   the ladder borrows unknown → incident → sighting → accent, and only the
   top tier is allowed the brand accent, which keeps it rare. */
.ev {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 2px var(--sp-2);
  border-radius: var(--r-sm);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  font-weight: 500;
  line-height: 1.5;
  letter-spacing: 0.02em;
  white-space: nowrap;
  border: 1px solid transparent;
}
.ev .i { font-size: 0.95em; opacity: 0.85; }
.ev-num { opacity: 0.6; font-variant-numeric: tabular-nums; }
.ev-unconfirmed { color: var(--sev-unknown);  background: var(--sev-unknown-bg);  border-color: var(--sev-unknown-line); }
.ev-reported    { color: var(--sev-incident); background: var(--sev-incident-bg); border-color: var(--sev-incident-line); }
.ev-verified    { color: var(--sev-sighting); background: var(--sev-sighting-bg); border-color: var(--sev-sighting-line); }
.ev-official    { color: var(--accent);       background: var(--accent-soft);     border-color: var(--accent-line); }

/* Filter panel: a fixed 3-up grid instead of .field-row's flex, so seven
   controls line up in columns rather than going ragged as they wrap. */
.filter-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--sp-4) var(--sp-5);
  /* Top-aligned: the controls have very different heights (a one-line input
     next to a radio group that wraps onto three), and bottom-alignment left
     the labels scattered down the panel. */
  align-items: start;
}
.filter-grid > * { min-width: 0; }
.filter-grid .dash-radio-items { gap: var(--sp-2) var(--sp-3); margin-top: var(--sp-2); }

.tag {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 2px var(--sp-2);
  border-radius: var(--r-pill);
  font-size: var(--fs-micro);
  font-weight: 500;
  line-height: 1.5;
  border: 1px solid transparent;
}
.tag.info    { background: var(--accent-soft);      color: var(--accent);       border-color: var(--accent-line); }
.tag.success { background: var(--sev-sighting-bg);  color: var(--sev-sighting); border-color: var(--sev-sighting-line); }
.tag.warning { background: var(--sev-incident-bg);  color: var(--sev-incident); border-color: var(--sev-incident-line); }
.tag.danger  { background: var(--sev-airport-bg);   color: var(--sev-airport);  border-color: var(--sev-airport-line); }
.tag.neutral { background: var(--sev-unknown-bg);   color: var(--sev-unknown);  border-color: var(--sev-unknown-line); }

/* Map legend — decodes the marker colours */
.sev-legend {
  display: flex;
  gap: var(--sp-2);
  flex-wrap: wrap;
  margin-bottom: var(--sp-4);
}

/* Admin moderation: the three classification selects (evidence tier, location
   type, status). Stacked, not a .field-row — inside a table cell the flex row
   squeezed each label against its own select. */
.mod-fields {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-2);
  min-width: 210px;
  /* Table cells are nowrap by default here; without this the label text and
     its full-width select fight for one line and the select runs off-cell. */
  white-space: normal;
}
.mod-fields label { display: block; color: var(--text-muted); }
.mod-fields select {
  margin-top: var(--sp-1);
  min-height: 36px;
  padding: var(--sp-1) var(--sp-2);
  font-size: var(--fs-small);
}

/* Admin moderation thumbnail */
.admin-thumb {
  max-width: 120px;
  max-height: 120px;
  display: block;
  margin-top: var(--sp-2);
  border-radius: var(--r-sm);
  border: 1px solid var(--border);
}

/* Inline forms (admin approve/reject) must not stretch in a flex row */
.ds-footer-links form { display: inline-flex; margin: 0; }

fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }
legend { padding: 0; }

/* -------------------------------------------------------- incident list */
.inc-list { display: flex; flex-direction: column; }
.inc-item {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  column-gap: var(--sp-4);
  padding: var(--sp-4);
  /* No negative gutter margin. It used to pull each row 16px outside its
     container, which put the severity rail outside the content column and out
     of line with every heading on the page. */
  margin: 0;
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  border-bottom: 1px solid var(--border);
  color: var(--text);
  text-decoration: none;
  transition: background var(--dur-fast) var(--ease);
}
/* Severity rail. Without it a row is an undifferentiated text block and the
   only colour cue is a 13px pill; the rail lets the whole list be scanned for
   airport closures at a glance, which is what people come here for. */
.inc-item {
  border-left: 3px solid transparent;
  transition: background var(--dur-fast) var(--ease),
              border-left-color var(--dur-fast) var(--ease);
}
/* Full severity colour, not the 32%-alpha `-line` token: at that alpha the rail
   was invisible against surface-0 and did no scanning work at all. */
.inc-item.rail-airport_closure { border-left-color: var(--sev-airport); }
.inc-item.rail-military        { border-left-color: var(--sev-military); }
.inc-item.rail-incident        { border-left-color: var(--sev-incident); }
.inc-item.rail-sighting        { border-left-color: var(--sev-sighting); }
.inc-item.rail-unknown         { border-left-color: var(--sev-unknown); }


.inc-item:hover { background: var(--surface-2); color: var(--text); text-decoration: none; }
.inc-list .inc-item:last-child { border-bottom: 0; }
.inc-body { min-width: 0; }
.inc-meta {
  display: flex;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  flex-wrap: wrap;
  margin-bottom: var(--sp-2);
}
.inc-time {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.inc-place {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.0625rem;
  line-height: 1.3;
  color: var(--text);
  margin-bottom: var(--sp-1);
}
.inc-summary { color: var(--text-muted); margin: 0; max-width: var(--measure); }
.inc-go {
  color: var(--text-dim);
  font-size: 1.125rem;
  transition: color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.inc-item:hover .inc-go { color: var(--accent); transform: translateX(3px); }

/* ---------------------------------------------------------------- tables */
.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface-1);
  /* right-edge fade = "there is more to the right", without a JS scroll spy */
  background-image: linear-gradient(
    to left, var(--surface-1), rgba(17, 21, 28, 0) 40px
  );
  background-attachment: local, scroll;
  background-repeat: no-repeat;
  background-position: right center;
  background-size: 40px 100%;
}
table { border-collapse: collapse; width: 100%; font-size: var(--fs-small); }
thead th {
  text-align: left;
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
tbody td {
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--border);
  color: var(--text);
  vertical-align: top;
  /* Data cells scroll sideways rather than collapsing into 4-line stacks on a
     narrow screen. Prose cells opt back in with .wrap. */
  white-space: nowrap;
}
tbody td.wrap, tbody td.wrap * { white-space: normal; }
tbody td.wrap { min-width: 22ch; }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover td { background: var(--surface-2); }
td.num, th.num { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* ----------------------------------------------------------------- forms */
label { display: block; color: var(--text); font-size: var(--fs-small); font-weight: 600; }
label small, .field-hint { display: block; font-weight: 400; color: var(--text-muted); font-size: var(--fs-micro); margin-top: 2px; }

input, textarea, select {
  width: 100%;
  font: inherit;
  font-size: var(--fs-body);
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: var(--sp-3);
  margin-top: var(--sp-2);
  min-height: 44px;
  transition: border-color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
  color-scheme: dark;
}
textarea { min-height: 120px; resize: vertical; line-height: var(--lh-body); }
input::placeholder, textarea::placeholder { color: var(--text-dim); }
input:hover, textarea:hover, select:hover { border-color: var(--border-strong); }
input:focus, textarea:focus, select:focus {
  outline: none;
  background: var(--surface-3);
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
input[type="radio"], input[type="checkbox"] {
  width: 18px;
  height: 18px;
  min-height: 0;
  margin: 0;
  flex-shrink: 0;
  accent-color: var(--accent);
  cursor: pointer;
}
input[type="date"], input[type="datetime-local"], input[type="time"] { color-scheme: dark; }
input::-webkit-calendar-picker-indicator { filter: invert(0.7); cursor: pointer; }

.field { margin-bottom: var(--sp-4); }
/* Bottom-aligned so a label that wraps in one column does not push its input
   out of line with the column beside it. */
.field-row { display: flex; gap: var(--sp-4); flex-wrap: wrap; align-items: flex-end; }
.field-row > * { flex: 1 1 180px; }

/* Radio cards */
.radio-group { display: flex; flex-direction: column; gap: var(--sp-2); margin-top: var(--sp-3); }
.radio-option {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: var(--sp-3);
  row-gap: var(--sp-1);
  align-items: start;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: var(--sp-3) var(--sp-4);
  cursor: pointer;
  font-weight: 400;
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.radio-option:hover { border-color: var(--border-strong); background: var(--surface-3); }
.radio-option:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.radio-option:has(input:checked) { border-color: var(--accent-line); background: var(--accent-soft); }
.radio-option input[type="radio"] { margin-top: 3px; }
.radio-option .opt-label {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-weight: 600;
  color: var(--text);
  font-size: var(--fs-small);
}
.radio-option .opt-sub {
  grid-column: 2;
  color: var(--text-muted);
  font-size: var(--fs-micro);
  line-height: 1.5;
}

/* Styled file input — the native control is visually hidden but still a real
   <input type="file"> in the DOM, focusable and keyboard-operable. */
.filebox {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-top: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface-2);
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-sm);
  cursor: pointer;
  min-height: 52px;
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.filebox:hover { border-color: var(--accent-line); background: var(--surface-3); }
.filebox:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.filebox input[type="file"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
  margin: 0;
  min-height: 0;
}
.filebox .i { color: var(--accent); font-size: 1.25rem; }
.filebox-text { font-size: var(--fs-small); color: var(--text-muted); }
.filebox-text b { display: block; color: var(--text); font-weight: 600; }

/* Compass bearing picker — citizen submit form.
   Two ways in, same value: drag/click/arrow-key the dial, or hit one of the
   eight direction chips. The phone-compass button is added by script only
   when the device actually exposes DeviceOrientation, so a desktop browser
   never sees a control it cannot honour. */
.bearing {
  display: flex;
  gap: var(--sp-4);
  align-items: flex-start;
  flex-wrap: wrap;
  margin-top: var(--sp-3);
  padding: var(--sp-4);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
}
.bearing-dial {
  flex: 0 0 auto;
  width: 148px;
  height: 148px;
  border-radius: 50%;
  cursor: crosshair;
  touch-action: none;         /* dragging the needle must not scroll the page */
  transition: box-shadow var(--dur-fast) var(--ease);
}
.bearing-dial:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-soft); }
.bearing-dial svg { display: block; width: 100%; height: 100%; }
.bearing-dial .dial-face { fill: var(--surface-1); stroke: var(--border-strong); }
.bearing-dial .dial-tick { stroke: var(--border-strong); }
.bearing-dial .dial-tick-major { stroke: var(--text-dim); }
.bearing-dial .dial-label {
  fill: var(--text-dim);
  font-family: var(--font-mono);
  font-size: 15px;
  text-anchor: middle;
  dominant-baseline: middle;
}
.bearing-dial .dial-label-n { fill: var(--text-muted); }
.bearing-dial .dial-hub { fill: var(--border-strong); }
.bearing-needle { transition: transform var(--dur-fast) var(--ease); }
.bearing-needle .needle-head { fill: var(--accent); }
.bearing-needle .needle-tail { fill: var(--border-strong); }
/* Unset: the needle is parked at north but must not read as a chosen north. */
.bearing.is-unset .bearing-needle { opacity: 0.25; }
.bearing.is-live .bearing-needle { transition: none; }
.bearing.is-live .bearing-needle .needle-head { fill: var(--accent-hover); }

.bearing-side { flex: 1 1 220px; min-width: 200px; }
.bearing-read {
  margin: 0 0 var(--sp-3);
  font-family: var(--font-mono);
  font-size: var(--fs-body);
  color: var(--text);
}
.bearing.is-unset .bearing-read { color: var(--text-muted); font-size: var(--fs-small); }
.bearing-quick {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-2);
  margin-bottom: var(--sp-3);
}
.bearing-chip {
  min-height: 36px;
  padding: var(--sp-2) 0;
  background: var(--surface-3);
  color: var(--text-muted);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.bearing-chip:hover { border-color: var(--border-strong); color: var(--text); }
.bearing-chip:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.bearing-chip[aria-pressed="true"] {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent);
}
.bearing-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
/* .btn sets display:inline-flex, which beats the UA's [hidden] rule (author
   origin wins over UA regardless of specificity) — without this, a button
   script-hidden via .hidden = true would still be painted. */
.bearing-actions .btn[hidden] { display: none; }
.bearing-note { margin: var(--sp-3) 0 0; color: var(--text-muted); }
.bearing-note:empty { display: none; }

@media (max-width: 480px) {
  .bearing { gap: var(--sp-3); }
  .bearing-dial { width: 120px; height: 120px; }
}

/* Bearing readout on an incident — a rotated copy of the existing arrow icon,
   not a second arrow asset. i-arrow-right points east (90 deg), so the
   rotation applied inline is `bearing - 90`. */
/* Two classes deep so it beats `.strip-item .i { color: var(--text-dim) }`. */
.i.bearing-arrow { color: var(--accent); }

/* Two-column work layout: main column + supporting sidebar */
.split {
  display: grid;
  grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr);
  gap: var(--sp-5);
  align-items: start;
}
.split-aside { position: sticky; top: var(--sp-5); }
@media (max-width: 900px) {
  .split { grid-template-columns: 1fr; }
  .split-aside { position: static; }
}

/* --------------------------------------------------------------- buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 44px;
  padding: var(--sp-3) var(--sp-5);
  border-radius: var(--r-sm);
  border: 1px solid var(--border-strong);
  background: var(--surface-2);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--fs-small);
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.btn:hover { background: var(--surface-3); border-color: var(--border-strong); color: var(--text); text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
  font-size: var(--fs-body);
  padding: var(--sp-3) var(--sp-6);
}
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: var(--accent-ink); }
.btn-sm { min-height: 36px; padding: var(--sp-2) var(--sp-3); font-size: var(--fs-micro); }
.btn-ok     { color: var(--sev-sighting); border-color: var(--sev-sighting-line); background: var(--sev-sighting-bg); }
.btn-ok:hover     { background: rgba(111, 194, 141, 0.2); color: var(--sev-sighting); }
.btn-danger { color: var(--sev-airport);  border-color: var(--sev-airport-line);  background: var(--sev-airport-bg); }
.btn-danger:hover { background: rgba(242, 119, 127, 0.2); color: var(--sev-airport); }
.btn:disabled, .btn[disabled] { opacity: 0.45; cursor: not-allowed; }

/* --------------------------------------------------------- section cards */
/* Used to break long prose pages (/about, /ukraine) into scannable blocks. */
.section-card {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-5) var(--sp-5) var(--sp-5) var(--sp-5);
  margin-bottom: var(--sp-4);
}
.section-card > :last-child { margin-bottom: 0; }
.section-card-head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-4);
  padding-bottom: var(--sp-3);
  border-bottom: 1px solid var(--border);
}
.section-card-head h3 { margin: 0; }
.section-card-head .i {
  color: var(--accent);
  font-size: 1.125rem;
  width: 1.125em; height: 1.125em;
}
.section-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
  gap: var(--sp-4);
  align-items: stretch;
}
.section-grid > .section-card { margin-bottom: 0; }

/* Numbered method steps */
.steps { list-style: none; padding: 0; margin: 0; counter-reset: step; max-width: var(--measure); }
.steps li {
  position: relative;
  counter-increment: step;
  padding-left: var(--sp-7);
  margin-bottom: var(--sp-4);
  color: var(--text-muted);
}
.steps li:last-child { margin-bottom: 0; }
.steps li strong { color: var(--text); }
.steps li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 0;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border-radius: var(--r-pill);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  font-weight: 500;
}

/* Link list with icons */
.linklist { list-style: none; padding: 0; margin: 0; max-width: none; }
.linklist li {
  display: flex;
  gap: var(--sp-3);
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--border);
  color: var(--text-muted);
  font-size: var(--fs-small);
  margin: 0;
}
.linklist li:last-child { border-bottom: 0; }
.linklist .i { color: var(--text-dim); margin-top: 0.25em; }
.linklist-nav li { align-items: center; padding: 0; }
.linklist-nav a {
  display: flex;
  align-items: center;
  min-height: 44px;
  flex: 1;
}
.src-title-link {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 44px;
  margin-top: var(--sp-1);
}

/* News widget rows */
.news-item { display: block; padding: var(--sp-3) 0; border-bottom: 1px solid var(--border); text-decoration: none; }
.news-item:last-child { border-bottom: 0; }
.news-item .news-title { color: var(--text); font-weight: 500; font-size: var(--fs-small); line-height: 1.45; }
.news-item:hover .news-title { color: var(--accent); }
.news-meta {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-top: var(--sp-1);
  color: var(--text-dim);
  font-size: var(--fs-micro);
  flex-wrap: wrap;
}

details {
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface-1);
  padding: var(--sp-3) var(--sp-4);
}
details[open] { padding-bottom: var(--sp-4); }
details > summary {
  cursor: pointer;
  color: var(--text);
  font-weight: 600;
  font-size: var(--fs-small);
  list-style: none;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 44px;
}
details > summary::-webkit-details-marker { display: none; }
details > summary::before {
  content: "";
  width: 1em;
  height: 1em;
  flex-shrink: 0;
  background-color: var(--accent);
  -webkit-mask-image: var(--ico-arrow-r);
  mask-image: var(--ico-arrow-r);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  transition: transform var(--dur-fast) var(--ease);
}
details[open] > summary::before { transform: rotate(90deg); }

/* Empty state */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--sp-3);
  padding: var(--sp-7) var(--sp-5);
  color: var(--text-muted);
}
.empty-state .empty-icon {
  width: 56px;
  height: 56px;
  display: grid;
  place-items: center;
  border-radius: var(--r-pill);
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text-dim);
  font-size: 1.5rem;
}
.empty-state h2, .empty-state h3 { margin: 0; font-size: var(--fs-h3); }
.empty-state p { margin: 0; max-width: 44ch; }

/* Group divider inside a long catalogue of figures — gives the page
   progression instead of eleven identical cards in a row. */
.group-head {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  margin: var(--sp-7) 0 var(--sp-4) 0;
  padding-bottom: var(--sp-3);
  border-bottom: 1px solid var(--border);
}
.group-head:first-child { margin-top: var(--sp-5); }
.group-head h2 { margin: 0; }
.group-head p { margin: 0; }
.group-code {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  border-radius: var(--r-sm);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  font-weight: 500;
}

/* Analysis figure plate — several N1-N7 figures are light-background
   matplotlib exports. Framing them on a raised surface keeps them from
   reading as a hole punched in the dark UI. */
.fig-plate {
  display: block;
  padding: var(--sp-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  text-decoration: none;
  transition: border-color var(--dur-fast) var(--ease);
}
.fig-plate:hover { border-color: var(--accent-line); }
.fig-img {
  width: 100%;
  height: auto;          /* intrinsic width/height attrs reserve the box */
  display: block;
  border-radius: var(--r-sm);
  background: var(--surface-1);
}

/* MapLibre chrome ships as a white pill with black text — the last hard white
   break in the UI. Restyled into the dark surface ladder; attribution stays
   fully visible and legible, as the CARTO/OSM licence requires. */
.maplibregl-ctrl-attrib,
.mapboxgl-ctrl-attrib {
  background: rgba(11, 14, 19, 0.82) !important;
  color: var(--text-muted) !important;
  border-radius: var(--r-sm) !important;
  font-size: 11px !important;
  padding: 2px 6px !important;
}
.maplibregl-ctrl-attrib a,
.mapboxgl-ctrl-attrib a { color: var(--text-muted) !important; }
.maplibregl-ctrl-attrib a:hover,
.mapboxgl-ctrl-attrib a:hover { color: var(--accent) !important; }
.maplibregl-ctrl-attrib-button,
.mapboxgl-ctrl-attrib-button { filter: invert(1) opacity(0.6); }
.maplibregl-ctrl-group,
.mapboxgl-ctrl-group {
  background: var(--surface-2) !important;
  border: 1px solid var(--border) !important;
  border-radius: var(--r-sm) !important;
}

/* --------------------------------------------------------------- plotly */
.js-plotly-plot .plotly,
.js-plotly-plot .plot-container { background: transparent !important; }
.js-plotly-plot .modebar { display: none !important; }

/* Dash dcc controls */
.Select-control,
.dash-dropdown,
.dash-dropdown > button { min-height: 44px !important; }
/* Dash ships a 4px radius of its own; the scale is 8/12/16/pill only. */
.dash-input-container,
.dash-input-element,
.dash-dropdown,
button.dash-dropdown,
.dash-dropdown-menu,
.dash-options-list { border-radius: var(--r-sm) !important; }
/* Dash ships its own focus ring; the focus colour is a brand signal, so ours wins. */
.dash-input-container:focus-within,
.dash-input-element:focus,
input.dash-input-element:focus,
button.dash-dropdown:focus,
button.dash-dropdown:focus-visible {
  outline: none !important;
  border-color: var(--accent) !important;
  box-shadow: 0 0 0 3px var(--accent-soft) !important;
}
.Select-control { display: flex !important; align-items: center; }
button.dash-dropdown { min-height: 44px !important; }
.Select-control, .Select-menu-outer, .Select--multi .Select-value {
  background: var(--surface-2) !important;
  color: var(--text) !important;
  border-color: var(--border) !important;
  border-radius: var(--r-sm) !important;
}
.Select-value-label, .Select-placeholder, .Select-input > input { color: var(--text) !important; }
.Select--multi .Select-value { background: var(--accent-soft) !important; color: var(--accent) !important; border-color: var(--accent-line) !important; }
.VirtualizedSelectOption { background: var(--surface-2) !important; color: var(--text) !important; }
.VirtualizedSelectFocusedOption { background: var(--surface-3) !important; }
.dash-dropdown .Select-arrow { border-top-color: var(--text-muted) !important; }

/* The installed Dash renders dcc.Dropdown as a <button class="dash-dropdown">
   plus a radix popover — not the react-select markup the .Select-* rules above
   were written for, so every dropdown on the site fell back to the browser's
   default WHITE button on a dark page. Same tokens, current selectors. */
button.dash-dropdown {
  display: flex;
  align-items: center;
  width: 100%;
  margin-top: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  font: inherit;
  font-size: var(--fs-body);
  text-align: left;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);
}
button.dash-dropdown:hover { border-color: var(--border-strong); background: var(--surface-3); }
.dash-dropdown-trigger { width: 100%; display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); }
.dash-dropdown-value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-dropdown-placeholder { color: var(--text-dim); }
.dash-dropdown-trigger-icon { color: var(--text-muted); flex-shrink: 0; }

.dash-dropdown-content {
  background: var(--surface-3);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-2);
  padding: var(--sp-1);
  overflow: auto;
}
.dash-dropdown-content .dash-options-list-option {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-sm);
  font-size: var(--fs-small);
  color: var(--text);
  cursor: pointer;
}
.dash-dropdown-content .dash-options-list-option:hover { background: var(--surface-2); }
.dash-dropdown-content .dash-options-list-option.selected {
  background: var(--accent-soft);
  color: var(--accent);
}
.dash-dropdown-search-container input {
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text);
}

/* Dash RadioItems / Checklist */
.dash-radio-items, .dash-checklist { display: flex; gap: var(--sp-4); flex-wrap: wrap; }
.dash-radio-items label, .dash-checklist label {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-weight: 400;
  color: var(--text-muted);
  cursor: pointer;
  min-height: 32px;
}

/* ------------------------------------------------- view tabs (/incidents)
   Kort / Liste / Trends over one filtered selection. Built on RadioItems
   rather than a bespoke button group: a radio group is what this is —
   exactly one of three — and it arrives keyboard-navigable and announced
   without a line of JS or a single aria attribute of our own.

   The native input is taken out of the paint but left in the accessibility
   tree and in focus order; the label is the visible control. */
.seg-tabs {
  display: flex;
  gap: var(--sp-1);
  flex-wrap: wrap;
  margin: var(--sp-5) 0 var(--sp-4);
  padding: var(--sp-1);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  /* The strip is a control, not a full-bleed band — it should not outrun the
     panels beneath it on a wide desktop. */
  width: fit-content;
  max-width: 100%;
}
.seg-tabs label {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 40px;
  padding: 0 var(--sp-4);
  border-radius: var(--r-sm);
  font-family: var(--font-display);
  font-size: var(--fs-small);
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--text-muted);
  cursor: pointer;
  white-space: nowrap;
  transition: background 120ms ease, color 120ms ease;
}
.seg-tabs label:hover { color: var(--text); background: var(--surface-3); }
.seg-tabs input { position: absolute; opacity: 0; width: 0; height: 0; }
.seg-tabs input:checked + span,
.seg-tabs label:has(input:checked) {
  color: var(--accent-ink);
  background: var(--accent);
}
/* Focus has to land on the label, since the input itself is invisible. */
.seg-tabs label:has(input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.seg-tabs label:has(input:checked) .i { background-color: var(--accent-ink); }

/* The geo projection has exactly one aspect (see DK_FRAME in dash_app), so the
   container is given that aspect and Plotly fills it. A fixed pixel height
   instead leaves the figure padding itself with empty plate — worst at phone
   width, where the map used 291 of 520px and the rest was dead space.
   max-width keeps it from becoming 880px tall on a wide desktop. */
.map-fit {
  width: 100%;
  max-width: 700px;
  /* = _FRAME_ASPECT in dash_app.py: (7.7° lon / 3.5° lat) x cos(56°N). If the
     two drift apart the figure re-acquires the dead margin this removes. */
  aspect-ratio: 1.2408;
  margin-inline: auto;
}

/* All three panels stay mounted so their callbacks keep resolving; only one
   is shown. `content-visibility` keeps the hidden two out of layout and paint
   without unmounting them. */
.tab-panel.is-hidden {
  display: none;
  content-visibility: hidden;
}

/* Markdown rendered by dcc.Markdown */
.markdown-body { max-width: var(--measure); }
.markdown-body p, .markdown-body li { color: var(--text-muted); }
.markdown-body strong { color: var(--text); }
.markdown-body h2, .markdown-body h3 { color: var(--text); margin-top: var(--sp-5); }
.markdown-body blockquote {
  margin: var(--sp-4) 0;
  padding: var(--sp-3) var(--sp-4);
  border-left: 3px solid var(--accent-line);
  background: var(--surface-2);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  color: var(--text-muted);
}

/* ===================================================================
   Flow layer — components added in the 2026-09-09 premium/flow pass.
   =================================================================== */

/* ------------------------------------------------------- section headers
   The old landing was six identical `.panel` slabs stacked in a column: same
   surface, same border, same radius, same padding, no transitions between
   them. Nothing told you where one idea stopped and the next began. A named
   section header with a rule and a numeral gives the page a spine. */
.section-lead {
  display: flex;
  /* Top-aligned: bottom-aligning hung the action button off the last line of a
     three-line description, where it read as unattached to the heading. */
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-4);
  flex-wrap: wrap;
  margin: var(--sp-8) 0 var(--sp-4) 0;
  padding-bottom: var(--sp-3);
  border-bottom: 1px solid var(--border);
}
/* Optically level with the h2 next to it, not with the eyebrow above it. */
.section-lead > .btn,
.section-lead > .micro { margin-top: var(--sp-6); }
.section-lead:first-child { margin-top: 0; }
.section-lead-main { min-width: 0; flex: 1 1 380px; }
.section-lead h2 {
  margin: 0;
  font-size: 1.625rem;
  letter-spacing: -0.02em;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}
.section-lead .eyebrow { margin-bottom: var(--sp-2); }
.section-lead p {
  margin: var(--sp-2) 0 0 0;
  color: var(--text-muted);
  font-size: var(--fs-small);
  max-width: 64ch;
}
.section-lead .btn { flex-shrink: 0; }

/* ---------------------------------------------------------- metric strip
   Six identical bordered cards read as a form, not as instrumentation. This
   is one continuous panel divided by hairlines — the way a readout panel on
   real equipment is divided — so the numbers read as one system. */
.metrics {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  overflow: hidden;
}
.metric {
  padding: var(--sp-4) var(--sp-5);
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  transition: background var(--dur-fast) var(--ease);
}
.metric:hover { background: var(--surface-2); }
.metrics .metric:nth-child(3n) { border-right: 0; }
.metrics .metric:nth-last-child(-n + 3) { border-bottom: 0; }
.metric-label {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.09em;
}
.metric-value {
  font-family: var(--font-display);
  font-size: 2.25rem;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.metric-sub { color: var(--text-muted); font-size: var(--fs-micro); line-height: 1.45; margin-top: auto; padding-top: var(--sp-2); }
.metric.is-accent .metric-value { color: var(--accent); }
.metric.is-alert .metric-value { color: var(--sev-airport); }
/* A zero is a true reading, but rendered at full weight beside 62,160 it looks
   like a failure rather than "this channel has not fired yet". */
.metric.is-zero .metric-value { color: var(--text-dim); font-weight: 500; }

/* --------------------------------------------------- timeline month rule
   Thirty undifferentiated rows have no rhythm — the eye finds no rest and no
   sense of time passing. A sticky month rule turns the list into a chronology
   you can scan, and tells you where you are while you scroll. */
.inc-group {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) 0 var(--sp-2) 0;
  margin-top: var(--sp-4);
  background: linear-gradient(180deg, var(--surface-1) 72%, rgba(17, 21, 28, 0));
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.inc-list > .inc-group:first-child { margin-top: 0; }
.inc-group::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}
.inc-group .inc-group-n {
  color: var(--text-muted);
  letter-spacing: 0.04em;
  text-transform: none;
}

/* ------------------------------------------------------------ entrances
   One orchestrated arrival on load, not scattered micro-animation. Content
   is fully visible without JS and without motion preference. */
@media (prefers-reduced-motion: no-preference) {
  .reveal {
    animation: ds-rise 620ms var(--ease) backwards;
  }
  .reveal-1 { animation-delay: 40ms; }
  .reveal-2 { animation-delay: 110ms; }
  .reveal-3 { animation-delay: 180ms; }
  .reveal-4 { animation-delay: 250ms; }
}
@keyframes ds-rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

/* ------------------------------------------------------------ listen page
   The acoustic sensor is the most striking thing the site does and it looked
   like a disabled form: two flat boxes, three zeros, an em-dash readout and a
   literal `[event-log vises her]` placeholder. It now reads as an instrument
   with an obvious on-switch. */
.listen-stage {
  position: relative;
  background:
    radial-gradient(90% 120% at 50% 0%, rgba(255, 107, 53, 0.08) 0%, transparent 62%),
    var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--sp-6) var(--sp-5);
  overflow: hidden;
}
.listen-dial {
  position: relative;
  width: 172px;
  height: 172px;
  margin: 0 auto var(--sp-5) auto;
  border-radius: var(--r-pill);
  display: grid;
  place-items: center;
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
}
/* Concentric rings, still when idle, breathing while the sensor is live. */
.listen-dial::before,
.listen-dial::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: var(--r-pill);
  border: 1px solid var(--accent-line);
  opacity: 0;
}
/* "Listening" is derived from the DOM the existing script already maintains —
   it disables the start button while recording — so the state costs no JS. */
body:has(#startBtn:disabled) .listen-dial::before { animation: ds-ping 2.6s var(--ease) infinite; }
body:has(#startBtn:disabled) .listen-dial::after  { animation: ds-ping 2.6s var(--ease) 1.3s infinite; }
@keyframes ds-ping {
  0%   { opacity: 0.55; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.42); }
}
.listen-dial .i {
  font-size: 3.25rem;
  color: var(--text-dim);
  transition: color var(--dur) var(--ease);
}
body:has(#startBtn:disabled) .listen-dial { border-color: var(--accent); background: var(--accent-soft); }
body:has(#startBtn:disabled) .listen-dial .i { color: var(--accent); }

.listen-actions { display: flex; gap: var(--sp-3); justify-content: center; flex-wrap: wrap; }
.listen-status {
  text-align: center;
  color: var(--text-muted);
  font-size: var(--fs-small);
  margin: var(--sp-4) 0 0 0;
  min-height: 1.6em;
  max-width: none;
}

/* Signal meter with a marked decision threshold — a bar with no scale tells
   you nothing about whether 0.4 is a lot. */
.signal {
  position: relative;
  height: 14px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  overflow: hidden;
}
/* Must NOT be `.signal > div` — that also matched .signal-threshold and forced
   the threshold marker to width:0, so the scale had no visible tick. */
.signal > #confidenceBar {
  height: 100%;
  width: 0%;
  border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--sev-sighting), var(--sev-incident), var(--sev-airport));
  transition: width 300ms var(--ease);
}
.signal-scale {
  position: relative;
  height: 1.2em;
  margin-top: var(--sp-2);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  color: var(--text-dim);
}
.signal-scale span { position: absolute; transform: translateX(-50%); }
.signal-scale span:first-child { transform: none; left: 0; }
.signal-scale span:last-child { transform: none; right: 0; left: auto; }
/* A notch cut out of the bar rather than a line drawn under the fill — once the
   reading passes 0.5 the fill covered a tinted marker completely, which is
   exactly the moment the threshold matters most. */
.signal-threshold {
  position: absolute;
  top: -1px;
  bottom: -1px;
  left: 50%;
  width: 3px;
  margin-left: -1.5px;
  background: var(--surface-0);
  box-shadow: 0 0 0 1px rgba(230, 233, 239, 0.25);
  z-index: 2;
}

.readout-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-3);
  margin-top: var(--sp-5);
}
.readout-cell {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: var(--sp-3) var(--sp-4);
  min-width: 0;
}
.readout-cell strong {
  display: block;
  font-family: var(--font-display);
  font-size: 1.75rem;
  font-weight: 600;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}
.readout-cell span {
  display: block;
  color: var(--text-muted);
  font-size: var(--fs-micro);
  margin-top: 2px;
}

/* --------------------------------------------------------- form steps
   The submit page presented three equal-weight cards at once with no sense of
   sequence, and left a 1300px-tall empty column beside the lower two. Numbered
   steps turn it into something you are walked through. */
.step { position: relative; }
.step + .step { margin-top: var(--sp-4); }
.step-head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-4);
}
.step-n {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  border-radius: var(--r-pill);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  font-weight: 500;
}
.step-head h2 { margin: 0; font-size: var(--fs-h3); }
.step-head .step-opt {
  margin-left: auto;
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
}

/* Submit action bar — the old primary button sat orphaned at the bottom-left
   of a 2400px page, at the same size as a secondary control. */
/* Deliberately NOT sticky. A bottom-sticky bar here sat permanently on top of
   whichever field the visitor was filling in, and there is no room to pad the
   form out from under it without making the column feel loose. */
.form-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  flex-wrap: wrap;
  margin-top: var(--sp-5);
  padding: var(--sp-4) var(--sp-5);
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-2);
}
.form-actions .btn-primary { flex-shrink: 0; }
.form-actions p { margin: 0; color: var(--text-muted); font-size: var(--fs-micro); max-width: 46ch; }

/* --------------------------------------------------------- next-step rail
   Every page used to end at the footer, which made the site feel like a set of
   separate documents. Each page now hands the visitor the next move. */
.nextup {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--sp-3);
  margin-top: var(--sp-7);
}
.nextup-card {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-4);
  /* These are the last thing on the page and the only ask it makes; at the old
     16px padding and surface-1 they whispered next to everything above them. */
  padding: var(--sp-5);
  min-height: 116px;
  border-radius: var(--r-md);
  border: 1px solid var(--border-strong);
  background: var(--surface-2);
  color: var(--text);
  text-decoration: none;
  transition: border-color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.nextup-card:hover {
  border-color: var(--accent-line);
  background: var(--surface-2);
  color: var(--text);
  text-decoration: none;
  transform: translateY(-2px);
}
.nextup-card .i {
  color: var(--accent);
  font-size: 1.5rem;
  width: 1.5em;
  height: 1.5em;
  margin-top: 0.05em;
  flex-shrink: 0;
}
.nextup-card b {
  display: block;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.1875rem;
  letter-spacing: -0.015em;
  line-height: 1.25;
}
.nextup-card span { display: block; color: var(--text-muted); font-size: var(--fs-small); line-height: 1.5; margin-top: var(--sp-2); }

/* ---------------------------------------------------------------- mobile */
@media (max-width: 900px) {
  .hero h1 { max-width: none; }
  .metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .metrics .metric:nth-child(3n) { border-right: 1px solid var(--border); }
  .metrics .metric:nth-child(2n) { border-right: 0; }
  .metrics .metric:nth-last-child(-n + 3) { border-bottom: 1px solid var(--border); }
  .metrics .metric:nth-last-child(-n + 2) { border-bottom: 0; }
}
@media (max-width: 720px) {
  :root {
    --fs-body: 1rem;      /* 16px floor on mobile */
    --fs-lead: 1.0625rem;
    --fs-h1: 1.75rem;
    --fs-h2: 1.3125rem;
    --fs-h3: 1.0625rem;
  }
  .ds-shell { padding: var(--sp-4) var(--sp-4) var(--sp-7) var(--sp-4); }
  .hero {
    padding: var(--sp-6) 0 var(--sp-5) 0;
    margin: calc(-1 * var(--sp-4)) calc(-1 * var(--sp-4)) var(--sp-5) calc(-1 * var(--sp-4));
  }
  .hero-inner { padding: 0 var(--sp-4); }
  .stat-grid { grid-template-columns: 1fr; }
  .section-grid { grid-template-columns: 1fr; }
  /* Both call cards must clear the fold on a 375x812 phone — the copy above
     them is trimmed here rather than allowed to push them off-screen. */
  .hero { padding: var(--sp-5) 0 var(--sp-5) 0; }
  .hero-lead { font-size: 1.125rem; margin-bottom: var(--sp-3); }
  .hero-why { margin-bottom: var(--sp-5); }
  .hero-trust { padding-top: var(--sp-3); }
  .cta-row { grid-template-columns: 1fr; gap: var(--sp-3); margin-bottom: var(--sp-5); }
  .cta-card { padding: var(--sp-4); column-gap: var(--sp-3); min-height: 76px; }
  .cta-card .cta-title { font-size: 1.0625rem; }
  .cta-card .cta-icon { font-size: 1.25rem; }
  .hero-trust {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--sp-2);
    font-size: var(--fs-micro);
  }
  .hero-trust a { min-height: 32px; }
  .panel { padding: var(--sp-4); }
  .section-card { padding: var(--sp-4); }
  .stat-card .value { font-size: 1.75rem; }
  .ds-nav {
    gap: 2px;
    width: 100%;
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    margin-left: calc(-1 * var(--sp-2));
    padding-bottom: var(--sp-1);
  }
  .ds-nav::-webkit-scrollbar { display: none; }
  .ds-nav-link {
    padding: var(--sp-2);
    font-size: var(--fs-micro);
    white-space: nowrap;
    flex-shrink: 0;
  }
  .ds-header > .ds-actions { width: 100%; margin-left: 0; order: 2; }
  .ds-header > .ds-nav { order: 3; }
  .ds-actions .ds-nav-link { flex: 1 1 0; justify-content: center; }
  .btn-sm { min-height: 44px; }
  /* Stacked, so the bottom-alignment used on the desktop row would become a
     horizontal one and indent the fields. */
  .field-row { flex-direction: column; align-items: stretch; gap: var(--sp-3); }
  .field-row > * { flex: 1 1 auto; }
  .strip { gap: var(--sp-3) var(--sp-4); padding: var(--sp-3); }
  .inc-item { padding: var(--sp-4) var(--sp-3); }

  /* --- flow-layer components --- */
  .metrics { grid-template-columns: 1fr; }
  .metrics .metric { border-right: 0 !important; border-bottom: 1px solid var(--border) !important; }
  .metrics .metric:last-child { border-bottom: 0 !important; }
  .metric { padding: var(--sp-4); }
  .metric-value { font-size: 1.875rem; }
  .section-lead { margin-top: var(--sp-7); }
  .section-lead h2 { font-size: 1.375rem; }
  .readout-grid { grid-template-columns: 1fr; gap: var(--sp-2); }
  .readout-cell { display: flex; align-items: baseline; gap: var(--sp-3); }
  .readout-cell strong { font-size: 1.5rem; }
  .readout-cell span { margin-top: 0; }
  .listen-stage { padding: var(--sp-5) var(--sp-4); }
  .listen-dial { width: 128px; height: 128px; }
  .listen-dial .i { font-size: 2.5rem; }
  .listen-actions .btn { flex: 1 1 0; }
  /* A sticky bar would eat a phone screen; it becomes a normal block. */
  .form-actions { position: static; box-shadow: none; padding: var(--sp-4); }
  .form-actions .btn-primary { width: 100%; }
  .hero-map { padding: var(--sp-2); }
  .hero-map-foot { padding: var(--sp-3) 0 0 0; }
  .hero-map-cap { white-space: normal; }
}

/* `--fs-display` must be redeclared on :root, not inside a rule set. */
@media (max-width: 720px) {
  :root { --fs-display: clamp(2.125rem, 9vw, 2.75rem); }
}

@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;
  }
}

/* -------------------------------------------------------- icon geometry
   Lucide (ISC) geometry as CSS custom properties, so every icon reference
   above stays a one-word class name. */
:root {
  --ico-list:     url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 6h13'/%3E%3Cpath d='M8 12h13'/%3E%3Cpath d='M8 18h13'/%3E%3Cpath d='M3 6h.01'/%3E%3Cpath d='M3 12h.01'/%3E%3Cpath d='M3 18h.01'/%3E%3C/svg%3E");
  --ico-trend:    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='22 7 13.5 15.5 8.5 10.5 2 17'/%3E%3Cpolyline points='16 7 22 7 22 13'/%3E%3C/svg%3E");
  --ico-news:     url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 1-2 2Zm0 0a2 2 0 0 1-2-2v-9c0-1.1.9-2 2-2h2'/%3E%3Cpath d='M18 14h-8'/%3E%3Cpath d='M15 18h-5'/%3E%3Cpath d='M10 6h8v4h-8z'/%3E%3C/svg%3E");
  --ico-scan:     url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 7V5a2 2 0 0 1 2-2h2'/%3E%3Cpath d='M17 3h2a2 2 0 0 1 2 2v2'/%3E%3Cpath d='M21 17v2a2 2 0 0 1-2 2h-2'/%3E%3Cpath d='M7 21H5a2 2 0 0 1-2-2v-2'/%3E%3Cpath d='M7 12h10'/%3E%3C/svg%3E");
  --ico-flag:     url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z'/%3E%3Cpath d='M4 22v-7'/%3E%3C/svg%3E");
  --ico-chart:    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 3v18h18'/%3E%3Cpath d='M18 17V9'/%3E%3Cpath d='M13 17V5'/%3E%3Cpath d='M8 17v-3'/%3E%3C/svg%3E");
  --ico-mic:      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z'/%3E%3Cpath d='M19 10v2a7 7 0 0 1-14 0v-2'/%3E%3Cpath d='M12 19v3'/%3E%3C/svg%3E");
  --ico-upload:   url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4'/%3E%3Cpolyline points='17 8 12 3 7 8'/%3E%3Cpath d='M12 3v12'/%3E%3C/svg%3E");
  --ico-info:     url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 16v-4'/%3E%3Cpath d='M12 8h.01'/%3E%3C/svg%3E");
  --ico-code:     url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='16 18 22 12 16 6'/%3E%3Cpolyline points='8 6 2 12 8 18'/%3E%3C/svg%3E");
  --ico-siren:    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 18v-6a5 5 0 1 1 10 0v6'/%3E%3Cpath d='M5 21a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-1a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2z'/%3E%3Cpath d='M21 12h1'/%3E%3Cpath d='M18.5 4.5 18 5'/%3E%3Cpath d='M2 12h1'/%3E%3Cpath d='M12 2v1'/%3E%3Cpath d='m4.929 4.929.707.707'/%3E%3C/svg%3E");
  --ico-check:    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
  --ico-arrow-r:  url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14'/%3E%3Cpolyline points='12 5 19 12 12 19'/%3E%3C/svg%3E");
  --ico-arrow-l:  url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M19 12H5'/%3E%3Cpolyline points='12 19 5 12 12 5'/%3E%3C/svg%3E");
  --ico-pin:      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z'/%3E%3Ccircle cx='12' cy='10' r='3'/%3E%3C/svg%3E");
  --ico-plane:    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M17.8 19.2 16 11l3.5-3.5C21 6 21.5 4 21 3c-1-.5-3 0-4.5 1.5L13 8 4.8 6.2c-.5-.1-.9.1-1.1.5l-.3.5c-.2.5-.1 1 .3 1.3L9 12l-2 3H4l-1 1 3 2 2 3 1-1v-3l3-2 3.5 5.3c.3.4.8.5 1.3.3l.5-.2c.4-.3.6-.7.5-1.2z'/%3E%3C/svg%3E");
  --ico-ship:     url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 21c.6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 2.6 0 2.4 2 5 2 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1'/%3E%3Cpath d='M19.38 20A11.6 11.6 0 0 0 21 14l-9-4-9 4c0 2.9.94 5.34 2.81 7.76'/%3E%3Cpath d='M19 13V7a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v6'/%3E%3Cpath d='M12 10v4'/%3E%3Cpath d='M12 2v3'/%3E%3C/svg%3E");
  --ico-cloud:    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z'/%3E%3C/svg%3E");
  --ico-users:    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2'/%3E%3Ccircle cx='9' cy='7' r='4'/%3E%3Cpath d='M22 21v-2a4 4 0 0 0-3-3.87'/%3E%3Cpath d='M16 3.13a4 4 0 0 1 0 7.75'/%3E%3C/svg%3E");
  --ico-camera:   url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3z'/%3E%3Ccircle cx='12' cy='13' r='3'/%3E%3C/svg%3E");
  --ico-wave:     url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 12h2l3-9 5 18 3-11 2 2h5'/%3E%3C/svg%3E");
  --ico-clock:    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpolyline points='12 6 12 12 16 14'/%3E%3C/svg%3E");
  --ico-mail:     url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='2' y='4' width='20' height='16' rx='2'/%3E%3Cpath d='m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7'/%3E%3C/svg%3E");
  --ico-file:     url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z'/%3E%3Cpath d='M14 2v5h5'/%3E%3Cpath d='M16 13H8'/%3E%3Cpath d='M16 17H8'/%3E%3Cpath d='M10 9H8'/%3E%3C/svg%3E");
  --ico-warn:     url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z'/%3E%3Cpath d='M12 9v4'/%3E%3Cpath d='M12 17h.01'/%3E%3C/svg%3E");
  --ico-database: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cellipse cx='12' cy='5' rx='9' ry='3'/%3E%3Cpath d='M3 5v14a9 3 0 0 0 18 0V5'/%3E%3Cpath d='M3 12a9 3 0 0 0 18 0'/%3E%3C/svg%3E");
  --ico-book:     url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 7v14'/%3E%3Cpath d='M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z'/%3E%3C/svg%3E");
  --ico-rss:      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 11a9 9 0 0 1 9 9'/%3E%3Cpath d='M4 4a16 16 0 0 1 16 16'/%3E%3Ccircle cx='5' cy='19' r='1'/%3E%3C/svg%3E");
  --ico-external: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 3h6v6'/%3E%3Cpath d='M10 14 21 3'/%3E%3Cpath d='M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6'/%3E%3C/svg%3E");
  --ico-radio:    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='2'/%3E%3Cpath d='M4.93 19.07a10 10 0 0 1 0-14.14'/%3E%3Cpath d='M7.76 16.24a6 6 0 0 1 0-8.49'/%3E%3Cpath d='M16.24 7.76a6 6 0 0 1 0 8.49'/%3E%3Cpath d='M19.07 4.93a10 10 0 0 1 0 14.14'/%3E%3C/svg%3E");
  --ico-search:   url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.3-4.3'/%3E%3C/svg%3E");
  --ico-filter:   url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolygon points='22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3'/%3E%3C/svg%3E");
  --ico-shield:   url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z'/%3E%3C/svg%3E");
  --ico-inbox:    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='22 12 16 12 14 15 10 15 8 12 2 12'/%3E%3Cpath d='M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z'/%3E%3C/svg%3E");
  --ico-map:      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14.106 5.553a2 2 0 0 0 1.788 0l3.659-1.83A1 1 0 0 1 21 4.619v12.764a1 1 0 0 1-.553.894l-4.553 2.277a2 2 0 0 1-1.788 0l-4.212-2.106a2 2 0 0 0-1.788 0l-3.659 1.83A1 1 0 0 1 3 19.381V6.618a1 1 0 0 1 .553-.894l4.553-2.277a2 2 0 0 1 1.788 0z'/%3E%3Cpath d='M15 5.764v15'/%3E%3Cpath d='M9 3.236v15'/%3E%3C/svg%3E");
}

/* ── Provenance badges (/analyses) ─────────────────────────────────────────
   Every claim on the analyses page must declare whether it rests on a real
   measurement or on synthetic/theoretical work. The badge is the mechanism;
   it is deliberately high-contrast so the distinction cannot be skimmed past. */
.prov {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 2px 8px;
  border-radius: var(--r-sm);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: 0.02em;
  white-space: nowrap;
  flex-shrink: 0;
}
.prov-real {
  background: rgba(45, 164, 85, 0.13);
  border: 1px solid rgba(45, 164, 85, 0.45);
  color: #4fc47a;
}
.prov-sim {
  background: rgba(153, 153, 153, 0.10);
  border: 1px solid var(--border-bright, #3a3a3e);
  color: var(--text-muted);
}
.panel-head .prov { margin-left: auto; }
.group-head .prov { margin-left: auto; }

/* Contradiction callout — where a real measurement complicates a prior
   theoretical claim. Distinct from .callout so it cannot be mistaken for
   a generic note. */
.callout-contra {
  border-left: 3px solid var(--sev-airport-closure, #d62728);
  background: rgba(214, 39, 40, 0.07);
}
.callout-contra .i { color: var(--sev-airport-closure, #d62728); }

/* Measured-value table on /analyses */
.meas-table { width: 100%; border-collapse: collapse; font-size: var(--fs-micro); }
.meas-table th, .meas-table td {
  text-align: left;
  padding: 6px 10px;
  border-bottom: 1px solid var(--border);
}
.meas-table th { color: var(--text-dim); font-weight: 500; }
.meas-table td { color: var(--text); }
.meas-table td.num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.meas-table tr:last-child td { border-bottom: none; }
.table-scroll { overflow-x: auto; }

/* ---------- playback scrubber ----------------------------------------- */
/* Dash 4 ships the slider unstyled against a dark ground: the track, the
   thumb and the tick labels all render near-invisible. These rules are the
   design system's own tokens applied to the component's parts — no new
   colours are introduced. */
.playback-slider { margin: var(--sp-5) var(--sp-3) var(--sp-6); }
.playback-slider .dash-slider-root { height: 26px; }
.playback-slider .dash-slider-track {
  background: var(--surface-3);
  border: 1px solid var(--border);
  height: 6px;
  border-radius: 3px;
}
.playback-slider [role="slider"] {
  width: 16px;
  height: 16px;
  background: var(--accent);
  border: 2px solid var(--surface-0);
  border-radius: 50%;
  box-shadow: 0 0 0 3px var(--accent-soft);
  cursor: grab;
}
.playback-slider [role="slider"]:active { cursor: grabbing; }
.playback-slider [role="slider"]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* Each tick is a real measurement time, so the labels have to be readable —
   they are the only thing telling the reader that the steps are uneven. */
.playback-slider .dash-slider-mark {
  color: var(--text-muted);
  font-family: var(--font-mono);
  letter-spacing: -0.01em;
  white-space: nowrap;
}
