/* ==========================================================================
   Groundfact — main stylesheet
   Hand-written, no framework. One file. CSS custom properties for theming.

   Design language: quiet, editorial, statistical — closer to a well-set
   research publication (think Our World in Data) than a SaaS dashboard.
   Generous whitespace, restrained colour, serif for headings and reading
   copy, a plain system sans for UI chrome (nav, labels, buttons).

   Theme strategy (do not deviate — see project contract):
     - LIGHT is the default and the primary theme. The complete light
       palette is defined on bare :root.
     - Dark tokens are defined ONLY under :root[data-theme="dark"], set by
       the inline bootstrap script in base.html from localStorage
       ("gf-theme"), or by the header theme-toggle button.
     - There is deliberately NO `@media (prefers-color-scheme: dark)`
       block: an unset choice always renders light, even on a dark-mode
       OS. This is a product decision, not an oversight.
   No colour is ever defined only inside a media query.
   ========================================================================== */

:root {
  /* --- Palette: light (default) ------------------------------------- */
  /* Cool grey / petrol teal (2026-08-09). The previous warm-cream +
     burnt-orange pair sat almost on top of PrivateNode's cream + red, so
     Groundfact read as a sibling product rather than its own thing. Same
     token relationships, no yellow cast, accent moved a full quadrant
     away from red. */
  --gf-bg: #f4f6f7;
  --gf-surface: #ffffff;
  --gf-ink: #14181b;
  --gf-muted: #5a666e;
  --gf-line: #dde4e7;
  --gf-accent: #0f766e;
  --gf-accent-wash: #e6f2f1;
  --gf-grid: #e8edef;

  /* Text/iconography that sits ON a filled --gf-accent surface. Not a new
     hue: white in light mode, and the page's own dark ground in dark mode,
     where the accent is a bright teal that white text fails against
     (1.9:1). With this pair both themes clear 5:1. */
  --gf-on-accent: #ffffff;

  /* The chrome band: a dark band in light mode that inverts in dark mode
     (design concepts, screen 1a). Both headers are built on it -- the app
     shell's .context-bar and, since the homepage redesign, the global
     .site-header too -- so the chrome reads the same whichever half of the
     site you are on. It is the ONLY inverted surface: everything below a
     header sits on the ordinary page ground. */
  --gf-hdr: #1b2226;
  --gf-hdr-ink: #eef2f4;
  --gf-hdr-muted: #97a4ac;
  --gf-hdr-line: #2e373c;
  /* The accent as it must read ON the band. It is simply the *other*
     theme's accent, because the band is the inverted surface: the teal that
     works on the page ground fails on it and vice versa. Measured on the
     band, 8.66:1 in light and 4.86:1 in dark, against 2.94:1 / 1.65:1 for
     the page's own accent on that same ground -- which is why the
     wordmark's two accent dots need it and would otherwise vanish. */
  --gf-hdr-accent: #2dd4bf;
  /* The band's own derived pair, consumed by the theme toggle's hover fill
     and border. Literals for the reason given under the derived tokens
     below: 8% and 22% of --gf-hdr-ink into --gf-hdr / --gf-hdr-line. */
  --gf-hdr-sunken: #2c3336;
  --gf-hdr-line-strong: #586064;

  /* --- Derived tokens ---------------------------------------------------
     Derived by construction, literal by necessity. Each is what
     color-mix(in srgb, <ink> N%, <base>) produces from the tokens above,
     measured rather than eyeballed -- but written out, and written out
     again in the dark block below, because color-mix() is a progressive
     feature (Chrome 111+, Safari 16.2+, Firefox 113+) and a custom property
     is the worst possible place to depend on one.

     A colour function a browser cannot parse is still *valid* as a custom
     property value -- custom properties accept almost any token sequence --
     so the declaration is accepted, the fallback declaration underneath it
     never wins, and the failure surfaces much later at substitution time as
     "invalid at computed-value time" in whatever consumes it. For a
     background that means `transparent`. That shipped: the citation
     popover's "Open at <source>" button, whose hover fill is
     --gf-accent-strong, went to transparent and put white label text on a
     white card -- on touch, the only route to a source -- with a fully
     green test suite. Foundational tokens do not get to be progressive. */
  --gf-bg-sunken: #ebedee;
  --gf-line-strong: #b1b7ba;
  --gf-accent-strong: #10655f;
  /* a:visited, likewise: 45% muted into the accent. */
  --gf-accent-visited: #316f6e;
  --gf-shadow-sm: 0 1px 2px rgba(20, 24, 27, 0.08);
  /* One step of elevation above --gf-shadow-sm, and deliberately barely
     there: a 1px contact edge plus a wide, very low-alpha ambient one. Used
     by the single element on the site that is genuinely raised off the page
     -- the homepage's computed analysis. Depth is the only thing marking it
     out, so it has to read as lifted, not as a floating dialog, and nothing
     else on the page may borrow it or the hierarchy collapses. */
  --gf-shadow-md: 0 1px 2px rgba(20, 24, 27, 0.05), 0 10px 28px -8px rgba(20, 24, 27, 0.13);
  /* Leftward elevation for the chat rail's dividing line (.zone-c): the
     rail sits *above* the widget column and expands over it, and this is
     the only cue at rest that expanding will cover rather than compress.
     Literal rgba, never color-mix() in a custom property -- an
     unresolvable color-mix() makes the whole declaration invalid at
     computed-value time and the consuming rule silently loses its value. */
  --gf-elev-rail: -10px 0 26px rgba(20, 24, 27, 0.07);

  /* --- Type ------------------------------------------------------------ */
  --gf-font-serif: ui-serif, Georgia, "Iowan Old Style", "Times New Roman", serif;
  --gf-font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --gf-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  --line-height-heading: 1.15;
  --line-height-body: 1.6;

  /* --- Spacing & shape --------------------------------------------------- */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 1rem;
  --space-4: 1.5rem;
  --space-5: 2.5rem;
  --space-6: 4rem;
  --radius-sm: 4px;
  --radius-md: 8px;

  /* Two measures, deliberately. --max-width is a *reading* measure: 46rem is
     roughly 75 characters of this serif at body size, and every prose page
     (/about, /privacy, indicator copy) and .home-prose (34rem, tighter
     still) depends on it. Never widen it to fill a screen.

     --max-width-wide is the *shell* measure: the outer edges the site
     header, the site footer and every homepage section share, so the
     wordmark, the nav and the hero figure below all sit on the same two
     vertical lines. 76rem = 1216px; with the shell's own padding-inline it
     is still fully gutter'd at 1280px, ~75px each side at 1366px and ~112px
     at 1440px -- it fills a laptop without ever touching the edges. */
  --max-width: 46rem;
  --max-width-wide: 76rem;
}

/* --- Palette: dark, explicit only (no prefers-color-scheme fallback) --- */
:root[data-theme="dark"] {
  --gf-bg: #0e1417;
  --gf-surface: #151d21;
  --gf-ink: #eef2f4;
  --gf-muted: #93a1a9;
  --gf-line: #263136;
  --gf-accent: #2dd4bf;
  --gf-accent-wash: #10221f;
  --gf-grid: #1b262b;

  /* #2dd4bf is bright enough that white text on it reads at 1.9:1; the
     page's own ground reads at 10:1. */
  --gf-on-accent: #0e1417;

  /* Zone A band inverts: light-on-dark everywhere else, so the band itself
     becomes the light element on the page. */
  --gf-hdr: #eef2f4;
  --gf-hdr-ink: #14181b;
  --gf-hdr-muted: #5a666e;
  --gf-hdr-line: #cdd7db;
  --gf-hdr-accent: #0f766e;
  --gf-hdr-sunken: #dde1e3;
  --gf-hdr-line-strong: #a4adb1;

  /* The derived tokens again -- see the note in :root. They used to be
     inherited from :root and re-resolve here through --gf-ink; with
     literals they have to be restated, which is the price of not depending
     on color-mix(). */
  --gf-bg-sunken: #171d20;
  --gf-line-strong: #525b60;
  --gf-accent-strong: #50d9c9;
  --gf-accent-visited: #5bbdb5;

  --gf-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  /* Same geometry, re-weighted for a dark ground: a shadow on #0e1417 has
     very little room to darken, so the lift is carried by a harder contact
     edge and a deeper ambient than the light theme needs. */
  --gf-shadow-md: 0 1px 2px rgba(0, 0, 0, 0.35), 0 10px 28px -8px rgba(0, 0, 0, 0.6);
  --gf-elev-rail: -10px 0 26px rgba(0, 0, 0, 0.45);
}

/* ==========================================================================
   Reset & base
   ========================================================================== */

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

html {
  -webkit-text-size-adjust: 100%;
  /* A citation-chip popover (position: absolute, anchored to its chip) was
     found overflowing the viewport's right edge at exactly 1250px -- a real
     tablet's portrait width the site owner tested on. The popover itself is
     already clamped (width: min(320px, 88vw)) and several contexts flip its
     anchor per the comment above .citation-chip__popover, but a handful of
     in-between widths remained fixable only by refusing the page itself to
     scroll horizontally, which is what every other rule on this page already
     assumes ("no horizontal overflow at any width"). Vertical scrolling is
     completely unaffected. */
  overflow-x: hidden;
}

body {
  margin: 0;
  background: var(--gf-bg);
  color: var(--gf-ink);
  font-family: var(--gf-font-serif);
  font-size: 1.0625rem;
  line-height: var(--line-height-body);
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  /* clip, not hidden: same guarantee (the page still cannot be scrolled
     sideways -- verified at 320/375/768/1280/1440/1920), but `hidden` makes
     <body> a scroll container, and a scroll container whose height is its
     content silently kills `position: sticky` for everything inside it --
     which is exactly what the chat rail below now depends on. html keeps
     `hidden`: its overflow propagates to the viewport, so it never becomes
     a scroll container itself. */
  overflow-x: clip;
}

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

a {
  color: var(--gf-accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.15em;
}

a:visited {
  color: var(--gf-accent-visited);
}

a:hover {
  text-decoration-thickness: 2px;
}

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

h1,
h2,
h3,
h4 {
  font-family: var(--gf-font-serif);
  line-height: var(--line-height-heading);
  font-weight: 600;
  margin: 0 0 var(--space-3);
}

p {
  margin: 0 0 var(--space-3);
}

code {
  font-family: var(--gf-font-mono);
  font-size: 0.9em;
  background: var(--gf-bg-sunken);
  padding: 0.1em 0.35em;
  border-radius: var(--radius-sm);
}

.wrap {
  max-width: var(--max-width);
  margin-inline: auto;
  padding-inline: var(--space-4);
}

/* The shell container: same gutters as .wrap, the wide measure instead of
   the reading one. Used by the site header and footer so their contents sit
   on exactly the same two edges as the homepage's own columns (.home-wide,
   further down, is deliberately the identical box) rather than in a 46rem
   strip above 76rem of content. */
.shell {
  max-width: var(--max-width-wide);
  margin-inline: auto;
  padding-inline: var(--space-4);
}

.skip-link {
  position: absolute;
  left: -999px;
  top: auto;
}

.skip-link:focus {
  left: var(--space-3);
  top: var(--space-3);
  z-index: 100;
  background: var(--gf-surface);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  box-shadow: var(--gf-shadow-sm);
}

/* ==========================================================================
   Header
   ========================================================================== */

/* The site header is the one inverted surface on the site: a dark band in
   light mode that becomes the light element in dark mode. That is the whole
   of the site owner's ask -- "keep everything white besides the header in
   light mode and vice versa" -- and it puts .site-header on exactly the
   --gf-hdr-* pair .context-bar already uses on the app pages, so the chrome
   reads the same whichever half of the site you are on. Everything below a
   header sits on the ordinary page ground; depth on the page itself comes
   from --gf-shadow-md, not from a second block of colour.

   It re-points the generic surface tokens onto the --gf-hdr-* set for its
   own subtree rather than restating a colour on each descendant, so the
   wordmark, the nav, the theme toggle and the logo's two accent dots all
   follow with no per-component override -- and anything added to the header
   later inherits the inversion for free.

   Two derived tokens are re-pointed as well, and they are exactly the two
   the header consumes -- the theme toggle's hover fill and its hover
   border. Those cannot simply be inherited: :root's --gf-bg-sunken is a
   sunken step of the *page's* ground, so on the band it would paint a pale
   chip on a dark bar. --gf-hdr-sunken / --gf-hdr-line-strong are the band's
   own equivalents, defined per theme at the top of this file.

   Contrast on the band, measured both ways: --gf-hdr-ink 14.3:1 light /
   15.9:1 dark, --gf-hdr-muted (the nav links) 6.3:1 light / 5.2:1 dark. */
.site-header {
  background: var(--gf-hdr);
  --gf-bg: var(--gf-hdr);
  --gf-surface: var(--gf-hdr);
  --gf-ink: var(--gf-hdr-ink);
  --gf-muted: var(--gf-hdr-muted);
  --gf-line: var(--gf-hdr-line);
  --gf-accent: var(--gf-hdr-accent);
  --gf-bg-sunken: var(--gf-hdr-sunken);
  --gf-line-strong: var(--gf-hdr-line-strong);
  color: var(--gf-hdr-ink);
}

/* The band runs edge to edge; its CONTENTS sit on the content column.
   Two rounds of owner feedback settled this. Painting only the inner box made
   the bar stop short of the viewport edges and read as "chopped"; leaving the
   wordmark on the old narrow .wrap left it 288px inside everything below it.
   Full-bleed ground plus a .shell inner is the pairing that satisfies both:
   the dark runs to the edges, and the logo starts on exactly the same x as the
   headline and the chart. */
.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block: var(--space-3);
}

