/* ------------------------------------------------------------------------
   atlas.css — the atlas application's own chrome, and nothing else.

   The top bar, the panel, the cards that float over the map, the timeline,
   the legend, the region dialog and the mobile sheets. Loaded after
   tokens.css and base.css and never without them: the reset, the four type
   voices, the glass ladder, the button pair and the weather switch live in
   base.css, so a rule exists here only because the application has a surface
   the reading pages do not.

   Carries no colour of its own — every value is a var() into tokens.css. The
   one exception is the alpha-on-white inner highlight inside a shadow, which
   is a lighting term rather than a colour.

   The atlas runs denser than the reading pages, so type steps down onto
   --text-app-xs / -sm / (base) / -lg. It inherits the VOICES unchanged:
   uppercase labels take --tracking-eyebrow, every number and unit takes
   --tracking-data with tabular figures, and nothing is bolder than 500.
   ---------------------------------------------------------------------- */


/* The handle a bottom sheet is dragged by (src/globe/sheet.js). In the markup
   at every width, shown only under the mobile breakpoint — which is why it is
   declared here and not next to the sheet rules: same specificity, so the media
   query has to come later in the file to win. */
.sheet-grip { display: none; }

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--card); color: var(--fg);
  padding: 10px 14px; border: 1px solid var(--border); border-radius: var(--radius-control);
}
.skip-link:focus { left: 12px; top: 12px; }

/* ---------------------------------------------------------------- layout */

#app {
  display: grid;
  grid-template-columns: 1fr var(--panel-w);
  grid-template-rows: var(--topbar-h) 1fr;
  grid-template-areas: "top top" "stage panel";
  height: 100dvh;
}

/* The application's chrome is WEATHER, painted rather than filtered.
   Everywhere else on the site, chrome is glass floating on the sky and the
   blur does the work; here the bar and the panel are cells of the #app grid
   with the map between them, so there is nothing behind them to filter and
   glass over flat ground resolves to white. The same picture is therefore
   painted directly: two soft light pools over a hued diagonal — cloud in
   Clear, rain sky in Rain — off the chrome ladder in tokens.css.

   The pools are lighting, not colour: --chrome-lift and --chrome-sunk are the
   ladder's own two steps, so the cloud cannot drift from the surface it sits
   on, and Rain gets the same shape at Rain's values without a second rule.

   The bottom hairline is a divider, which is the one edge the system allows;
   a card would not get one. */
.topbar, .panel {
  background:
    radial-gradient(128% 88% at 16% 4%,  var(--chrome-lift) 0, transparent 62%),
    radial-gradient(92% 66% at 92% 96%,  var(--chrome-sunk) 0, transparent 58%),
    linear-gradient(160deg, var(--chrome), var(--chrome-sunk));
  box-shadow: var(--glass-inner);
  transition: background var(--t-theme);
}

.topbar {
  grid-area: top;
  display: flex; align-items: center; gap: 16px;
  padding: 0 14px;
  border-bottom: 1px solid var(--ink-hairline);
  z-index: 4;
}

/* The search geometry lives here rather than on .citysearch because the
   High-Resolution hint is positioned off it. Percentages in --search-w resolve
   at the use site, which is this same box for both. */
.stage {
  grid-area: stage; position: relative; overflow: hidden; background: var(--ground-sunk);
  --search-x: 14px; --search-y: 14px; --search-w: min(264px, calc(100% - 28px));
}
#map { position: absolute; inset: 0; }

/* The same painted sky as the top bar (above), so the application's two chrome
   surfaces are one piece of weather rather than two panels that happen to
   match. Everything inside the panel steps off the ladder it is painted from:
   the live view card up to --chrome-lift, the two you are not in down to
   --chrome-sunk. */
.panel {
  grid-area: panel;
  display: flex; flex-direction: column;
  border-left: 1px solid var(--ink-hairline);
  min-height: 0;
  z-index: 3;
}
.panel-scroll { flex: 1; overflow-y: auto; padding: 14px; min-height: 0; }

/* ----------------------------------------------------------------- brand */

/* Wordmark only — no glyph, no strapline. The three view names in the panel
   already say what the page is; a subtitle repeating them would be noise. */
.brand { display: flex; align-items: center; flex: 0 0 auto; }
.brand strong { font-size: 15px; font-weight: var(--weight-strong); letter-spacing: -.015em; }
.brand a { color: inherit; text-decoration: none; display: inline-flex;
           align-items: center; gap: 8px; }
.brand a:hover { text-decoration: underline; text-underline-offset: 3px; }

/* ------------------------------------------------------------- top field */

.topbar-controls {
  display: flex; align-items: center; gap: 12px;
  margin-left: auto; flex-wrap: wrap; justify-content: flex-end;
}

.field { display: flex; align-items: center; gap: 7px; }
.field label {
  font-size: var(--text-app-sm); color: var(--ink-faint);
  text-transform: uppercase; letter-spacing: var(--tracking-eyebrow);
  font-weight: var(--weight-strong);
}
.range-dash { color: var(--muted-fg); }

/* High-Resolution: the static badge that replaces the timeline controls. */
.hex-badge {
  display: inline-flex; align-items: center;
  padding: 4px 11px; border-radius: var(--radius-pill);
  background: var(--muted); color: var(--muted-fg);
  font-size: var(--text-app-lg); white-space: nowrap;
}

select, input[type="date"] {
  appearance: none;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  padding: 5px 9px;
  font-size: var(--text-app-lg);
  min-width: 0;
}
select { padding-right: 26px;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 12px center, right 7px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

/* A button styled as a text link, at home in a row of controls rather than
   looking like a form element. Without an explicit background it would keep
   the user agent's `buttonface`. */
.link-btn {
  /* inline-flex with an explicit line-height, so the <a> and the <button> box
     to the same height: an <a> is a text box and a <button> a replaced-ish one,
     and left to their defaults they resolve two different heights from the
     same padding. */
  display: inline-flex; align-items: center; line-height: 1.35;
  font-size: var(--text-app-lg); color: var(--muted-fg); text-decoration: none;
  background: transparent;
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  padding: 4px 11px; white-space: nowrap;
  cursor: pointer;
  transition: color var(--t-ui), border-color var(--t-ui);
}
.link-btn:hover { color: var(--fg); border-color: var(--border-strong); }

/* --------------------------------------------------------- segmented ctl */

/* The same pattern as base.css's weather switch: an ink --wash behind the
   group, and the selected option lit by the glass gradient and its inner
   highlight. The selection is a value step, never the accent. */
.segmented {
  display: inline-flex;
  background: var(--wash);
  border: 1px solid var(--ink-hairline);
  border-radius: var(--radius-control);
  padding: 2px;
  gap: 2px;
}
.segmented button {
  border: 0; background: transparent; cursor: pointer;
  padding: 4px 11px; border-radius: var(--radius-default);
  font-size: var(--text-app-lg); color: var(--muted-fg);
  white-space: nowrap;
  transition: background .12s ease, color .12s ease;
}
.segmented button:hover { color: var(--fg); }
.label-short { display: none; }
.segmented button[aria-checked="true"] {
  background: linear-gradient(145deg, var(--glass-a), var(--glass-b));
  color: var(--fg);
  box-shadow: var(--glass-inner), var(--shadow-chip);
  font-weight: var(--weight-strong);
}

.icon-btn {
  border: 1px solid var(--border); background: var(--card);
  border-radius: var(--radius-pill); width: 30px; height: 30px;
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; color: var(--muted-fg);
}
.icon-btn:hover { color: var(--fg); border-color: var(--border-strong); }
.icon-theme {
  width: 13px; height: 13px; border-radius: 50%;
  border: 1.5px solid currentColor;
  background: linear-gradient(90deg, currentColor 50%, transparent 50%);
}

/* ---------------------------------------------------------- toggle group */

/* THE NAME IS THE KEY. There is no swatch and no tint: the view's colour is
   the colour its title is set in, which is the smallest thing that can carry
   an identity and the only one that cannot drift away from the word it keys.
   The hue is the vivid trio (tokens.css) rather than the map's deepened one —
   at 13 px type a 10 px patch's chroma reads as muddy ink, not as red. */
.toggle-group { display: flex; flex-direction: column; gap: 6px; margin-bottom: 18px; }
.toggle-group button {
  display: flex; flex-direction: column;
  gap: 2px;
  text-align: left;
  cursor: pointer;
  padding: 10px 12px;
  border: 0;
  border-radius: var(--radius-control);
  background: var(--chrome-sunk);
  transition: background .12s ease, box-shadow .12s ease;
}
/* The hue each card belongs to, declared once and read by the title inside. */
.toggle-group button[data-toggle="demand"] { --sw: var(--deficit-vivid); }
.toggle-group button[data-toggle="supply"] { --sw: var(--resource-vivid); }
.toggle-group button[data-toggle="bridge"] { --sw: var(--bridge-vivid); }
/* Selection is a VALUE step, not a tint: the top of the chrome ladder, lit
   along its edge and lifted by one soft shadow, against the bottom of the same
   ladder for the two you are not in. The ladder rather than an ink wash
   because the step has to point the same way in both weathers — a white film
   over a dark ground makes the cards you are NOT in the brightest thing in the
   panel. Hover rises to the selected surface without the shadow, so the card
   previews the state it is offering. No border either way — a surface that
   needs an edge to be legible has the wrong value step. */
.toggle-group button:hover { background: var(--chrome-lift); }
.toggle-group button[aria-checked="true"] {
  background: var(--chrome-lift);
  box-shadow: var(--glass-inner), var(--shadow-chip);
}
.tg-label { font-size: var(--text-app-lg); font-weight: var(--weight-strong); letter-spacing: -.01em; color: var(--sw); }
.tg-sub   { font-size: var(--text-app); color: var(--muted-fg); }

/* ---------------------------------------------------------------- blocks */

.block { margin-bottom: 20px; }
.block-title {
  margin: 0 0 8px;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  font-size: var(--text-app-sm); font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--ink-faint);
}
.block-note { margin: 0 0 10px; font-size: var(--text-app); color: var(--muted-fg); }
/* Literal code — a store key, a formula. The only place left in the panel
   that is allowed a second family. */
