/* MeshTorry site styles — REDESIGN PROTOTYPE (Claude, 2026-09-29, Filip's brief).
   Self-contained on purpose: this page must be servable from ANY root without depending on the
   app's stylesheet or build step.

   ── WHAT CHANGED AND WHY ────────────────────────────────────────────────────────────────────────
   Filip, 2026-09-29: "I dont like our website .. it seems too clustered and not easy to see what it
   is about .. somewhere in between [meshmapper.net and ours] .. but still keeping my/our soul."

   The old page was one index.html wearing five hats as tab panels, so the landing view had to carry
   a hero + a screenshot + three facts + a note + a seven-item FAQ at once. The fix is structural,
   not cosmetic: one screen does ONE job, and the depth moved into real sub-pages with real URLs.

   What is deliberately KEPT (the "soul"):
     · every colour token below is still the app's — copied, never imported (sources listed below)
     · mono, uppercase, letter-spaced micro-labels; 4px radii; 1px hairline borders
     · the first-person voice in the copy ("Short is fine. I read these.")
   What is deliberately TAKEN from the meshmapper reference:
     · purpose visible without scrolling, and three large unmissable doors
     · facts as a compact scannable strip instead of paragraphs
   What is deliberately NOT taken: its region directory, its density-for-its-own-sake, its anonymity.

   ── THE PALETTE AND THE COMPONENT SHAPES ARE THE APP'S ──────────────────────────────────────────
   Every value below is COPIED from the app, never imported. Source of truth:
     · tokens ......... frontend/src/styles/main.css            `:root`
     · the accent ..... frontend/src/map/hexLayer.js            DEFAULT_TERRITORY_BASE
     · accent-dim ..... main.js `_accentDim()` = the accent * 0.6
     · the map ........ frontend/src/map/hexLayer.js            the colour constants named inline

   ⚠️ THE ACCENT IS NOT THE ONE IN main.css. The app's boot calls `applyAccent(localStorage||
   DEFAULT_TERRITORY_BASE)` (main.js), which overwrites `--accent`/`--accent-dim` on :root before the
   first paint. So the app's real gold is **#d0a536 → dim #7d6320**; main.css's #b8860b/#7a5a08 are
   pre-boot values nobody ever sees. It is also player-configurable, so this is the DEFAULT.

   `frontend/tests/site-palette.mjs` asserts these against the app's own files — keep it passing.

   ⚠️ Prose is never painted `--text-dim` (2.6:1 on this background — decoration, not text).
   ────────────────────────────────────────────────────────────────────────────────────────────── */

:root {
  /* surfaces + lines — main.css :root */
  --bg-primary: #0d0f0d;
  --bg-surface: #141714;
  --bg-elevated: #1c201c;
  --border: #2a302a;
  --border-strong: #3a443a;

  /* text — main.css :root.
     ⚠️ `--text-dim` is a CHROME tone (2.6:1 on --bg-primary): markers only, never words to read. */
  --text-primary: #c8d4c8;
  --text-secondary: #7a8c7a;
  --text-dim: #4a5a4a;

  /* accent — hexLayer.js DEFAULT_TERRITORY_BASE + _accentDim() */
  --accent: #d0a536;
  --accent-dim: #7d6320;

  /* the app's error tone — a literal in main.css's `.auth-error` */
  --danger: #c86a4a;

  /* derived, per theme */
  --nav-bg: rgba(20, 23, 20, 0.92);
  --scroll-track: rgba(0, 0, 0, 0.25);
  --accent-hover: #e4bd52;
  --pulse-from: rgba(208, 165, 54, 0.5);
  --pulse-to: rgba(208, 165, 54, 0);

  --font-mono: "JetBrains Mono", "Fira Code", "Cascadia Code", monospace;
  --font-ui: "Inter", system-ui, sans-serif;

  --radius: 4px;
  --max: 64rem;
  --gutter: clamp(16px, 4vw, 32px);
}