.site-header__right {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--gf-ink);
  text-decoration: none;
  font-family: var(--gf-font-sans);
}

/* Mark and wordmark are one lockup: the mark's cap is optically matched to
   the wordmark's, so the two sizes move together or not at all. 32px against
   1.4rem holds the same relationship the 28px/1.25rem pair had, one step up
   -- enough to carry a 76rem shell without becoming a badge. .context-bar is
   a ~64px toolbar rather than a header block and keeps the smaller pair. */
.gf-logo {
  width: 32px;
  height: 32px;
  color: var(--gf-ink);
  flex: none;
}

.wordmark__text {
  font-size: 1.4rem;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.context-bar .gf-logo {
  width: 28px;
  height: 28px;
}

.context-bar .wordmark__text {
  font-size: 1.25rem;
}

/* Phone widths: the header row is wordmark + nav + toggle, and the larger
   lockup is what tips it into wrapping. Step it back down (24px/1.1rem --
   still above where it was before) and tighten the gaps rather than letting
   the row break in two. Measured at 320px: the row fits with room to spare,
   which is the point -- see the note in the 40rem block further down about
   why it must never stack. */
@media (max-width: 30rem) {
  .site-header__inner {
    gap: var(--space-3);
    padding-block: var(--space-2);
  }

  .site-header__right {
    gap: var(--space-3);
  }

  .site-header .gf-logo {
    width: 24px;
    height: 24px;
  }

  .site-header .wordmark__text {
    font-size: 1.1rem;
  }

  .site-header .site-nav {
    gap: var(--space-3);
  }

  .site-header .site-nav a {
    font-size: 0.85rem;
  }
}

.site-nav {
  display: flex;
  gap: var(--space-4);
  font-family: var(--gf-font-sans);
}

.site-nav a {
  color: var(--gf-muted);
  text-decoration: none;
  font-size: 0.95rem;
}

.site-nav a:hover {
  color: var(--gf-ink);
}

.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 1px solid var(--gf-line);
  border-radius: 999px;
  background: var(--gf-surface);
  color: var(--gf-ink);
  cursor: pointer;
}

.theme-toggle:hover {
  border-color: var(--gf-line-strong);
  background: var(--gf-bg-sunken);
}

.theme-toggle__icon--moon {
  display: none;
}

:root[data-theme="dark"] .theme-toggle__icon--sun {
  display: none;
}

:root[data-theme="dark"] .theme-toggle__icon--moon {
  display: block;
}

/* ==========================================================================
   Main content areas
   ========================================================================== */

main {
  display: block;
  padding-block: var(--space-6) var(--space-5);
}

/* On the homepage the first element is the hero, which the site header
   should introduce rather than stand well clear of: the figure has to lead
   the first screen, and 4rem of empty page above the headline is 4rem the
   chart does not get. */
.page-home main {
  padding-block-start: var(--space-3);
}

/* .hero* lives with the rest of the homepage, at the bottom of this file. */

.prose {
  max-width: 38rem;
}

.prose h1 {
  font-size: 2rem;
  margin-bottom: var(--space-4);
}

/* ==========================================================================
   Citation chip — the product's signature visual element (spec §8.4)
   [favicon 16px] Source · dataset · snapshot date, the whole chip a link to
   the origin, hover/focus popover with full attribution, licence and an
   "as of" build stamp.

   Anatomy: a square hairline box on the surface (not a pill) — it reads as
   an object with edges rather than a decorative tag, and holds up at small
   sizes stacked several across a row (approved interface concepts,
   screen 1d: "the default everywhere").
   ========================================================================== */

.citation-chip-wrap {
  position: relative;
  display: inline-flex;
  vertical-align: baseline;
  max-width: 100%;
}

.citation-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  padding: 0.3em 0.6em;
  border: 1px solid var(--gf-line);
  border-radius: var(--radius-sm);
  background: var(--gf-surface);
  color: var(--gf-muted);
  font-family: var(--gf-font-sans);
  font-size: 0.8rem;
  line-height: 1.3;
  text-decoration: none;
  max-width: 100%;
}

.citation-chip strong {
  color: var(--gf-ink);
  font-weight: 600;
}

.citation-chip-wrap:hover .citation-chip,
.citation-chip:focus-visible {
  border-color: var(--gf-accent);
  box-shadow: inset 0 -2px 0 var(--gf-accent);
}

/* Never colour alone: staleness also spells itself out in words. */
.citation-chip--stale {
  color: var(--gf-accent);
}

.citation-chip__stale-label {
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 0.7em;
  color: var(--gf-accent);
}

.citation-chip__favicon {
  flex: none;
  width: 15px;
  height: 15px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 3px;
  background: var(--gf-accent-wash);
  border: 1px solid var(--gf-line);
  color: var(--gf-accent);
  font-family: var(--gf-font-sans);
  font-size: 0.55rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  line-height: 1;
  overflow: hidden;
  /* Real favicons are fetched at ingest-time and cached to
     /static/favicons/{domain}.png (spec §8.4); until a source has one this
     monogram (its short_name, e.g. "WB", "UIS") fills the same slot rather
     than a blank square. */
}

.citation-chip__favicon img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.citation-chip__label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* Without this, the label's own nowrap text sets the flex item's
     min-content width, which an inline-flex ancestor with no explicit
     width (.citation-chip, .citation-chip-wrap) then refuses to shrink
     below -- the ellipsis never engages and a citation-chip-heavy grid
     item (e.g. the reconciliation spotlight) forces its whole grid track
     wider than the viewport at narrow widths (found at 375px on /nepal). */
  min-width: 0;
}

.citation-chip__dot {
  opacity: 0.55;
}

/* Small / dense-row modifier: several chips side by side (indicator-page
   attribution list, per-source table, block citation rows). */
.citation-chip--small .citation-chip {
  padding: 0.2em 0.5em;
  font-size: 0.72rem;
  gap: 0.3em;
}

.citation-chip--small .citation-chip__favicon {
  width: 13px;
  height: 13px;
  font-size: 0.5rem;
}

/* Inline modifier: a hairline underline with no box, for a citation that
   sits inside a sentence of running prose rather than a standalone row. */
.citation-chip--inline .citation-chip {
  border: none;
  border-radius: 0;
  border-bottom: 1px solid var(--gf-line);
  padding: 0 0 0.1em;
  background: transparent;
}

.citation-chip--inline.citation-chip-wrap:hover .citation-chip,
.citation-chip--inline .citation-chip:focus-visible {
  border-bottom-color: var(--gf-accent);
  box-shadow: none;
}

.citation-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
}

/* Popover: full attribution, licence, cadence, "as of" build stamp. Opens
   on hover (with a short CSS transition-delay so it doesn't flicker on a
   pass-through) and immediately on keyboard focus. */
.citation-chip__popover {
  position: absolute;
  bottom: calc(100% + 7px);
  left: 0;
  z-index: 40;
  width: min(320px, 88vw);
  background: var(--gf-surface);
  border: 1px solid var(--gf-ink);
  border-radius: var(--radius-sm);
  padding: 0.8rem 0.9rem;
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.16);
  font-family: var(--gf-font-sans);
  opacity: 0;
  visibility: hidden;
  /* --chip-pop-shift: set inline, per element, by chart.js's citation-chip
     module -- a horizontal nudge (px) applied on top of whichever edge this
     popover is anchored from, computed from its *actual* on-screen position
     so it never runs off the viewport. left:0 (the default here) and the
     left:auto;right:0 override just below are both "correctly positioned
     in most cases, not all" -- a widget-grid card's citations, for
     instance, can land in any column, so no single fixed anchor is right
     for every instance the way it is for the two contexts below. The CSS
     anchor is still the right static default (and the only positioning a
     no-JS visitor gets); this is an additive, measured correction on top
     of it, not a replacement. */
  transform: translateY(3px) translateX(var(--chip-pop-shift, 0px));
  transition:
    opacity 0.12s ease,
    transform 0.12s ease,
    visibility 0s linear 0.12s;
  pointer-events: none;
}

/* Pre-existing gap found while checking for horizontal overflow (spec: no
   horizontal overflow at any width): a popover anchored left:0 relative to
   its own chip overflows the viewport's right edge once the chip itself
   sits in the last column of a right-hand layout -- the indicator page's
   attribution aside (grid's last 19rem column) and the reconciliation
   detail cards (a 2-up grid whose second card sits flush against it).
   Anchoring from the right in exactly those two contexts, rather than
   changing the default (which is correctly positioned everywhere else,
   confirmed by measuring zero overflow on the context and topic pages at
   375/768/1024/1440px), fixes both without touching the common case. */
.indicator-page__aside .citation-chip__popover,
.recon-detail__card .citation-chip__popover {
  left: auto;
  right: 0;
}

/* [data-open="true"]: the touch-tap disclosure state, set/cleared by
   app/static/js/chart.js's citation-chip module on a coarse pointer (no
   hover to rely on there) -- see that file and partials/citation_chip.html
   for the full interaction. Mouse hover and keyboard focus (:focus-within)
   are unchanged, CSS-only, no JS involved. */
.citation-chip-wrap:hover .citation-chip__popover,
.citation-chip-wrap:focus-within .citation-chip__popover,
.citation-chip-wrap[data-open="true"] .citation-chip__popover {
  opacity: 1;
  visibility: visible;
  transform: translateY(0) translateX(var(--chip-pop-shift, 0px));
  transition-delay: 0.15s, 0.15s, 0s;
  pointer-events: auto;
}

/* Below ~480px the popover is anchored as a fixed bottom sheet rather than
   positioned relative to its own chip: a chip several columns into a
   wrapped .citation-chip-row can sit anywhere across the width of a
   320-430px viewport, and the popover's own width (min(320px, 88vw)) left-
   or right-anchored to that chip could still run off either edge -- the
   same class of bug already found and fixed for a different popover at
   1250px (see html's overflow-x comment above). Anchoring to the viewport
   instead of the chip sidesteps the geometry entirely, which matters more
   here because this is also the only way most touch visitors ever see the
   popover at all. */
@media (max-width: 30rem) {
  .citation-chip__popover {
    position: fixed;
    left: 0.75rem;
    right: 0.75rem;
    bottom: 0.75rem;
    top: auto;
    width: auto;
    max-width: none;
    z-index: 260; /* above the mobile chat slide-over (200) and its trigger (150) */
    transform: translateY(8px);
  }

  .indicator-page__aside .citation-chip__popover,
  .recon-detail__card .citation-chip__popover {
    left: 0.75rem;
    right: 0.75rem;
  }

  .citation-chip-wrap:hover .citation-chip__popover,
  .citation-chip-wrap:focus-within .citation-chip__popover,
  .citation-chip-wrap[data-open="true"] .citation-chip__popover {
    transform: translateY(0);
  }
}

.citation-chip__popover dl {
  margin: 0;
}

.citation-chip__popover-title {
  font-family: var(--gf-font-serif);
  font-size: 0.95rem;
  font-weight: 600;
  margin-bottom: 0.3rem;
}

.citation-chip__popover-attribution {
  font-size: 0.78rem;
  line-height: 1.55;
  color: var(--gf-muted);
  margin: 0 0 0.5rem;
}

.citation-chip__popover-row {
  display: flex;
  gap: 0.6rem;
  padding: 0.35rem 0;
  border-top: 1px solid var(--gf-line);
  font-size: 0.76rem;
  line-height: 1.5;
}

.citation-chip__popover-row dt {
  flex: none;
  width: 4.5rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  font-size: 0.66rem;
  color: var(--gf-muted);
}

.citation-chip__popover-row dd {
  margin: 0;
  color: var(--gf-ink);
}

.citation-chip__popover-go {
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--gf-line);
  font-size: 0.75rem;
  color: var(--gf-accent);
}

/* The popover's prominent primary action -- a real button-styled link, not
   a small text link, per the site owner's brief: this is what actually
   navigates a touch visitor to the source, since the chip's own first tap
   only opens the popover on a coarse pointer (see chart.js). */