.block-note code { font-family: var(--mono); font-size: var(--text-app-sm); background: var(--muted); padding: 1px 4px; border-radius: 4px; }
.hint { cursor: help; border-bottom: 1px dotted var(--border-strong); }

/* The one-character route from a panel section to its methodology section. */
.info {
  flex: 0 0 auto;
  width: 15px; height: 15px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border-strong); color: var(--muted-fg);
  font-size: var(--text-app-xs); font-weight: var(--weight-strong); font-style: italic;
  letter-spacing: 0; text-transform: none; text-decoration: none;
  transition: color .12s ease, border-color .12s ease;
}
.info:hover { color: var(--fg); border-color: var(--fg); }

.doc-link {
  color: var(--fg); text-decoration: none;
  border-bottom: 1px solid var(--border-strong);
  white-space: nowrap;
}
.doc-link:hover { border-bottom-color: var(--fg); }

.block-empty {
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-control);
  background: var(--muted);
  padding: 12px 13px;
}
/* An empty state is a sentence, not a section heading: the uppercase
   letter-spaced treatment the other block titles carry reads as shouting when
   the text is "Unused cloud water — no data". */
.block-empty .block-title {
  color: var(--fg); margin-bottom: 6px;
  font-size: var(--text-app-lg); font-weight: var(--weight-strong);
  text-transform: none; letter-spacing: -.005em;
}
.block-empty .block-note { margin-bottom: 0; color: var(--muted-fg); }
.block-empty code { user-select: all; }

/* ------------------------------------------------------------ layer list */

.layer-list { display: flex; flex-direction: column; gap: 3px; }
.layer-role {
  font-size: var(--text-app-xs); letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase; font-weight: var(--weight-strong);
  color: var(--ink-faint); margin: 9px 0 3px;
}
.layer-role:first-child { margin-top: 0; }

.layer-item {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: baseline;
  gap: 8px;
  width: 100%;
  text-align: left;
  cursor: pointer;
  padding: 6px 9px;
  border: 1px solid transparent;
  border-radius: var(--radius-control);
  background: transparent;
  font-size: var(--text-app-lg);
}
.layer-item:hover { background: var(--muted); }
.layer-item[aria-checked="true"] {
  background: var(--wash); border-color: var(--ink-hairline);
  box-shadow: var(--glass-inner); font-weight: var(--weight-strong);
}
.layer-item[disabled] { opacity: .45; cursor: not-allowed; }
.layer-item .li-asof {
  font-size: var(--text-app-sm); color: var(--muted-fg);
  font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-data);
}
.layer-item .li-unit {
  font-size: var(--text-app-sm); color: var(--muted-fg); white-space: nowrap;
  font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-data);
}
.layer-item .li-off {
  font-size: var(--text-app-xs); letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase; font-weight: var(--weight-strong);
  color: var(--ink-faint); border: 1px solid var(--border);
  border-radius: var(--radius-pill); padding: 0 6px;
}

/* ---------------------------------------------------------------- slider */

.slider-row { display: flex; align-items: center; gap: 12px; }
.slider-row output {
  font-family: var(--font-data); font-size: var(--text-app-lg);
  font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-data);
  min-width: 56px; text-align: right;
}
/* Sliders.
 *
 *  `--pct` is written by JS (setSliderFill) and is what makes the track read as
 *  filled-so-far rather than as a bare rail: WebKit has no ::-moz-range-progress
 *  equivalent, so the fill is a hard-stop gradient on the track itself. Firefox
 *  gets the native pseudo-element below and ignores the gradient's second half.
 *
 *  The filled half is the accent and the rest is a hairline: a slider fill is
 *  a control STATE, which — with the focus ring — is the one job besides a
 *  primary action the accent is allowed to do.
 */
input[type="range"] {
  appearance: none; -webkit-appearance: none;
  --pct: 0;
  --track: 5px;
  flex: 1; height: 22px; background: transparent; cursor: pointer;
}
input[type="range"]::-webkit-slider-runnable-track {
  height: var(--track); border-radius: var(--radius-pill);
  background: linear-gradient(to right,
    var(--accent) 0 calc(var(--pct) * 1%),
    var(--ink-hairline) calc(var(--pct) * 1%) 100%);
}
input[type="range"]::-moz-range-track {
  height: var(--track); border-radius: var(--radius-pill); background: var(--ink-hairline);
}
input[type="range"]::-moz-range-progress {
  height: var(--track); border-radius: var(--radius-pill); background: var(--accent);
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 15px; height: 15px;
  margin-top: calc((var(--track) - 15px) / 2);
  border-radius: 50%; background: var(--accent);
  border: 2px solid var(--lit-edge);
  box-shadow: var(--shadow-chip);
  transition: transform .12s ease, box-shadow .12s ease;
}
input[type="range"]::-moz-range-thumb {
  width: 15px; height: 15px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--lit-edge);
  box-shadow: var(--shadow-chip);
  transition: transform .12s ease, box-shadow .12s ease;
}
input[type="range"]:hover::-webkit-slider-thumb { transform: scale(1.13); }
input[type="range"]:hover::-moz-range-thumb { transform: scale(1.13); }
input[type="range"]:active::-webkit-slider-thumb,
input[type="range"]:focus-visible::-webkit-slider-thumb {
  transform: scale(1.2); box-shadow: 0 0 0 5px var(--accent-glow);
}
input[type="range"]:active::-moz-range-thumb,
input[type="range"]:focus-visible::-moz-range-thumb {
  transform: scale(1.2); box-shadow: 0 0 0 5px var(--accent-glow);
}
input[type="range"]:disabled { cursor: not-allowed; opacity: .5; }
.slider-ticks {
  display: flex; justify-content: space-between;
  font-size: var(--text-app-sm); color: var(--muted-fg);
  margin-top: 2px; font-variant-numeric: tabular-nums;
  letter-spacing: var(--tracking-data);
}

