/* ============================================================
   The Rising Tide — design tokens & layout
   Palette: deep ocean base, coral heat accents, off-white text.
   WCAG AA contrast minimum against --ocean-950.
   ============================================================ */

/* ---- self-hosted variable fonts (vendored from Fontsource, OFL) ----
   Latin + Latin Extended only; the page is English. Weight axis 100–900,
   so `font-weight: 560` below interpolates rather than snapping. */

@font-face {
  font-family: "Inter Variable";
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url(../vendor/fonts/inter-latin-wght-normal.woff2) format("woff2-variations");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Inter Variable";
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url(../vendor/fonts/inter-latin-ext-wght-normal.woff2) format("woff2-variations");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Fraunces Variable";
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url(../vendor/fonts/fraunces-latin-wght-normal.woff2) format("woff2-variations");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Fraunces Variable";
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url(../vendor/fonts/fraunces-latin-ext-wght-normal.woff2) format("woff2-variations");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  /* ocean depths */
  --ocean-950: #06121f;
  --ocean-900: #0a1c2e;
  --ocean-800: #10293f;
  --ocean-700: #1a3a55;
  --teal-500: #2e8b8b;
  --teal-300: #5fc4bf;

  /* Advancing points get their own token, brighter than --teal-300, because
     they have to separate from a blue ground *and* from the slate outline
     while retreating (coral) separates from both for free. --teal-300 keeps
     its interface jobs (links, tabs, the sparkline band); this is data. */
  --advance-300: #7fe3dd;

  /* The atoll outline, deliberately *not* teal. Teal belongs to the advancing
     points, and points are nudged 7px off the shore, so a teal outline put a
     teal line beside every teal triangle and the pair read as one ring of
     texture. Neutral slate keeps the island as ground and leaves the hue
     opposition (teal advancing / coral retreating) to carry the data. */
  --shore-500: #46617a;

  /* Land Tuvaluans built, in the Built chapter. Deliberately neither
     teal nor coral: those two are spoken for as advancing/retreating shoreline,
     and reclaimed land is a third kind of thing entirely — not a direction the
     shore moved, but something people put there. Warm sand reads as human
     agency against a palette that is otherwise all water. */
  --built-300: #f0c46a;

  /* anomaly heat ramp */
  --coral-300: #ffb39b;
  --coral-500: #f4795b;
  --coral-700: #d24a35;

  /* text */
  --text-100: #f2f0eb;
  --text-300: #c9cfd6;
  --text-500: #8fa0af;

  --font-display: "Fraunces Variable", Georgia, serif;
  --font-text: "Inter Variable", system-ui, sans-serif;

  --step-gap: 70vh;
  --max-prose: 34rem;
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

/* ============================================================
   Layout: one screen, four tabs. The page itself never scrolls;
   the beat panel and the methodology dialog scroll internally.
   ============================================================ */

body {
  margin: 0;
  height: 100dvh;
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr) auto;
  overflow: hidden;
  background: var(--ocean-950);
  color: var(--text-100);
  font-family: var(--font-text);
  font-size: clamp(0.95rem, 0.9rem + 0.2vw, 1.05rem);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--teal-300); }
strong { font-weight: 620; color: var(--text-100); }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden; white-space: nowrap;
}

/* ----------------------------------------------------------- masthead */

.masthead {
  padding: clamp(1rem, 2.4vh, 1.9rem) clamp(1.25rem, 4vw, 3.5rem) 0;
  display: grid;
  gap: 0.5rem;
}

.masthead h1 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 560;
  font-size: clamp(1.6rem, 1.1rem + 2.2vw, 2.9rem);
  line-height: 1.06;
  letter-spacing: -0.02em;
}

.masthead h1 em { font-style: italic; color: var(--teal-300); }

/* Under the headline, not beside it. The page is one non-scrolling screen, so
   the height this costs has to be bought back somewhere: the abstract runs much
   wider than a reading column (it is three sentences, not an article) and was
   cut by a third to pay for it. Below 860px it wraps as tall as it needs to —
   that layout scrolls anyway. */
.abstract {
  margin: 0;
  max-width: 128ch;
  color: var(--text-300);
  font-style: italic;
  font-size: clamp(0.9rem, 0.85rem + 0.3vw, 1.05rem);
}

