/*
  idahomesh.com redesign — "Topographic"
  ---------------------------------------
  One stylesheet for the whole site (home page now, the other ~10 pages
  later). Read top to bottom:

    1. Design tokens (colors, spacing, radii, fonts)
    2. Reset / base element styles
    3. Layout helpers (container, section, grid)
    4. Topographic motif (contour texture, ridge dividers, section marks)
    5. Header / nav
    6. Hero (full-bleed live map + headline)
    7. Live data bands (stats, chat)
    8. Card components (protocols, groups, MeshWars, steps)
    9. Footer
    10. Small utilities
    11. Interior page header (lighter hero for content pages)
    12. Prose / long-form content
    13. Callouts (note / warning asides)
    14. Resource & link lists (tools, hardware, community recs)
    15. Logo link cards (additional-links style logo grids)
    16. Glossary / definitions (terminology page)
    17. Procedure (vertical numbered steps, distinct from §8's .steps grid)
    18. Code / config snippet blocks
    19. Tables
    20. Embeds (full-bleed / card maps and iframes)
    21. In-page anchor nav & back-to-top
    22. Responsive overrides — original hero/nav rules (mobile-first
        exceptions live near what they override, but the big breakpoint
        rules are collected at the end)
    23. Responsive overrides — shared components added in §11-21

  No build step. Every class name says what it's for — grep this file
  before adding a new page's markup, most of what you need already exists.
*/

/* ---------- 1. Design tokens ---------------------------------------- */

:root {
  /* Base surface colors. --bg is the page; --bg-panel/--bg-elevated step
     up in lightness for things that sit "on top of" the page (cards,
     dropdowns, the header). Values match the deployed site's own dark
     palette (site.webmanifest theme_color) rather than inventing a new one. */
  --bg: #0a0a0f;
  --bg-elevated: #101319;
  --bg-panel: #16181c;
  --border: rgba(255, 255, 255, 0.10);
  --border-strong: rgba(255, 255, 255, 0.18);

  /* Text */
  --text: #eef1f4;
  --text-muted: #93a2b4;
  --text-dim: #6b7688;

  /* Accent — swapped by the Meshtastic/MeshCore toggle in js/nav.js.
     Defaults to Meshtastic green. --accent-ink is the text color used
     ON TOP of an accent-filled surface (buttons, the active toggle
     pill): both accents are light/mid-brightness, so dark ink reads far
     better on them than white does — the old site used white-on-green
     for its primary button, which fails contrast; this fixes that. */
  --accent: #67ea94;
  --accent-hover: #8ff2b3;
  --accent-ink: #06170d;
}

[data-theme="meshcore"] {
  --accent: #3b82f6;
  --accent-hover: #60a5fa;
  --accent-ink: #08101f;
}

/* ---------- 2. Reset / base ------------------------------------------ */

* {
  box-sizing: border-box;
}

html {
  /* Smooth scroll for the in-page nav anchors (#protocols, #groups, #start). */
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  * {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

body {
  margin: 0;
  /* System font stack only — no web fonts, nothing to download, nothing
     to flash-of-unstyled-text. Matches the original site's choice. */
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  background: var(--bg);
  color: var(--text);
  line-height: 1.5;
  /* The page itself must never scroll sideways — wide children (tables,
     code, the map) get their own overflow-x instead. See .scroll-x. */
  overflow-x: hidden;
}

h1, h2, h3, h4 {
  margin: 0;
  font-weight: 800;
  letter-spacing: -0.01em;
  line-height: 1.1;
}

p {
  margin: 0;
}

a {
  color: var(--accent);
  text-decoration-color: rgba(255, 255, 255, 0.25);
}

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

img {
  max-width: 100%;
  display: block;
}

button {
  font: inherit;
  color: inherit;
}

/* One focus style, used everywhere, so keyboard users always get it. */
a:focus-visible,
button:focus-visible,
input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Visually hidden but still reachable by screen readers / keyboard —
   used for the skip link's label and the theme toggle's group text. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  top: -100%;
  left: 12px;
  z-index: 1000;
  background: var(--accent);
  color: var(--accent-ink);
  padding: 10px 16px;
  border-radius: 8px;
  font-weight: 700;
  text-decoration: none;
}

.skip-link:focus {
  top: 12px;
}

/* Eyebrow label — the small caps monospace tag above most headings
   ("THE PROTOCOLS", "GET ON THE MESH", etc). Monospace signals "system
   status readout", which fits a site about radios and live data. */
.eyebrow {
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0 0 0.75rem;
}

/* ---------- 3. Layout helpers ----------------------------------------- */

.container {
  max-width: 1180px;
  margin-inline: auto;
  padding-inline: 20px;
}

.section {
  padding-block: clamp(3rem, 6vw, 5.5rem);
  position: relative;
}

/* A container that scrolls its OWN overflow horizontally, rather than
   letting the page scroll sideways. Used for anything wide: the live
   chat feeds on narrow screens, and (on future pages) tables/code. */
.scroll-x {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1.25rem;
}

/* ---------- 4. Topographic motif -------------------------------------- */

/* Faint contour-line texture, tiled behind a section. Kept to a whisper
   (opacity) so it reads as "terrain" out of the corner of your eye, not
   as a busy pattern fighting the copy. */
.topo-texture {
  position: relative;
}

.topo-texture::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url("../img/contour-texture.svg");
  background-size: 400px 400px;
  opacity: 0.05;
  pointer-events: none;
}