/* ---------------------------------------------------------------- legend */

.legend-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
.legend-layer { font-size: var(--text-app-lg); font-weight: var(--weight-strong); }
.legend-units {
  font-size: var(--text-app-sm); color: var(--muted-fg);
  font-family: var(--font-data); font-variant-numeric: tabular-nums;
  letter-spacing: var(--tracking-data);
}

.legend { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.legend li { display: flex; align-items: center; gap: 8px; font-size: var(--text-app); }
/* No border. A ramp step is a measurement, and an outline around it is a
   second edge competing with the step above and below it. */
.legend .sw {
  width: 16px; height: 12px; border-radius: 3px;
  flex: 0 0 auto;
}
/* Bare land — the map's land colour, which is what "no shortfall" and the
   bridge's exactly-zero bin paint as. It has to be the LAND colour, not the
   panel's surface: the two are the same white in the light theme and are not in
   the dark one. */
/* The one legend swatch that keeps an edge. The ramp steps carry none — the
   sequential legend is six fills and 1px gaps, no borders — but
   this one is not a step: it paints `--map-land`, which in Clear is the same
   #ffffff as the panel behind it, so without an edge the row reads as a
   label with nothing beside it. An inset ring rather than a border, so the
   swatch stays exactly the size of the six it lines up with. */
.sw-bare {
  background: var(--map-land);
  box-shadow: inset 0 0 0 1px var(--ink-hairline-strong);
}
.sw-none {
  background:
    repeating-linear-gradient(45deg, transparent 0 3px, var(--border-strong) 3px 4px);
  background-color: var(--card);
}
/* The wind swatch is the mark itself, not a colour chip: the arrow carries a
   direction, and a rectangle of ink would say nothing the row's text does not.
   Drawn pointing east, which is where the mid-latitude westerlies point and so
   the commonest thing on the map. Zinc, like the arrow — hue is kept for
   the data ramp. */
.sw-wind {
  background: none;
  border-radius: 0;
  align-self: center;
  color: var(--map-region-text, var(--muted-fg));
  clip-path: polygon(0 40%, 62% 40%, 62% 22%, 100% 50%, 62% 78%, 62% 60%, 0 60%);
  background-color: currentColor;
}
.lg-txt { color: var(--muted-fg); font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-data); }
/* Band heading over each ramp when the map paints two at once (in drought /
   below normal). Same treatment as the modal's group titles, so "this list
   describes that band" reads the same way in both places. */
.legend-tier {
  font-size: var(--text-app-xs); letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase; font-weight: var(--weight-strong);
  color: var(--ink-faint); margin: 0 0 5px;
}
#legendList + .legend-tier { margin-top: 10px; }
.legend-extra { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border); }

.asof {
  margin-top: 10px; padding-top: 9px;
  border-top: 1px solid var(--border);
  font-size: var(--text-app); color: var(--muted-fg);
}
.asof .asof-row { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
.asof .asof-row + .asof-row { margin-top: 3px; }
/* A long label wraps; the date must not — it broke mid-date ("2026-08- / 06"). */
.asof b {
  font-weight: var(--weight-strong); color: var(--fg);
  font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-data);
  white-space: nowrap; text-align: right;
}
.asof .asof-warn { margin-top: 7px; font-style: italic; }

/* --------------------------------------------------------------- switches */

.switch { display: flex; align-items: center; gap: 9px; padding: 4px 0; cursor: pointer; font-size: var(--text-app-lg); }
.switch input { appearance: none; -webkit-appearance: none; margin: 0;
  width: 32px; height: 18px; border-radius: var(--radius-pill);
  background: var(--border-strong); position: relative; cursor: pointer;
  transition: background .14s ease; flex: 0 0 auto; }
.switch input::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 14px; height: 14px; border-radius: 50%; background: var(--bg);
  transition: transform .14s ease;
}
.switch input:checked { background: var(--fg); }
.switch input:checked::after { transform: translateX(14px); }
.cloud-date { padding: 6px 0 2px 41px; }
.cloud-date label { font-size: var(--text-app); color: var(--muted-fg); display: block; margin-bottom: 4px; }
.cloud-date .block-note { margin-top: 6px; }

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

/* Continent scope. Sits in the dialog head, under the title it rewrites — it
   changes what every number below is about, so it is not a control that can be
   allowed to look like a filter tucked beside the chart. */
.world-scope {
  display: flex; align-items: center; gap: 8px;
  margin-top: 9px; padding-right: 36px;
}
.world-scope[hidden] { display: none; }
.world-scope label {
  font-size: var(--text-app-xs); letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase; font-weight: var(--weight-strong);
  color: var(--ink-faint);
}
.world-scope select {
  font: inherit; font-size: var(--text-app-lg); font-weight: var(--weight-strong);
  color: var(--fg); background: var(--card);
  border: 1px solid var(--border-strong); border-radius: var(--radius-control);
  padding: 3px 8px; cursor: pointer;
}
.world-scope select:hover { border-color: var(--fg); }

/* The chart doubles as the month picker (src/globe/chrome.js
   worldHistogram): the selected month owns its whole slot, behind the bars,
   so the reading and the control are the same object. */
.wh-plot svg { cursor: pointer; }
.wh-pick { fill: var(--wash); }

/* Stat rows, not a chart: a handful of numbers the reader takes one at a time.
   The value leads at display size and the sentence sits beside it, so the list
   can be skimmed down its left edge. */
.gstats { list-style: none; margin: 0 0 4px; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.gstats li { display: flex; align-items: baseline; gap: 10px; }
.gs-val {
  font-size: 21px; font-weight: var(--weight-strong); letter-spacing: var(--tracking-data);
  font-variant-numeric: tabular-nums; color: var(--fg);
  flex: 0 0 auto; min-width: 92px; text-align: right;
}
/* The unit rides with the number but never competes with it. */
.gs-unit {
  font-size: var(--text-app-sm); font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-data); color: var(--muted-fg); margin-left: 3px;
}
.gs-txt { font-size: var(--text-app); line-height: 1.4; color: var(--muted-fg); }

/* The month-by-month histogram, given real height: this is the page whose job
   is the long view. */
.wh { margin-top: 2px; }
/* The scale lives beside the plot, not inside it: the SVG is stretched with
   preserveAspectRatio="none", which would squash any text drawn in it. */
.wh-frame { display: flex; align-items: stretch; gap: 7px; }
.wh-axis {
  display: flex; flex-direction: column; justify-content: space-between;
  font-size: var(--text-app-xs); color: var(--muted-fg);
  font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-data);
  text-align: right; flex: 0 0 auto; line-height: 1;
}
.wh-plot { flex: 1; min-width: 0; }
.wh-plot svg { display: block; width: 100%; height: 96px; overflow: visible; }
.wh-readout {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 12px;
  font-size: var(--text-app); color: var(--muted-fg); min-height: 18px; margin-bottom: 6px;
}
.wh-month {
  color: var(--fg); font-weight: var(--weight-strong);
  font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-data);
}
.wh-key {
  display: inline-flex; align-items: center; gap: 5px;
  font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-data);
}
.wh-key i { width: 9px; height: 9px; border-radius: 2px; display: inline-block; }
.wh-of, .wh-hint { font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-data); }
.wh-ends {
  display: flex; justify-content: space-between; padding-left: 30px;
  font-size: var(--text-app-xs); color: var(--muted-fg); margin-top: 3px;
  font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-data);
}

/* ---------------------------------------------------------- gating block */