/* --------------------------------------------------------------- tabs */

/* Wraps, never scrolls. This used to be `overflow-x: auto` with the scrollbar
   hidden, which meant that on a narrow window the last chapter or two simply
   were not there — off the right edge, with nothing on screen to say so and no
   scrollbar to drag. A table of contents that hides entries is not a table of
   contents. Wrapping costs one row of vertical space, and only at the widths
   where the alternative was losing a chapter. */
.tabs {
  display: flex;
  flex-wrap: wrap;
  row-gap: 0;
  column-gap: clamp(0.25rem, 1vw, 0.75rem);
  padding: clamp(0.7rem, 1.6vh, 1.2rem) clamp(1.25rem, 4vw, 3.5rem) 0;
  border-bottom: 1px solid var(--ocean-800);
}

.tabs [role="tab"] {
  appearance: none;
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  padding: 0.45rem 0.2rem 0.7rem;
  color: var(--text-500);
  font: inherit;
  font-size: 0.9rem;
  white-space: nowrap;
  /* Never squash a chapter name to make the row fit — wrap to a second row
     instead. Without this a flex line can compress items below their content
     and the label spills out of its own button. */
  flex-shrink: 0;
  cursor: pointer;
  display: inline-flex;
  align-items: baseline;
  gap: 0.45rem;
  transition: color 160ms ease, border-color 160ms ease;
}

.tabs [role="tab"]:hover { color: var(--text-300); }

.tabs [role="tab"][aria-selected="true"] {
  color: var(--text-100);
  border-bottom-color: var(--teal-300);
}

.tab-n {
  font-family: var(--font-display);
  font-size: 0.78rem;
  color: var(--teal-300);
  font-variant-numeric: tabular-nums;
}

:where(button, [role="tab"], input, a):focus-visible {
  outline: 2px solid var(--teal-300);
  outline-offset: 3px;
  border-radius: 2px;
}

/* -------------------------------------------------------------- stage */

.stage {
  min-height: 0;
  display: grid;
  /* Funafuti is tall and narrow, so the map fit is height-bound and a wider
     map column only buys empty margin. Give the width to the panel instead:
     shorter line wraps mean the beat copy and its chart fit without the panel
     needing to scroll. */
  grid-template-columns: minmax(0, 1fr) min(38rem, 38vw);
  gap: clamp(1rem, 3vw, 3rem);
  padding: clamp(0.75rem, 2vh, 1.5rem) clamp(1.25rem, 4vw, 3.5rem);
}

.map-wrap {
  position: relative;
  margin: 0;
  min-height: 0;
  display: flex;
}

#map { flex: 1; min-height: 0; }
.map { width: 100%; height: 100%; display: block; overflow: visible; }

.outline path {
  fill: var(--ocean-800);
  stroke: var(--shore-500);
  stroke-width: 1.1;
}

.points { transition: opacity 400ms ease; }
.points.hidden { opacity: 0; pointer-events: none; }

/* Symbols necessarily overlap (transects are ~31 m apart). The halo is the
   page background, so a cluster reads as separate triangles, not one blob. */
.points path {
  fill-opacity: 0.82;
  stroke: var(--ocean-950);
  stroke-width: 0.35;
  vector-effect: non-scaling-stroke;
  paint-order: stroke fill;
  transition: opacity 220ms ease;
}

/* Advancing is the weaker read of the two: teal is darker than coral and sits
   against a blue ground, so 0.82 muted it before it had to compete at all.
   Retreating keeps 0.82 -- coral does not need the help, and lifting both
   would just restore the imbalance at a higher level. */
.points path[data-dir="up"] { fill-opacity: 0.95; }

/* Low-confidence years: the shape stays readable, the fill retreats.
   Must stay *after* the [data-dir] rule above -- the two selectors have equal
   specificity (0,2,1), so source order is the only thing making mute win. */
.points.muted path { fill-opacity: 0.3; }

/* A chip, not floating text: the legend sits directly on the map, where the
   ocean behind it is nearly the same value as the page background and the
   words had nothing to separate them from the atoll. */
/* The people marker: only where the camera is on Fogafale. */
.people {
  opacity: 0;
  transition: opacity 400ms ease;
  pointer-events: none;
}
/* Built is framed on Fogafale too, and the marker is the only thing
   naming the islet the reclamation sits on. */