/* ── LIGHT MODE (Filip, 2026-09-29: "add a dark/light switcher") ─────────────────────────────────
   ⚠️ `:root` above is UNTOUCHED and stays the app's dark palette. That matters for two reasons: the
   app itself is dark-only, so dark is the identity and the default here; and `site-palette.mjs`
   asserts those exact `:root` values against `main.css`/`hexLayer.js`. Light lives in overrides, so
   the sheet can gain a theme without the drift check losing its grip on the original.

   Two selectors, both required:
     :root[data-theme="light"]                  an explicit choice, set by theme.js from localStorage
     @media (prefers-color-scheme: light) on
        :root:not([data-theme="dark"])          the OS preference, honoured until someone chooses
   The `:not([data-theme="dark"])` guard is the whole trick: without it, a visitor on a light-themed
   OS who explicitly picks dark would be overruled by their own OS on the next page load.

   The palette is not a naive inversion. It keeps the green-tinted neutral and the gold, but the gold
   is DARKENED for light mode: #d0a536 on a near-white background is about 2:1, which is unreadable as
   link text. #7d6320 (the app's own accent-dim) lands near 5.5:1 and is still recognisably the same
   colour. Same reasoning for --danger. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --bg-primary: #f4f6f3;
    --bg-surface: #eaeee8;
    --bg-elevated: #e2e7df;
    --border: #ccd4c8;
    --border-strong: #aab6a5;
    --text-primary: #1b221b;
    --text-secondary: #4b594a;
    --text-dim: #7d8a7c;
    --accent: #7d6320;
    --accent-dim: #a8842c;
    --danger: #a8482a;
    --nav-bg: rgba(234, 238, 232, 0.94);
    --scroll-track: rgba(0, 0, 0, 0.08);
    --accent-hover: #5e4a18;
    --pulse-from: rgba(125, 99, 32, 0.45);
    --pulse-to: rgba(125, 99, 32, 0);
  }
}

:root[data-theme="light"] {
  --bg-primary: #f4f6f3;
  --bg-surface: #eaeee8;
  --bg-elevated: #e2e7df;
  --border: #ccd4c8;
  --border-strong: #aab6a5;
  --text-primary: #1b221b;
  --text-secondary: #4b594a;
  --text-dim: #7d8a7c;
  --accent: #7d6320;
  --accent-dim: #a8842c;
  --danger: #a8482a;
  --nav-bg: rgba(234, 238, 232, 0.94);
  --scroll-track: rgba(0, 0, 0, 0.08);
  --accent-hover: #5e4a18;
  --pulse-from: rgba(125, 99, 32, 0.45);
  --pulse-to: rgba(125, 99, 32, 0);
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--bg-primary);
  color: var(--text-primary);
  font: 16px/1.65 var(--font-ui);
  -webkit-text-size-adjust: 100%;
}

a { color: var(--accent); }
a:hover { color: var(--accent-hover); }

code {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.05em 0.35em;
  font-family: var(--font-mono);
  font-size: 0.9em;
}

/* Custom scrollbars — copied from the app (main.css). */
::-webkit-scrollbar { height: 8px; width: 8px; }
::-webkit-scrollbar-track { background: var(--scroll-track); }
::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--accent-dim); }
::-webkit-scrollbar-corner { background: transparent; }

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

/* Skip link — the nav is sticky and the hero is tall; a keyboard visitor should not tab the whole
   header on every sub-page. */
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--bg-elevated);
  color: var(--text-primary);
  padding: 8px 14px;
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  z-index: 2000;
}
.skip:focus { left: 8px; top: 8px; }

/* ── nav = the app's status bar ───────────────────────────────────────────── */
.nav {
  position: sticky;
  top: 0;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0 var(--gutter);
  min-height: 48px;
  /* Per-theme token, not a literal: this was `rgba(20,23,20,.92)` and stayed a dark bar in light mode. */
  background: var(--nav-bg);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: 13px;
  /* Above Leaflet, always: its panes reach 400-700 and its controls 800-1000. */
  z-index: 1200;
}

.nav-inner {
  width: 100%;
  max-width: var(--max);
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: 1rem;
}