.gating-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.gating-list li { font-size: var(--text-app); color: var(--muted-fg); border-left: 2px solid var(--border-strong); padding-left: 9px; }
/* A literal store key, so it keeps the mono face. */
.gating-list b { color: var(--fg); font-weight: var(--weight-strong); font-family: var(--mono); font-size: var(--text-app-sm); }
.gating-list .gating-more { border-left-color: transparent; }

/* ----------------------------------------------------------- attribution */

.attribution {
  border-top: 1px solid var(--border);
  padding: 9px 14px;
  font-size: var(--text-app-sm); line-height: 1.45;
  color: var(--muted-fg);
  display: flex; flex-direction: column; gap: 3px;
}
.attribution .attr-link {
  color: var(--fg); text-decoration: none; font-size: var(--text-app); font-weight: var(--weight-strong);
  border-bottom: 1px solid var(--border-strong); align-self: flex-start;
}
.attribution .attr-link:hover { border-bottom-color: var(--fg); }

/* The Overview trigger sits beside Methodology and must read as its twin — a
   button brings the platform's own face, so only the FAMILY is reset here.
   `font: inherit` would reset the size too, and at (0,1,1) it outranks
   .link-btn's own `font-size` at (0,1,0): the button would draw at the
   inherited size while the link beside it draws at --text-app-lg. */
button.link-btn { font-family: inherit; cursor: pointer; }

/* ------------------------------------------------------------- overlays */

.stage-overlay {
  position: absolute; inset: 0; z-index: 2;
  display: flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  backdrop-filter: blur(2px);
  pointer-events: none;
}
.stage-overlay-inner {
  background: linear-gradient(145deg, var(--glass-a), var(--glass-b));
  border: 0;
  border-radius: var(--radius-card);
  box-shadow: var(--glass-shadow), var(--glass-inner);
  -webkit-backdrop-filter: saturate(185%) blur(20px);
  backdrop-filter: saturate(185%) blur(20px);
  padding: 12px 18px; font-size: var(--text-app-lg); color: var(--muted-fg);
  max-width: 360px; text-align: center;
}
.stage-overlay-inner p { margin: 0; }

.maplibregl-ctrl-attrib, .maplibregl-ctrl-logo { display: none !important; }
.maplibregl-canvas:focus-visible { outline: 2px solid var(--ring); outline-offset: -2px; }

/* The distance scale. MapLibre's own control, dressed as page chrome: it is a
   measurement, so it stays in the neutral scale and never borrows a data hue.
   Lifted clear of the credits that now sit under it in the same corner: 114px
   is their 76px offset plus the two 15px lines they always wrap to plus a
   gap. It therefore tracks the TIMELINE (through the credits) and not
   --topbar-height.

   MapLibre ships it with a translucent white fill, which on this page reads as
   a floating white rectangle rather than a scale bar. Stripped back to what a
   scale bar actually is — a bracket the width of the distance, labelled above
   it — and the label is kept legible over ocean, land and a dark bin alike by a
   halo rather than by a panel. */
.maplibregl-ctrl-bottom-left { margin-bottom: 114px; }
.maplibregl-ctrl-bottom-left .maplibregl-ctrl {
  background: none; box-shadow: none;
}
.maplibregl-ctrl-scale {
  background: none;
  box-sizing: border-box;
  border: 1px solid var(--muted-fg);
  border-top: 0;
  border-radius: 0;
  color: var(--fg);
  font-family: var(--font-data);
  font-size: var(--text-app-sm);
  font-weight: var(--weight-strong);
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--tracking-data);
  padding: 0 0 3px;
  text-align: center;
  text-shadow: 0 0 3px var(--bg), 0 0 3px var(--bg), 0 0 3px var(--bg);
}

/* ------------------------------------------------------------ map credits */

/* The source line, as fine print on the map rather than a row in the panel.
   Bottom-left, directly under the distance scale — the two are one stack of
   map fine print, credits below, scale above (the scale's own offset is set
   off this block, see .maplibregl-ctrl-bottom-left).

   It clears the timeline card, which is centred and can grow to within 65px of
   either edge: 76px is that card's 14px offset plus its ~58px height plus a
   line's breathing room. Sitting above the card rather than in the corner
   beside it is what keeps the width free — at 420px the line always wraps to
   exactly two, at every stage width the desktop layout allows, which is what
   lets the scale above it take a fixed offset.

   No card: like the scale, it stays legible over ocean, land and a dark bin
   alike by a halo rather than by a panel. `pointer-events: none` keeps it out
   of the way of a drag that starts on the corner it sits in. */
.map-credits {
  position: absolute; z-index: 3;
  left: 14px; bottom: 76px;
  max-width: min(420px, calc(100% - 28px));
  margin: 0;
  text-align: left;
  font-size: var(--text-app-xs); line-height: 1.5;
  color: var(--muted-fg);
  text-shadow: 0 0 3px var(--bg), 0 0 3px var(--bg), 0 0 3px var(--bg);
  pointer-events: none;
}

/* -------------------------------------------------------------- timeline */

/* The merged live/snapshot control: one month at a time, opening on the most
   recent one, with playback. It floats over the map rather than sitting in the
   top bar because it is a reading of the map, not a setting. */
.timeline {
  position: absolute; z-index: 3;
  left: 50%; transform: translateX(-50%);
  bottom: 14px;
  width: min(560px, calc(100% - 130px));
  display: flex; align-items: center; gap: 12px;
  padding: 8px 12px;
  background: linear-gradient(145deg, var(--glass-a), var(--glass-b));
  border: 0; border-radius: var(--radius-pill);
  box-shadow: var(--glass-shadow), var(--glass-inner);
  -webkit-backdrop-filter: saturate(185%) blur(20px);
  backdrop-filter: saturate(185%) blur(20px);
}

.play-btn {
  flex: 0 0 auto;
  width: 30px; height: 30px; border-radius: 50%;
  border: 1px solid var(--border-strong); background: var(--card);
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; color: var(--fg);
}
.play-btn:hover:not(:disabled) { border-color: var(--fg); }
.play-btn:disabled { opacity: .4; cursor: not-allowed; }
/* One element, two glyphs: a triangle, or two bars once playing. */
.icon-play {
  width: 0; height: 0;
  border-left: 9px solid currentColor;
  border-top: 6px solid transparent;
  border-bottom: 6px solid transparent;
  margin-left: 2px;
}
.play-btn.is-playing .icon-play {
  width: 9px; height: 11px; margin-left: 0;
  border: 0;
  background: linear-gradient(currentColor, currentColor) left / 3px 100% no-repeat,
              linear-gradient(currentColor, currentColor) right / 3px 100% no-repeat;
}

.tl-track { flex: 1; min-width: 0; }
.tl-track input[type="range"] { width: 100%; height: 18px; }
.tl-ends {
  display: flex; justify-content: space-between;
  font-size: var(--text-app-xs); color: var(--muted-fg);
  font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-data);
  margin-top: -2px;
}

/* Fixed width, because the readout sits next to the track: if this box resized
   when the month name or the Latest button changed, the flex track would resize
   with it and the thumb would slide out from under a drag in progress. */
.tl-readout {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: flex-end;
  gap: 8px; width: 142px;
}
#tlMonth {
  font-size: var(--text-app-lg); font-weight: var(--weight-strong);
  font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-data);
  min-width: 62px; text-align: right;
}
/* The latest month is the default arrival state, so it is marked rather than
   labelled: a dot, not a second word competing with the month itself. Ink,
   not a view colour — this is chrome reporting a status, and the data hues
   are quarantined to the map and its legend. */
#tlMonth.is-latest::after {
  content: ""; display: inline-block;
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--ink-faint); margin-left: 6px; vertical-align: middle;
}
.tl-latest {
  border: 1px solid var(--border-strong); background: var(--card);
  border-radius: var(--radius-pill); padding: 2px 9px;
  font-size: var(--text-app-sm); color: var(--muted-fg); cursor: pointer;
  white-space: nowrap;
}
.tl-latest:hover { color: var(--fg); border-color: var(--fg); }
/* Hidden by visibility, not [hidden]: it must keep its footprint (see
   .tl-readout) or appearing mid-drag would move the track under the cursor. */