body[data-beat="1"] .people,
body[data-beat="4"] .people { opacity: 1; }
.people-dot {
  fill: var(--text-100);
  stroke: var(--ocean-950);
  stroke-width: 1.2;
  vector-effect: non-scaling-stroke;
}
.people-leader {
  stroke: var(--text-500);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
.people-label {
  font-family: var(--font-text);
  fill: var(--text-100);
  font-size: 13px;
  paint-order: stroke fill;
  stroke: var(--ocean-950);
  stroke-width: 3;
  stroke-linejoin: round;
}
.people-place { font-weight: 620; }
.people-count { fill: var(--text-300); font-size: 11.5px; font-weight: 400; }

.legend {
  position: absolute;
  bottom: 0; left: 0;
  display: none;
  gap: 1rem;
  align-items: center;
  padding: 0.4rem 0.7rem;
  border-radius: 0.3rem;
  background: color-mix(in srgb, var(--ocean-950) 82%, transparent);
  font-size: 0.85rem;
  font-weight: 560;
  color: var(--text-300);
}
body[data-beat="3"] .legend { display: flex; }

/* The keys are buttons: 771 triangles at whole-atoll zoom are a band of
   texture, and the quickest answer to "which of these are advancing" is to
   take the others away. This hides marks; it never moves them. */
.legend .key {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font: inherit;
  padding: 0.15rem 0.4rem;
  margin: -0.15rem 0;
  border: 1px solid transparent;
  border-radius: 0.25rem;
  background: none;
  cursor: pointer;
}
.legend .key:hover { border-color: currentColor; }
.legend .key:focus-visible { outline: 2px solid var(--text-100); outline-offset: 1px; }
.legend .key[aria-pressed="true"] {
  border-color: currentColor;
  background: color-mix(in srgb, currentColor 16%, transparent);
}
.legend-hint {
  color: var(--text-500);
  font-weight: 400;
  font-size: 0.78em;
}
/* The glyph is the thing being matched against the map, so it outgrows its
   own label rather than sharing its size. */
.legend .mark {
  font-size: 1.3em;
  line-height: 1;
}
.legend .up { color: var(--advance-300); }
.legend .down { color: var(--coral-500); }

/* Dimmed, not removed: the shore they sit on is the same shore either way, and
   a reader who isolates one class should still see how much of the rim the
   other one accounts for. */
.points.only-up path[data-dir="down"],
.points.only-down path[data-dir="up"] { opacity: 0.07; }

/* ---------------------------------------------------------- unit dots */

.dots { margin: 0.2rem 0 0; }
.dots-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 0.28rem;
  max-width: 22rem;
}
.dot {
  width: 0.42rem;
  height: 0.42rem;
  border-radius: 50%;
  background: var(--ocean-700);
}
.dot.on { background: var(--advance-300); }
.dots-note {
  margin: 0.45rem 0 0;
  font-size: 0.75rem;
  color: var(--text-500) !important;
}
.dots-key {
  margin: 0.35rem 0 0;
  font-size: 0.75rem;
  color: var(--text-500) !important;
  line-height: 1.5;
}
.dots-swatch {
  display: inline-block;
  width: 0.42rem;
  height: 0.42rem;
  border-radius: 50%;
  background: var(--ocean-700);
  margin: 0 0.15rem 0 0.3rem;
}
.dots-swatch.on { background: var(--advance-300); margin-left: 0; }

/* ------------------------------------------------------------- panels */

.panels { min-height: 0; display: grid; }

/* The [hidden] attribute is a UA rule, so any author `display` beats it —
   the inactive panels must be switched off explicitly. */
.panel[hidden] { display: none; }

.panel {
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-right: 0.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  animation: rise 320ms ease both;
}

@keyframes rise {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

.panel p { margin: 0; color: var(--text-300); }
.prose { display: grid; gap: 0.7rem; }

.panel .figure {
  font-family: var(--font-display);
  font-weight: 600;
  /* Capped lower than the viewport term would give: the panel column is now
     wide enough that a 3.2rem figure plus its chart no longer fits the screen,
     and the chart is the part carrying the argument. */
  font-size: clamp(1.9rem, 1.3rem + 2.6vw, 2.5rem);
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--text-100);
}