/* Ridgeline divider between sections. One SVG silhouette, recolored per
   use with mask-image + background-color instead of keeping a colored
   copy of the file for every accent — see img/ridge-divider.svg. */
.ridge {
  height: 48px;
  background-color: var(--border-strong);
  mask-image: url("../img/ridge-divider.svg");
  -webkit-mask-image: url("../img/ridge-divider.svg");
  mask-size: 100% 100%;
  -webkit-mask-size: 100% 100%;
  mask-repeat: no-repeat;
  -webkit-mask-repeat: no-repeat;
}

.ridge-flip {
  transform: scaleY(-1);
}

/* Section mark — the small angular glyph that stands in for a bullet
   next to every eyebrow, instead of plain text. Defined once as an SVG
   <symbol> in index.html and reused everywhere with <use>, so the glyph
   only has to be drawn once. */
.section-mark {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

.section-mark svg {
  width: 14px;
  height: 14px;
  color: var(--accent);
  flex: none;
}

/* ---------- 5. Header / nav -------------------------------------------- */

/* Deliberately NOT position:fixed. An overlaid sticky header sounds
   nice, but it forces every full-bleed section under it (the hero map,
   in particular) to manage z-index against a fixed sibling — one such
   fight already happened here: Leaflet's internal panes go up to
   z-index 700, which otherwise escapes past the header. Keeping the
   header in normal flow, sitting above the hero rather than over it,
   avoids that whole class of bug for one row of header height. */
.site-header {
  position: relative;
  z-index: 100;
  background: var(--bg-panel);
  border-bottom: 1px solid var(--border);
}

.header-inner {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  padding-block: 0.7rem;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  font-weight: 800;
  font-size: 1rem;
  color: var(--text);
  text-decoration: none;
  margin-right: auto;
}

.brand:hover {
  color: var(--text);
}

.brand-mark {
  border-radius: 6px;
}

/* Hamburger toggle — only shown under the mobile breakpoint (see §11). */
.nav-toggle {
  display: none;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px;
  cursor: pointer;
}

.nav-toggle svg {
  width: 20px;
  height: 20px;
  display: block;
}

.nav {
  display: flex;
  align-items: center;
  gap: 1.5rem;
}

.nav-list {
  list-style: none;
  display: flex;
  align-items: center;
  gap: 0.25rem;
  margin: 0;
  padding: 0;
}

.nav-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 0.55rem 0.7rem;
  border-radius: 8px;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--text-muted);
  text-decoration: none;
  background: transparent;
  border: 0;
  cursor: pointer;
}

.nav-link:hover,
.nav-link[aria-expanded="true"] {
  color: var(--text);
  background: rgba(255, 255, 255, 0.06);
}

.nav-link-play {
  color: var(--accent);
}

.nav-link-play:hover {
  color: var(--accent-hover);
}

.nav-dd-caret {
  width: 9px;
  height: 9px;
  transition: transform 0.15s ease;
}

.nav-dropdown[data-open="true"] .nav-dd-caret {
  transform: rotate(180deg);
}

.nav-dropdown {
  position: relative;
}

.nav-dd-menu {
  list-style: none;
  margin: 0;
  padding: 0.4rem;
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  min-width: 220px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.45);
  display: none;
}

.nav-dropdown[data-open="true"] .nav-dd-menu {
  display: block;
}