.tl-latest.is-off { visibility: hidden; }

/* ------------------------------------------------------------- tooltips */

.tipbox {
  position: fixed; z-index: 60;
  max-width: 280px;
  background: linear-gradient(145deg, var(--glass-a), var(--glass-b));
  color: var(--fg);
  border: 0; border-radius: var(--radius-control);
  box-shadow: var(--glass-shadow), var(--glass-inner);
  -webkit-backdrop-filter: saturate(185%) blur(20px);
  backdrop-filter: saturate(185%) blur(20px);
  padding: 9px 11px;
}
.tipbox .tip-text { margin: 0; font-size: var(--text-app); line-height: 1.45; color: var(--muted-fg); }
.tipbox .tip-link {
  display: inline-block; margin-top: 7px;
  font-size: var(--text-app); color: var(--fg); text-decoration: none;
  border-bottom: 1px solid var(--border-strong);
}
.tipbox .tip-link:hover { border-bottom-color: var(--fg); }

/* --------------------------------------------------------------- tooltip */

/* The opaque rung of the ladder (base.css `.glass--solid`), not the blurred
   one: this box is repositioned on every pointer move over the map, and a
   backdrop-filter that re-reads the canvas each frame is the one place the
   glass costs more than it is worth. */
#hoverTip {
  position: absolute; z-index: 3; pointer-events: none;
  background: linear-gradient(145deg, var(--glass-a), var(--glass-b)), var(--ground-raised);
  color: var(--fg);
  border: 0; border-radius: var(--radius-control);
  box-shadow: var(--glass-shadow), var(--glass-inner);
  padding: 6px 9px; font-size: var(--text-app); max-width: 220px;
  transform: translate(10px, 10px);
}
#hoverTip .ht-name { font-weight: var(--weight-strong); }
#hoverTip .ht-val { color: var(--muted-fg); font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-data); }
/* The fallback line under a "no shortfall" reading; empty until it resolves. */
#hoverTip .ht-sub {
  color: var(--muted-fg); font-size: var(--text-app);
  font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-data);
}
#hoverTip .ht-sub:empty { display: none; }

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

/* The application window: level 3 of the ladder, and the only one on screen.
   Opaque rather than blurred because it carries a page of reading, but it
   takes the lifted shadow and the lit edge so it belongs to the same
   material as everything under it. */
dialog {
  border: 1px solid var(--glass-line-lit); border-radius: var(--radius-window);
  background: var(--card); color: var(--fg);
  box-shadow: var(--glass-shadow-lg), var(--glass-inner);
  padding: 0; width: min(980px, calc(100vw - 32px));
  max-height: min(88dvh, 900px);
}
/* ONE scroller, not two. The dialog is a column that clips; only `.dialog-body`
   below scrolls. Sizing the body off the viewport instead — `88dvh - 150px`,
   guessing at the height of the head, tabs and foot — puts a second
   scrollbar around the first on two ordinary paths: whenever those three come to
   more than 150 px (the Region|Country tabs alone add 30), and on any screen
   tall enough for `min(88dvh, 900px)` to pick 900, where the body would still
   be allowed to grow to 88dvh - 150.
   `dialog[open]`, not `dialog`: an author `display` on the bare element beats
   the UA's `dialog:not([open]) { display: none }` by origin, whatever the
   specificity, and every closed dialog on the page would render. */
dialog[open] { display: flex; flex-direction: column; overflow: hidden; }
dialog::backdrop { background: var(--scrim); backdrop-filter: blur(2px); }
.dialog-close-form { position: absolute; top: 12px; right: 12px; margin: 0; }
.dialog-close { font-size: 18px; line-height: 1; }
.dialog-head { padding: 18px 20px 12px; border-bottom: 1px solid var(--border); }
.dialog-head h2 { margin: 0; font-size: 17px; font-weight: var(--weight-strong); letter-spacing: -.01em; padding-right: 36px; }
.dialog-meta { margin: 4px 0 0; font-size: var(--text-app); color: var(--muted-fg); }
/* Region | Country. Two readings of the same place, so they sit under the head
   they retitle rather than beside it. */
.dialog-tabs {
  display: flex; gap: 4px;
  padding: 8px 20px 0;
  border-bottom: 1px solid var(--border);
}
.dialog-tabs[hidden] { display: none; }
.dialog-tabs button {
  appearance: none; background: none; border: 0;
  border-bottom: 2px solid transparent;
  padding: 5px 2px 7px; margin-bottom: -1px;
  font: inherit; font-size: var(--text-app-lg); font-weight: var(--weight-strong);
  color: var(--muted-fg); cursor: pointer;
  min-width: 68px; text-align: left;
}
.dialog-tabs button:hover { color: var(--fg); }
.dialog-tabs button[aria-selected="true"] { color: var(--fg); border-bottom-color: var(--fg); }

.dialog-head, .dialog-tabs, .dialog-foot { flex: 0 0 auto; }
/* min-height: 0 — without it a flex item refuses to shrink below its content
   and the overflow moves back up to the dialog. */
.dialog-body { padding: 6px 20px 14px; overflow-y: auto; flex: 1 1 auto; min-height: 0; }
.dialog-foot { padding: 10px 20px 16px; border-top: 1px solid var(--border); font-size: var(--text-app-sm); color: var(--muted-fg); }

/* The plain-language reading at the top of the panel: the "here" card's
   sentences, in the card's own type scale, set off from the data rows below by
   a tint rather than a rule so it reads as a summary and not as a first row. */
.rb-highlight {
  margin: 12px 0 2px;
  padding: 11px 13px 9px;
  /* No rule — the tint alone is what sets this block off. */
  border-radius: var(--radius-control);
  background: var(--wash);
}
/* Collapsed, it is one label; open, the label is the eyebrow over the reading.
   The marker is the browser's own triangle — a disclosure control the reader
   already knows, and one fewer glyph to keep in step with the theme. */
.rb-highlight-sum {
  font-size: var(--text-app-xs); letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase; font-weight: var(--weight-strong);
  color: var(--ink-faint); cursor: pointer;
}
.rb-highlight-sum::marker { color: var(--ink-faint); }
.rb-highlight[open] .rb-highlight-sum { margin-bottom: 7px; }
.rb-highlight .hc-body { margin-top: 0; }
.rb-highlight-note {
  margin: 8px 0 0; font-size: var(--text-app-sm); color: var(--muted-fg);
}

/* What the region IS: facts about the ground, under the reading and above
   the three views. Static labels, not controls — so they take the .cr-chip pill
   geometry without its border-colour hover or its pointer. */
.rb-tags {
  display: flex; flex-wrap: wrap; gap: 4px;
  margin: 8px 0 2px;
}
.rb-tag {
  font-size: var(--text-app-sm); line-height: 1.5;
  padding: 1px 8px; border-radius: var(--radius-pill);
  border: 1px solid var(--border); background: var(--wash); color: var(--muted-fg);
  font-variant-numeric: tabular-nums;
}

.series-group-title {
  font-size: var(--text-app-xs); letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase; font-weight: var(--weight-strong);
  color: var(--ink-faint); margin: 16px 0 6px;
}
.series-row {
  display: grid;
  /* Free to be any width: the sparkline's viewBox is measured off this column
     at render time (`measureSparkWidth`, src/globe/modal.js), so the
     preserveAspectRatio="none" SVG always draws 1 unit to 1 px.
     The fixed track is the LABEL, and the curve takes whatever is left: the
     label column holds a name, a value and a unit and does not need the ~410 px
     a 980 px dialog would hand it, while the chart is the only thing in the
     row whose legibility scales with width. */
  grid-template-columns: minmax(0, 320px) minmax(0, 1fr);
  gap: 10px; align-items: center;
  padding: 7px 0;
  border-top: 1px solid var(--border);
}
.series-row:first-of-type { border-top: 0; }
.sr-meta { min-width: 0; }
/* Rank is carried by colour, not by weight: the row names sit at --ink-soft
   and a headline row steps up to --ink (see .role-headline below). */