.citation-chip__popover-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  margin-top: 0.65rem;
  padding: 0.55rem 0.8rem;
  border: none;
  border-radius: var(--radius-sm);
  background: var(--gf-accent);
  color: var(--gf-on-accent);
  font-family: var(--gf-font-sans);
  font-size: 0.8rem;
  font-weight: 600;
  text-decoration: none;
  text-align: center;
}

.citation-chip__popover-cta:hover,
.citation-chip__popover-cta:focus-visible {
  background: var(--gf-accent-strong);
  color: var(--gf-on-accent);
}

/* ==========================================================================
   Footer
   ========================================================================== */

.site-footer {
  border-top: 1px solid var(--gf-line);
  color: var(--gf-muted);
  font-family: var(--gf-font-sans);
  font-size: 0.875rem;
}

.site-footer__inner {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
  justify-content: space-between;
  padding-block: var(--space-4);
}

.site-footer__meta {
  margin: 0;
}

.site-footer__links {
  display: flex;
  gap: var(--space-3);
}

.site-footer__links a {
  color: var(--gf-muted);
  text-decoration: none;
}

.site-footer__links a:hover {
  color: var(--gf-ink);
  text-decoration: underline;
}

/* ==========================================================================
   Context shell — the three-zone app view (spec §8.3)
   ========================================================================== */

/* The global <main> (app/templates/base.html) carries top/bottom padding
   for prose pages; the app shell's Zone A wants to sit flush against the
   site header instead. :has() is a progressive enhancement here -- browsers
   without it just keep the default breathing room above Zone A, which is
   harmless. */
main:has(> .gf-app) {
  padding-block-start: 0;
}

.gf-app {
  padding-block-end: var(--space-5);
}

/* Zone A — context switcher */
.context-bar {
  background: var(--gf-hdr);
  color: var(--gf-hdr-ink);
  border-bottom: 1px solid var(--gf-hdr-line);
}

.context-bar__inner {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 0.7rem var(--space-4);
  font-family: var(--gf-font-sans);
}

.context-scope {
  position: relative;
}

.context-scope__summary {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.45rem 0.7rem;
  border: 1px solid var(--gf-hdr-line);
  background: rgba(128, 128, 128, 0.1);
  color: var(--gf-hdr-ink);
  border-radius: var(--radius-sm);
  cursor: pointer;
  list-style: none;
  min-width: 9rem;
}

.context-scope__summary::-webkit-details-marker,
.context-scope__summary::marker {
  display: none;
}

.context-scope__dot {
  width: 6px;
  height: 6px;
  flex: none;
  background: var(--gf-accent);
  border-radius: 999px;
}

.context-scope__label {
  display: flex;
  flex-direction: column;
  gap: 1px;
  text-align: left;
}

.context-scope__eyebrow {
  font-size: 0.6rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--gf-hdr-muted);
}

.context-scope__value {
  font-size: 0.9rem;
}

.context-scope__caret {
  margin-left: auto;
  font-size: 0.6rem;
  color: var(--gf-hdr-muted);
}

.context-scope[open] .context-scope__summary {
  border-color: var(--gf-accent);
}

.context-scope__menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 80;
  width: 20rem;
  max-width: 88vw;
  background: var(--gf-surface);
  color: var(--gf-ink);
  border: 1px solid var(--gf-ink);
  border-radius: var(--radius-sm);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
  padding: 0.4rem;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.context-scope__menu a {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.6rem 0.7rem;
  border-radius: var(--radius-sm);
  color: var(--gf-ink);
  text-decoration: none;
}

.context-scope__menu a:hover,
.context-scope__menu a:focus-visible {
  background: var(--gf-accent-wash);
}

.context-scope__menu a[aria-current="true"] {
  background: var(--gf-bg-sunken);
}

.context-scope__menu-note {
  font-size: 0.68rem;
  color: var(--gf-muted);
}

/* The wordmark, nav and status/theme-toggle group that now live inside the
   single Zone A band (spec §8.3, design concepts screen "1a"). The band
   itself is .context-bar, already built on the --gf-hdr-* tokens, which
   already invert under [data-theme="dark"] -- these rules only make the
   *content* (logo, nav links, toggle chrome) follow that inversion too,
   rather than the generic --gf-ink/--gf-line tokens meant for light
   surfaces. Contrast checked both ways: --gf-hdr-muted on --gf-hdr is
   6.27:1 in light and 5.45:1 in dark -- both clear AA (4.5:1). */
.context-bar .wordmark,
.context-bar .gf-logo {
  color: var(--gf-hdr-ink);
}

.context-bar .site-nav a {
  color: var(--gf-hdr-muted);
}

.context-bar .site-nav a:hover {
  color: var(--gf-hdr-ink);
}

.context-bar .theme-toggle {
  border-color: var(--gf-hdr-line);
  background: transparent;
  color: var(--gf-hdr-muted);
}

.context-bar .theme-toggle:hover {
  border-color: var(--gf-accent);
  background: transparent;
  color: var(--gf-accent);
}

.context-bar__right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-4);
  font-size: 0.78rem;
  color: var(--gf-hdr-muted);
}

.context-bar__status-item {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding-left: var(--space-3);
  border-left: 1px solid var(--gf-hdr-line);
  white-space: nowrap;
}

.context-bar__status-dot {
  width: 7px;
  height: 7px;
  background: var(--gf-accent);
  flex: none;
}

/* Found while testing a real tablet in portrait at ~1250px: with no
   shedding, .context-bar__inner (flex, nowrap by default) either forced the
   band wider than the viewport or wrapped its contents into a ragged
   second/third row as things got tight. Shedding items one at a time keeps
   it a single, tidy row all the way down -- wordmark, scope switcher and
   theme toggle always remain (the scope switcher especially must never be
   the thing dropped: it is the only way to change context). The source
   count is the least essential item (purely informational, restated on
   every widget card's freshness badge), so it goes first; the nav (Docs /
   About, both also reachable from the site footer) goes next, at the same
   80rem/64rem thresholds as the zone-split breakpoint below. */
@media (max-width: 80rem) {
  .context-bar__status-item {
    display: none;
  }
}

@media (max-width: 64rem) {
  .context-bar .site-nav {
    display: none;
  }
}

/* Phone widths (checked at 390/375/320px): shedding the source count and
   nav alone was not enough -- the scope switcher's own footprint (a
   bordered, min-width: 9rem box with a "SCOPE" eyebrow stacked above the
   value) was wide and tall enough on its own to force a second row, and
   the band as a whole read as three stacked rows eating a third of the
   viewport before any content appeared. Target here is the single ~64px
   bar the design specifies (screen "Three-zone app view"): a toolbar, not
   a header block. Wordmark, scope switcher and theme toggle are the three
   things that must always remain (never wrap, never disappear) -- getting
   them onto one row means shrinking the wordmark and stripping the scope
   switcher down to its bare "Nepal ▾" content, not touching the toggle
   (already the tallest element in the bar, and the floor for how short the
   bar can get without shrinking it too). */
@media (max-width: 30rem) {
  .context-bar__inner {
    padding-block: 0.65rem;
    gap: 0.5rem;
  }

  .context-bar .wordmark__text {
    font-size: 1rem;
  }

  .context-bar .gf-logo {
    width: 22px;
    height: 22px;
  }

  .context-scope__summary {
    min-width: 0;
    padding: 0.3rem 0.5rem;
    gap: 0.35rem;
  }

  /* The eyebrow is what forces the summary onto two lines internally; drop
     it here and let the value stand alone -- "Nepal ▾", not "SCOPE\nNepal
     ▾". It comes back at wider breakpoints where there is room to spare. */
  .context-scope__eyebrow {
    display: none;
  }

  .context-scope__value {
    font-size: 0.82rem;
    white-space: nowrap;
  }
}

/* Permalink strip: URL encodes everything (spec §8.3) — shown as a real,
   selectable readonly field rather than a decorative label. */
.permalink-strip {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 0.4rem var(--space-4);
  background: var(--gf-bg-sunken);
  border-bottom: 1px solid var(--gf-line);
  font-family: var(--gf-font-sans);
  font-size: 0.75rem;
  color: var(--gf-muted);
  overflow: hidden;
}

.permalink-strip__eyebrow {
  flex: none;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-size: 0.65rem;
}

.permalink-strip__field {
  flex: 1;
  min-width: 0;
  border: none;
  background: transparent;
  color: var(--gf-ink);
  font-family: var(--gf-font-mono);
  font-size: 0.75rem;
  padding: 0.15rem 0;
}

.permalink-strip__field:focus-visible {
  outline: 2px solid var(--gf-accent);
  outline-offset: 2px;
}

/* Zones B + C -- deliberately edge-to-edge (spec §8.3: "the three-zone app
   view should use the full viewport width"). Unlike a reading page (`/`,
   `/about`, indicator prose), which wraps in .wrap (--max-width: 46rem) for
   a comfortable line length, the app shell has no overall max-width at
   all: .gf-app is never wrapped in .wrap, by design, so this grid already
   fills whatever viewport it is given. Only the chat rail gets a fixed,
   sensible width (360-420px, clamped rather than a single rem value so it
   eases between the two rather than snapping); the widget area takes
   everything else, all the way to the edges, on everything from a small
   laptop up through ultrawide. */
.gf-zones {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0;
  align-items: start;
}

/* 80rem, not 64rem: a real tablet in portrait at ~1250px (78.125rem) sits
   just above the old 64rem cut -- it got the permanent-rail layout below
   with no bottom trigger to open it on a screen not really wide enough for
   a comfortable side-by-side rail either, leaving chat unreachable there.
   80rem gives that width the mobile/slide-over treatment instead; matched
   by the three max-width: 79.9375rem breakpoints below and by
   app/static/js/chat-toggle.js's MOBILE_QUERY, which must stay in lockstep
   with this value or the trigger becomes visible but inert. */
@media (min-width: 80rem) {
  .gf-zones {
    grid-template-columns: minmax(0, 1fr) clamp(360px, 30vw, 420px);
    transition: grid-template-columns 0.22s ease;
  }

  /* A rail, not a column that scrolls away. The panel is its own
     viewport-tall box pinned to the top of the screen; the conversation
     area (.chat-panel__body) scrolls inside it, so the composer sits on the
     bottom edge of the viewport however far the widget column has scrolled
     (site owner: "the ask about Nepal button on the desktop should be
     firmly fixed onto the bottom of the page even when user scrolls").
     sticky, not fixed, so it still starts below Zone A instead of covering
     the header band, and so it stops at the end of the grid. No z-index at
     rest on purpose: a citation popover in Zone B (z-index 40) must still
     be able to overlap the rail, exactly as it could before it was
     positioned at all. */
  .zone-c {
    position: sticky;
    top: 0;
    height: 100vh;
    box-shadow: var(--gf-elev-rail);
    transition: width 0.22s ease, margin-left 0.22s ease;
  }

  /* The rail's own top edge only reaches the top of the screen once Zone A
     and the permalink strip have scrolled away, which would leave the ask
     box hanging just below the fold on first paint -- the one moment it
     most needs to be visible. Sticking the composer to the bottom of the
     viewport as well floats it up over the conversation for exactly those
     first ~110px of scroll and is a no-op afterwards, because from then on
     that is already where it sits. It needs its own background for the
     overlap (the panel's, not a new colour). */
  .chat-panel__composer {
    position: sticky;
    bottom: 0;
    background: var(--gf-surface);
  }

  /* Expanded state (app/static/js/chat-toggle.js, #chat-expand): the panel
     expands *over* the widget column, it does not squeeze it. The grid
     tracks are deliberately untouched -- widening the chat track reflowed
     Zone B and re-wrapped the three-across widget cards to two, which is
     the opposite of what expanding a panel should do -- so the panel keeps
     its own track and overhangs to the left by exactly the extra width. A
     negative margin cannot change how the minmax(0, 1fr) track is sized, so
     nothing underneath moves by a pixel. Nested inside this same breakpoint
     so the class is inert below it even if a stored localStorage choice
     from a wider session is still applied. */
  .gf-zones--chat-expanded .zone-c {
    width: 70vw;
    margin-left: calc(clamp(360px, 30vw, 420px) - 70vw);
    z-index: 60;
  }
}

.zone-b {
  padding: var(--space-4) clamp(var(--space-4), 3vw, 3rem);
  min-width: 0;
}

.zone-b__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-4);
}

.zone-b__heading {
  margin: 0 0 0.2rem;
  font-size: 1.5rem;
}

.zone-b__subhead {
  margin: 0;
  font-family: var(--gf-font-sans);
  font-size: 0.78rem;
  color: var(--gf-muted);
}

.zone-b__stamp {
  font-family: var(--gf-font-sans);
  font-size: 0.72rem;
  color: var(--gf-muted);
  white-space: nowrap;
}