.nav-dd-link {
  display: block;
  padding: 0.55rem 0.7rem;
  border-radius: 8px;
  font-size: 0.88rem;
  font-weight: 500;
  color: var(--text-muted);
  text-decoration: none;
}

.nav-dd-link:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.06);
}

/* Meshtastic / MeshCore accent toggle. Cosmetic only — it recolors
   accents sitewide via the --accent custom property, it does not filter
   any content. See js/nav.js for the localStorage read/write. */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  background-color: #0d131c;
  border: 1px solid #25313f;
  border-radius: 999px;
  padding: 3px;
  gap: 2px;
}

.theme-toggle-btn {
  appearance: none;
  border: 0;
  background-color: transparent;
  color: #93a2b4;
  font-family: ui-monospace, monospace;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  padding: 6px 12px;
  border-radius: 999px;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.theme-toggle-btn[aria-pressed="true"] {
  background-color: var(--accent);
  color: var(--accent-ink);
}

.theme-toggle-btn:hover:not([aria-pressed="true"]) {
  color: #eaf0f6;
}

/* ---------- 6. Hero ----------------------------------------------------- */

.hero {
  position: relative;
  min-height: 86vh;
  min-height: 86svh;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
}

/* The live Leaflet map, full-bleed behind everything in the hero. Given
   a plain background color up front so there's no flash of white while
   the tiles + JS load. */
.hero-map {
  position: absolute;
  inset: 0;
  background: #0c1210;
  /* Leaflet gives its internal panes their own z-indexes up to 700 (for
     popups). Without this, those panes have no local stacking context to
     be contained by, so they compare directly against the rest of the
     page and paint OVER the fixed header (z-index: 100) even though the
     header is a later, higher-stacked sibling. `isolation: isolate`
     gives .hero-map its own stacking context so Leaflet's internal
     z-indexes stay trapped inside it. */
  isolation: isolate;
}

/* Dark scrim so the overlaid headline stays readable regardless of what
   the map looks like underneath (bright tiles, a dense node cluster,
   etc). Strongest at the bottom, where the text sits. */
.hero-scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    rgba(6, 7, 10, 0.94) 0%,
    rgba(6, 7, 10, 0.8) 32%,
    rgba(6, 7, 10, 0.45) 60%,
    rgba(6, 7, 10, 0.4) 100%
  );
  pointer-events: none;
}

.hero-content {
  position: relative;
  padding-block: 3rem 3.5rem;
  max-width: 640px;
}

/* The eyebrow sits directly over live map tiles/markers rather than a
   flat panel background like every other .eyebrow on the page, so the
   default muted gray (tuned for reading on --bg-panel) doesn't have
   enough contrast here. Brighten it and give it its own shadow rather
   than relying on .hero-scrim alone, which is weakest in the middle of
   the hero where this line tends to land. */
.hero-content .eyebrow {
  color: var(--text);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85), 0 1px 14px rgba(0, 0, 0, 0.6);
}

.hero h1,
.hero-body {
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.55);
}

.hero h1 {
  font-size: clamp(2rem, 5vw, 3.1rem);
  line-height: 1.05;
  margin-bottom: 0.9rem;
}

.hero-body {
  font-size: 1.05rem;
  line-height: 1.6;
  color: #cfd6dd;
  max-width: 46ch;
  margin-bottom: 1.5rem;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-weight: 700;
  font-size: 0.95rem;
  text-decoration: none;
  padding: 0.8rem 1.4rem;
  border-radius: 11px;
  border: 1px solid transparent;
  cursor: pointer;
}

.btn-primary {
  background: var(--accent);
  color: var(--accent-ink);
}

.btn-primary:hover {
  background: var(--accent-hover);
  color: var(--accent-ink);
}

.btn-outline {
  color: var(--text);
  border-color: var(--border-strong);
  background: rgba(255, 255, 255, 0.04);
}

.btn-outline:hover {
  color: var(--text);
  border-color: var(--accent);
}

/* Small "LIVE" badge in the hero's corner, mirroring the one on the old
   /livemap page. Updated by js/map.js once node data has loaded. */
.hero-live-badge {
  position: absolute;
  top: 1rem;
  right: 1rem;
  z-index: 5;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: rgba(10, 10, 10, 0.82);
  color: var(--text);
  font: 600 0.8rem/1 system-ui, sans-serif;
  padding: 8px 12px;
  border-radius: 8px;
  border: 1px solid var(--border);
}

.pulse-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 8px var(--accent);
  animation: pulse 1.8s ease-in-out infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