.brand {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 14px;
  letter-spacing: 0.04em;
  text-decoration: none;
  color: var(--text-primary);
  white-space: nowrap;
}
.brand span { color: var(--accent); }

.nav nav {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 2px;
  flex-wrap: wrap;
}

.nav nav a {
  display: inline-block;
  padding: 6px 10px;
  color: var(--text-secondary);
  text-decoration: none;
  border-radius: var(--radius);
  letter-spacing: 0.03em;
  white-space: nowrap;
}
.nav nav a:hover { color: var(--text-primary); background: var(--bg-elevated); }
.nav nav a[aria-current="page"] { color: var(--accent); }

.nav nav a.cta {
  color: var(--bg-primary);
  background: var(--accent);
  font-weight: 700;
  margin-left: 6px;
}
.nav nav a.cta:hover { background: var(--accent-hover); color: var(--bg-primary); }

/* ── theme toggle (2026-09-29) ────────────────────────────────────────────── */
/* Ships `hidden` and theme.js reveals it: with JS off the button could not do anything, and a dead
   control in the nav of every page is worse than no control. Same reasoning as the map's view
   switcher below. */
.theme-toggle {
  display: inline-grid;
  place-items: center;
  width: 30px;
  height: 30px;
  margin-left: 6px;
  padding: 0;
  background: transparent;
  color: var(--text-secondary);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  flex: none;
}
.theme-toggle:hover { color: var(--accent); border-color: var(--accent-dim); }
.theme-toggle[hidden] { display: none; }
.theme-toggle svg { width: 15px; height: 15px; display: block; }
/* One button, two icons: the sheet picks which is drawn, so theme.js only ever sets an attribute on
   <html> and never has to keep markup in step with state. */
.theme-toggle .i-sun { display: none; }
.theme-toggle .i-moon { display: block; }
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .theme-toggle .i-sun { display: block; }
  :root:not([data-theme="dark"]) .theme-toggle .i-moon { display: none; }
}
:root[data-theme="light"] .theme-toggle .i-sun { display: block; }
:root[data-theme="light"] .theme-toggle .i-moon { display: none; }
:root[data-theme="dark"] .theme-toggle .i-sun { display: none; }
:root[data-theme="dark"] .theme-toggle .i-moon { display: block; }

/* ── the map's view switcher (Filip, 2026-09-29) ──────────────────────────── */
/* "not just as a switch in a layer menu somewhere, make it more obvious and easy to see." So it is a
   full-width segmented control directly above the map, in the page flow, not a Leaflet layers control
   tucked in a corner. Ships `hidden`; map.js reveals it only once the map has actually booted. */
.map-views {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 10px;
  padding: 5px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.map-views[hidden] { display: none; }

.map-views button {
  flex: 1 1 auto;
  padding: 9px 12px;
  background: transparent;
  color: var(--text-secondary);
  border: 1px solid transparent;
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
.map-views button:hover { color: var(--text-primary); background: var(--bg-elevated); }
.map-views button[aria-pressed="true"] {
  background: var(--bg-elevated);
  border-color: var(--accent-dim);
  color: var(--accent);
}
/* The swatch carries the same colour the layer draws with, so the control explains itself without a
   legend: two dots for coverage (the two networks), a filled hex for territory. */
.map-views .sw { width: 9px; height: 9px; border-radius: 50%; flex: none; }
.map-views .sw-hex { border-radius: 1px; transform: rotate(45deg); }
.map-views .sw-pair { display: inline-flex; gap: 3px; }

.map-views-note {
  margin: 0 0 10px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-secondary);
}

/* ── layout ───────────────────────────────────────────────────────────────── */
main { display: block; }

.wrap {
  width: 100%;
  max-width: var(--max);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

.band { padding: clamp(40px, 7vw, 72px) 0; }
.band + .band { border-top: 1px solid var(--border); }
.band-surface { background: var(--bg-surface); }

/* ── type ─────────────────────────────────────────────────────────────────── */
h1, h2, h3 { font-family: var(--font-mono); font-weight: 700; }

h1 {
  font-size: clamp(2rem, 6vw, 3.2rem);
  line-height: 1.1;
  letter-spacing: -0.01em;
  margin: 0 0 1rem;
}

/* Section headings keep the app's family traits — uppercase, letter-spaced, an accent rule — but at
   DOCUMENT sizes. The app's 11-13px chrome sizes invert the hierarchy on a page a stranger reads. */
h2 {
  font-size: 1rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--accent);
  margin: 0 0 1.25rem;
  display: flex;
  align-items: center;
  gap: 12px;
}
h2::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

h3 {
  font-size: 0.9rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-primary);
  margin: 0 0 0.5rem;
}