.sr-name { font-size: var(--text-app-lg); font-weight: var(--weight-strong); color: var(--ink-soft); }
.sr-units {
  font-size: var(--text-app-sm); color: var(--muted-fg);
  font-family: var(--font-data); font-variant-numeric: tabular-nums;
  letter-spacing: var(--tracking-data);
}
.sr-value { font-size: 15px; font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-data); }
.sr-value.sr-null { color: var(--muted-fg); font-size: var(--text-app-lg); }
.sr-when {
  font-size: var(--text-app-sm); color: var(--muted-fg);
  font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-data);
}
/* F10: a DDI built on fewer than four dimensions. A caveat, not an alarm — the
   value is still valid, just thinner — so it stays in the muted chrome scale
   and never borrows the demand ramp's red. */
.sr-dims {
  font-size: var(--text-app-xs); color: var(--muted-fg); margin-top: 2px;
  border: 1px solid var(--border); border-radius: 3px;
  padding: 0 4px; display: inline-block; line-height: 15px;
}
/* The seasonal row's opportunity series, riding over the frequency bars. The
   fill is set inline from the coverage ramp; the ring is the panel surface, so
   a dot inside the bar it is drawn over still reads as a separate mark. The
   peak's ring is ink instead — the one static emphasis, and it follows the
   reader's weather rather than being pinned to a light-mode value. */
.sr-dot { stroke: var(--card); stroke-width: 1.5; }
.sr-dot-peak { stroke: var(--ink); stroke-width: 1.6; }

/* Calendar scale under the seasonal-timing bars. Its x axis is the twelve
   months of the year, not the record's months, so it carries its own labels
   rather than borrowing the panel's shared month axis. */
.sr-months {
  display: grid; grid-template-columns: repeat(12, 1fr);
  font-size: var(--text-app-xs); color: var(--muted-fg); margin-top: 3px;
}
.sr-months span { text-align: center; }
.sr-month-peak { color: var(--fg); font-weight: var(--weight-strong); }

/* Legend for the dashed deficit_mean overlay on the Water shortfall row: the
   swatch mirrors the sparkline's .spark-mean dash so the two read as one. */
.sr-mean {
  font-size: var(--text-app-sm); color: var(--muted-fg); margin-top: 2px;
  display: flex; align-items: center; gap: 5px;
  font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-data);
}
.sr-mean-key {
  display: inline-block; width: 14px; height: 0;
  border-top: 1.4px dashed var(--muted-fg); flex: 0 0 auto;
}
/* Solid key for the country histogram's two stacked counts (the map's own two
   colours), as against the dashed one above for an overlaid curve. */
.sr-count-key {
  display: inline-block; width: 10px; height: 10px; border-radius: 2px;
  flex: 0 0 auto;
}
/* Who was counted in the hovered month. Spans the whole row: chips under both
   columns rather than squeezed beside the chart. */
.cr-list {
  grid-column: 1 / -1;
  display: flex; flex-wrap: wrap; gap: 4px;
  margin-top: 2px;
}
.cr-list:empty { display: none; }
.cr-chip {
  appearance: none; font: inherit; font-size: var(--text-app-sm); line-height: 1.5;
  padding: 1px 6px; border-radius: var(--radius-pill); cursor: pointer;
  border: 1px solid var(--border-strong); background: var(--card); color: var(--fg);
}
.cr-chip:hover { border-color: var(--fg); }
/* The whole outline in the band's own colour — set inline from the active
   theme's ramps, so it cannot drift from the bars above it. The chip's own text
   stays reader-chrome — text never wears the data hue. */
.cr-chip.cr-drought:hover, .cr-chip.cr-below:hover { border-color: var(--fg) !important; }
/* "and N more" unrolls the rest of the list, so it is a control, not a caption. */
.cr-more {
  appearance: none; font: inherit; font-size: var(--text-app-sm); line-height: 1.5;
  background: none; border: 0; padding: 1px 4px;
  color: var(--muted-fg); align-self: center; cursor: pointer;
  text-decoration: underline; text-underline-offset: 2px;
}
.cr-more:hover { color: var(--fg); }
.sr-spark { position: relative; }
.sr-spark svg { display: block; width: 100%; height: 40px; overflow: visible; }
/* .spark-line is the invisible full path the hover crosshair reads its
   geometry from; the visible curve is the value-coloured .spark-seg strokes. */