.panel .figure .unit {
  display: block;
  font-family: var(--font-text);
  font-weight: 400;
  font-size: 0.88rem;
  letter-spacing: 0.02em;
  color: var(--text-500);
  margin-top: 0.5rem;
  line-height: 1.4;
}

.coda {
  border-top: 1px solid var(--ocean-700);
  padding-top: 0.8rem;
  color: var(--text-500) !important;
  font-size: 0.88rem;
}

/* Per-chapter provenance. Every chapter says where its numbers came from
   without the reader having to open anything — the methodology dialog stays the
   full record, this is the pointer to it. Deliberately the quietest text in the
   panel: attribution has to be present, not prominent. */
.sources {
  margin: 0.7rem 0 0;
  padding-top: 0.45rem;
  border-top: 1px solid var(--ocean-800);
  font-size: 0.68rem;
  line-height: 1.5;
  color: var(--text-500);
}
.sources-label {
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 0.9em;
  color: var(--text-500);
  opacity: 0.75;
  margin-right: 0.25rem;
}
.sources a { color: var(--text-300); text-decoration-color: var(--ocean-700); }
.sources a:hover { color: var(--text-100); }
.sources .note-link { font-size: inherit; margin-left: 0.15rem; }

/* --------------------------------------------------------- sparklines */

.spark-wrap { margin-top: auto; padding-top: 0.5rem; }
/* Capped: the viewBox is 3:1, so on a wide panel a full-width chart grows
   taller than the space the panel has, and pushes its own legend and caption
   off screen. The chart wants to be wide and short, not big. */
.spark { width: 100%; max-width: 27rem; height: auto; display: block; }
.spark-line { fill: none; stroke-width: 1.6; }

.spark-zero {
  stroke: var(--ocean-700);
  stroke-width: 1;
  stroke-dasharray: 1 3;
}

.spark-trend {
  stroke: var(--coral-500);
  stroke-width: 1.3;
  stroke-dasharray: 5 4;
}

/* The two periods the headline compares. Background, so the fill stays under
   everything and the median rule is the only part that reads as a mark. */
.spark-band rect {
  fill: var(--teal-300);
  fill-opacity: 0.07;
}

.spark-band-level {
  stroke: var(--teal-300);
  stroke-width: 1.6;
  stroke-opacity: 0.75;
}

.spark-tag {
  font-size: 10px;
  fill: var(--text-500);
  letter-spacing: 0.02em;
}

/* Boundary ticks (2005, 2015). Dimmer than the end labels: those anchor the
   axis, these annotate a period inside it. */
.spark-tag.mark { fill: var(--text-500); fill-opacity: 0.85; }
.spark-mark-rule {
  stroke: var(--ocean-700);
  stroke-width: 1;
}

.spark-value {
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.spark-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1rem;
  margin: 0.35rem 0 0 !important;
  font-size: 0.72rem;
  color: var(--text-500) !important;
}

.spark-key { display: inline-flex; align-items: center; gap: 0.4rem; }
.spark-key::before {
  content: "";
  width: 1.25rem;
  border-top: 1px dotted var(--ocean-700);
}
.spark-key.trend { color: var(--coral-500); }
.spark-key.trend::before { border-top: 1.5px dashed var(--coral-500); }
.spark-key.band::before {
  height: 0.6rem;
  border-top: 1.6px solid var(--teal-300);
  background: color-mix(in srgb, var(--teal-300) 12%, transparent);
}

/* A caption that hands the reader to the methodology instead of restating it,
   so the panel stays inside one screen. */
.note-link {
  display: inline;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--teal-300);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}
.note-link:hover { color: var(--text-100); }

.spark-note,
.land-note {
  margin-top: 0.4rem !important;
  font-size: 0.74rem;
  line-height: 1.45;
  color: var(--text-500) !important;
}

.land-wrap { margin-top: auto; padding-top: 0.5rem; }

/* ---- emissions per person, Tuvalu against the world ---- */

.emit-wrap { margin-top: auto; padding-top: 0.5rem; }
/* Capped like every other panel chart; the panel column is ~600 px and a
   380-unit viewBox would otherwise scale up and push its own note off screen. */
.emit { width: 100%; max-width: 27rem; height: auto; display: block; }