p { margin: 0 0 1rem; }
p:last-child { margin-bottom: 0; }

.lede {
  font-size: clamp(1.05rem, 2.2vw, 1.2rem);
  color: var(--text-primary);
  max-width: 46ch;
}

.note {
  color: var(--text-secondary);
  font-size: 0.95rem;
  max-width: 68ch;
}

.eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--text-secondary);
  margin: 0 0 1rem;
  display: flex;
  align-items: center;
  gap: 8px;
}

/* A live dot, because the beta IS live and that is the single most useful thing a visitor learns
   in the first second.
   ⚠️ NOT `.dot`: map.js's legend emits `<i class="dot">` for the heard-node swatches, so a bare
   `.dot` rule reached into the legend and set every swatch pulsing. Renamed 2026-09-29. */
.live-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 0 var(--pulse-from);
  animation: pulse 2.4s ease-out infinite;
  flex: none;
}
@keyframes pulse {
  0% { box-shadow: 0 0 0 0 var(--pulse-from); }
  70% { box-shadow: 0 0 0 7px var(--pulse-to); }
  100% { box-shadow: 0 0 0 0 var(--pulse-to); }
}
@media (prefers-reduced-motion: reduce) {
  .live-dot { animation: none; }
  html { scroll-behavior: auto; }
}

/* ── hero ─────────────────────────────────────────────────────────────────── */
.hero {
  position: relative;
  overflow: hidden;
  padding: clamp(44px, 9vw, 88px) 0 clamp(36px, 6vw, 56px);
  border-bottom: 1px solid var(--border);
}

/* The hex grid behind the hero: this is a territory game on hexes, so the backdrop says so before a
   single word is read. Inline SVG, no dependency, pointer-events off, and low enough contrast that
   it never competes with the text on it. */
.hero-bg {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.5;
  mask-image: linear-gradient(to bottom, #000 0%, #000 55%, transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 55%, transparent 100%);
}
.hero-bg svg { width: 100%; height: 100%; display: block; }

.hero .wrap { position: relative; z-index: 1; }

.hero h1 { max-width: 16ch; }

/* ── the three doors ──────────────────────────────────────────────────────── */
.doors {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  margin: clamp(28px, 4vw, 40px) 0 0;
  padding: 0;
  list-style: none;
}

.door {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 18px 16px 16px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--text-primary);
  transition: border-color 0.15s ease, background 0.15s ease, transform 0.15s ease;
}
.door:hover,
.door:focus-visible {
  border-color: var(--accent-dim);
  background: var(--bg-elevated);
  transform: translateY(-2px);
  color: var(--text-primary);
}

.door-label {
  font-family: var(--font-mono);
  font-size: 0.95rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.door:hover .door-label { color: var(--accent); }

.door-desc {
  font-size: 0.9rem;
  color: var(--text-secondary);
  line-height: 1.5;
}

.door-arrow {
  font-family: var(--font-mono);
  color: var(--accent);
  margin-top: auto;
  padding-top: 10px;
  font-size: 0.85rem;
  letter-spacing: 0.08em;
}

/* The primary door reads as primary. One of three, not three of three — meshmapper's three equal
   buttons make you choose; a visitor who does not know the game yet should be nudged to the map,
   because the map is the thing that explains it fastest. */
.door.lead { border-color: var(--accent-dim); background: var(--bg-elevated); }
.door.lead .door-label { color: var(--accent); }

/* ── the fact strip ───────────────────────────────────────────────────────── */
.strip {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 24px;
  margin: 26px 0 0;
  padding: 14px 0 0;
  border-top: 1px solid var(--border);
  list-style: none;
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--text-secondary);
  letter-spacing: 0.02em;
}
.strip li { display: flex; align-items: center; gap: 8px; }
.strip li::before {
  content: "";
  width: 5px;
  height: 5px;
  background: var(--accent-dim);
  transform: rotate(45deg);
  flex: none;
}
.strip strong { color: var(--text-primary); font-weight: 600; }