/* Leaflet controls, popups and attribution redrawn to match the dark
   theme (Leaflet's defaults are a light-mode gray box). */
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
  background: var(--bg-panel);
  color: var(--text);
  border: 1px solid var(--border);
}

.leaflet-popup-content {
  font: 13px/1.45 system-ui, sans-serif;
  margin: 10px 12px;
}

.leaflet-control-layers,
.leaflet-control-attribution {
  background: rgba(16, 18, 20, 0.92) !important;
  color: var(--text-muted) !important;
  border: 1px solid var(--border) !important;
}

.leaflet-control-attribution a {
  color: var(--text-muted) !important;
}

.leaflet-control-layers label {
  font: 13px/1.4 system-ui, sans-serif;
}

.leaflet-bar a {
  background: var(--bg-panel);
  color: var(--text);
  border-color: var(--border);
}

.map-pop-name {
  font-weight: 700;
  font-size: 14px;
  margin-bottom: 3px;
}

.map-pop-net {
  font-weight: 700;
}

.map-pop-net.mt { color: #67ea94; }
.map-pop-net.mc { color: #3b82f6; }

.map-pop-meta {
  color: var(--text-muted);
  font-size: 12px;
}

/* ---------- 7. Live data bands ------------------------------------------ */

.stats-band {
  border-bottom: 1px solid var(--border);
}

.stats-counts {
  display: flex;
  flex-wrap: wrap;
  gap: 2rem;
  padding-block: 2rem;
}

.stat {
  min-width: 140px;
}

.stat-num {
  display: block;
  font-size: clamp(1.8rem, 4vw, 2.6rem);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.stat-num-mt { color: #67ea94; }
.stat-num-mc { color: #3b82f6; }

.stat-label {
  display: flex;
  flex-direction: column;
  margin-top: 0.35rem;
  font-size: 0.9rem;
  color: var(--text-muted);
}

.stat-label-scope {
  font-size: 0.78rem;
  color: var(--text-dim);
}

.chat-band {
  border-bottom: 1px solid var(--border);
}

.chat-feeds {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.25rem;
  padding-bottom: 2.5rem;
}

.chat-feed {
  position: relative;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 14px;
  overflow: hidden;
}

/* When a feed has more messages than fit in .chat-list's max-height,
   js/live.js adds this class so the cut-off reads as "scroll for
   more" rather than a message truncated mid-sentence. Sits above the
   scrollable list, ignores pointer events so it never blocks the
   scroll it's advertising. */
.chat-feed.has-overflow::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 40px;
  background: linear-gradient(to bottom, rgba(22, 24, 28, 0), var(--bg-panel) 88%);
  pointer-events: none;
}

.chat-feed-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 0.9rem 1rem;
  font-weight: 700;
  border-bottom: 1px solid var(--border);
}

.chat-feed-head-mt { color: #67ea94; }
.chat-feed-head-mc { color: #3b82f6; }

.chat-feed-scope {
  font-weight: 500;
  font-size: 0.78rem;
  color: var(--text-dim);
}

.chat-list {
  max-height: 320px;
  overflow-y: auto;
  padding: 0.25rem 0;
}

.chat-empty {
  padding: 1rem;
  color: var(--text-dim);
  font-size: 0.9rem;
}

.chat-row {
  padding: 0.6rem 1rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}

.chat-row:last-child {
  border-bottom: 0;
}

.chat-row-top {
  display: flex;
  justify-content: space-between;
  gap: 0.75rem;
  font-size: 0.8rem;
  margin-bottom: 0.15rem;
}

.chat-sender {
  font-weight: 700;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-time {
  color: var(--text-dim);
  flex: none;
}

.chat-text {
  font-size: 0.88rem;
  color: var(--text-muted);
  overflow-wrap: anywhere;
}

/* ---------- 8. Cards ----------------------------------------------------- */

.card {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 1.5rem;
}

.card-tag {
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: 0.6rem;
}

.card-title {
  font-size: 1.1rem;
  margin-bottom: 0.6rem;
}

.card-title-link {
  color: var(--text);
  text-decoration: none;
}

.card-title-link:hover {
  color: var(--accent);
}

.card-text {
  color: var(--text-muted);
  font-size: 0.92rem;
  line-height: 1.55;
}

.lede {
  color: var(--text-muted);
  font-size: 1.02rem;
  max-width: 60ch;
  margin: 0.5rem 0 2rem;
}

/* Group cards ("Find your group") */

.group-card {
  display: flex;
  flex-direction: column;
}

.group-dom {
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 0.72rem;
  color: var(--text-dim);
  margin-bottom: 0.5rem;
}

.group-tag {
  display: inline-block;
  align-self: flex-start;
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--accent);
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.2rem 0.6rem;
  margin-bottom: 0.7rem;
}

.group-card .card-text {
  flex: 1;
  margin-bottom: 1.1rem;
}

/* Sub-groupings within "Find your group" (State vs Regional). Brighter
   and bolder than the section's own muted .eyebrow so the two tiers
   read as a clear hierarchy under it, not as more of the same label;
   reuses the same peak glyph via .section-mark. */
.group-tier-label {
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--text);
  margin: 0 0 1rem;
}

.group-tier + .group-tier {
  margin-top: 2.5rem;
}

/* A tier with a single card (Regional) shouldn't stretch that one card
   across the full grid width the way a 260px auto-fit track would. */
.card-grid-solo {
  grid-template-columns: minmax(260px, 620px);
}

/* Regional feature tier ("Find your group") — the Intermountain Mesh is
   the destination the section drives toward, so it gets real weight
   instead of sitting in the standard .card-grid: full container width,
   a raised panel treatment (same gradient + border-strong recipe as
   .meshwars-panel below), an accent top edge, and a step up in type
   size. Everything else (fonts, colors, link/button styles) still
   comes from .card/.group-card/.btn — this only adds scale. */
.group-tier-feature .group-tier-label {
  font-size: 0.8rem;
  color: var(--accent);
}

.group-card-feature {
  background: linear-gradient(160deg, var(--bg-panel), var(--bg-elevated));
  border: 1px solid var(--border-strong);
  border-top: 3px solid var(--accent);
  border-radius: 20px;
  padding: clamp(1.75rem, 4vw, 2.75rem) clamp(1.5rem, 4vw, 3rem);
}

.group-card-feature .card-title {
  font-size: clamp(1.35rem, 2.4vw, 1.75rem);
  margin-bottom: 0.85rem;
}

.group-card-feature .card-text {
  font-size: 1rem;
  max-width: 68ch;
  margin-bottom: 1.5rem;
}

/* State tier, demoted to a footnote — the site owner wants these found
   by looking, not offered as a peer choice next to Regional. Links stay
   real, in the DOM, keyboard-reachable and at full text contrast
   (--text-muted, not --text-dim, to clear 4.5:1); only the visual
   weight drops: no card, no border, no button, small type, set well
   below the feature card instead of in its own labeled tier. */
.group-footnote {
  margin-top: 2.5rem;
  font-size: 0.85rem;
  color: var(--text-muted);
}

.group-footnote a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--border-strong);
  text-underline-offset: 2px;
}

.group-footnote a:hover,
.group-footnote a:focus-visible {
  color: var(--accent);
}

/* MeshWars callout */

.meshwars-panel {
  background: linear-gradient(160deg, var(--bg-panel), var(--bg-elevated));
  border: 1px solid var(--border-strong);
  border-radius: 20px;
  padding: clamp(1.5rem, 4vw, 3rem);
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  gap: 2.5rem;
  align-items: center;
}

.meshwars-copy .section-title {
  margin-bottom: 0.9rem;
}

.meshwars-copy p {
  color: var(--text-muted);
  line-height: 1.65;
  margin-bottom: 1rem;
}

.meshwars-copy p:last-of-type {
  margin-bottom: 1.5rem;
}

.team-chips {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0 0 1.75rem;
  padding: 0;
}

.team-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--text);
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.3rem 0.7rem 0.3rem 0.5rem;
}