.zone-c {
  border-left: 2px solid var(--gf-accent);
  background: var(--gf-surface);
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/* Below the zone-split breakpoint, Zone C stops being a grid row (spec
   §8.3: "mobile collapses to a full-screen slide-over", not an endless
   scroll under the widget grid). position: fixed takes it out of
   document flow entirely -- it is no longer something you scroll past,
   it is a panel that slides over the content, opened by #chat-trigger and
   closed by #chat-close, the html/body scroll lock, or Escape (all wired
   in app/static/js/chat-toggle.js). */
@media (max-width: 79.9375rem) {
  .zone-c {
    position: fixed;
    inset: 0;
    z-index: 200;
    border-left: none;
    border-top: 3px solid var(--gf-accent);
    box-shadow: 0 -16px 40px rgba(14, 20, 23, 0.26);
    transform: translateY(100%);
    transition: transform 0.28s ease;
  }

  .zone-c[data-open="true"] {
    transform: translateY(0);
  }

  .chat-panel__body {
    padding-bottom: var(--space-5);
  }
}

/* html/body scroll lock while the slide-over is open (set/cleared by
   chat-toggle.js). Applied to both because iOS Safari needs the lock on
   body specifically; documentElement covers everything else. */
html.gf-scroll-lock,
body.gf-scroll-lock {
  overflow: hidden;
  height: 100%;
}

/* Close control, top-right of the slide-over -- only meaningful once Zone
   C is an overlay, so it stays out of the way of the permanent rail. */
.chat-panel__close {
  display: none;
}

@media (max-width: 79.9375rem) {
  .chat-panel__close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    top: 0.7rem;
    right: 0.7rem;
    z-index: 1;
    width: 2.25rem;
    height: 2.25rem;
    border: 1px solid var(--gf-line);
    border-radius: 999px;
    background: var(--gf-surface);
    color: var(--gf-muted);
    font-size: 1.15rem;
    line-height: 1;
    cursor: pointer;
  }

  .chat-panel__close:hover,
  .chat-panel__close:focus-visible {
    color: var(--gf-accent);
    border-color: var(--gf-accent);
  }
}

/* Expand/collapse control, sitting ON the dividing line itself (site owner's
   sketch: a horizontal double-arrow on the rule between the two sides) --
   only meaningful on the permanent rail (>=80rem); below that the panel is
   already the full-screen slide-over above, so there is nothing to expand
   and the control is display:none, matching .chat-panel__close's inverse.
   It is absolutely positioned against .zone-c (which is `position: sticky`
   at this breakpoint, so it is the containing block) and centred on the
   left border, half over each side. The button itself is a 44x44 hit
   target; the visible mark inside it is a much smaller pill, so the line
   stays quiet. Markup + icon swap in partials/chat_panel.html; behaviour,
   localStorage persistence, Escape/click-away and the post-resize chart
   call in app/static/js/chat-toggle.js. */
.chat-panel__expand {
  display: none;
}

@media (min-width: 80rem) {
  .chat-panel__expand {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    top: 50%;
    /* -1px, not 0: `left` is measured from the padding box, so 0 would
       centre the mark on the inner edge of the 2px accent border rather
       than on the line itself. */
    left: -1px;
    z-index: 1;
    width: 2.75rem;
    height: 2.75rem;
    padding: 0;
    transform: translate(-50%, -50%);
    border: none;
    background: none;
    color: var(--gf-muted);
    cursor: pointer;
  }

  .chat-panel__expand-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.35rem;
    height: 2.4rem;
    border: 1px solid var(--gf-line);
    border-radius: 999px;
    background: var(--gf-surface);
    box-shadow: var(--gf-elev-rail);
  }

  .chat-panel__expand:hover,
  .chat-panel__expand:focus-visible {
    color: var(--gf-accent);
  }

  .chat-panel__expand:hover .chat-panel__expand-mark,
  .chat-panel__expand:focus-visible .chat-panel__expand-mark {
    border-color: var(--gf-accent);
  }
}

.chat-panel__expand-icon--collapse {
  display: none;
}

#chat-expand[aria-expanded="true"] .chat-panel__expand-icon--expand {
  display: none;
}

#chat-expand[aria-expanded="true"] .chat-panel__expand-icon--collapse {
  display: block;
}

/* Persistent trigger for Zone C on narrow viewports (spec §8.3 follow-up:
   "no affordance to open it"). A bottom bar rather than a FAB, matching
   the mobile screen in the approved design -- fixed, always visible, and
   labelled in full ("Ask about <context>'s data") rather than an icon
   alone, so its purpose reads without guessing. Real aria-expanded/
   aria-controls, not a decorative button: app/static/js/chat-toggle.js is
   what makes those states true.

   Redesigned full-width and accent-coloured (site owner feedback, testing
   on a real tablet at ~1250px): the original quiet surface-coloured bar
   with a thin accent top border read as part of the page chrome, not as an
   affordance -- he could not find it. A solid --gf-accent bar the full
   width of the viewport, white text, no ambiguity, is the same treatment
   the design gives the primary "Ask" submit button. */
.chat-trigger {
  display: none;
}

@media (max-width: 79.9375rem) {
  .chat-trigger {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.65rem;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 150;
    padding: 1rem var(--space-4);
    border: none;
    background: var(--gf-accent);
    box-shadow: 0 -10px 24px rgba(14, 20, 23, 0.22);
    font-family: var(--gf-font-sans);
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--gf-on-accent);
    cursor: pointer;
    text-align: center;
  }

  .chat-trigger:hover,
  .chat-trigger:focus-visible {
    background: var(--gf-accent-strong);
  }

  /* The slide-over already carries its own close control and covers the
     screen; showing the bar underneath it too would just be a second,
     redundant hit target stacked behind the panel. */
  .chat-trigger[aria-expanded="true"] {
    display: none;
  }

  .chat-trigger__mark {
    flex: none;
    width: 1.1rem;
    height: 1.1rem;
    border: 1.5px solid var(--gf-on-accent);
    position: relative;
  }

  .chat-trigger__mark::after {
    content: "";
    position: absolute;
    left: 1px;
    bottom: -6px;
    width: 0;
    height: 0;
    border-left: 4px solid var(--gf-on-accent);
    border-top: 4px solid transparent;
  }

  .chat-trigger__label {
    letter-spacing: 0.01em;
  }

  /* Room for the fixed bottom bar so it never overlaps the last widget
     card or the composer hint. */
  .gf-app {
    padding-block-end: 5rem;
  }
}

/* ==========================================================================
   Widget grid (Zone B, spec §8.3)
   ========================================================================== */

.widget-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: 1px;
  background: var(--gf-line);
  border: 1px solid var(--gf-line);
}

.widget-card {
  background: var(--gf-surface);
  padding: var(--space-3) var(--space-4) var(--space-3);
  display: flex;
  flex-direction: column;
  min-height: 15rem;
  /* Grid items default to min-width: auto (their content's min-content
     size); without this a card can force its whole grid track wider than
     the viewport rather than let long unbroken text/chips shrink or wrap. */
  min-width: 0;
}

.widget-card__head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin-bottom: 0.1rem;
}

.widget-card__title {
  margin: 0;
  font-size: 1rem;
  line-height: 1.3;
}

.widget-card__title a {
  color: inherit;
  text-decoration: none;
}

.widget-card__title a:hover {
  text-decoration: underline;
}

.widget-card__badge {
  margin-left: auto;
  flex: none;
  font-family: var(--gf-font-sans);
  font-size: 0.62rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--gf-muted);
  border: 1px solid var(--gf-line);
  border-radius: var(--radius-sm);
  padding: 0.15rem 0.4rem;
  white-space: nowrap;
}

.widget-card__unit {
  font-family: var(--gf-font-sans);
  font-size: 0.75rem;
  color: var(--gf-muted);
  margin-bottom: var(--space-2);
}

.widget-card__value-row {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.2rem;
}

.widget-card__value {
  font-size: 1.5rem;
  letter-spacing: -0.02em;
}

.widget-card__year {
  font-family: var(--gf-font-sans);
  font-size: 0.75rem;
  color: var(--gf-muted);
}

.widget-card__headline {
  font-family: var(--gf-font-sans);
  font-size: 0.78rem;
  line-height: 1.4;
  color: var(--gf-muted);
  margin: 0.3rem 0 0;
}

.widget-card__chart {
  height: 5.5rem;
  margin: 0.3rem 0 var(--space-2);
}

.widget-card__foot {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.widget-card__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.8rem;
  padding-top: 0.55rem;
  border-top: 1px solid var(--gf-line);
  font-family: var(--gf-font-sans);
  font-size: 0.72rem;
  color: var(--gf-muted);
}

.widget-card__actions button,
.widget-card__actions a.widget-card__ask {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: var(--gf-muted);
  cursor: pointer;
}

.widget-card__actions button:hover,
.widget-card__actions button:focus-visible,
.widget-card__actions a.widget-card__ask:hover {
  color: var(--gf-accent);
}

.widget-card__ask {
  margin-left: auto;
  color: var(--gf-accent) !important;
  text-decoration: none;
  border-bottom: 1px solid var(--gf-accent);
  white-space: nowrap;
}

.widget-card--unavailable {
  color: var(--gf-muted);
}

/* Widgets span the full grid row on the narrowest layout so a card is never
   squeezed under 15rem, per the responsive-to-320px requirement. */
@media (max-width: 26rem) {
  .widget-grid {
    grid-template-columns: 1fr;
  }

  /* At the 320px floor, a chip anywhere but the card's left padding can
     still push a 320px-wide popover past the right edge (found while
     sweeping 320px specifically, below this file's normal 375px+ target).
     Narrower here closes that gap without shrinking the popover anywhere
     it was already comfortable. */
  .citation-chip__popover {
    width: min(250px, 80vw);
  }

}

/* ==========================================================================
   Reconciliation spotlight — "two sources, two numbers" (spec §5.3, §8.3)
   ========================================================================== */

.recon-spotlight {
  grid-column: 1 / -1;
  background: var(--gf-surface);
  padding: var(--space-3) var(--space-4);
  min-width: 0; /* see .widget-card */
}

.recon-spotlight__head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin-bottom: var(--space-2);
}

.recon-badge {
  font-family: var(--gf-font-sans);
  font-size: 0.66rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gf-accent);
  border: 1px solid var(--gf-accent);
  border-radius: var(--radius-sm);
  padding: 0.15rem 0.45rem;
}

.recon-spotlight__kicker {
  font-family: var(--gf-font-sans);
  font-size: 0.78rem;
  color: var(--gf-muted);
}

.recon-spotlight__stamp {
  margin-left: auto;
  font-family: var(--gf-font-sans);
  font-size: 0.72rem;
  color: var(--gf-muted);
}

.recon-spotlight__title {
  margin: 0 0 0.2rem;
  font-size: 1.2rem;
}

/* The range line: leads every reconciled metric now, instead of every
   source's reading laid out in full (site owner feedback -- listing each
   source cost a lot of vertical space for one metric). */
.recon-range {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0 0 var(--space-2);
}

.recon-range__value {
  font-size: 1.7rem;
  letter-spacing: -0.02em;
  line-height: 1;
}

.recon-range__unit {
  font-family: var(--gf-font-sans);
  font-size: 0.85rem;
  color: var(--gf-muted);
}

.recon-range__sep {
  color: var(--gf-muted);
}

.recon-range__note {
  font-family: var(--gf-font-sans);
  font-size: 0.85rem;
  color: var(--gf-muted);
}

/* The disclosure: why the sources differ, plus the per-source readings and
   their citation chips -- present for every reader (server-rendered, not
   JS-injected), just deferred behind a native <details>/<summary> so it
   opens on click, touch and keyboard (Enter/Space) with no bespoke JS. */
.recon-disclosure {
  margin-bottom: var(--space-2);
}

.recon-disclosure__trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  list-style: none;
  cursor: pointer;
  font-family: var(--gf-font-sans);
  font-size: 0.78rem;
  color: var(--gf-accent);
  border: 1px solid var(--gf-line);
  border-radius: 999px;
  padding: 0.2rem 0.6rem 0.2rem 0.5rem;
  background: var(--gf-surface);
}

.recon-disclosure__trigger::-webkit-details-marker,
.recon-disclosure__trigger::marker {
  display: none;
}

.recon-disclosure__trigger:hover,
.recon-disclosure__trigger:focus-visible {
  border-color: var(--gf-accent);
  background: var(--gf-accent-wash);
}

.recon-disclosure__icon {
  flex: none;
  display: block;
}

.recon-disclosure__panel {
  margin-top: 0.6rem;
  border: 1px solid var(--gf-line);
  background: var(--gf-bg-sunken);
  padding: var(--space-3);
}

.recon-disclosure__explanation {
  margin: 0 0 var(--space-3);
  font-family: var(--gf-font-sans);
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--gf-ink);
  max-width: 70ch;
}

.recon-disclosure__rows {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: 1px;
  background: var(--gf-line);
  border: 1px solid var(--gf-line);
  margin-bottom: var(--space-3);
}

.recon-figure {
  background: var(--gf-surface);
  padding: var(--space-2) var(--space-3);
  min-width: 0;
}