/* ── buttons ──────────────────────────────────────────────────────────────── */
.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  margin: 1.5rem 0 0;
}

.button {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 18px;
  background: var(--accent);
  color: var(--bg-primary);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.button:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: var(--bg-primary); }
.button[disabled] { opacity: 0.5; cursor: default; }

.button.ghost {
  background: transparent;
  color: var(--text-primary);
  border-color: var(--border-strong);
}
.button.ghost:hover { border-color: var(--accent-dim); color: var(--accent); background: transparent; }

.linkish {
  background: none;
  border: 0;
  padding: 0;
  color: var(--accent);
  font: inherit;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* ── the tier ladder ──────────────────────────────────────────────────────── */
.ladder {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: rung;
}

.rung {
  position: relative;
  padding: 14px 12px 12px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  /* The ladder IS a progression, so the left edge thickens with the tier — the same language the map
     uses, where a thicker border means a higher tier (see map.js / hexLayer.js). */
  border-left-width: 2px;
}
.rung:nth-child(1) { border-left-color: #555a5e; }
.rung:nth-child(2) { border-left-color: #6b7250; }
.rung:nth-child(3) { border-left-color: var(--accent-dim); }
.rung:nth-child(4) { border-left-color: var(--accent-dim); border-left-width: 3px; }
.rung:nth-child(5) { border-left-color: var(--accent); border-left-width: 4px; }

.rung-name {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-primary);
  margin: 0 0 4px;
}
.rung p { font-size: 0.85rem; color: var(--text-secondary); line-height: 1.45; margin: 0; }

/* ── cards ────────────────────────────────────────────────────────────────── */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 12px;
}

.card {
  padding: 18px 16px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.card p { font-size: 0.95rem; color: var(--text-secondary); margin: 0; }
.card p + p { margin-top: 0.75rem; }

/* ── facts (sub-pages) ────────────────────────────────────────────────────── */
.facts { list-style: none; padding: 0; margin: 0 0 1rem; }
.facts li {
  padding: 12px 0 12px 18px;
  border-top: 1px solid var(--border);
  position: relative;
  color: var(--text-secondary);
  max-width: 72ch;
}
.facts li:last-child { border-bottom: 1px solid var(--border); }
.facts li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 19px;
  width: 5px;
  height: 5px;
  background: var(--accent-dim);
  transform: rotate(45deg);
}
.facts strong { color: var(--text-primary); }

/* ── steps (how it works) ─────────────────────────────────────────────────── */
.steps { counter-reset: step; list-style: none; padding: 0; margin: 0 0 1.5rem; }
.steps li {
  counter-increment: step;
  position: relative;
  padding: 14px 0 14px 46px;
  border-top: 1px solid var(--border);
  color: var(--text-secondary);
  max-width: 72ch;
}
.steps li:last-child { border-bottom: 1px solid var(--border); }
.steps li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 13px;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--accent);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
}
.steps strong { color: var(--text-primary); }

/* ── FAQ ──────────────────────────────────────────────────────────────────── */
details {
  border-top: 1px solid var(--border);
  padding: 0;
}
details:last-of-type { border-bottom: 1px solid var(--border); }
details summary {
  cursor: pointer;
  padding: 14px 0;
  font-family: var(--font-mono);
  font-size: 0.95rem;
  color: var(--text-primary);
  list-style: none;
  display: flex;
  align-items: center;
  gap: 10px;
}
details summary::-webkit-details-marker { display: none; }
details summary::before {
  content: "+";
  color: var(--accent);
  font-weight: 700;
  width: 1ch;
  flex: none;
}
details[open] summary::before { content: "−"; }
details p {
  color: var(--text-secondary);
  padding: 0 0 14px 2ch;
  max-width: 72ch;
  margin: 0;
}