.emit-name { font-size: 11.5px; fill: var(--text-300); }
.emit-year { font-size: 10px; fill: var(--text-500); }

.emit-bar.tuvalu { fill: var(--teal-300); }
/* The world's bar is the backdrop, not the subject: it is the thing Tuvalu is
   nearly invisible against, so it reads as mass rather than as a mark. */
.emit-bar.world { fill: var(--coral-500); fill-opacity: 0.55; }

.emit-value {
  font-size: 11.5px;
  font-weight: 620;
  font-variant-numeric: tabular-nums;
}
.emit-value.tuvalu { fill: var(--teal-300); }
.emit-value.world { fill: var(--coral-300); }

/* Cut *into* the bar, in the page background, so the divisions read as
   subdivisions of one quantity rather than as 16 separate blocks. */
.emit-tick { stroke: var(--ocean-950); stroke-width: 1.5; }

.emit-mark { stroke: var(--text-300); stroke-width: 1; }
.emit-mark-label {
  font-size: 10px;
  font-weight: 600;
  fill: var(--text-300);
}
.emit-remainder { font-size: 10px; fill: var(--text-500); }
.emit-unit { font-size: 9.5px; fill: var(--text-500); }

.emit-note {
  margin-top: 0.4rem !important;
  font-size: 0.74rem;
  line-height: 1.45;
  color: var(--text-500) !important;
}

/* ---- the land comparison ---- */

.land { width: 100%; max-width: 27rem; height: auto; display: block; }

.land-name { font-size: 11.5px; fill: var(--text-300); }
.land-when { font-size: 10px; fill: var(--text-500); }
.land-value {
  font-size: 11.5px;
  font-weight: 620;
  font-variant-numeric: tabular-nums;
}

.land-row.natural .land-bar { fill: var(--advance-300); }
.land-row.natural .land-value { fill: var(--advance-300); }
.land-row.built .land-bar { fill: var(--built-300); }
.land-row.built .land-value { fill: var(--built-300); }


/* ---- the ballot queue (Leaving) ---- */

.queue-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  /* Wide enough for all 31 blocks on one line: a queue that wraps reads as two
     groups rather than as one length. */
  max-width: 27rem;
}
.qyear {
  width: 0.55rem;
  height: 1.5rem;
  border-radius: 0.12rem;
  background: var(--ocean-700);
}
/* The one round that has actually run. The rest is arithmetic on a published
   annual cap, not a schedule anyone has committed to. */
.qyear.now { background: var(--built-300); }

.queue-note {
  margin: 0.5rem 0 0;
  font-size: 0.75rem;
  color: var(--text-500) !important;
}
.queue-note-2 {
  margin-top: 0.5rem !important;
  font-size: 0.74rem;
  line-height: 1.45;
  color: var(--text-500) !important;
}

/* ---- methodology reading list ---- */

.reading {
  margin: 0.4rem 0 0;
  padding-left: 1.1rem;
}
.reading li { margin-bottom: 0.55rem; }

/* ----------------------------------------------------------- scrubber */

.scrubber {
  margin-top: auto;
  padding: 0.75rem 0.85rem 0.85rem;
  background: var(--ocean-900);
  border: 1px solid var(--ocean-700);
  border-radius: 8px;
}

.scrub-head { display: flex; align-items: baseline; gap: 0.55rem; }

.scrub-year {
  font-family: var(--font-display);
  font-size: 1.35rem;
  font-weight: 600;
  color: var(--text-100);
  font-variant-numeric: tabular-nums;
}

.scrub-caption { font-size: 0.76rem; color: var(--text-500); }

/* The years no longer play on their own, so the control has to ask. Quiet: it
   is an invitation, not an instruction, and it sits on the same baseline as the
   caption rather than becoming a third line of chrome. */
.scrub-hint {
  margin-left: auto;
  font-size: 0.72rem;
  color: var(--text-500);
  font-style: italic;
}

.scrub-ticks { display: flex; gap: 2px; margin: 0.5rem 0 0.3rem; }
.scrub-ticks .tick {
  flex: 1;
  height: 3px;
  background: var(--teal-500);
  border-radius: 1px;
}
.scrub-ticks .tick.low { background: var(--ocean-700); }

.scrubber input[type="range"] { width: 100%; accent-color: var(--teal-300); }