.recon-figure__eyebrow {
  font-family: var(--gf-font-sans);
  font-size: 0.65rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gf-muted);
  margin-bottom: 0.4rem;
}

.recon-figure__value {
  font-size: 1.7rem;
  letter-spacing: -0.02em;
  line-height: 1;
}

.recon-figure__sub {
  font-family: var(--gf-font-sans);
  font-size: 0.72rem;
  color: var(--gf-muted);
  margin: 0.4rem 0 0.6rem;
}

.recon-figure__link {
  font-family: var(--gf-font-sans);
  font-size: 0.72rem;
}

/* Expanded treatment on the indicator page (design's "reconciliation,
   expanded" screen) */
.recon-detail {
  border: 1px solid var(--gf-line);
  margin: var(--space-4) 0;
}

.recon-detail__head {
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--gf-line);
}

.recon-detail__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 1px;
  background: var(--gf-line);
}

.recon-detail__card {
  background: var(--gf-surface);
  padding: var(--space-3) var(--space-4);
  min-width: 0; /* see .widget-card */
}

.recon-detail__kind {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--gf-font-sans);
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gf-muted);
  margin-bottom: var(--space-2);
}

.recon-detail__swatch {
  width: 0.9rem;
  height: 0.9rem;
  background: var(--gf-accent);
  flex: none;
}

.recon-detail__card:nth-child(2) .recon-detail__swatch {
  background: var(--gf-muted);
}

.recon-detail__value {
  font-size: 2rem;
  letter-spacing: -0.02em;
  line-height: 1;
  margin-bottom: 0.4rem;
}

.recon-detail__range-wrap {
  padding: var(--space-3) var(--space-4) 0;
}

.recon-detail > .recon-disclosure {
  padding: 0 var(--space-4) var(--space-4);
  margin-bottom: 0;
}

/* ==========================================================================
   Honest empty state (design concepts, screen 1g) — a data gap treated as a
   designed surface, not an error page.
   ========================================================================== */

.empty-state {
  border: 1px solid var(--gf-line);
  background: var(--gf-surface);
  padding: var(--space-4);
}

.empty-state__flag {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin: 0 0 var(--space-2);
}

.empty-state__marker {
  width: 0.6rem;
  height: 0.6rem;
  border: 2px solid var(--gf-accent);
  transform: rotate(45deg);
  flex: none;
}

.empty-state__flag-label {
  font-family: var(--gf-font-sans);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gf-accent);
}

.empty-state__body {
  margin: 0 0 0.5rem;
  font-size: 1.05rem;
  line-height: 1.6;
}

.empty-state__note {
  margin: 0 0 var(--space-3);
  font-family: var(--gf-font-sans);
  font-size: 0.8rem;
  color: var(--gf-muted);
  max-width: 68ch;
}

.empty-state__list {
  border: 1px solid var(--gf-line);
}

.empty-state__row {
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--gf-line);
  display: flex;
  gap: var(--space-3);
  align-items: baseline;
  flex-wrap: wrap;
  justify-content: space-between;
}

.empty-state__row:last-child {
  border-bottom: none;
}

.empty-state__row-name {
  font-size: 0.95rem;
}

.empty-state__row-detail {
  font-family: var(--gf-font-sans);
  font-size: 0.75rem;
  color: var(--gf-muted);
}

/* ==========================================================================
   Widget-card and indicator-page unavailable slot: the compact version of
   the honest empty state that fits inside a single grid cell.
   ========================================================================== */

.unavailable {
  margin-top: auto;
  font-family: var(--gf-font-sans);
  font-size: 0.8rem;
  color: var(--gf-muted);
  line-height: 1.5;
}

/* Outside a flex column (e.g. standalone below the grid), auto margin has
   nothing to push against -- use ordinary spacing instead. */
.unavailable--block {
  margin-top: var(--space-3);
}

/* ==========================================================================
   Indicator permalink page (spec §8.2, §8.3, §8.8)
   ========================================================================== */

.indicator-page {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  border: 1px solid var(--gf-line);
  background: var(--gf-surface);
}

@media (min-width: 60rem) {
  .indicator-page {
    grid-template-columns: minmax(0, 1fr) 19rem;
  }
}

.indicator-page__canonical {
  padding: 0.45rem var(--space-4);
  border-bottom: 1px solid var(--gf-line);
  background: var(--gf-bg-sunken);
  font-family: var(--gf-font-mono);
  font-size: 0.72rem;
  color: var(--gf-muted);
  overflow-x: auto;
  white-space: nowrap;
  grid-column: 1 / -1;
}

.indicator-page__main {
  padding: var(--space-4);
  min-width: 0;
}

@media (min-width: 60rem) {
  .indicator-page__main {
    border-right: 1px solid var(--gf-line);
  }
}

.indicator-page__eyebrow {
  font-family: var(--gf-font-sans);
  font-size: 0.72rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--gf-muted);
  margin-bottom: var(--space-2);
}

.indicator-page__title {
  font-size: clamp(1.6rem, 1.3rem + 1.2vw, 2.1rem);
  margin-bottom: var(--space-2);
}

.indicator-page__definition {
  font-size: 1.05rem;
  color: var(--gf-muted);
  max-width: 66ch;
  margin-bottom: var(--space-3);
}

.indicator-facts {
  display: flex;
  flex-wrap: wrap;
  border: 1px solid var(--gf-line);
  margin-bottom: var(--space-4);
}

.indicator-facts__item {
  flex: 1 1 8rem;
  padding: 0.65rem 0.8rem;
  border-right: 1px solid var(--gf-line);
  border-bottom: 1px solid var(--gf-line);
}

.indicator-facts__item:last-child {
  border-right: none;
}

.indicator-facts__key {
  font-family: var(--gf-font-sans);
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gf-muted);
  margin-bottom: 0.3rem;
}

.indicator-facts__value {
  font-size: 0.95rem;
}

.indicator-chart {
  height: 18rem;
  margin-bottom: var(--space-2);
}

.indicator-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding: 0.65rem 0;
  border-top: 1px solid var(--gf-line);
  border-bottom: 1px solid var(--gf-line);
  font-family: var(--gf-font-sans);
  font-size: 0.78rem;
  color: var(--gf-muted);
  margin-bottom: var(--space-4);
}

.indicator-actions button {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: var(--gf-muted);
  cursor: pointer;
}

.indicator-actions button:hover,
.indicator-actions button:focus-visible {
  color: var(--gf-accent);
}

.indicator-actions__ask {
  margin-left: auto;
  color: var(--gf-accent);
  border-bottom: 1px solid var(--gf-accent);
  text-decoration: none;
  white-space: nowrap;
}

.indicator-table-wrap {
  overflow-x: auto;
}

.indicator-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--gf-font-sans);
  font-size: 0.85rem;
}

.indicator-table th {
  text-align: left;
  padding: 0.55rem 0.7rem 0.55rem 0;
  border-bottom: 1px solid var(--gf-ink);
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  font-size: 0.66rem;
  color: var(--gf-muted);
  white-space: nowrap;
}

.indicator-table td {
  padding: 0.65rem 0.7rem 0.65rem 0;
  border-bottom: 1px solid var(--gf-line);
}

.indicator-table td.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.indicator-note {
  margin: var(--space-2) 0 0;
  font-family: var(--gf-font-sans);
  font-size: 0.8rem;
  line-height: 1.6;
  color: var(--gf-muted);
  border-left: 2px solid var(--gf-accent);
  padding-left: var(--space-3);
  max-width: 74ch;
}

.indicator-page__subheading {
  font-size: 1.25rem;
  margin-top: 0;
}

.indicator-page__subheading--tight {
  font-size: 1.1rem;
  margin-top: var(--space-4);
}

.indicator-page__caveats {
  font-family: var(--gf-font-sans);
  font-size: 0.85rem;
  color: var(--gf-muted);
  max-width: 70ch;
}

.aside-sdg {
  font-family: var(--gf-font-sans);
  font-size: 0.85rem;
  margin: 0;
}

.empty-state__list--spaced {
  margin-top: var(--space-3);
}

.recon-detail__prose {
  margin: 0;
  font-family: var(--gf-font-sans);
  font-size: 0.8rem;
  line-height: 1.6;
  color: var(--gf-muted);
  max-width: 74ch;
}

.indicator-page__aside {
  padding: var(--space-4) var(--space-3);
  background: var(--gf-bg);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.aside-section__heading {
  font-family: var(--gf-font-sans);
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gf-muted);
  margin-bottom: var(--space-2);
}

.aside-section__list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.aside-coverage {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  font-family: var(--gf-font-sans);
  font-size: 0.8rem;
}

.aside-coverage__row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  padding-bottom: 0.55rem;
  border-bottom: 1px solid var(--gf-line);
}

.aside-coverage__row:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

.aside-coverage__row dt {
  color: var(--gf-muted);
}

.aside-coverage__row dd {
  margin: 0;
}

.aside-caveats {
  margin: 0;
  padding-left: 1.1rem;
  font-family: var(--gf-font-sans);
  font-size: 0.8rem;
  line-height: 1.65;
  color: var(--gf-muted);
}

.aside-caveats li {
  margin-bottom: 0.45rem;
}

.aside-related a {
  font-size: 0.88rem;
}

/* ==========================================================================
   Topic landing page
   ========================================================================== */

.topic-header {
  margin-bottom: var(--space-4);
}

.topic-header__title {
  font-size: 2rem;
  margin-bottom: 0.4rem;
}

.topic-header__lede {
  color: var(--gf-muted);
  max-width: 60ch;
}

.topic-card__year {
  font-size: 0.75rem;
  color: var(--gf-muted);
}

.topic-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: 1px;
  background: var(--gf-line);
  border: 1px solid var(--gf-line);
}

.topic-card {
  background: var(--gf-surface);
  padding: var(--space-3);
}

.topic-card__title a {
  color: var(--gf-ink);
  text-decoration: none;
  font-size: 1rem;
}

.topic-card__title a:hover {
  color: var(--gf-accent);
}

.topic-card__unit {
  font-family: var(--gf-font-sans);
  font-size: 0.75rem;
  color: var(--gf-muted);
  margin: 0.3rem 0 0.5rem;
}

.topic-card__value {
  font-size: 1.2rem;
}

.topic-card__none {
  font-family: var(--gf-font-sans);
  font-size: 0.8rem;
  color: var(--gf-muted);
}

/* ==========================================================================
   Chart container + text alternative
   ========================================================================== */

.chart-container {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 4rem;
}

.chart-container__canvas {
  width: 100%;
  height: 100%;
}

.chart-data-table {
  margin-top: 0.4rem;
  font-family: var(--gf-font-sans);
}

/* A real control, not a bare disclosure triangle: the marker is removed on
   every engine and the summary is laid out as a small outlined button with
   its own chevron, drawn in CSS so no markup change is needed per call site. */
.chart-data-table > summary {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  list-style: none;
  cursor: pointer;
  padding: 0.25rem 0.6rem;
  border: 1px solid var(--gf-line);
  border-radius: 999px;
  font-size: 0.72rem;
  color: var(--gf-muted);
  background: var(--gf-surface);
}

.chart-data-table > summary::-webkit-details-marker {
  display: none;
}

.chart-data-table > summary::marker {
  content: "";
}

.chart-data-table > summary::after {
  content: "";
  width: 0.4rem;
  height: 0.4rem;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-1px) rotate(45deg);
}

.chart-data-table[open] > summary::after {
  transform: translateY(2px) rotate(225deg);
}

.chart-data-table > summary:hover {
  color: var(--gf-ink);
  border-color: var(--gf-line-strong);
}

.chart-data-table tbody th {
  font-weight: 400;
  text-align: left;
}

.chart-data-table__scroll {
  overflow-x: auto;
}

.chart-data-table table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.78rem;
  margin-top: 0.4rem;
}

.chart-data-table th,
.chart-data-table td {
  text-align: right;
  padding: 0.3rem 0.5rem;
  border-bottom: 1px solid var(--gf-line);
}

.chart-data-table th:first-child,
.chart-data-table td:first-child {
  text-align: left;
}

/* ==========================================================================
   Chat panel (Zone C) + analysis block — markup/CSS owned here; the SSE
   wiring that populates it live is a separate agent's work (see the DOM
   contract documented in app/templates/partials/chat_panel.html).
   ========================================================================== */

.chat-panel__head {
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--gf-line);
}

.chat-panel__head-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.chat-panel__mark {
  flex: none;
  width: 1rem;
  height: 1rem;
  border: 1.5px solid var(--gf-accent);
  position: relative;
}

.chat-panel__mark::after {
  content: "";
  position: absolute;
  left: 1px;
  bottom: -5px;
  width: 0;
  height: 0;
  border-left: 4px solid var(--gf-accent);
  border-top: 4px solid transparent;
}

.chat-panel__title {
  margin: 0;
  font-size: 1.05rem;
}

