/* CanILiveThere — plain, boring, readable CSS. Runtime stays framework-
   and build-free (fetch + JSON.parse, plain DOM); a small Node prerender
   step now runs before publish for the no-JS static fallback (see
   tools/prerender-locations.js) — that step is offline/local, not a
   runtime dependency.
   Accessibility/readability outrank cleverness (mission brief, 2026-07-09).

   Light-mode tokens below + the dark-mode block at the end are the v2
   addendum's "site warmth + dark theme" ruling (2026-07-10): a subtle
   warm-neutral shift on chrome tokens
   (surface/panel/border/gap), one real pre-existing WCAG AA fix
   (--accent), and a full dark theme — "an atlas at night," not the
   light theme with the lights off; every text/background pair below is
   WCAG-contrast-checked in the addendum itself, not eyeballed.

   v7 ("The Explorer's Atlas", 2026-07-12): parchment/oxblood palette,
   replacing the v2 near-white/blue pair below — every new pairing
   re-checked (WCAG relative-luminance contrast) against the new
   parchment ground, not carried over on the old numbers. Dark theme is
   explicitly out of scope this pass (same caution v4/v6 used for map
   water/the dark ramp before extending them later) — the dark tokens at
   the end of this file are untouched. */

:root {
  --ink: #1b1b1b;
  --paper: #F2E8D5;
  --muted: #5a5a5a;
  /* v7 §1.2: reuses --country-border's own hex — one ink family for both
     UI hairlines and the map's own border stroke, not two. 2.69:1 vs
     paper (decorative divider, not a text pairing — the 4.5:1 text floor
     doesn't apply). */
  --line: #a58a66;
  /* "Pasted card" surface — deliberately close in luminance to --paper
     itself (1.06:1); the border+shadow contract (see .panel-card below)
     does the separating, not a ground-contrast gap. Still holds real
     TEXT contrast: 15.96:1 ink-on-it, 6.39:1 muted-on-it. */
  --panel: #FBF6E9;
  --accent: #8C2F1B;
  --gap-bg: #E4D8BC;
  --gap-ink: #635c50;
  /* v6 addendum §2.2: aubergine, brown retired (was #3a2a1a) — clears
     10.9-16.3:1 against all three map surfaces, ΔE 27.2 from the ramp's
     own weakest stop (#330063) — same dark violet-purple family, staying
     distinct (reviewed as a "coherence check, met"). */
  --eliminated: #370036;
  --conditional: #e07b1a;
  --focus: #0b5fff;
  /* Fixed dark chip, not theme-reactive: this floating tooltip always
     inverts against whatever the page background is, so it must not
     resolve from --ink (which flips to near-white in dark mode and would
     put white text on a white chip). */
  --tooltip-bg: #1b1b1b;
  --chip-active-bg: #1f6ea3;

  --badge-high-border: #2c7fb8;
  --badge-high-text: #1a4f73;
  --badge-high-bg: #eaf3fa;
  --badge-medium-border: #a1dab4;
  --badge-medium-text: #2f6b45;
  --badge-medium-bg: #eef8f0;
  --badge-speculative-border: #e0b04a;
  --badge-speculative-text: #7a5c14;
  --badge-speculative-bg: #fdf3df;
  --div-match-ink: #2f6b45;
  --div-match-bg: #eef8f0;
  --div-diverge-ink: #b02a2a;
  --div-diverge-bg: #fceaea;
  --div-unchecked-ink: #b95d00;
  --div-unchecked-bg: #fdf0e0;

  /* Map-specific chrome (v2 addendum §2.4: warm the room, not the water —
     the water stays blue, the unresearched-country fill warms with the
     rest of the chrome, since that's the fill ~90% of the map's landmass
     wears on first paint). v4 addendum R3 §3.2 narrows (not reverses)
     that call: still unambiguously water/blue-family, shifted toward a
     friendlier teal, validated against the score ramp's own light-end
     floor (2.05:1, real margin) — #eef4f8 -> #e7f4f5. --country-fill
     (v4 R3 §3.3 confirms this is already the "terrain-paper" warm tan the
     addendum asked for — unchanged). */
  --map-water: #e7f4f5;
  --country-fill: #d6cdb8;
  /* v6 addendum R3 §3.1: sand-tone, thinner, translucent — retires the
     hardcoded #ffffff "municipal overlay" white grid flagged in review
     ("less of these straight lines... an exciting map, not a municipal
     overlay"). VALIDATED: 2.07:1 against the landmass (soft, visible, no
     crisp white line), 2.90:1 where borders meet water. No dark-mode
     override defined here — out of scope this pass, not validated against
     the dark landmass fill; carries over as-is until a future pass checks
     it (named, not silently assumed fine). */
  --country-border: #a58a66;
}