.scrub-flag {
  margin-top: 0.5rem !important;
  font-size: 0.74rem;
  line-height: 1.45;
  color: var(--coral-300) !important;
}

.scrubber.low-confidence { border-color: var(--coral-700); }

/* ------------------------------------------------------------- footer */

.colophon {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem 1.25rem;
  padding: 0.7rem clamp(1.25rem, 4vw, 3.5rem) 0.9rem;
  border-top: 1px solid var(--ocean-800);
  font-size: 0.72rem;
  color: var(--text-500);
}

.method-link {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--teal-300);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

/* ------------------------------------------------------------- dialog */

#method-dialog {
  width: min(60rem, 92vw);
  max-height: 86vh;
  padding: 0;
  border: 1px solid var(--ocean-700);
  border-radius: 10px;
  background: var(--ocean-900);
  color: var(--text-300);
}

#method-dialog::backdrop { background: rgb(3 9 16 / 0.72); }

.dialog-inner { display: flex; flex-direction: column; max-height: 86vh; }

.dialog-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.1rem clamp(1rem, 3vw, 2rem);
  border-bottom: 1px solid var(--ocean-800);
  position: sticky;
  top: 0;
  background: var(--ocean-900);
}

.dialog-head h2 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 560;
  font-size: 1.3rem;
  color: var(--text-100);
}

.dialog-close {
  appearance: none;
  background: none;
  border: 1px solid var(--ocean-700);
  border-radius: 6px;
  width: 2rem; height: 2rem;
  color: var(--text-300);
  font-size: 1.15rem;
  line-height: 1;
  cursor: pointer;
}
.dialog-close:hover { color: var(--text-100); border-color: var(--teal-500); }

.dialog-body {
  overflow-y: auto;
  padding: 1.2rem clamp(1rem, 3vw, 2rem) 2rem;
}

.dialog-body h3 {
  font-family: var(--font-text);
  font-size: 0.74rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--teal-300);
  margin: 1.8rem 0 0.5rem;
}

.dialog-body p {
  margin: 0 0 0.85rem;
  font-size: 0.88rem;
  line-height: 1.62;
  max-width: 68ch;
}

.dialog-body code {
  font-size: 0.85em;
  color: var(--text-100);
  background: var(--ocean-950);
  padding: 0.1em 0.35em;
  border-radius: 3px;
}

/* The source table is the widest thing in the dialog, so it scrolls inside
   its own box rather than making the dialog scroll sideways. */
.table-wrap {
  overflow-x: auto;
  margin: 0 0 0.9rem;
  max-width: 68ch;
}

.source-table {
  border-collapse: collapse;
  font-size: 0.82rem;
  line-height: 1.45;
  min-width: 34rem;
}

.source-table th,
.source-table td {
  text-align: left;
  vertical-align: top;
  padding: 0.42rem 1rem 0.42rem 0;
  border-bottom: 1px solid var(--ocean-800);
}

.source-table th {
  font-family: var(--font-text);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-500);
  font-weight: 600;
}

/* The middle column is a file path: it may wrap, but not mid-word, or the
   table's min-width fights the dialog on narrow screens. */
.source-table td:nth-child(2) {
  color: var(--text-100);
  overflow-wrap: anywhere;
}

/* ------------------------------------------------------- narrow screens */

@media (max-width: 860px) {
  body { overflow-y: auto; height: auto; min-height: 100dvh; }

  .stage {
    grid-template-columns: 1fr;
    grid-template-rows: 42dvh auto;
  }

  .panel { overflow: visible; }
  .masthead h1 { font-size: clamp(1.5rem, 1.2rem + 3vw, 2.2rem); }
}

/* ----------------------------------------------------- reduced motion */

/* Blanket, not a list. The previous version named .points and .panel, which
   left the point-symbol fade, the Fogafale marker fade and anything added later
   still animating — a per-rule allowlist only stays correct if every future
   edit remembers it exists. Near-zero rather than `none` so that end states are
   still applied and nothing that waits on a transition can hang.

   The d3 camera and symbol transitions are gated separately, in src/map.js
   against the same media query, because those are attribute tweens rather than
   CSS. Chrome's "Emulate prefers-reduced-motion" affects window.matchMedia too,
   so one toggle tests both halves. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