.team-chip-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex: none;
}

.team-red .team-chip-dot { background: #ef4444; }
.team-green .team-chip-dot { background: #22c55e; }
.team-blue .team-chip-dot { background: #3b82f6; }
.team-purple .team-chip-dot { background: #a855f7; }
.team-yellow .team-chip-dot { background: #eab308; }
.team-orange .team-chip-dot { background: #f97316; }
.team-pink .team-chip-dot { background: #ec4899; }

.meshwars-facts {
  list-style: none;
  margin: 0;
  padding: 1.25rem;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid var(--border);
  border-radius: 14px;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.meshwars-facts li {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.meshwars-fact-label {
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.meshwars-fact-value {
  font-size: 0.95rem;
  color: var(--text);
  line-height: 1.4;
}

/* Steps ("Get on the mesh") */

.steps {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.5rem;
}

.step-n {
  display: block;
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 0.85rem;
  color: var(--accent);
  margin-bottom: 0.5rem;
}

.step-title {
  font-size: 1.02rem;
  margin-bottom: 0.4rem;
}

/* ---------- 9. Footer ------------------------------------------------- */

.site-footer {
  border-top: 1px solid var(--border);
  padding-block: 2.5rem;
}

.footer-brand {
  font-weight: 800;
  color: var(--text);
  margin-bottom: 0.4rem;
}

.footer-text {
  color: var(--text-dim);
  font-size: 0.85rem;
}

/* ---------- 10. Small utilities ---------------------------------------- */

.section-title {
  font-size: clamp(1.5rem, 3vw, 2.1rem);
  margin: 0.6rem 0 0.5rem;
}

/* ---------- 11. Interior page header ----------------------------------- */

/* Lighter-weight hero for the nine content pages, sitting where .hero
   sits on the home page but without a full-bleed map underneath —
   these pages need a title + short intro, not a live map background.
   Reuses .eyebrow, .section-mark and .lede rather than inventing new
   text styles, so this reads as the same family as the home page. */
.page-hero {
  padding-block: clamp(2.5rem, 5vw, 4rem) clamp(1.75rem, 4vw, 3rem);
  border-bottom: 1px solid var(--border);
}

.page-title {
  font-size: clamp(2rem, 4.5vw, 2.75rem);
  margin: 0.4rem 0 0.25rem;
}

/* When a page-hero's lede is the only intro copy (no further prose
   below it before the next section), drop the .lede's default bottom
   margin so the border-bottom above sits close instead of leaving a
   big gap. */
.page-hero .lede:last-child {
  margin-bottom: 0;
}

/* ---------- 12. Prose / long-form content -------------------------------- */

/* Long-form body copy (Connect & Contribute, Radio Config framing
   text, etc). Caps line length to a comfortable measure regardless of
   how wide .container is, and gives h2/h3 their own rhythm instead of
   reusing .section-title (which is sized for one big heading per
   section, not a stack of subheadings inside one). */
.prose {
  max-width: 72ch;
}

.prose h2 {
  font-size: clamp(1.3rem, 2.4vw, 1.6rem);
  color: var(--text);
  margin: 2.5rem 0 0.75rem;
}

.prose h2:first-child {
  margin-top: 0;
}

.prose h3 {
  font-size: 1.05rem;
  color: var(--text);
  margin: 1.75rem 0 0.5rem;
}

.prose p {
  color: var(--text-muted);
  line-height: 1.7;
  margin: 0 0 1rem;
}

.prose ul,
.prose ol {
  color: var(--text-muted);
  line-height: 1.7;
  margin: 0 0 1rem;
  padding-left: 1.4rem;
}

.prose li {
  margin-bottom: 0.4rem;
}

.prose li:last-child {
  margin-bottom: 0;
}

.prose a {
  font-weight: 600;
}

.prose strong {
  color: var(--text);
}

/* ---------- 13. Callouts ------------------------------------------------- */

/* Note / warning asides — the radio configurator copy has a few of
   these ("Guest login always stays open, so a non-blank admin
   password is required"). Left-border color plus a matching small
   icon carries the distinction; no color-only signal (the label text
   itself also says what kind of aside it is). */
.callout {
  display: flex;
  gap: 0.75rem;
  align-items: flex-start;
  padding: 1rem 1.25rem;
  margin: 1.5rem 0;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: 12px;
  font-size: 0.92rem;
  line-height: 1.6;
  color: var(--text-muted);
}

.callout-icon {
  flex: none;
  width: 18px;
  height: 18px;
  margin-top: 0.15rem;
  color: var(--accent);
}

.callout-title {
  display: block;
  font-weight: 700;
  color: var(--text);
  margin-bottom: 0.2rem;
}

.callout-warning {
  border-left-color: #eab308;
}

.callout-warning .callout-icon {
  color: #eab308;
}

/* ---------- 14. Resource & link lists ------------------------------------ */

/* One list pattern for every "name + optional note + outbound link"
   list on the site: Tools/Links' grouped tool lists, and each
   hardware category on Community Recommendations (used inside a
   .card from §8, so a category becomes a card with a .card-title and
   one of these lists — no new card variant needed). */
.link-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin: 0 0 1.5rem;
  padding: 0;
}

.link-list:last-child {
  margin-bottom: 0;
}

.link-item {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.3rem 0.5rem;
  padding: 0.6rem 0.85rem;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 10px;
}

.link-item-name {
  font-weight: 700;
  color: var(--text);
  text-decoration: none;
}

.link-item-name:hover {
  color: var(--accent);
}

.link-item-note {
  color: var(--text-dim);
  font-size: 0.85rem;
}

/* ---------- 15. Logo link cards ------------------------------------------ */

/* Additional Links' "where to buy" / "documentation" sections are
   image-only cards where the vendor's own logo is the whole link.
   Grid + card are separate from §8's .card-grid/.card because the
   whole tile is the <a>, and content is centered art rather than a
   card-tag/title/body stack. */
.logo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 1.25rem;
  margin: 0 0 2rem;
}

.logo-card {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 96px;
  padding: 1.5rem;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 14px;
  text-decoration: none;
  transition: border-color 0.15s ease;
}

.logo-card:hover {
  border-color: var(--accent);
}

.logo-card img {
  max-height: 48px;
  width: auto;
}

/* ---------- 16. Glossary / definitions ------------------------------------ */

/* Terminology's term/definition pairs. A <dl> for real semantics
   (screen readers announce term/definition pairs), styled as a
   two-column row rather than the browser's default stacked layout. */
.glossary {
  display: grid;
  gap: 0.6rem;
  margin: 0 0 2rem;
}

.glossary-item {
  display: grid;
  grid-template-columns: 84px 1fr;
  gap: 1rem;
  align-items: baseline;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--border);
}

.glossary-item:last-child {
  border-bottom: 0;
}

.glossary-term {
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-weight: 700;
  font-size: 0.85rem;
  color: var(--accent);
  margin: 0;
}

.glossary-def {
  font-size: 0.92rem;
  line-height: 1.5;
  color: var(--text-muted);
  margin: 0;
}

/* ---------- 17. Procedure (vertical numbered steps) ----------------------- */

/* §8 already has .steps — a 4-across grid of short entries for the
   home page's "Get on the mesh". This is the long-form counterpart:
   a vertical numbered sequence with room for real paragraphs under
   each step (Connect & Contribute's observer setup, Radio Config's
   apply flow). A thin connector line between the number badges is
   the only thing standing in for "these happen in order". */
.procedure {
  display: flex;
  flex-direction: column;
  gap: 1.75rem;
  margin: 1.5rem 0 2rem;
}

.procedure-step {
  display: grid;
  grid-template-columns: 44px 1fr;
  gap: 1rem;
}

.procedure-n {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  flex: none;
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-weight: 700;
  color: var(--accent);
  border: 1px solid var(--border-strong);
  border-radius: 50%;
}

.procedure-step:not(:last-child) .procedure-n::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  width: 1px;
  height: 1.75rem;
  background: var(--border);
  transform: translateX(-50%);
}

.procedure-title {
  font-size: 1rem;
  color: var(--text);
  margin: 0 0 0.4rem;
}

.procedure-body {
  color: var(--text-muted);
  font-size: 0.92rem;
  line-height: 1.6;
}

.procedure-body p {
  margin: 0 0 0.75rem;
}

.procedure-body p:last-child {
  margin-bottom: 0;
}

/* ---------- 18. Code / config snippet blocks ------------------------------ */

/* Settings values and CLI-style config (Radio Config's field list,
   Connect & Contribute's MQTT broker details). Darker than
   --bg-panel so it reads as "a machine's own output" rather than
   another card, monospace, and scrolls its own overflow — a long
   unbroken value (a URL, a hex key) must never widen the page. */
.code-block {
  overflow-x: auto;
  margin: 1rem 0 1.5rem;
  padding: 1rem 1.25rem;
  background: #05070a;
  border: 1px solid var(--border);
  border-radius: 12px;
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 0.85rem;
  line-height: 1.7;
  color: #d7e0ea;
}

.code-block code {
  white-space: pre;
}

/* ---------- 19. Tables --------------------------------------------------- */

.table-wrap {
  overflow-x: auto;
  margin: 1rem 0 1.5rem;
  border: 1px solid var(--border);
  border-radius: 12px;
}

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}

.table th,
.table td {
  padding: 0.65rem 0.9rem;
  text-align: left;
  white-space: nowrap;
  border-bottom: 1px solid var(--border);
}

.table th {
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  background: var(--bg-panel);
}

.table td {
  color: var(--text-muted);
}

.table tr:last-child td {
  border-bottom: 0;
}

/* ---------- 20. Embeds (full-bleed / card maps and iframes) --------------- */

/* One container for every iframe/self-hosted-map embed on the maps
   pages (mtmap, corescope, the three panels on /maps). Height comes
   from a modifier so the same markup shape works both as a near
   full-page embed and as one card in a grid; children (an <iframe> or
   a plain <div> a page's own JS turns into a Leaflet map) are told to
   fill it rather than each embed re-declaring width/height. */
.embed {
  position: relative;
  width: 100%;
  overflow: hidden;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 16px;
}

.embed > iframe,
.embed > div {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

.embed-hero {
  height: 85vh;
}

.embed-card {
  height: 420px;
}

.embed-note {
  color: var(--text-dim);
  font-size: 0.85rem;
  margin-bottom: 1rem;
}

.map-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 1.5rem;
  margin: 1.5rem 0;
}

.map-card {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.map-card-label {
  font-weight: 700;
  color: var(--text);
  font-size: 0.95rem;
}

/* ---------- 21. In-page anchor nav & back-to-top -------------------------- */

/* A row of pill links to jump between a long page's own h2 sections
   (Community Recommendations' hardware categories, Tools/Links'
   protocol groups), plus a matching "back to top" link for the
   bottom. Reuses #icon-caret (already in every page's icon sprite,
   see index.html) rotated 180deg rather than drawing a new glyph. */
.toc {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0 0 2rem;
  padding: 0;
  list-style: none;
}

.toc a {
  display: inline-block;
  padding: 0.4rem 0.8rem;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text-muted);
  text-decoration: none;
  border: 1px solid var(--border);
  border-radius: 999px;
}

.toc a:hover {
  color: var(--text);
  border-color: var(--accent);
}

.back-to-top {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 2rem;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text-muted);
  text-decoration: none;
}

.back-to-top:hover {
  color: var(--accent);
}

.back-to-top svg {
  width: 12px;
  height: 12px;
  transform: rotate(180deg);
}

/* ---------- 22. Responsive — original hero/nav rules ---------------------- */

@media (max-width: 900px) {
  .meshwars-panel {
    grid-template-columns: 1fr;
  }

  .steps {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 760px) {
  .nav-toggle {
    display: inline-flex;
  }

  .nav {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    background: var(--bg-panel);
    border-bottom: 1px solid var(--border);
    padding: 0.5rem;
    display: none;
  }

  .site-header[data-menu-open="true"] .nav {
    display: flex;
  }

  .nav-list {
    flex-direction: column;
    align-items: stretch;
    gap: 0.15rem;
  }

  .nav-dd-menu {
    position: static;
    box-shadow: none;
    margin-top: 0.2rem;
  }

  .theme-toggle {
    margin: 0.6rem 0.4rem 0.3rem;
    align-self: flex-start;
  }

  /* Taller than one screen on purpose: the hero's text block is a
     roughly fixed height (font sizes don't shrink with viewport
     height), so at the old 86svh most of the section was headline and
     almost none of it was visible map. Growing the section gives that
     visible map band real height back while the text keeps the same
     tighter mobile spacing below. */
  .hero {
    min-height: 112vh;
    min-height: 112svh;
  }

  .hero-content {
    padding-block: 1.5rem 2rem;
  }

  .hero-body {
    margin-bottom: 1.15rem;
  }

  /* Leaflet's own layer-control checkboxes also live top-right, and on
     a narrow screen there isn't room for both — move the badge to the
     opposite corner instead of fighting the control for space. */
  .hero-live-badge {
    top: auto;
    right: auto;
    bottom: 0.75rem;
    left: 0.75rem;
  }

  .chat-feeds {
    grid-template-columns: 1fr;
  }

  .steps {
    grid-template-columns: 1fr;
  }
}

/* ---------- 23. Responsive — shared components ---------------------------- */

@media (max-width: 760px) {
  /* Same collapse used for .glossary-item — a fixed 84px term column
     gets cramped once the container is this narrow; stack term over
     definition instead. */
  .glossary-item {
    grid-template-columns: 1fr;
    gap: 0.2rem;
  }

  .procedure-step {
    grid-template-columns: 36px 1fr;
  }

  .procedure-n {
    width: 36px;
    height: 36px;
    font-size: 0.85rem;
  }
}