/* v7 §1.4: self-hosted Fraunces (headings/verdict/portrait) + Work Sans
   (body/tables/UI chrome) — law 7 bans system fonts by name (Inter,
   Roboto, Arial); the old body stack named three of them directly.
   REAL BUILD GAP, named plainly, not silently worked around: this
   session's sandbox has no outbound network access (Bash `curl`
   denied, WebFetch can't return binary font files) — I could not
   actually download the real WOFF2 subsets tonight. The @font-face
   rules below are wired correctly for when the files land (see
   fonts/README.md for the exact acquisition steps); until then every
   fallback below is a bare CSS generic (serif / sans-serif) — NEVER a
   named system font — so the banned-name rule holds even in this
   degraded state. Visually this means the site currently renders in
   the browser's own default serif/sans, not Fraunces/Work Sans, until
   the two files are dropped into fonts/. Flagged as a fast-follow
   browser errand, not fixed by writing a fake binary. */
@font-face {
  font-family: "Fraunces";
  src: url("../fonts/fraunces-latin-600.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Fraunces";
  src: url("../fonts/fraunces-latin-700.woff2") format("woff2");
  font-weight: 650 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Fraunces";
  src: url("../fonts/fraunces-latin-italic-400.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "Work Sans";
  src: url("../fonts/work-sans-latin-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Work Sans";
  src: url("../fonts/work-sans-latin-500.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Work Sans";
  src: url("../fonts/work-sans-latin-600.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

* { box-sizing: border-box; }

html { font-size: 16px; }

body {
  margin: 0;
  position: relative;
  font-family: "Work Sans", sans-serif;
  color: var(--ink);
  background: var(--paper);
  line-height: 1.5;
}

/* v7 §1.5.1: procedural paper grain — one feTurbulence+feColorMatrix SVG
   filter, encoded once as a data URI and reused everywhere (body here,
   .panel-card below), no external asset, no JS. feColorMatrix reduces
   the noise to alpha-only output (R'/G'/B' held at 0, A' = luminance),
   so this is a pure darkening mask, not a colored texture — applied via
   mix-blend-mode:multiply at low opacity so it only ever darkens, never
   lightens or shifts hue. baseFrequency/opacity are a starting-guess
   craft call (untested rendered, per the spec's own flag) — tune by eye
   before shipping if it reads too subtle or too muddy on real text. */
:root {
  --grain-svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch' result='noise'/%3E%3CfeColorMatrix in='noise' type='matrix' values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0.2126 0.7152 0.0722 0 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url%28%23n%29'/%3E%3C/svg%3E");
}
body::before {
  content: "";
  position: fixed;
  inset: 0;
  background-image: var(--grain-svg);
  background-repeat: repeat;
  mix-blend-mode: multiply;
  opacity: 0.05;
  pointer-events: none;
  z-index: 999;
}

a { color: var(--accent); }
a:focus, button:focus, select:focus, input:focus { outline: 3px solid var(--focus); outline-offset: 2px; }

/* ---------- top bar (v4 addendum R4: brand/nav/theme-toggle only —
   site-level chrome, first thing on the page, unchanged position from
   the old .site-header. The persona block below is a SEPARATE piece,
   moved out of the header entirely — see .persona-block further down. */
.site-topbar {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  flex-wrap: wrap;
  border-bottom: 1px solid var(--line);
  padding: 0.75rem 1.25rem;
}
.brand {
  font-weight: 700;
  font-size: 1.25rem;
  text-decoration: none;
  color: var(--ink);
}
.site-nav { display: flex; gap: 1rem; }
.site-nav a { text-decoration: none; color: var(--muted); padding: 0.25rem 0; border-bottom: 2px solid transparent; }
.site-nav a.active, .site-nav a:hover { color: var(--ink); border-bottom-color: var(--accent); }

/* Theme toggle (v4 addendum R2 §"Toggle placement/copy"): fixed label,
   state-reflecting aria-pressed + .active hook, not label-swapping. */
#theme-toggle {
  margin-left: auto;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 0.3rem 0.75rem;
  font-size: 0.85rem;
  color: var(--ink);
  cursor: pointer;
}
#theme-toggle.active { background: var(--chip-active-bg); border-color: var(--chip-active-bg); color: #fff; }

/* ---------- persona block (v4 addendum R4 §4.2/4.3): now lives inside
   <main>, after each page's own H1/orientation, not in the top bar. */
.persona-block { margin: 0.75rem 0 0.25rem; max-width: 100%; }
/* Bug fix: v7 §7.1 widened every <option> to a full multi-
   sentence descriptor (~350-500 chars); a bare native <select> has no
   intrinsic width cap, so some browsers size its closed box to fit the
   longest/currently-selected option's raw text, pushing it past the
   viewport edge. width:100% (bounded by .persona-block's own max-width,
   in turn bounded by <main>'s) plus the ellipsis treatment below fixes
   this for ANY option length, not just today's specific strings — a
   future-content-length-proof fix, not a one-off truncation number. The
   full sentence stays fully readable in .persona-blurb below (unchanged
   otherwise); this only stops the closed <select> box itself from
   overflowing. */
.persona-block select {
  font-size: 0.95rem;
  padding: 0.2rem 0.4rem;
  width: 100%;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.persona-blurb { margin: 0.35rem 0 0; font-size: 0.85rem; color: var(--muted); min-height: 1.1em; }

/* ---------- disclosure "recede" mechanism (v2/v1 §2.2): a <details>
   pattern, closed by default, worded summary line — never hidden, never
   shortened, just not mandatory reading before the page's own content. */
details.recede { margin: 0.5rem 0 0; font-size: 0.85rem; color: var(--muted); }
details.recede > summary {
  cursor: pointer;
  font-size: 0.8rem;
  color: var(--muted);
  padding: 0.15rem 0;
}
details.recede > summary:hover { color: var(--ink); }
details.recede .recede-body { margin-top: 0.4rem; }

.disclaimer {
  margin: 0.5rem 0 0;
  font-size: 0.78rem;
  color: var(--muted);
  /* v7 §1.2, flag 2: the old fill (#fff8e6) nearly vanished against the
     new parchment (1.15:1, measured) — deepened rather than left to the
     border alone, a same-hue-family darkening (still reads as "pale
     warning yellow," not a new color concept). */
  background: #f5e9be;
  border: 1px solid #d8b862;
  border-radius: 4px;
  padding: 0.4rem 0.6rem;
}

/* ---------- footer ---------- */
.site-footer {
  border-top: 1px solid var(--line);
  margin-top: 2rem;
  padding: 1rem 1.25rem 2rem;
  font-size: 0.8rem;
  color: var(--muted);
}

main { max-width: 1200px; margin: 0 auto; padding: 1rem 1.25rem 3rem; }

/* v7 §1.4: Fraunces replaces the v4 web-safe serif stack entirely — h1/h2
   only, weight 600, upright (h3 deliberately excluded, same "h1, h2 only"
   scoping the v4 ruling used). Body/tables/UI stay on Work Sans (above),
   legibility there isn't a warmth trade. */
h1 { font-size: 1.6rem; font-family: "Fraunces", serif; font-weight: 600; }
h2 { font-size: 1.2rem; margin-top: 2rem; font-family: "Fraunces", serif; font-weight: 600; }
h3 { font-size: 1.05rem; }

/* v7 §2.2/§1.4: the verdict block's headline and any callout-weight text
   — heavier Fraunces than a plain h1/h2, per the weight contract. */
.callout-weight { font-family: "Fraunces", serif; font-weight: 700; }

/* ---------- badges ---------- */
.badge {
  display: inline-block;
  font-size: 0.72rem;
  padding: 0.1rem 0.45rem;
  border-radius: 10px;
  border: 1px solid var(--line);
  color: var(--muted);
  background: var(--panel);
  white-space: nowrap;
}
.badge-high { border-color: var(--badge-high-border); color: var(--badge-high-text); background: var(--badge-high-bg); }
.badge-medium { border-color: var(--badge-medium-border); color: var(--badge-medium-text); background: var(--badge-medium-bg); }
.badge-speculative { border-color: var(--badge-speculative-border); color: var(--badge-speculative-text); background: var(--badge-speculative-bg); }
/* No explicit confidence field on the fact — distinct from Speculative,
   which is an earned, explicit tier. Same neutral look as the base .badge
   (repeated explicitly here so the intent reads in the CSS, not just JS). */
.badge-neutral { border-color: var(--line); color: var(--muted); background: var(--panel); }
.badge-gap { border-color: var(--gap-ink); color: var(--gap-ink); background: var(--gap-bg); font-style: italic; }
.div-match { border-color: var(--div-match-ink); color: var(--div-match-ink); background: var(--div-match-bg); }
.div-diverge { border-color: var(--div-diverge-ink); color: var(--div-diverge-ink); background: var(--div-diverge-bg); }
.div-unchecked { border-color: var(--div-unchecked-ink); color: var(--div-unchecked-ink); background: var(--div-unchecked-bg); }
.source-link { font-size: 0.78rem; }
.source-onfile { font-size: 0.78rem; color: var(--muted); font-style: italic; }

/* v7 §1.5.2: "card-as-pasted-in-plate" — a contract, not a color. Every
   panel/card surface gets --panel bg (already close in luminance to
   --paper by design, see the --panel token comment above; the border +
   shadow do the separating), a 1px --line border, a warm oxblood-family
   shadow (not generic gray), and a flatter, smaller corner radius than
   the old "modern app" 6px. Applies to the existing panel surfaces
   (.judgment-note, .next-best, .verify-yourself) and the new §2 chapter/
   verdict/portrait shells below — one class, applied by selector list so
   every current and future "pasted plate" stays visually consistent. */
.judgment-note, .next-best, .verify-yourself, .chapter, .verdict-block, .portrait-block {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 3px;
  box-shadow: 0 1px 4px rgba(140, 47, 27, 0.08);
  padding: 0.85rem 1rem;
  margin: 0.9rem 0;
}
/* The stronger grain pass promised in §1.5.1 — same filter, higher
   opacity than the body-wide overlay, so "pasted plate" surfaces read as
   slightly more textured paper than the page ground around them. */
.judgment-note::after, .next-best::after, .verify-yourself::after, .chapter::after, .verdict-block::after, .portrait-block::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background-image: var(--grain-svg);
  background-repeat: repeat;
  mix-blend-mode: multiply;
  opacity: 0.08;
  pointer-events: none;
}
/* .judgment-note keeps its own left-accent flag color on top of the
   shared plate treatment (semantic "watch this" marker, unchanged
   meaning from v2 §1.3) — a plate with one colored edge, not two
   competing visual languages. */
.judgment-note { border-left: 3px solid var(--conditional); font-size: 0.8rem; color: var(--muted); }

/* ---------- map surface ---------- */
.map-wrap { position: relative; width: 100%; }
#worldmap { width: 100%; height: auto; background: var(--map-water); border: 1px solid var(--line); border-radius: 6px; }
/* v2 addendum §4.2: pins over choropleth by visual weight, not DOM order —
   the choropleth (researched-country fills included, set inline by map.js)
   reads as flatter/quieter background context; pins keep full contrast so
   they draw the eye first regardless of paint order. */
/* v6 addendum R3 §3.1: --country-border (sand-tone) replaces the hardcoded
   white stroke; thinner (0.4 -> 0.25) and translucent (new stroke-opacity)
   on top of a color that already clears contrast alone. --line was
   checked and rejected for this role (1.08:1 against the landmass,
   functionally invisible) — right for paper hairlines, not warm-on-warm
   map fill. */
.country-path { fill: var(--country-fill); stroke: var(--country-border); stroke-width: 0.25; stroke-opacity: 0.65; opacity: 0.68; }
/* v4 addendum R3 §3.4: friendlier, not new geometry — radius bumped in
   map.js (6 -> 7); the halo ring here uses the theme's own --paper token
   (not a hardcoded white) so it reads correctly in both themes without a
   second color value, ~2px per the ruling. Explicitly untouched: the
   .eliminated hatch/stroke and the hover/focus stroke-to-ink treatment
   below — verdict affordance, none of it warmed by this ruling. */
.location-pin {
  cursor: pointer;
  /* v7 §1.3: halo stops resolving from --paper (drops the weakest ramp
     stop's margin against the new parchment to 3.01:1) and uses --panel
     instead — a dedicated "elevated" tone giving that same stop 3.39:1,
     closer to its old margin. No ramp hex touched. */
  stroke: var(--panel);
  /* v7 Part 9 item 3: constant on-screen stroke width across zoom levels.
     map.js sets --pin-stroke per-circle (PIN_HALO / current zoom scale);
     the var()'s own fallback (2) matches the pre-zoom fixed value exactly,
     so nothing changes for any code path that doesn't set it. A CSS
     custom property (not a plain inline style) so the :hover/:focus rule
     below can still scale its own thicker value off the same per-element
     base, instead of an inline style silently overriding hover feedback. */
  stroke-width: var(--pin-stroke, 2);
  filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.4));
}
.location-pin:hover, .location-pin:focus { stroke: var(--ink); stroke-width: calc(var(--pin-stroke, 2) * 1.25); }
.location-pin.eliminated { fill: url(#hatch-eliminated); stroke: var(--eliminated); }
/* v7 Part 9 item 2: cluster badges share the pin's own stroke/cursor/
   hover language (same class) but never a ramp hue fill (set inline by
   map.js, PENDING_COLOR) — the count numeral inside is plain white,
   readable against that neutral gray at every ramp position. */
.cluster-badge-count { fill: #fff; font-family: "Work Sans", sans-serif; font-weight: 600; }

/* v7 Part 9 item 1: zoom controls, fixed bottom-right of the map itself
   (.map-wrap is already position:relative) — one of three redundant
   entry points, the other two (scroll/pinch, keyboard) have no visible
   chrome by nature. */
.zoom-controls { position: absolute; right: 0.6rem; bottom: 0.6rem; display: flex; gap: 0.3rem; z-index: 10; }
.zoom-btn {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0.25rem 0.6rem;
  font-size: 0.85rem;
  cursor: pointer;
  color: var(--ink);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
}
.zoom-btn:hover { border-color: var(--accent); }
.pin-label-tooltip {
  position: absolute;
  pointer-events: none;
  background: var(--tooltip-bg);
  color: #fff;
  font-size: 0.8rem;
  padding: 0.4rem 0.6rem;
  border-radius: 4px;
  max-width: 280px;
  z-index: 20;
  display: none;
}
.map-legend { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; margin-top: 0.75rem; font-size: 0.85rem; }
.legend-scale { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
/* v6 addendum R2 §2.3: one named step (swatch + name) per ramp stop,
   replacing the old unlabeled swatch strip (.legend-swatch alone, still
   used bare by the persona legend items below). */
.legend-step { display: inline-flex; align-items: center; gap: 0.3rem; }
.legend-swatch { width: 18px; height: 14px; display: inline-block; border: 1px solid var(--line); }
.legend-item { display: flex; align-items: center; gap: 0.35rem; }
.legend-hatch-demo { width: 18px; height: 14px; display: inline-block; border: 1px solid var(--eliminated); background: repeating-linear-gradient(45deg, var(--eliminated), var(--eliminated) 2px, #fff 2px, #fff 5px); }

/* v6 addendum R4: Ormen Lange — monochrome border-ink line-art, never a
   click/focus target (non-interactive by construction, see map.js's own
   comment; pointer-events:none lives here, not inline, per this file's
   convention). */
.ormen-lange { pointer-events: none; }
.ormen-lange path { fill: none; stroke: var(--country-border); stroke-opacity: 0.55; stroke-width: 1; stroke-linecap: round; stroke-linejoin: round; }

/* ---------- perspective door (v7 Part 10, js/perspective-door.js) ---------- */
/* "Visible but foggy, not hidden" — the map keeps rendering underneath,
   just de-emphasized, while the door is open. */
#map-root.map-fogged { opacity: 0.4; filter: blur(2px); pointer-events: none; transition: opacity 0.2s ease, filter 0.2s ease; }

/* Click-and-drag panning (js/map.js "Entry point 2b") — the cursor is
   the only cue that the map is grabbable, since there's no other visible
   affordance for it; swaps to "grabbing" for the live drag itself. */
#map-root svg { cursor: grab; }
#map-root.map-panning svg { cursor: grabbing; }
#perspective-door {
  position: fixed;
  inset: 0;
  z-index: 500;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: rgba(27, 27, 27, 0.4);
  overflow-y: auto;
}
.door-panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 4px;
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
  padding: 1.5rem;
  max-width: 920px;
  width: 100%;
  max-height: calc(100vh - 3rem);
  overflow-y: auto;
}
.door-welcome {
  font-family: "Fraunces", serif;
  font-weight: 600;
  font-size: 1.1rem;
  max-width: 46em;
  margin: 0 0 1.25rem;
}
.door-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 0.75rem;
  margin-bottom: 1.25rem;
}
.door-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.4rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0.75rem 0.6rem;
  cursor: pointer;
  color: var(--ink);
  font: inherit;
}
.door-tile:hover, .door-tile:focus { border-color: var(--accent); }
/* Neutral stub (Part 17): visible even with no portrait file present —
   never a broken-image icon (perspective-door.js removes a failed <img>
   on error, leaving this background showing through). */
.door-portrait {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background: var(--gap-bg);
  border: 1px solid var(--line);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}
.door-portrait img { width: 100%; height: 100%; object-fit: cover; display: block; }
.door-name { font-family: "Fraunces", serif; font-weight: 700; font-size: 1rem; }
.door-descriptor { font-size: 0.8rem; color: var(--muted); }
/* The escape hatch (Part 10): plain-text, deliberately less weighted
   than the eight tiles (no card chrome, no portrait) but not
   apologetic — same underline-link treatment as any other text link. */
.door-escape {
  display: block;
  margin: 0 auto;
  background: none;
  border: none;
  color: var(--accent);
  font-size: 0.9rem;
  text-decoration: underline;
  cursor: pointer;
  padding: 0;
  font-family: inherit;
}

/* ---------- lists ---------- */
.fit-def { caption-side: top; text-align: left; font-size: 0.8rem; color: var(--muted); padding: 0 0 0.4rem; }
table.rank-table { width: 100%; border-collapse: collapse; margin-top: 1rem; }
table.rank-table th, table.rank-table td { text-align: left; padding: 0.5rem 0.6rem; border-bottom: 1px solid var(--line); vertical-align: top; }
table.rank-table th { cursor: pointer; user-select: none; background: var(--panel); position: sticky; top: 0; }
table.rank-table th[aria-sort]::after { content: " ⇅"; color: var(--muted); font-size: 0.75rem; }
table.rank-table th[aria-sort="ascending"]::after { content: " ▲"; }
table.rank-table th[aria-sort="descending"]::after { content: " ▼"; }
.rank-fit-cell { display: flex; align-items: center; gap: 0.5rem; }
.fit-swatch { width: 16px; height: 16px; border-radius: 3px; border: 1px solid rgba(0,0,0,0.15); flex: none; }
.filters { display: flex; flex-wrap: wrap; gap: 0.75rem; margin: 1rem 0; align-items: center; }
.filters label { font-size: 0.85rem; }
.expand-row td { background: var(--panel); }
.breakdown-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 0.5rem; margin-top: 0.5rem; }
.breakdown-item { border: 1px solid var(--line); border-radius: 4px; padding: 0.4rem 0.5rem; background: #fff; font-size: 0.85rem; }
.btn-chip { border: 1px solid var(--line); cursor: pointer; }
.expand-toggle { background: none; border-radius: 4px; padding: 0.2rem 0.5rem; font-size: 0.8rem; }
.verdict-chip { display: inline-block; padding: 0.1rem 0.5rem; border-radius: 10px; font-size: 0.75rem; color: #fff; }
/* v4 addendum R1 §1.4: the verdict's full prose, always visible under the
   chip, not gated behind the breakdown toggle ("sell the no" framing). */
.verdict-prose { font-size: 0.8rem; color: var(--muted); margin-top: 0.3rem; max-width: 32em; }
/* v4 addendum R1 §1.2/1.3: persona verdict-first band group headers. */
.band-header-row td { background: var(--panel); font-size: 0.95rem; font-weight: 600; padding-top: 0.75rem; }
/* v4 addendum R1 §1.6: the visit-layer interim pointer, uniform across
   every band. */
.visit-link { display: inline-block; margin-top: 0.3rem; font-size: 0.78rem; }

/* Purpose-list selector (v2 addendum §3): the "Pick what matters most"
   chips above the Lists table. */
.purpose-lists { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; margin: 0.75rem 0; }
.purpose-chip {
  background: var(--panel);
  border-radius: 14px;
  padding: 0.3rem 0.75rem;
  font-size: 0.85rem;
  color: var(--ink);
}
.purpose-chip.active { background: var(--chip-active-bg); border-color: var(--chip-active-bg); color: #fff; }
.purpose-more { font-size: 0.85rem; padding: 0.25rem 0.4rem; }
.purpose-explainer { font-size: 0.82rem; color: var(--muted); margin: 0.35rem 0 0.75rem; }

/* ---------- location page ---------- */
.loc-header { display: flex; justify-content: space-between; align-items: baseline; flex-wrap: wrap; gap: 0.5rem; }
/* v4 addendum R4 §4.3: renderPersonaBlock() inserts #persona-slot as the
   H1's own next sibling (per the ruling's own insertAdjacentElement
   contract) — which lands it INSIDE .loc-header's flex row, next to the
   H1, unless forced onto its own line. flex-basis:100% is that force —
   keeps the ruling's literal DOM placement, fixes the visual consequence
   it would otherwise have in this page's particular flex context. */
.loc-header #persona-slot { flex-basis: 100%; }

/* v7 §2.2: the verdict block — headline-first, replaces the old always-
   verbose persona panel. Plate treatment from the shared contract above;
   this block's own rules are typography/layout only. */
.verdict-headline { font-family: "Fraunces", serif; font-weight: 700; font-size: 1.1rem; margin: 0 0 0.4rem; }
.verdict-block .verdict-chip { font-size: 0.85rem; padding: 0.15rem 0.65rem; }
.verdict-block .verdict-prose { font-size: 0.95rem; color: var(--ink); max-width: 40em; margin: 0.4rem 0; }
.redflag-pointer { display: inline-block; margin-top: 0.4rem; font-weight: 600; }
.fit-link-line { font-size: 0.82rem; margin-top: 0.5rem; }
.fit-link-line a { font-weight: 600; }

/* v7 §2.3: the portrait — a narrative-copy slot. Fraunces italic marks
   this as VOICE, not data, per the spec's own "no new color/label
   needed" reasoning. Empty when no copy exists yet (hard placeholder
   rule — location.js renders nothing at all in that case, this rule is
   dormant/never-matched until real copy lands, not a visible stub). */
.portrait-block { font-family: "Fraunces", serif; font-style: italic; font-weight: 400; font-size: 1.02rem; line-height: 1.6; }
/* The hook+number "teaser" line — Work Sans, not italic, deliberately
   visually distinct from the portrait paragraph above it (data-flavored
   despite living inside the voice slot) — a builder's own placement
   reading of an interpretation already flagged as uncertain upstream,
   not a settled spec ruling; see location.js's own comment. */
.portrait-teaser { font-family: "Work Sans", sans-serif; font-style: normal; font-size: 0.85rem; color: var(--muted); margin-top: 0.5rem; }

/* v7 §2.4: chapters — <details class="chapter">, closed by default, every
   location, no exceptions. Reuses the plate contract; adds <summary>
   styling so a reader scanning closed headers can still tell what each
   chapter is and (for Red Flags) that it's the heavier one. */
.chapter > summary { cursor: pointer; font-size: 1.1rem; font-family: "Fraunces", serif; font-weight: 600; list-style: none; }
.chapter > summary::-webkit-details-marker { display: none; }
.chapter > summary::before { content: "▸ "; color: var(--muted); }
.chapter[open] > summary::before { content: "▾ "; }
.chapter > summary:hover { color: var(--accent); }
.chapter .chapter-intro { font-size: 0.88rem; color: var(--muted); margin: 0.5rem 0 0.75rem; }
/* Red Flags keeps its stronger heading treatment even while collapsed —
   the <summary> itself carries the weight now that nothing stays open by
   default (v1 §2.1.1's left-border-accent + tint, moved onto the
   <details> element itself). */
/* div-diverge-bg (warning-red family, already used for the divergence
   badge's own "doesn't match practice" state) is the right semantic
   fit here — NOT --gap-bg, which means "unresearched," the opposite of
   what a Red Flags chapter is (thoroughly researched, alarming). */
.chapter-redflags { border-left: 3px solid var(--div-diverge-ink); background: var(--div-diverge-bg); }
.chapter-redflags > summary { color: var(--div-diverge-ink); }

/* v7 Part 14: illegal-but-practiced routes — reuses the exact same
   left-accent + tint pattern as .chapter-redflags above (no new chrome
   language), scoped to a single row inside whichever chapter a
   `mechanism_legality: prohibited-enforced` fact's own section maps to.
   Never route-card chrome (Part 14 item 1) — deliberately not built on
   .fact-list/.fact-item. Dormant CSS until location.js actually renders
   one of these (see that file's own comment) — same empty-state
   discipline as every other rule on this page. */
.illegal-routes { margin-top: 1rem; }
.illegal-routes h3 { font-size: 0.95rem; color: var(--div-diverge-ink); margin: 0 0 0.5rem; }
.illegal-route-row {
  border-left: 3px solid var(--div-diverge-ink);
  background: var(--div-diverge-bg);
  border-radius: 3px;
  padding: 0.6rem 0.75rem;
  margin-bottom: 0.6rem;
}
.illegal-route-row .fact-value strong { color: var(--div-diverge-ink); }

.section-nav { display: flex; gap: 0.75rem; flex-wrap: wrap; margin: 1rem 0; border-bottom: 1px solid var(--line); padding-bottom: 0.5rem; }
.section-nav a { text-decoration: none; font-size: 0.9rem; }
.fact-list { list-style: none; margin: 0; padding: 0; }
.fact-item { border-bottom: 1px dotted var(--line); padding: 0.6rem 0; display: grid; grid-template-columns: 1fr; gap: 0.15rem; }
.fact-label { font-weight: 600; }
.fact-value { font-size: 1rem; }
.fact-meta { display: flex; gap: 0.4rem; flex-wrap: wrap; align-items: center; font-size: 0.78rem; color: var(--muted); }
.fact-notes { font-size: 0.85rem; color: var(--muted); }
.scope-tag { font-size: 0.72rem; color: var(--muted); font-style: italic; }
.criterion-scorebar { display: flex; gap: 0.4rem; flex-wrap: wrap; margin: 0.75rem 0; }
.criterion-chip { border: 1px solid var(--line); border-radius: 6px; padding: 0.3rem 0.5rem; font-size: 0.82rem; display: flex; align-items: center; gap: 0.4rem; }
/* .next-best's own plate treatment (bg/border/radius/shadow) comes from
   the shared panel-card contract above — margin-top kept here since it's
   a spacing need specific to this module's position on the page. */
.next-best { margin-top: 2rem; }
.change-event { border-left: 3px solid var(--line); padding-left: 0.6rem; margin-bottom: 0.6rem; }
.sev-3 { border-left-color: #b02a2a; }
.sev-2 { border-left-color: #b95d00; }
.sev-1 { border-left-color: #2c7fb8; }

/* "What to verify yourself" (v2 addendum §5.4) — plate treatment shared,
   see panel-card contract above. */
.verify-yourself { margin-top: 1.5rem; }
.verify-yourself ul { margin: 0.5rem 0 0; padding-left: 1.25rem; }

/* Confidence-badge pull affordance (v2 addendum §5.3.2): the existing
   confidence badge becomes a click/tap target expanding to source name,
   date, and source-count gloss in one place. */
.fact-meta .badge[data-toggle-source] { cursor: pointer; }
.source-detail { font-size: 0.78rem; color: var(--muted); margin-top: 0.2rem; display: none; }
.source-detail.open { display: block; }

@media (max-width: 700px) {
  .site-topbar { flex-direction: column; align-items: flex-start; }
  #theme-toggle { margin-left: 0; }
}

/* ==========================================================================
   Dark theme — "an atlas at night," a genuine second surface derived
   against its own dark background (v2 addendum §2.3). v4 addendum R2:
   this used to be a bare `@media (prefers-color-scheme: dark)` auto-flip
   (OS-read default, no toggle) — now a `[data-theme="dark"]` attribute
   block, set only by app-shared.js's applyStoredTheme()/toggleTheme(),
   never by the OS. Same declarations throughout, different trigger only.
   Every text/background pair below is WCAG-contrast-checked in the v2
   addendum's own tables; the ~10 remaining badge/border chip colors not
   individually listed there follow the same named recipe (hold the hue,
   raise lightness until contrast clears its floor) — computed here, not
   eyeballed, but not run through the palette script (that tool checks
   sequential/categorical ramps, not one-off UI chip pairs).
   ========================================================================== */
:root[data-theme="dark"] {
    --ink: #f5f2ea;
    --paper: #1e1a14;
    --muted: #c9c2b0;
    --line: #3d362a;
    --panel: #2a241c;
    --accent: #5fa8e0;
    --gap-bg: #2c2c28;
    --gap-ink: #a39c8c;
    /* v6 addendum §2.2: aubergine (was #846546, brown) — clears 3.19:1
       against dark paper, matching brown's old floor. See colors.js's own
       ELIMINATED_STROKE_DARK comment for the full coherence caveat. */
    --eliminated: #984f92;
    --conditional: #e07b1a; /* already clears dark, no separate value needed */
    --focus: #0b5fff; /* clears the 3:1 non-text/UI-component floor as-is */
    --map-water: #16232b; /* deep, clearly-blue-not-black; distinct from --panel */
    --country-fill: #4a4234;

    /* Chip-on-white-text roles need their own dark-mode value, distinct
       from --accent's link-text tuning: #5fa8e0 (right for text against
       --paper) fails AA as a background under hardcoded white chip text. */
    --chip-active-bg: #2a6f9e;

    /* Text colors below are re-derived against each badge's OWN tinted
       background (not just the page's dark paper) — a badge's small text
       (~0.72rem) needs the full 4.5:1 AA floor against whatever it actually
       sits on, and the first pass here (checked only against --paper-dark)
       under-shot that once the tinted chip backgrounds were factored in;
       caught and fixed before shipping, not left at the first plausible
       value. div-diverge's text/border color (#e5554f) is the addendum's
       own validated value (§2.3), unchanged — only its background here is
       darkened enough for that fixed color to clear 4.5:1 against it. */
    --badge-high-border: #4699d3;
    --badge-high-text: #4699d3;
    --badge-high-bg: #1b2b37;
    --badge-medium-border: #49a66b;
    --badge-medium-text: #49a66b;
    --badge-medium-bg: #203226;
    --badge-speculative-border: #bf901f;
    --badge-speculative-text: #bf901f;
    --badge-speculative-bg: #392f19;
    --div-match-ink: #49a66b;
    --div-match-bg: #203226;
    --div-diverge-ink: #e5554f;
    --div-diverge-bg: #311816;
    --div-unchecked-ink: #e87400;
    --div-unchecked-bg: #3f2912;
}

[data-theme="dark"] body { background: var(--paper); }
[data-theme="dark"] .disclaimer { background: #3a2f14; border-color: #a47c1b; color: var(--muted); }

[data-theme="dark"] .sev-3 { border-left-color: #bf2e2e; }
[data-theme="dark"] .sev-2 { border-left-color: #a05000; }
[data-theme="dark"] .sev-1 { border-left-color: #256b9b; }

[data-theme="dark"] .breakdown-item { background: var(--panel); }