.chat-panel__scope-badge {
  font-family: var(--gf-font-sans);
  font-size: 0.7rem;
  color: var(--gf-muted);
  border: 1px solid var(--gf-line);
  border-radius: var(--radius-sm);
  padding: 0.15rem 0.45rem;
}

.chat-panel__trail {
  margin-left: auto;
  font-family: var(--gf-font-sans);
  font-size: 0.75rem;
  color: var(--gf-muted);
}

.chat-panel__intro {
  margin: 0.5rem 0 0;
  font-family: var(--gf-font-sans);
  font-size: 0.76rem;
  line-height: 1.55;
  color: var(--gf-muted);
}

.chat-panel__body {
  flex: 1;
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  overflow-y: auto;
  /* The rail is its own scroller now (and, expanded, an overlay sitting on
     top of the widget column): a trackpad flick that reaches the end of the
     conversation must stop there, not hand the rest of the gesture to the
     page behind it. */
  overscroll-behavior: contain;
}

.chat-panel__label {
  font-family: var(--gf-font-sans);
  font-size: 0.66rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gf-muted);
}

.chat-panel__label--spaced {
  margin-bottom: 0.6rem;
}

.suggested-chip {
  display: block;
  width: 100%;
  text-align: left;
  background: var(--gf-bg);
  border: 1px solid var(--gf-line);
  border-radius: var(--radius-sm);
  padding: 0.7rem 0.8rem;
  font-family: var(--gf-font-serif);
  font-size: 0.9rem;
  line-height: 1.4;
  color: var(--gf-ink);
  cursor: pointer;
}

.suggested-chip:hover,
.suggested-chip:focus-visible {
  border-color: var(--gf-accent);
  color: var(--gf-accent);
  background: var(--gf-accent-wash);
}

.chat-panel__notice {
  border-top: 1px solid var(--gf-line);
  padding-top: var(--space-3);
  font-family: var(--gf-font-sans);
  font-size: 0.78rem;
  color: var(--gf-muted);
  line-height: 1.55;
}

.chat-panel__composer {
  padding: var(--space-3) var(--space-4) var(--space-4);
  border-top: 1px solid var(--gf-line);
}

.chat-input {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  border: 1.5px solid var(--gf-accent);
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.75rem;
  background: var(--gf-surface);
}

.chat-input__bar {
  flex: none;
  width: 1.5px;
  height: 1.1rem;
  background: var(--gf-accent);
}

.chat-input__field {
  flex: 1;
  border: none;
  background: transparent;
  color: var(--gf-ink);
  font: inherit;
  font-size: 0.9rem;
  min-width: 0;
}

.chat-input__field::placeholder {
  color: var(--gf-muted);
}

.chat-input__field:disabled {
  color: var(--gf-muted);
  cursor: not-allowed;
}

.chat-input__submit {
  flex: none;
  background: var(--gf-accent);
  color: var(--gf-on-accent);
  border: none;
  border-radius: var(--radius-sm);
  font-family: var(--gf-font-sans);
  font-size: 0.78rem;
  padding: 0.5rem 0.8rem;
}

.chat-input__submit:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

.chat-panel__composer-hint {
  margin: 0.5rem 0 0;
  font-family: var(--gf-font-sans);
  font-size: 0.7rem;
  color: var(--gf-muted);
}

/* Analysis block (static placeholder demo; real blocks are streamed in) */
.analysis-block {
  border: 1px solid var(--gf-line);
  background: var(--gf-surface);
  border-radius: var(--radius-md);
}

.analysis-block__meta {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4) 0;
  font-family: var(--gf-font-sans);
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gf-muted);
}

.analysis-block__timestamp {
  margin-left: auto;
  letter-spacing: normal;
  text-transform: none;
  font-size: 0.72rem;
}

.analysis-block__question {
  margin: 0.6rem 0 0;
  padding: 0 var(--space-4) var(--space-3);
  font-size: 1.1rem;
  line-height: 1.4;
  font-weight: 600;
}

.analysis-block__interpretation {
  padding: var(--space-3) var(--space-4);
  background: var(--gf-bg);
  border-top: 1px solid var(--gf-line);
  border-bottom: 1px solid var(--gf-line);
}

.analysis-block__interp-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: 0.65rem;
  font-family: var(--gf-font-sans);
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gf-muted);
}

.interp-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.interp-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 0.4rem;
  font-family: var(--gf-font-sans);
  font-size: 0.75rem;
  padding: 0.3rem 0.55rem;
  border: 1px dashed var(--gf-line);
  border-radius: var(--radius-sm);
  background: var(--gf-surface);
  color: var(--gf-ink);
  text-align: left;
}

.interp-chip__key {
  color: var(--gf-muted);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-size: 0.62rem;
}

.interp-chip__edit {
  color: var(--gf-accent);
  font-size: 0.62rem;
}

.phase-indicator {
  display: flex;
  margin-top: var(--space-3);
  border: 1px solid var(--gf-line);
  background: var(--gf-surface);
  font-family: var(--gf-font-sans);
}

.phase-indicator__phase {
  flex: 1;
  padding: 0.5rem 0.6rem;
  font-size: 0.72rem;
  letter-spacing: 0.03em;
  text-align: center;
  color: var(--gf-muted);
  border-right: 1px solid var(--gf-line);
}

.phase-indicator__phase:last-child {
  border-right: none;
}

.phase-indicator__phase--done {
  color: var(--gf-ink);
}

.phase-indicator__phase--active {
  background: var(--gf-accent);
  color: var(--gf-on-accent);
}

.analysis-block__section {
  padding: var(--space-3) var(--space-4) 0;
}

.analysis-block__chart-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: 0.3rem;
}

.analysis-block__chart-head h3 {
  margin: 0;
  font-size: 1rem;
}

.analysis-block__chart-meta {
  font-family: var(--gf-font-sans);
  font-size: 0.72rem;
  color: var(--gf-muted);
  white-space: nowrap;
}

.analysis-block__chart {
  height: 14rem;
  margin: 0.5rem 0 0.75rem;
}

.analysis-block__commentary {
  font-size: 1rem;
  line-height: 1.65;
  color: var(--gf-muted);
  font-style: italic;
}

.analysis-block__citations {
  padding: 0 var(--space-4) var(--space-3);
}

.analysis-block__followups {
  padding: var(--space-3) var(--space-4) var(--space-4);
  border-top: 1px solid var(--gf-line);
  background: var(--gf-bg);
}

.followup-chip {
  font-family: var(--gf-font-sans);
  font-size: 0.78rem;
  padding: 0.4rem 0.65rem;
  border: 1px solid var(--gf-line);
  border-radius: var(--radius-sm);
  background: var(--gf-surface);
  color: var(--gf-ink);
}

/* ==========================================================================
   Live analysis blocks -- app/static/js/chat.js builds these as raw DOM
   nodes against the same classes as the static demo above, plus a handful
   of live-only ones (see partials/analysis_block.html's anatomy comment).
   ========================================================================== */

/* Everything below a live block's question -- interpretation, phase
   indicator, chart, commentary, citations, followups, or a clarification/
   error in their place -- is rebuilt inside this wrapper on every run
   (fresh question or a confirmed re-run alike), so it is what plays the
   entrance animation, not the whole card (the question and "Live answer"
   meta above it do not change on a re-run). */
.analysis-block__runtime {
  display: flex;
  flex-direction: column;
  animation: gf-block-in 0.3s ease both;
}

.analysis-block__interp-hint {
  font-weight: 400;
  text-transform: none;
  letter-spacing: normal;
  font-size: 0.72rem;
  color: var(--gf-muted);
}

/* The interpretation chip becomes this in place when clicked -- an inline
   text input, a <select> for transform, or the small period form -- rather
   than opening a modal, so the edit stays anchored to the chip it came
   from. */
.interp-chip-editor {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.55rem;
  border: 1px solid var(--gf-accent);
  border-radius: var(--radius-sm);
  background: var(--gf-surface);
  font-family: var(--gf-font-sans);
}

.interp-chip-editor input,
.interp-chip-editor select {
  font: inherit;
  font-size: 0.78rem;
  min-width: 0;
  border: 1px solid var(--gf-line);
  border-radius: var(--radius-sm);
  padding: 0.2rem 0.4rem;
  background: var(--gf-surface);
  color: var(--gf-ink);
}

.interp-chip-editor input[type="number"] {
  width: 5.5rem;
}

.interp-chip-editor label {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.75rem;
  color: var(--gf-muted);
  white-space: nowrap;
}

.interp-chip-editor__done {
  font-family: var(--gf-font-sans);
  font-size: 0.72rem;
  padding: 0.25rem 0.55rem;
  border: none;
  border-radius: var(--radius-sm);
  background: var(--gf-accent);
  color: var(--gf-on-accent);
  cursor: pointer;
}

/* Only shown/enabled once a chip has actually been edited (see chat.js's
   commitField/renderChips) -- correcting the reading and re-running is the
   product's core trust feature (spec §7.2), so it needs its own clear
   affordance rather than living inside the generic phase indicator. */
.interp-rerun-btn {
  margin-left: auto;
  font-family: var(--gf-font-sans);
  font-size: 0.72rem;
  letter-spacing: normal;
  text-transform: none;
  padding: 0.3rem 0.6rem;
  border: 1px solid var(--gf-accent);
  border-radius: var(--radius-sm);
  background: var(--gf-surface);
  color: var(--gf-accent);
  cursor: pointer;
}

.interp-rerun-btn:hover,
.interp-rerun-btn:focus-visible {
  background: var(--gf-accent);
  color: var(--gf-on-accent);
}

.interp-rerun-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  background: var(--gf-surface);
  color: var(--gf-accent);
}

.analysis-block__edit-error {
  margin: 0.5rem 0 0;
  font-family: var(--gf-font-sans);
  font-size: 0.75rem;
  color: var(--gf-accent);
}

/* A `clarification` SSE event (spec §7.7 step 2: ask, never guess) replaces
   the chart/commentary/citations/followups slots entirely -- there is
   nothing computed yet, so nothing below the question but this. */
.analysis-block__clarification {
  padding: var(--space-3) var(--space-4) var(--space-4);
  background: var(--gf-accent-wash);
  border-top: 1px solid var(--gf-line);
}

.analysis-block__clarification-question {
  margin: 0 0 0.6rem;
  font-size: 0.98rem;
  line-height: 1.5;
}

.analysis-block__clarification-question:not(:first-child) {
  margin-top: var(--space-3);
}

.analysis-block__clarification-options {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* A friendly message plus the request id, never a stack trace (spec §7.7
   rung 5) -- replaces everything below the question. */
.analysis-block__error {
  padding: var(--space-3) var(--space-4) var(--space-4);
  font-family: var(--gf-font-sans);
  font-size: 0.88rem;
  line-height: 1.6;
  color: var(--gf-accent);
}

.analysis-block__error code {
  font-size: 0.85em;
}

/* Standard visually-hidden pattern: present for assistive tech, removed
   from layout and the visual page entirely. Used for #chat-live-region,
   the single deliberate per-answer summary (see chat.js's announce() and
   partials/chat_panel.html's DOM contract for why the growing #chat-stream
   trail gets its own, separate aria-live region instead of sharing this
   one -- narrating every streamed word would be unusable). */
.gf-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;
}

@keyframes gf-block-in {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes gf-phase-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.65;
  }
}

.phase-indicator__phase--active {
  animation: gf-phase-pulse 1.4s ease-in-out infinite;
}

/* Motion is decorative here (an entrance fade and an "in progress" pulse,
   nothing that carries information not already given by text/aria-live) --
   both are switched off outright for anyone who has asked the OS for
   reduced motion, rather than merely shortened. */
@media (prefers-reduced-motion: reduce) {
  .analysis-block__runtime {
    animation: none;
  }

  .phase-indicator__phase--active {
    animation: none;
  }

  .gf-zones {
    transition: none;
  }

  /* The rail's expand/collapse (width + margin) and the mobile slide-over's
     transform are both animation, not information. */
  .zone-c {
    transition: none;
  }
}

/* ==========================================================================
   Small screens
   ========================================================================== */

@media (max-width: 40rem) {
  /* The site header deliberately stays a SINGLE row all the way down to
     320px (see the 30rem block near .site-header, which shrinks the lockup
     and tightens the gaps instead). It used to stack into two rows here,
     which put a ragged ~100px block of chrome above the headline -- and now
     that the header is the page's one inverted band, a two-row version of
     it reads as a slab rather than a bar. Measured at 320px: wordmark, nav
     and toggle fit one row with room to spare. */

  .site-footer__inner {
    flex-direction: column;
    align-items: flex-start;
  }

  .zone-b__head {
    flex-direction: column;
    align-items: flex-start;
  }

  .indicator-facts__item {
    flex-basis: 45%;
  }

  .recon-detail__value {
    font-size: 1.5rem;
  }

  .analysis-block__question {
    font-size: 1rem;
  }

  /* minmax()'s lower bound is a hard floor: at 320px the indicator page's
     content column (after its own padding) is narrower than the 16rem
     minimum these grids otherwise use, so they were overflowing their own
     container rather than shrinking (same fix as .widget-grid, further up
     this file, for the same reason). This block runs after both grids'
     unconditional definitions, so it is what actually wins the cascade. */
  .recon-detail__grid,
  .recon-disclosure__rows {
    grid-template-columns: 1fr;
  }
}