.spark-line { fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.spark-seg  { fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.spark-zero { stroke: var(--border-strong); stroke-width: 1; stroke-dasharray: 2 3; }
/* The ±1σ / ±2σ rules sit UNDER the zero rule in weight — finer dash, lower
   opacity — so the eye still reads normal as the anchor and the others as the
   scale around it. */
.spark-guide { stroke: var(--border-strong); stroke-width: 1; stroke-dasharray: 1 4; opacity: .5; }
.spark-sel  { stroke: var(--muted-fg); stroke-width: 1; vector-effect: non-scaling-stroke; }
/* The crosshair on the bar histogram, which has no curve for a dot to sit on.
   non-scaling-stroke because these histograms draw with preserveAspectRatio
   ="none" over a viewBox as wide as the month count: on the hex family's
   12-month axis that is a ~12x horizontal stretch, and a 1 px rule rendered
   12 px thick, covering the bars it was meant to point at. */
.spark-cursor-line { stroke: var(--fg); stroke-width: 1; vector-effect: non-scaling-stroke; }
/* .spark-gap bridges episodic gaps (no shortfall) with a faint dashed line —
   present for the eye, not implying data; .spark-pt marks each defined month. */
.spark-gap  { fill: none; stroke: var(--muted-fg); stroke-width: 1; stroke-dasharray: 2 3; opacity: .5; }
.spark-pt   { stroke: none; }
/* .spark-mean overlays deficit_mean (rain to reach normal) on the shortfall
   sparkline: a dashed reference line on the same scale, above the drought bar.
   Its stroke is per-run and set inline (light red where there is a gap to
   normal) — a `stroke` here would override that attribute, so there isn't one. */
.spark-mean { fill: none; stroke-width: 1.4; stroke-dasharray: 4 2; opacity: .8; }
.spark-dot  { stroke: var(--card); stroke-width: 1.5; }
.spark-empty { font-size: var(--text-app-sm); fill: var(--muted-fg); }

/* Baseline water stress: a static 0–5 score, so the spark column shows a
   position on that scale instead of a curve. The track carries Aqueduct's five
   bands (BWS_BANDS in src/globe/palette.js); the caveat stays underneath as
   subcopy. */
.bws-gauge { padding: 2px 0; }
.bws-track {
  position: relative; height: 6px; border-radius: 3px;
  border: 1px solid var(--border);
}
.bws-mark {
  position: absolute; top: 50%; width: 9px; height: 9px; border-radius: 50%;
  transform: translate(-50%, -50%);
  background: var(--fg); border: 1.5px solid var(--card);
}
.bws-scale {
  display: flex; justify-content: space-between;
  font-size: var(--text-app-xs); color: var(--muted-fg);
  font-variant-numeric: tabular-nums; letter-spacing: var(--tracking-data);
  margin-top: 2px;
}
.bws-note { font-size: var(--text-app-xs); color: var(--muted-fg); }

/* Role hierarchy in the region modal: headline rows lead, context rows recede
   (and recover on hover so they stay readable when wanted). Nothing is bolder
   than 500, so the headline steps up in COLOUR — full ink against the rows'
   --ink-soft — and the context row steps down to body weight. */
.series-row.role-headline .sr-name { color: var(--ink); }
.series-row.role-headline .spark-seg { stroke-width: 2.4; }
.series-row.role-context { opacity: .58; }
.series-row.role-context:hover { opacity: 1; }
.series-row.role-context .sr-name { font-weight: var(--weight-body); }
.series-row.role-context .spark-seg { stroke-width: 1.4; }

/* ----------------------------------------------------------------------- */
/* Place search — a floating card over the map's top-left. Mirrors the       */
/* timeline's blurred-card treatment so the two read as one control set.     */
/* ----------------------------------------------------------------------- */
.citysearch {
  position: absolute; z-index: 3;
  top: var(--search-y); left: var(--search-x);
  width: var(--search-w);
  display: flex; align-items: center; gap: 7px;
  padding: 7px 11px;
  background: linear-gradient(145deg, var(--glass-a), var(--glass-b));
  /* Transparent rather than absent: a card takes no hairline, but the focus
     ring below needs an edge to colour and the box must not resize when it
     appears. */
  border: 1px solid transparent; border-radius: var(--radius-pill);
  box-shadow: var(--glass-shadow), var(--glass-inner);
  -webkit-backdrop-filter: saturate(185%) blur(20px);
  backdrop-filter: saturate(185%) blur(20px);
}
.citysearch:focus-within { border-color: var(--ring); }
/* Magnifier: a ring with a short diagonal handle. */
.citysearch-icon {
  flex: 0 0 auto; position: relative; width: 13px; height: 13px;
}
.citysearch-icon::before {
  content: ""; position: absolute; top: 0; left: 0;
  width: 9px; height: 9px; border-radius: 50%;
  border: 1.6px solid var(--muted-fg);
}
.citysearch-icon::after {
  content: ""; position: absolute; right: 0; bottom: 0;
  width: 5px; height: 1.6px; background: var(--muted-fg);
  transform: rotate(45deg); transform-origin: right center;
}
.citysearch input {
  flex: 1 1 auto; min-width: 0;
  border: 0; background: transparent; padding: 0;
  font: inherit; font-size: var(--text-app-lg); color: var(--fg);
}
.citysearch input:focus { outline: none; }
.citysearch input::placeholder { color: var(--muted-fg); }
.citysearch input::-webkit-search-cancel-button { -webkit-appearance: none; }

.citysearch-results {
  position: absolute; left: 0; top: calc(100% + 6px);
  width: 100%; max-height: 46dvh; overflow-y: auto;
  margin: 0; padding: 4px; list-style: none;
  background: linear-gradient(145deg, var(--glass-a), var(--glass-b));
  border: 0; border-radius: var(--radius-control);
  box-shadow: var(--glass-shadow), var(--glass-inner);
  -webkit-backdrop-filter: saturate(185%) blur(20px);
  backdrop-filter: saturate(185%) blur(20px);
}
.citysearch-results li {
  display: flex; align-items: baseline; gap: 8px;
  padding: 6px 9px; border-radius: var(--radius-default); cursor: pointer;
}
.citysearch-results li[aria-selected="true"],
.citysearch-results li:hover { background: var(--wash); }
/* Shrinkable, not fixed. At `flex: 0 0 auto` a long name ("Ciudad Gustavo Díaz
   Ordaz") cannot give ground, so on a phone — where the dropdown is 240 px —
   it runs past the card's rounded edge and pushes the population note off the
   row entirely. It ellipsises second, after .cs-where has. */
.cs-name {
  flex: 0 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--text-app-lg); color: var(--fg);
}
/* The disambiguator is the row's only elastic part: it ellipsises rather than
   push the population/capital note off the card. */
.cs-where {
  flex: 0 4 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--text-app-sm); color: var(--muted-fg);
}
.cs-meta { flex: 0 0 auto; margin-left: auto; padding-left: 6px; font-size: var(--text-app-sm); color: var(--muted-fg); }
.citysearch-empty {
  padding: 8px 9px; font-size: var(--text-app); color: var(--muted-fg); cursor: default;
}
.citysearch-empty:hover { background: transparent; }

/* The High-Resolution nudge, in the same pill treatment as the search it sits
   beside. Ellipsises rather than run off a narrow stage. */
.hires-hint {
  position: absolute; z-index: 3;
  top: var(--search-y);
  left: calc(var(--search-x) + var(--search-w) + 8px);
  max-width: calc(100% - var(--search-x) - var(--search-w) - 22px);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  padding: 7px 12px;
  font: inherit; font-size: var(--text-app); font-weight: var(--weight-strong); color: var(--fg);
  background: linear-gradient(145deg, var(--glass-a), var(--glass-b));
  border: 1px solid transparent; border-radius: var(--radius-pill);
  box-shadow: var(--glass-shadow), var(--glass-inner);
  -webkit-backdrop-filter: saturate(185%) blur(20px);
  backdrop-filter: saturate(185%) blur(20px);
  cursor: pointer;
}
/* The lit edge of the glass, not a hairline: the surface catches the light
   on hover rather than growing a border. */
.hires-hint:hover { border-color: var(--glass-line-lit); }

/* ----------------------------------------------------------------------- */
/* "Here" card — the one plain-language reading of the visitor's own region. */
/* Same blurred-card treatment as the search and timeline, sitting directly  */
/* under the search so the map's top-left reads as one column.              */
/* ----------------------------------------------------------------------- */
.herecard {
  /* Below .citysearch (3), not level with it: the card sits directly under the
     search box, so at z-index 3 it wins a same-stacking-context tie on DOM order
     and swallows the clicks meant for the results dropdown expanding over it. */
  position: absolute; z-index: 2;
  /* 62px is the search box's own geometry — --search-y (14) plus its 34 px
     height plus a 14 px gap — so it follows the card above it, not
     --topbar-height. */
  top: 62px; left: 14px;
  width: min(320px, calc(100% - 28px));
  padding: 13px 15px 12px;
  background: linear-gradient(145deg, var(--glass-a), var(--glass-b));
  border: 0; border-radius: var(--radius-card);
  box-shadow: var(--glass-shadow), var(--glass-inner);
  -webkit-backdrop-filter: saturate(185%) blur(20px);
  backdrop-filter: saturate(185%) blur(20px);
}
.hc-title {
  margin: 0; font-size: 15px; font-weight: var(--weight-strong); letter-spacing: -.01em;
  padding-right: 20px;
}
.hc-where {
  margin: 2px 0 0; font-size: var(--text-app-xs);
  letter-spacing: var(--tracking-eyebrow); font-weight: var(--weight-strong);
  text-transform: uppercase; color: var(--ink-faint);
}
.hc-body { margin-top: 9px; }
.hc-lead   { margin: 0; font-size: var(--text-app-lg); line-height: 1.45; color: var(--fg); }
.hc-claim  { margin: 7px 0 0; font-size: var(--text-app-lg); line-height: 1.45; color: var(--fg); }
/* The one figure in each sentence, in the colour its own layer is painted in
   (tokens.css): --deficit for what is missing, --bridge for what could have
   been added. Not a prose palette — the same quantity is the same colour here,
   on the ramp and in the panel's rows. */
.hc-num { font-weight: var(--weight-strong); font-variant-numeric: tabular-nums; }
.hc-num--short { color: var(--deficit); }
.hc-num--gain  { color: var(--bridge); }
.hc-trend  { margin: 7px 0 0; font-size: var(--text-app); line-height: 1.4; color: var(--muted-fg); }
.hc-foot {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 11px; padding-top: 9px; border-top: 1px solid var(--border);
}
.hc-note { flex: 1 1 auto; font-size: var(--text-app-sm); color: var(--muted-fg); }
/* THE atlas's one primary action — "See the full picture" is the only thing
   on this screen the accent is spent on. Same recipe as base.css's
   .btn--primary, at the application's density. */