/* ── the map slot ─────────────────────────────────────────────────────────── */
.map-slot {
  height: clamp(340px, 62vh, 620px);
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  display: grid;
  place-items: center;
  overflow: hidden;
}
.map-placeholder {
  color: var(--text-secondary);
  text-align: center;
  max-width: 44ch;
  padding: 20px;
  font-size: 0.95rem;
}
.map-placeholder strong { display: block; color: var(--text-primary); margin-bottom: 6px; }

.map-status {
  margin: 10px 0 0;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-secondary);
}

.legend {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 18px;
  margin: 12px 0 0;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-secondary);
}

/* ⚠️ REQUIRED, not decoration. coverage.html ships the legend with the `hidden` attribute until the map
   has data, and an author `display:` declaration BEATS the UA sheet's `[hidden] { display: none }` — so
   without this rule the attribute is a silent no-op and the page shows an empty legend box before the
   map loads. The app was bitten by exactly this (`.auth-overlay[hidden]`, or the login overlay never
   hid). site-structure.mjs asserts it, and caught its absence here on 2026-09-29. */
.legend[hidden] { display: none; }

/* Leaflet lives inside .map-slot; it must fill it. */
.map-slot .leaflet-container {
  width: 100%;
  height: 100%;
  background: var(--bg-primary);
}

/* The still of the map on the landing page. */
.map-preview {
  display: block;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  line-height: 0;
  position: relative;
}
.map-preview img { width: 100%; height: auto; display: block; }
.map-preview:hover { border-color: var(--accent-dim); }
.map-preview::after {
  content: "OPEN THE LIVE MAP →";
  position: absolute;
  right: 10px;
  bottom: 10px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  color: var(--bg-primary);
  background: var(--accent);
  padding: 6px 10px;
  border-radius: var(--radius);
  line-height: 1;
}

/* ── the form ─────────────────────────────────────────────────────────────── */
form { max-width: 34rem; }

.field { margin: 0 0 1rem; }
/* legal.html's contact form shows its node-id field only for a node_removal request, via the `hidden`
   attribute (contact.js). Pinned by site-contact-form.mjs so a future `display:` on `.field` cannot
   silently turn that attribute into a no-op and leak a stale node id into another kind of request. */
.field[hidden] { display: none; }
.field label {
  display: block;
  font-family: var(--font-mono);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-secondary);
  margin: 0 0 6px;
}
.field .opt { text-transform: none; letter-spacing: 0; color: var(--text-dim); }

input[type="text"],
input[type="email"],
input[type="password"],
select,
textarea {
  width: 100%;
  padding: 9px 11px;
  background: var(--bg-primary);
  color: var(--text-primary);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  font: 15px/1.5 var(--font-ui);
}
input:focus, select:focus, textarea:focus { border-color: var(--accent); outline: none; }
textarea { resize: vertical; }

/* Kept as `.field .hint`, not `.hint`: site-palette.mjs holds a registry of every selector that carries
   prose a visitor READS, and asserts each one still exists so a rename cannot make its --text-dim check
   silently skip. Renaming this is allowed — but then update that list too. */
.field .hint { display: block; margin-top: 5px; font-size: 0.85rem; color: var(--text-secondary); }

.check {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  margin: 1.25rem 0;
  font-size: 0.92rem;
  color: var(--text-secondary);
}
.check input { margin-top: 3px; flex: none; }
.check strong { color: var(--text-primary); }

.notice {
  padding: 14px 16px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-left: 2px solid var(--accent-dim);
  border-radius: var(--radius);
  color: var(--text-secondary);
  font-size: 0.95rem;
  max-width: 60ch;
}
.notice strong { color: var(--text-primary); }