/* ==========================================================================
   Homepage (spec §8.7: "marketing = live demo")

   Structure, not decoration. The page had become an unbroken column of
   left-aligned prose at one measure with headings running straight into the
   next block; it read like a document, not a publication. Four rules fix
   most of it and everything below is an application of them:

     1. Running prose is held to a reading measure (.home-prose, 34rem).
        Data — the hero figure, the answer cards, the sources table — breaks
        out to a wider container (.home-wide), which is how a research
        publication sets a figure. That container is the *shell* measure,
        the same two edges the site header and footer sit on.
     2. The chart is the product's headline proof, so it is drawn directly
        onto the page: no card, no border, no surface of its own, minimal
        axis chrome, and enough height (30rem) that 35 years x 6 series read
        as trends. The page ground shows straight through the plot.
     3. Depth comes from shadow, not from blocks of colour. Exactly one
        element is raised (.analysis, on --gf-shadow-md); the site header is
        the page's only inverted surface. A page where everything is lifted
        has no hierarchy.
     4. Section headings are labels, not article headings — the small
        uppercase letter-spaced sans already used by .permalink-strip__eyebrow
        and .indicator-page__eyebrow. The serif headings inside a panel then
        have somewhere to be the largest thing.
   ========================================================================== */

/* -- The hero ------------------------------------------------------------- */

/* The whole first screen: copy, figure, and the single hairline that ends
   it. Everything above that rule is the hero; the analysis card below
   straddles it. The bottom padding is what the card climbs into, so the two
   values are set together (see .analysis) and neither may be tuned alone.
   The rule runs the full shell width, and the card is narrower, so the rule
   stays visible either side of it -- which is what makes the overlap read
   as an overlap rather than as a seam. */
.home-hero {
  padding-block-end: var(--space-6);
  border-bottom: 1px solid var(--gf-line);
}

/* The copy is the caption, not the artwork. It is set SIDE BY SIDE rather
   than stacked and it is deliberately short in the vertical: stacked, this
   same copy ran eight lines deep and pushed the figure off the first screen
   entirely. Every word is unchanged -- only the layout is compressed. The
   two columns are bottom-aligned so they share a baseline rather than
   floating at different heights. */
.hero {
  display: grid;
  gap: var(--space-2) var(--space-6);
  align-items: end;
}

/* 34rem is measured, not picked. The block's height is an interplay: widen
   this column and the headline drops to two lines but the lede beside it
   gains one, and the lede's line is the more expensive. Swept across
   32-40rem x 1.75-2.5rem of headline, 34rem paired with the 2.25rem cap
   below is the minimum: headline 2 lines, lede 5, block 153px against
   187px for the 2.5rem/3-line setting it replaced. 34px of copy is 34px of
   plot, so this is a fold decision as much as a typographic one. */
@media (min-width: 62rem) {
  .hero {
    grid-template-columns: minmax(0, 1fr);
  }
}

.hero__copy,
.hero__say {
  min-width: 0;
}

/* Tight leading is the point: at the site's 1.15 heading leading this ran
   three lines of air taller than the copy beside it, and every pixel here
   comes off the chart. 1.06 is as tight as a serif this size goes before
   the descenders of one line start touching the caps of the next. */
.hero__kicker {
  margin: 0 0 var(--space-2);
  font-family: var(--gf-font-sans);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gf-muted);
}

.hero__headline {
  /* Trimmed after the owner saw it live: "the one governed semantic piece is
     important but taking too much space" and "the graph loses visibility".
     The ceiling comes down from 2.25rem and the measure from 30ch, so the
     headline sets as two tight lines rather than two wide ones, and every
     pixel saved here goes to the plot below via the fold clamp. */
  font-size: clamp(1.75rem, 1.1rem + 2.1vw, 2.4rem);
  line-height: 1.05;
  letter-spacing: -0.015em;
  text-wrap: balance;
  /* Sized for the capability headline, which is longer than a product
     tagline. At 18ch it broke over four lines and read as a stack, not a
     sentence; at 24ch it was three. 30ch lets the column above decide. */
  max-width: 30ch;
  letter-spacing: -0.02em;
  text-wrap: balance;
  margin-bottom: 0;
}

.hero__lede {
  font-size: 0.95rem;
  line-height: 1.5;
  margin-top: var(--space-2);
  color: var(--gf-muted);
  color: var(--gf-muted);
  max-width: 52ch;
  margin-bottom: 0;
}

/* Nepal-as-first-instance line under the hero lede. Quiet by design: it is
   context, not a claim -- the capability is the headline. */
.hero__instance {
  margin: var(--space-2) 0 0;
  font-family: var(--gf-font-sans, ui-sans-serif, system-ui, sans-serif);
  font-size: 0.9rem;
  color: var(--gf-muted);
  letter-spacing: 0.01em;
}

/* The one live proof line -- every number in it is read from the current
   build at render time (app/routers/pages.py:_home_stats), never
   hard-coded, so it cannot go stale or overstate what's loaded. It sits in
   the headline column rather than in a slot of its own: it is evidence for
   the claim directly above it, and folding it in there costs the first
   screen nothing while balancing the two columns' heights. */
.hero__stats {
  margin: var(--space-3) 0 0;
  font-family: var(--gf-font-sans);
  font-size: 0.85rem;
  color: var(--gf-muted);
}

.hero__stats strong {
  color: var(--gf-ink);
  font-weight: 600;
}

/* The build date is hyphenated and carries a time; left alone the line
   breaks after "2026-08-", which reads as a typo rather than a wrap. */
.hero__asof {
  white-space: nowrap;
}

@media (max-width: 48rem) {
  /* Nothing to balance against at phone width: the ch cap plus text-wrap:
     balance set this headline as four ragged lines of wildly different
     length. Let it fill the column and only avoid orphans. */
  .hero__kicker {
  margin: 0 0 var(--space-2);
  font-family: var(--gf-font-sans);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gf-muted);
}

.hero__headline {
    max-width: none;
    text-wrap: pretty;
  }
}

/* -- Section rhythm and containers --------------------------------------- */

/* Wider than .wrap's reading measure, and deliberately so: a figure that
   breaks the text column is a publication convention, not a misalignment.
   Only data elements use it. Identical box to .shell, which is the point --
   the header's wordmark and nav sit on exactly these edges. */
.home-wide {
  max-width: var(--max-width-wide);
  margin-inline: auto;
  padding-inline: var(--space-4);
}

/* Above laptop width the gutters open up a little so 1216px of container
   never runs flush to a 1280px viewport's edges. Declared after both
   containers so it wins on equal specificity. */
@media (min-width: 64rem) {
  .shell,
  .home-wide {
    padding-inline: var(--space-5);
  }
}

.home-prose {
  max-width: 34rem;
}

.home-section {
  padding-block: var(--space-6) 0;
}

.home-section + .home-section {
  padding-block-start: var(--space-6);
}

/* Label, not headline: small, uppercase, letter-spaced sans -- the house
   treatment (.permalink-strip__eyebrow, .indicator-page__eyebrow). */
.home-section__label,
.hero-figure__label {
  font-family: var(--gf-font-sans);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gf-muted);
  margin: 0 0 var(--space-3);
}

.home-section__label {
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--gf-line);
}

/* The hero's own label takes the same treatment without the rule: on the
   first screen there is exactly one hairline and it is the one that ends
   the hero. This label is an eyebrow over the figure's title, so it sits
   inside .figure__titles and costs the fold nothing -- the head's height is
   set by the value beside it either way. */
.hero-figure__label {
  margin-bottom: var(--space-1);
}

.home-section__lede {
  font-family: var(--gf-font-sans);
  font-size: 0.92rem;
  color: var(--gf-muted);
  margin-bottom: var(--space-4);
}

.home-section__more {
  margin: var(--space-4) 0 0;
  font-family: var(--gf-font-sans);
  font-size: 0.9rem;
}

/* The bounded surface every homepage block sits on. */
.panel {
  background: var(--gf-surface);
  border: 1px solid var(--gf-line);
  border-radius: var(--radius-md);
  padding: var(--space-4);
}

/* -- The hero figure: drawn ON the page, not placed on it ----------------- */

/* No card. No border, no radius, no surface of its own, no padding box --
   the page ground shows straight through the plot (ECharts' own
   backgroundColor is rgba(0,0,0,0) in config/chart_theme.json and chart.js
   restates it at option level, and the `bare` mode drops the remaining
   container-like chrome). The figure is the page at this point, not an
   object sitting on it. */
.figure {
  margin: 0;
}

/* Space between the copy and the figure head. One step of the scale, and
   the only gap between them: the hero's whole vertical budget is spent on
   the plot. */
.hero-figure {
  margin-top: var(--space-4);
}

.figure__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
}

/* The figure's own provenance, under the artwork it cites: source, dataset
   and snapshot date, each chip still the full citation component with its
   licence/attribution popover. Below the plot rather than in the head
   because the head is above the fold and the chips are the one part of the
   figure that can afford not to be. */
.figure__sources {
  margin-top: var(--space-3);
}

.figure__titles {
  min-width: 0;
}

.figure__title {
  margin: 0;
  font-size: clamp(1.25rem, 1.05rem + 0.9vw, 1.6rem);
}

.figure__unit {
  margin: 0.25rem 0 0;
  font-family: var(--gf-font-sans);
  font-size: 0.82rem;
  color: var(--gf-muted);
}

/* The headline value and its year are one component, not two words jammed
   together: a large serif figure with a small muted sans companion, on a
   shared baseline. Deliberately the *same* rule as the widget cards rather
   than a second treatment that would drift from it. */
.widget-card__value-row,
.figure__value-row {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.2rem;
}

.figure__value-row {
  flex: none;
  margin-bottom: 0;
}

.widget-card__value,
.figure__value {
  font-size: 1.5rem;
  letter-spacing: -0.02em;
}

.figure__value {
  font-size: clamp(1.9rem, 1.4rem + 2.2vw, 2.75rem);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.widget-card__year,
.figure__year {
  font-family: var(--gf-font-sans);
  font-size: 0.75rem;
  color: var(--gf-muted);
}

.figure__year {
  font-size: 0.85rem;
  white-space: nowrap;
}

/* The page's centrepiece, not a grid tile (a .widget-card__chart is 5.5rem).
   It has to hold 35 years x 6 series that split into two very separate
   groups -- Nepal at 138 in 1990, China and Japan already in single digits
   -- and at anything shorter those two groups collapse into each other
   along the floor of the axis.

   Sized against the *viewport*, not just picked, because the hard
   requirement is that the whole canvas clears the fold on the tightest
   realistic laptop. Everything above the plot at desktop widths measures
   341.5px -- header (68.8px), main's top padding, the copy block, the gap,
   and the figure's head -- and 22.5rem = 360px is that plus a margin for
   the line-count differences other platforms' serif metrics can introduce.
   What is left is the plot, capped at 30rem so it never becomes a mural on
   a tall monitor and floored at 16rem so a short window still gets a chart
   rather than a sparkline.

   Measured in headless Chromium against the real warehouse: 408px of canvas
   at 1366x768, bottom edge at 749.5 of 768; 480px at 1440x900, bottom at
   821.5 of 900. If the copy above ever grows a line, the clamp absorbs it
   rather than pushing the plot through the fold. */
.figure__canvas {
  height: clamp(16rem, calc(100vh - 22.5rem), 30rem);
}

@media (max-width: 60rem) {
  .figure__canvas {
    height: 22rem;
  }
}

@media (max-width: 40rem) {
  /* Six series means a two-row legend at this width; chart.js reserves the
     rows it actually needs (see legendRows), so the box has to carry them
     plus a plot that still reads as trends. */
  .figure__canvas {
    height: 17rem;
  }
}

/* -- The computed analysis, hovering over the hero's closing rule --------- */

/* The one element on this page that is raised off the ground, and the only
   user of --gf-shadow-md. It is pulled up so it straddles .home-hero's
   bottom border -- the figure above it and the page below it then read as
   one composition rather than as two stacked blocks -- and it is held
   narrower than the shell so that rule stays visible either side of it,
   which is what makes the overlap legible as an overlap.

   The shadow is deliberately near-invisible: a 1px contact edge plus a wide
   13%-alpha ambient. Anything heavier turns a provenance note into a
   marketing card. z-index keeps it above the rule regardless of source
   order. */
.analysis {
  position: relative;
  z-index: 1;
  max-width: 56rem;
  margin-block-start: calc(var(--space-5) * -1);
  margin-inline: auto;
  border-left: 3px solid var(--gf-accent);
  box-shadow: var(--gf-shadow-md);
}

@media (min-width: 48rem) {
  .analysis {
    padding: var(--space-4) var(--space-5);
  }
}

/* The section carrying the lifted card starts flush: its own top spacing is
   the hero's bottom padding, which the card is pulled up into. */
.home-section--lifted {
  padding-block-start: 0;
}

.analysis__label {
  font-family: var(--gf-font-sans);
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--gf-muted);
  margin: 0 0 var(--space-2);
}