.hc-cta {
  flex: 0 0 auto; padding: 5px 10px;
  font: inherit; font-size: var(--text-app); font-weight: var(--weight-strong);
  color: var(--accent-ink); background: var(--accent);
  border: 0; border-radius: var(--radius-pill); cursor: pointer;
  box-shadow: 0 6px 18px var(--accent-glow), inset 0 1px 0 #ffffff30;
  transition: background-color var(--t-ui), transform var(--t-ui), box-shadow var(--t-ui);
}
.hc-cta:hover { background: var(--accent-hover); transform: translateY(-1px); }
.hc-close {
  position: absolute; top: 8px; right: 9px;
  padding: 0; width: 20px; height: 20px; line-height: 1;
  font: inherit; font-size: 16px; color: var(--muted-fg);
  background: transparent; border: 0; border-radius: var(--radius-pill); cursor: pointer;
}
.hc-close:hover { background: var(--wash); color: var(--ink); }

@media (max-width: 900px) {
  /* ---------------------------------------------------------------------
     The globe is full-bleed: `#app` is one stage the height of the viewport,
     and everything else floats over it. The top bar keeps its place at the top
     because the view toggles are the page's primary control and a sheet that
     hid them would be a page; the panel and the region detail rise from the
     bottom edge as sheets. */

  #app {
    grid-template-columns: 1fr;
    grid-template-rows: 100dvh;
    grid-template-areas: "stage";
    height: 100dvh;
  }

  /* ONE row, scrolled sideways, not four rows wrapped.
     Wrapping put 172 px of chrome — 22 % of a 360x780 phone — permanently over
     the globe, which makes "full-bleed" a claim the screen does not support.
     A horizontally scrolling control strip is the standard answer and costs one
     line of the viewport instead of four. The wordmark goes — the tab title
     already says whose page this is — but the mark stays: it's the only route
     back to the rest of the site the atlas has (no site nav here), so it can't
     disappear entirely.

     The glass and the blur come from the desktop rule; here the bar only
     leaves the grid and floats, so its height is its content's — which is
     why the offsets below are not derived from --topbar-height. */
  .topbar {
    position: fixed; top: 0; left: 0; right: 0; z-index: 6;
    height: auto; padding: 6px 10px; flex-wrap: nowrap;
    border-bottom: 1px solid var(--ink-hairline);
  }
  .topbar .brand strong { display: none; }
  .topbar-controls {
    margin-left: 0; justify-content: flex-start; flex-wrap: nowrap;
    gap: 8px; overflow-x: auto; scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    /* The strip scrolls sideways; it must not eat a vertical swipe meant for
       the map underneath. */
    touch-action: pan-x;
  }
  .topbar-controls::-webkit-scrollbar { display: none; }
  .topbar-controls > * { flex: 0 0 auto; }

  /* Same control strip, denser: the segmented groups are the widest thing in
     it, so their padding is what decides how much of the row scrolls off
     screen. "High-Resolution" is the one label that doesn't earn its width
     here — the badge and the tooltip already say what the view is. */
  .segmented button { padding: 4px 8px; font-size: var(--text-app-sm); }
  .label-full { display: none; }
  .label-short { display: inline; }

  /* Clear of the fixed top bar — of its CONTENT height (a 30 px control plus
     12 px of padding), not of --topbar-height, which the mobile bar does not
     use. */
  .stage { --search-x: 10px; --search-y: 56px; --search-w: min(240px, calc(100% - 20px)); }
  .herecard { top: 100px; left: 10px; width: min(300px, calc(100% - 20px)); }

  /* --- the sheets ------------------------------------------------------ */

  .sheet-grip {
    display: flex; align-items: center; justify-content: center;
    width: 100%; height: 26px; flex: 0 0 auto;
    border: 0; background: transparent; cursor: grab;
    /* The grip is dragged, so the browser must not take the gesture for
       scrolling — same reason as the chart scrubbers. */
    touch-action: none;
  }
  .sheet-grip span {
    display: block; width: 38px; height: 4px; border-radius: var(--radius-pill);
    background: var(--border-strong);
  }
  .sheet-grip:active { cursor: grabbing; }

  .panel, #regionDialog[open] {
    position: fixed; left: 0; right: 0; bottom: 0; top: auto;
    margin: 0; width: 100%;
    height: var(--sheet-h, 13dvh);
    max-height: 100dvh;
    border: 0; border-top: 1px solid var(--border);
    border-radius: var(--radius-window) var(--radius-window) 0 0;
    box-shadow: var(--shadow);
    transition: height .22s ease;
    display: flex; flex-direction: column;
    /* Nothing escapes the sheet's rounded box. The panel's last child is its
       `.attribution` footer, which without this lays itself out *below* the
       101 px peek and floats over the map on its own. */
    overflow: hidden;
    overscroll-behavior: contain;
  }
  .panel { z-index: 5; }
  /* Above the control sheet, because it is opened over it. */
  #regionDialog[open] { z-index: 7; padding: 0; }
  .panel-scroll { padding: 10px 12px 18px; }
  /* At `peek` the sheet is 101 px: a grip, and one thing worth seeing. That
     thing is the view you are in — the three toggles at the top of the panel.
     The attribution is a fixed-height sibling of the scroller, so leaving it in
     takes 45 of those pixels and squeezes the toggles into a 30 px sliver. */
  .panel[data-snap="peek"] .attribution { display: none; }

  /* The three views become a row, so `peek` shows all of them — and which one
     is on — rather than the first of a vertical stack. It also makes switching
     views the one thing you can do without expanding the sheet at all, which
     on a phone is the thing you do most. The sub-line goes: at this width it
     is the difference between three chips and three cards. */
  .toggle-group { flex-direction: row; gap: 6px; margin-bottom: 12px; }
  .toggle-group button {
    flex: 1 1 0; min-width: 0;
    padding: 8px 9px;
  }
  .toggle-group .tg-sub { display: none; }
  .toggle-group .tg-label { font-size: var(--text-app); line-height: 1.25; }
  /* The desktop head opens on 18 px of padding because nothing is above it.
     Here the 26 px sheet grip is, so that 18 would make 44 px of empty card
     between the top of the sheet and the region's name — on the surface with
     the least height to spend. The side padding follows .panel-scroll's 12,
     not the dialog's 20. */
  .dialog-head { padding: 4px 14px 10px; }
  .dialog-tabs { padding: 6px 14px 0; }
  .dialog-body { padding: 6px 14px 14px; }
  .dialog-foot { padding: 8px 14px 14px; }
  .dialog-close-form { top: 4px; right: 8px; }

  .series-row { grid-template-columns: 1fr; }

  /* The timeline docks directly above the control sheet and rides up with it. */
  .timeline {
    width: calc(100% - 20px); gap: 9px; padding: 7px 10px;
    bottom: calc(var(--panel-sheet-h, 13dvh) + 10px);
    transition: bottom .22s ease;
    z-index: 4;
  }
  .tl-ends { display: none; }
  #tlMonth { min-width: 54px; }
  .maplibregl-ctrl-bottom-left { margin-bottom: calc(var(--panel-sheet-h, 13dvh) + 54px); }
  /* Rides up with the sheet like the scale bar, but on the opposite edge: the
     corner stack the desktop uses does not fit a phone, where the sheet, the
     timeline and the scale already own the bottom-left. Two points smaller
     than the desktop fine print — the stage is a third of the width, so the
     line has three or four wraps to spend and the smaller face keeps that
     block off the map. */
  .map-credits {
    right: 14px; left: auto;
    bottom: calc(var(--panel-sheet-h, 13dvh) + 64px);
    max-width: 62%;
    text-align: right;
    font-size: calc(var(--text-app-xs) - 2px);
    transition: bottom .22s ease;
  }
}
/* Nothing follows the mobile block. `.sheet-grip { display: none }` at the
   top of this file has the same specificity as the rule that shows it above,
   so the media query only wins by coming last. */