.honeypot { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* The result node carries every outcome — "Sending…", the receipt and four failures — so it is
   painted by tone. A rejection must not look like a receipt (Claude's review of 6417802). */
.form-result { margin: 1rem 0 0; font-size: 0.95rem; color: var(--text-secondary); }
.form-result[data-tone="error"] { color: var(--danger); }
.form-result[data-tone="ok"] { color: var(--accent); }
.form-result .button { margin-top: 10px; }

/* ── footer ───────────────────────────────────────────────────────────────── */
.foot {
  border-top: 1px solid var(--border);
  padding: 28px 0 40px;
  color: var(--text-secondary);
  font-size: 0.9rem;
}
.foot .wrap { display: flex; flex-wrap: wrap; gap: 12px 28px; align-items: baseline; }
.foot p { margin: 0; max-width: 46ch; }
.foot nav { display: flex; flex-wrap: wrap; gap: 14px; margin-left: auto; font-family: var(--font-mono); font-size: 12px; }
.foot nav a { color: var(--text-secondary); text-decoration: none; }
.foot nav a:hover { color: var(--accent); }

/* ── responsive ───────────────────────────────────────────────────────────── */
@media (max-width: 860px) {
  .doors { grid-template-columns: 1fr; }
  .ladder { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 620px) {
  .nav { padding: 6px var(--gutter); }
  .nav-inner { flex-wrap: wrap; gap: 6px 10px; }
  .nav nav { margin-left: 0; width: 100%; gap: 0; }
  .nav nav a { padding: 6px 8px; font-size: 12px; }
  .nav nav a.cta { margin-left: auto; }
  .ladder { grid-template-columns: 1fr; }
  .foot nav { margin-left: 0; }
}

/* ── the hero's hex grid follows the theme ────────────────────────────────── */
/* Presentation attributes (stroke="#2a302a") cannot take a var(), so the colours were literals and
   the grid stayed dark-on-light in light mode. CSS properties CAN take one, so the markup carries
   only geometry and opacity now, and the sheet paints it. */
.hero-bg .grid { stroke: var(--border); }
.hero-bg .cells { stroke: var(--accent-dim); fill: var(--accent); }
.hero-bg .cells .idle { stroke: var(--border-strong); fill: var(--text-dim); }

/* ── the legend's own pieces, and Leaflet's chrome ────────────────────────────
   ⚠️ RESTORED 2026-09-29. The stylesheet rewrite dropped these, and nothing caught it because they
   are never written in the HTML: map.js INJECTS the legend markup at runtime, and Leaflet injects its
   own. The result on the live site was a legend whose colour swatches were zero-size inline elements
   (invisible) and default white tooltips on a dark map. Recovered from 1cef642's site.css and made
   theme-aware. site-structure.mjs now asserts that every class the site JS injects has a rule here. */
.legend .lg { display: inline-flex; align-items: center; gap: 0.35rem; }
.legend .lg i {
  width: 12px;
  height: 12px;
  border-radius: 3px;
  display: inline-block;
  flex: none;
}
/* A heard node is a dot, a hex tier is a square: the legend uses the same shapes the map does. */
.legend .lg i.dot { border-radius: 50%; }
.legend .lg.muted { opacity: 0.75; }

/* Leaflet's own controls, in the app's values. The literals these replace were dark rgba() and stayed
   dark over the light basemap, so they are tokens now. */
.leaflet-container { font: inherit; font-family: var(--font-mono); }
.leaflet-tooltip {
  background: var(--nav-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: 11px;
  padding: 2px 6px;
}
.leaflet-control-zoom a,
.leaflet-control-attribution {
  background: var(--nav-bg) !important;
  border-color: var(--border) !important;
  color: var(--text-secondary) !important;
}
.leaflet-control-attribution a { color: var(--text-secondary) !important; }
.leaflet-control-zoom a:hover { color: var(--text-primary) !important; }
.leaflet-control-zoom a:focus,
.leaflet-control-zoom a:active {
  background: var(--accent-dim) !important;
  border-color: var(--accent) !important;
  color: var(--bg-primary) !important;
  outline: none !important;
}

/* ── the coverage map's controls and its tap-a-hex popup (2026-10-04) ─────────────────────────────
   Filip: "add a visible toggle, to switch between the territory and coverage". The view switch (Coverage |
   Territory) and the node-dots switch sit on one row above the map; on a phone they stack. */
.map-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: stretch;
  margin: 0 0 10px;
}
.map-controls .map-views { flex: 1 1 18rem; margin: 0; }
.map-nodes-toggle {
  flex: 0 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 9px 14px;
  background: var(--bg-surface);
  color: var(--text-secondary);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
}
/* ⚠️ Required: the button ships `hidden` and the display above would otherwise beat the attribute. */
.map-nodes-toggle[hidden] { display: none; }
.map-nodes-toggle:hover { color: var(--text-primary); background: var(--bg-elevated); }
.map-nodes-toggle[aria-pressed="true"] { color: var(--text-primary); border-color: var(--border-strong); }
.map-nodes-toggle[aria-pressed="false"] .sw-pair { opacity: 0.3; }
.map-nodes-toggle .sw { width: 9px; height: 9px; border-radius: 50%; flex: none; display: inline-block; }
.map-nodes-toggle .sw-pair { display: inline-flex; gap: 3px; }
.map-views button { font-size: 13px; }

/* The legend's coverage ladder: four swatches per network, strongest first. */
.legend .lg-band { gap: 3px; }
.legend .lg-band b { font-weight: 700; color: var(--text-primary); margin-right: 4px; }
.legend .lg-band i { width: 14px; height: 12px; border-radius: 2px; }
.legend .lg i.split { border-radius: 2px; }
.legend .lg i.dashed { background: transparent; border: 1.5px dashed; }

/* The popup: Leaflet's own white box restyled in the page's tokens, so it follows the theme. */
.mt-popup .leaflet-popup-content-wrapper {
  background: var(--bg-surface);
  color: var(--text-primary);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.35);
}
.mt-popup .leaflet-popup-tip { background: var(--bg-surface); border: 1px solid var(--border-strong); }
.mt-popup .leaflet-popup-content { margin: 12px 14px; font-family: var(--font-mono); font-size: 12px; line-height: 1.45; }
.mt-popup a.leaflet-popup-close-button { color: var(--text-secondary); }
.pop { min-width: 0; }
.pop-h { font-weight: 700; font-size: 13px; margin: 0 22px 8px 0; color: var(--text-primary); }
.pop-sec { margin: 0 0 8px; padding: 0 0 8px; border-bottom: 1px solid var(--border); }
.pop-sec:last-child { border-bottom: 0; }
.pop-net { margin: 2px 0; }
.pop-rptr b { color: var(--text-primary); }
.pop-sub { color: var(--text-secondary); margin: 0 0 4px; }
.pop-list { list-style: none; margin: 0; padding: 0; max-height: 14rem; overflow-y: auto; }
.pop-row { display: flex; align-items: baseline; gap: 6px; padding: 2px 0; }
.pop-name { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.pop-more summary { cursor: pointer; color: var(--accent); margin: 4px 0; }
.pop-foot { font-size: 11px; }
.pop .rssi { flex: none; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.pop .dim { color: var(--text-secondary); }
.pop .net {
  flex: none;
  display: inline-block;
  min-width: 2.2em;
  padding: 0 4px;
  border-radius: 2px;
  font-size: 10px;
  font-weight: 700;
  text-align: center;
  color: #0d0f0d;
  background: var(--border-strong);
}
.pop .net-mt { background: #5fd768; }
.pop .net-mc { background: #4a90e2; }
.pop .rptr-tag {
  display: inline-block;
  padding: 0 4px;
  border: 1px solid var(--accent-dim);
  border-radius: 2px;
  color: var(--accent);
  font-size: 10px;
  font-weight: 700;
}

/* An empty result node should take no space; it carries every outcome of the form. */
.form-result:empty { display: none; }