.analysis__body {
  margin: 0;
  max-width: 46rem;
  font-size: 1.05rem;
  line-height: 1.55;
}

.analysis__foot {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--gf-line);
}

.analysis__note {
  margin: 0;
  font-family: var(--gf-font-sans);
  font-size: 0.75rem;
  line-height: 1.45;
  color: var(--gf-muted);
  max-width: 40rem;
}

/* -- Suggested questions: the zero-inference demo (spec §7.8) ------------- */

.qa-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3);
  align-items: start;
}

@media (max-width: 48rem) {
  .qa-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

.qa-card {
  background: var(--gf-surface);
  border: 1px solid var(--gf-line);
  border-radius: var(--radius-md);
}

.qa-card[open] {
  border-color: var(--gf-line-strong);
}

/* No native disclosure triangle on any engine: `display: flex` removes the
   ::marker in Chromium and Firefox, the two rules below cover WebKit and
   older Firefox. The control is the whole header row plus the chevron. */
.qa-card__control {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  list-style: none;
  cursor: pointer;
  padding: var(--space-3) var(--space-4);
  font-family: var(--gf-font-sans);
  font-size: 0.95rem;
  font-weight: 600;
  line-height: 1.35;
  color: var(--gf-ink);
  border-radius: var(--radius-md);
}

.qa-card__control::-webkit-details-marker {
  display: none;
}

.qa-card__control::marker {
  content: "";
}

.qa-card__control:hover {
  background: var(--gf-accent-wash);
}

.qa-card__control:focus-visible {
  outline: 2px solid var(--gf-accent);
  outline-offset: -2px;
}

.qa-card__question {
  flex: 1 1 auto;
  min-width: 0; /* see .citation-chip__label -- same wrap-not-overflow reason */
}

.qa-card__chevron {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.6rem;
  height: 1.6rem;
  border: 1px solid var(--gf-line);
  border-radius: 999px;
  color: var(--gf-accent);
  background: var(--gf-bg);
  transition: transform 0.15s ease;
}

.qa-card[open] .qa-card__chevron {
  transform: rotate(180deg);
}

.qa-card__body {
  padding: var(--space-3) var(--space-4) var(--space-4);
  border-top: 1px solid var(--gf-line);
}

.qa-card__value-row {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0 0 0.2rem;
}

.qa-card__value {
  font-size: 1.6rem;
  letter-spacing: -0.02em;
  line-height: 1;
}

.qa-card__meta {
  font-family: var(--gf-font-sans);
  font-size: 0.78rem;
  color: var(--gf-muted);
}

.qa-card__headline {
  margin: 0 0 var(--space-3);
  font-family: var(--gf-font-sans);
  font-size: 0.85rem;
  line-height: 1.45;
  color: var(--gf-muted);
}

.qa-card__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
}

.qa-card__provenance {
  margin: 0;
  font-family: var(--gf-font-sans);
  font-size: 0.72rem;
  color: var(--gf-muted);
}

/* -- The sources table ---------------------------------------------------- */

/* The wrapper scrolls, never the page. tabindex="0" in the markup makes the
   region keyboard-scrollable, which a bare overflow container is not. */
.source-table__scroll {
  overflow-x: auto;
  border: 1px solid var(--gf-line);
  border-radius: var(--radius-md);
  background: var(--gf-surface);
}

.source-table__scroll:focus-visible {
  outline: 2px solid var(--gf-accent);
  outline-offset: -2px;
}

.source-table {
  width: 100%;
  min-width: 44rem;
  border-collapse: collapse;
  font-family: var(--gf-font-sans);
  font-size: 0.85rem;
}

.source-table__caption {
  caption-side: top;
  text-align: left;
  padding: var(--space-3) var(--space-4);
  font-size: 0.78rem;
  line-height: 1.45;
  color: var(--gf-muted);
  border-bottom: 1px solid var(--gf-line);
}

.source-table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  text-align: left;
  vertical-align: bottom;
  padding: var(--space-2) var(--space-3);
  background: var(--gf-bg-sunken);
  border-bottom: 1px solid var(--gf-line-strong);
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gf-muted);
  white-space: nowrap;
}

.source-table tbody th,
.source-table td {
  vertical-align: top;
  padding: var(--space-3);
  border-bottom: 1px solid var(--gf-line);
  text-align: left;
  font-weight: 400;
}

.source-table tbody tr:last-child th,
.source-table tbody tr:last-child td {
  border-bottom: none;
}

.source-table tbody tr:hover {
  background: var(--gf-bg-sunken);
}

.source-table__col-num,
.source-table__col-num.source-table__col-num {
  text-align: right;
  /* Wide enough that "1,280,976 values loaded" -- ILOSTAT's row in the
     current build, the longest this cell gets -- stays on one line. */
  min-width: 9.5rem;
}

.source-table__col-source {
  min-width: 12rem;
}

.source-table__source {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
}

.source-table__favicon {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  margin-top: 0.1rem;
  border: 1px solid var(--gf-line);
  border-radius: var(--radius-sm);
  background: var(--gf-surface);
  overflow: hidden;
  font-size: 0.6rem;
  color: var(--gf-muted);
}

.source-table__favicon img {
  width: 14px;
  height: 14px;
  object-fit: contain;
}

.source-table__names {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.source-table__names a {
  font-weight: 600;
  color: var(--gf-ink);
  text-decoration: none;
  border-bottom: 1px solid var(--gf-line-strong);
}

.source-table__names a:hover {
  color: var(--gf-accent);
}

.source-table__host {
  font-size: 0.72rem;
  color: var(--gf-muted);
}

.source-table__primary {
  display: block;
  color: var(--gf-ink);
}

.source-table__primary--none {
  color: var(--gf-muted);
}

.source-table__sub {
  display: block;
  margin-top: 0.15rem;
  font-size: 0.72rem;
  line-height: 1.4;
  color: var(--gf-muted);
  max-width: 16rem;
}

.source-table__sub--stale {
  color: var(--gf-ink);
  font-weight: 600;
}

.source-table__nowrap {
  white-space: nowrap;
}

/* -- How it works: three short steps -------------------------------------- */

.home-how__steps {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.home-how__index {
  display: block;
  margin-bottom: 0.4rem;
  font-family: var(--gf-font-sans);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--gf-accent);
}

.home-how__title {
  margin: 0 0 0.4rem;
  font-size: 1.05rem;
}

.home-how__step p {
  margin: 0;
  font-family: var(--gf-font-sans);
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--gf-muted);
}

@media (max-width: 48rem) {
  .home-how__steps {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ==========================================================================
   Shared answer / bookmark page (spec §8.5) -- app/templates/share.html.
   Reuses .interp-chips, .citation-chip-row, .followup-chip, .chart-container
   etc. from the sections above; only the page's own scaffolding lives here.
   ========================================================================== */

.share-page {
  max-width: 40rem;
  padding-top: var(--space-5);
  padding-bottom: var(--space-6);
}

.share-page__back {
  margin: 0 0 var(--space-4);
  font-family: var(--gf-font-sans);
  font-size: 0.85rem;
}

.share-page__back a {
  color: var(--gf-muted);
}

.share-page__meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.share-page__badge {
  display: inline-flex;
  align-items: center;
  font-family: var(--gf-font-sans);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--gf-accent);
  background: var(--gf-accent-wash);
  border-radius: 999px;
  padding: 0.25rem 0.6rem;
}

.share-page__badge--pinned {
  color: var(--gf-muted);
  background: var(--gf-bg-sunken);
}

.share-page__scope {
  font-family: var(--gf-font-sans);
  font-size: 0.8rem;
  color: var(--gf-muted);
}

.share-page__question {
  margin: 0 0 0.3rem;
  font-size: 1.6rem;
}

.share-page__restated {
  margin: 0 0 var(--space-4);
  color: var(--gf-muted);
  font-style: italic;
}

.share-page__note {
  font-family: var(--gf-font-sans);
  font-size: 0.85rem;
  color: var(--gf-accent-strong);
  background: var(--gf-accent-wash);
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.9rem;
  margin: 0 0 var(--space-4);
}

.share-page__note--pinned {
  color: var(--gf-muted);
  background: var(--gf-bg-sunken);
}

.share-page__chart {
  min-height: 20rem;
  margin-bottom: var(--space-3);
}

.share-page__commentary {
  font-size: 1.05rem;
  margin-bottom: var(--space-4);
}

.share-page__citations,
.share-page__followups {
  margin-bottom: var(--space-4);
}

.share-page__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-4) 0;
  padding-top: var(--space-3);
  border-top: 1px solid var(--gf-line);
}

.share-page__actions button {
  font-family: var(--gf-font-sans);
  font-size: 0.8rem;
  background: var(--gf-surface);
  border: 1px solid var(--gf-line);
  border-radius: var(--radius-sm);
  padding: 0.4rem 0.75rem;
  cursor: pointer;
  color: var(--gf-ink);
}

.share-page__actions button:hover {
  border-color: var(--gf-line-strong);
}

.share-page__ask {
  margin-left: auto;
  font-family: var(--gf-font-sans);
  font-size: 0.85rem;
  font-weight: 600;
}

.share-page__footer {
  margin-top: var(--space-5);
  font-family: var(--gf-font-sans);
  font-size: 0.78rem;
  color: var(--gf-muted);
}

@media (max-width: 26rem) {
  .share-page__actions {
    flex-direction: column;
    align-items: stretch;
  }

  .share-page__ask {
    margin-left: 0;
  }
}

/* ==========================================================================
   Embed page (spec §8.5, §12) -- app/templates/embed.html. A standalone
   document (does not extend base.html), so its own minimal reset lives
   here too rather than assuming body/link rules defined above apply --
   they do (same stylesheet), this just adds the embed-specific scaffolding.
   ========================================================================== */

.gf-embed-body {
  margin: 0;
}

.gf-embed {
  max-width: 34rem;
  margin-inline: auto;
  padding: var(--space-3) var(--space-3) var(--space-2);
}

.gf-embed__title {
  margin: 0 0 var(--space-2);
  font-family: var(--gf-font-serif);
  font-size: 1.1rem;
  font-weight: 600;
}

.gf-embed__chart {
  min-height: 14rem;
  margin-bottom: var(--space-2);
}

.gf-embed__note {
  font-family: var(--gf-font-sans);
  font-size: 0.75rem;
  color: var(--gf-muted);
  margin: 0 0 var(--space-2);
}

.gf-embed__citations {
  margin-bottom: var(--space-2);
}

.gf-embed__footer {
  padding-top: var(--space-2);
  border-top: 1px solid var(--gf-line);
}

.gf-embed__footer a {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: var(--gf-font-sans);
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--gf-muted);
  text-decoration: none;
}

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

.gf-embed__mark {
  width: 16px;
  height: 16px;
  flex: none;
}

@media (max-width: 20rem) {
  .gf-embed {
    padding-inline: var(--space-2);
  }
}

/* ==========================================================================
   Account page -- bookmarks list (spec §8.5, §9). Loaded client-side by
   app/static/js/bookmarks.js from GET /api/bookmarks.
   ========================================================================== */

.account-bookmarks {
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--gf-line);
}

.account-bookmarks h2 {
  font-size: 1.05rem;
  margin: 0 0 var(--space-3);
}

.bookmark-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.6rem;
}

.bookmark-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: 0.65rem 0.85rem;
  border: 1px solid var(--gf-line);
  border-radius: var(--radius-sm);
  background: var(--gf-surface);
}

.bookmark-row__main {
  min-width: 0;
}

.bookmark-row__title {
  display: block;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bookmark-row__meta {
  display: block;
  font-family: var(--gf-font-sans);
  font-size: 0.75rem;
  color: var(--gf-muted);
  margin-top: 0.15rem;
}

.bookmark-row__delete {
  flex: none;
  font-family: var(--gf-font-sans);
  font-size: 0.78rem;
  background: transparent;
  border: 1px solid var(--gf-line);
  border-radius: var(--radius-sm);
  padding: 0.35rem 0.6rem;
  cursor: pointer;
  color: #b91c1c;
}

.bookmark-row__delete:hover {
  border-color: #b91c1c;
}

.account-bookmarks__empty,
.account-bookmarks__error {
  font-family: var(--gf-font-sans);
  font-size: 0.85rem;
  color: var(--gf-muted);
}

@media (max-width: 26rem) {
  .bookmark-row {
    flex-direction: column;
    align-items: stretch;
  }
}
