/* src/css/tokens.css */
/**
 * Design tokens.
 *
 * Every chart colour is a custom property so switching theme is a repaint, not a re-render:
 * no JS runs, no path is regenerated, nothing reflows.
 *
 * Colour is spent on exactly one thing — distance from the 20-year median. There is no
 * red/green anywhere. Reservoirs draw down every winter; a falling line is not a warning,
 * and colouring it like one would be editorialising with someone else's data.
 *
 * Contrast ratios in the comments are measured (see tools/check-contrast.mjs) against the
 * surface each token is actually used on, in both themes.
 */

:root {
  color-scheme: light;

  /* ---- type ---- */
  --font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
          "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --t-2xs: 0.6875rem;   /* 11px — gauge chords, map labels */
  --t-xs:  0.75rem;     /* 12px — axis ticks, tile subs, footnotes */
  --t-sm:  0.8125rem;   /* 13px — eyebrow, tile labels, meta */
  --t-base: 1rem;
  --t-md:  1.0625rem;   /* lede, panel sentences */
  --t-lg:  clamp(1.25rem, 4vw, 1.5rem);
  --t-xl:  clamp(1.5rem, 6vw, 2.25rem);
  --t-tile: 1.375rem;
  --t-hero: clamp(2rem, 11cqi, 3rem);

  /* ---- spacing (4px base) ---- */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
  --sp-5: 24px; --sp-6: 32px; --sp-7: 48px; --sp-8: 64px;
  --gutter: clamp(16px, 4vw, 32px);

  /* ---- radius ---- */
  --r-1: 6px; --r-2: 10px; --r-3: 16px; --r-pill: 999px;

  /* ---- elevation (light only; dark uses borders instead) ---- */
  --e-1: 0 1px 2px rgb(14 23 32 / 0.06), 0 1px 1px rgb(14 23 32 / 0.04);
  --e-2: 0 8px 24px -8px rgb(14 23 32 / 0.28);

  /* ---- surfaces ---- */
  --c-bg: #FFFFFF;
  --c-panel: #F7F9FB;
  --c-panel-2: #EEF3F7;
  --c-chip: #EDF2F6;
  --c-hairline: #E4EAEF;
  --c-hairline-strong: #CBD6DE;

  /* ---- text ---- */
  --c-ink: #0E1720;             /* 18.1:1 on bg  — AAA */
  --c-muted: #4A5A68;           /*  7.1:1 on bg, 6.7:1 on panel — AA */
  --c-on-accent: #FFFFFF;

  /* ---- chart furniture ---- */
  --c-grid: #DDE4EA;
  --c-axis: #AEBBC5;
  --c-tick: #5A6976;            /*  5.7:1 on bg — AA for the small axis text */

  /* ---- the 20-year reference band ---- */
  --c-band: #DEEAF4;
  --c-band-edge: #5F92B4;       /*  3.4:1 — a graphical object, AA needs 3:1 */
  --c-median: #3E4C59;          /*  8.8:1 on bg, 7.2:1 on the band */

  /* ---- water / current series ---- */
  --c-water: #1E7FC2;           /*  4.3:1 on bg, 3.7:1 on the empty tube */
  --c-water-2: #5AAEE0;
  --c-accent: #005894;          /*  7.4:1 on bg — links, selected chip */
  --c-accent-soft: #E1EEF8;
  --c-gauge-empty: #E8EEF3;
  --c-gauge-rim: #AEBBC5;

  /* ---- deviation from the median: the one thing colour encodes ---- */
  /* Amber for a deficit, blue for a surplus. Distinguishable under protanopia,
     deuteranopia and tritanopia — a red/green pair would not be. */
  --c-deficit: #A9660B;         /*  4.7:1 on bg */
  --c-deficit-soft: #F6E9D5;
  --c-surplus: #1E6FA8;         /*  5.4:1 on bg */
  --c-surplus-soft: #DDEAF5;
  --c-neutral: #5A6976;

  /* Five-step diverging scale for the map. Kept light enough that the cut-out area labels
     stay legible on every step without needing a per-step text colour. */
  --c-dev-low-2:  #E8B96B;
  --c-dev-low-1:  #F4DDB4;
  --c-dev-mid:    #E7EDF2;
  --c-dev-high-1: #C3DCEF;
  --c-dev-high-2: #85B8DD;

  /* ---- per-area identity, used only where several series share one chart ----
     Derived from the Okabe-Ito palette, which is colour-blind-safe by construction, then
     darkened uniformly (x0.78) for contrast on white. The hue identity is the SAME in both
     themes — only lightness changes — so an area never changes colour when you toggle.
     Measured worst-case dichromatic separation across both themes: 40 (was 10 before). */
  --c-area-1: #B37C00;   /* orange     3.6:1 */
  --c-area-2: #438CB6;   /* sky        3.7:1 */
  --c-area-3: #007B5A;   /* green      5.3:1 */
  --c-area-4: #A64900;   /* vermillion 5.9:1 */
  --c-area-5: #9F5E82;   /* purple     4.8:1 */

  --focus: 3px solid #005894;
  --focus-offset: 2px;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --c-bg: #0E1720;
    --c-panel: #16212C;
    --c-panel-2: #1D2A37;
    --c-chip: #1D2A37;
    --c-hairline: #26333F;
    --c-hairline-strong: #3B4A57;

    --c-ink: #E9EFF4;           /* 15.4:1 on bg — AAA */
    --c-muted: #A3B4C2;         /*  8.0:1 on bg, 7.2:1 on panel — AA */
    --c-on-accent: #06101A;

    --c-grid: #243039;
    --c-axis: #46545F;
    --c-tick: #A3B4C2;

    --c-band: #16323F;
    --c-band-edge: #4E8CB4;     /*  4.6:1 on bg */
    --c-median: #C2D0DB;        /* 11.0:1 on bg */

    --c-water: #2E93D9;         /*  6.3:1 on bg */
    --c-water-2: #6BBBEA;
    --c-accent: #7CC4F0;        /*  9.4:1 on bg */
    --c-accent-soft: #14293A;
    --c-gauge-empty: #16212C;
    --c-gauge-rim: #3B4A57;

    --c-deficit: #E0A33C;       /*  9.0:1 on bg */
    --c-deficit-soft: #2C2313;
    --c-surplus: #5AAEE0;       /*  8.4:1 on bg */
    --c-surplus-soft: #12293A;
    --c-neutral: #A3B4C2;

    --c-dev-low-2:  #7A5410;
    --c-dev-low-1:  #4A3712;
    --c-dev-mid:    #24313C;
    --c-dev-high-1: #1B3D53;
    --c-dev-high-2: #2A5F84;

    --c-area-1: #E9AC21;   /* orange     8.9:1 */
    --c-area-2: #6CBEEC;   /* sky        8.8:1 */
    --c-area-3: #21AB85;   /* green      6.2:1 */
    --c-area-4: #DA7321;   /* vermillion 5.6:1 */
    --c-area-5: #D38AB2;   /* purple     6.9:1 */

    /* Shadows are invisible on dark surfaces and still cost paint time. */
    --e-1: none;
    --e-2: none;

    --focus: 3px solid #7CC4F0;
  }
}

/* The theme toggle stamps data-theme on the root; it must win in both directions, so the
   dark block above is scoped to :not([data-theme="light"]) and this one repeats it. */
:root[data-theme="dark"] {
  color-scheme: dark;

  --c-bg: #0E1720;
  --c-panel: #16212C;
  --c-panel-2: #1D2A37;
  --c-chip: #1D2A37;
  --c-hairline: #26333F;
  --c-hairline-strong: #3B4A57;

  --c-ink: #E9EFF4;
  --c-muted: #A3B4C2;
  --c-on-accent: #06101A;

  --c-grid: #243039;
  --c-axis: #46545F;
  --c-tick: #A3B4C2;

  --c-band: #16323F;
  --c-band-edge: #4E8CB4;
  --c-median: #C2D0DB;

  --c-water: #2E93D9;
  --c-water-2: #6BBBEA;
  --c-accent: #7CC4F0;
  --c-accent-soft: #14293A;
  --c-gauge-empty: #16212C;
  --c-gauge-rim: #3B4A57;

  --c-deficit: #E0A33C;
  --c-deficit-soft: #2C2313;
  --c-surplus: #5AAEE0;
  --c-surplus-soft: #12293A;
  --c-neutral: #A3B4C2;

  --c-dev-low-2:  #7A5410;
  --c-dev-low-1:  #4A3712;
  --c-dev-mid:    #24313C;
  --c-dev-high-1: #1B3D53;
  --c-dev-high-2: #2A5F84;

  --c-area-1: #E9AC21;   /* orange     8.9:1 */
  --c-area-2: #6CBEEC;   /* sky        8.8:1 */
  --c-area-3: #21AB85;   /* green      6.2:1 */
  --c-area-4: #DA7321;   /* vermillion 5.6:1 */
  --c-area-5: #D38AB2;   /* purple     6.9:1 */

  --e-1: none;
  --e-2: none;

  --focus: 3px solid #7CC4F0;
}

/* Windows high-contrast and forced-colors: let the OS win, but keep the series
   distinguishable by shape rather than hue. */
@media (forced-colors: active) {
  :root {
    --c-band: Canvas;
    --c-band-edge: CanvasText;
    --c-median: CanvasText;
    --c-water: Highlight;
    --c-ink: CanvasText;
    --c-muted: CanvasText;
  }
}

/* src/css/base.css */
/* Reset, typography, focus and the accessibility primitives. */

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
}

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

body {
  margin: 0;
  background: var(--c-bg);
  color: var(--c-ink);
  font-family: var(--font);
  font-size: var(--t-base);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  /* Every number on this page is compared against another number directly above or below
     it, so proportional digits would make columns wobble. */
  font-variant-numeric: tabular-nums;
}

h1, h2, h3, p, figure, ul, ol, dl { margin: 0; }
ul, ol { padding: 0; list-style: none; }
img, svg { max-width: 100%; }
svg { display: block; }

h1 {
  font-size: var(--t-xl);
  line-height: 1.15;
  font-weight: 700;
  letter-spacing: -0.015em;
  text-wrap: balance;
  max-inline-size: 22ch;
}

h2 {
  font-size: var(--t-lg);
  line-height: 1.25;
  font-weight: 600;
  letter-spacing: -0.01em;
  text-wrap: balance;
}

p { max-inline-size: 62ch; }

a {
  color: var(--c-accent);
  text-underline-offset: 0.15em;
  text-decoration-thickness: max(1px, 0.06em);
}
a:hover { text-decoration-thickness: max(2px, 0.1em); }

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  cursor: pointer;
}

:focus-visible {
  outline: var(--focus);
  outline-offset: var(--focus-offset);
  border-radius: var(--r-1);
}

/* Visually hidden but available to assistive technology. Used for the chart data tables,
   the ARIA live region and the h1 eyebrow. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  inset-inline-start: var(--sp-3);
  inset-block-start: -100%;
  z-index: 20;
  padding: var(--sp-2) var(--sp-4);
  background: var(--c-bg);
  color: var(--c-ink);
  border: 1px solid var(--c-hairline-strong);
  border-radius: var(--r-1);
}
.skip-link:focus { inset-block-start: var(--sp-3); }

/* --- numbers ------------------------------------------------------- */

.num { font-variant-numeric: tabular-nums; white-space: nowrap; }
.num--deficit { color: var(--c-deficit); }
.num--surplus { color: var(--c-surplus); }
.num--neutral { color: var(--c-neutral); }

/* --- print --------------------------------------------------------- */

@media print {
  body { background: #fff; color: #000; }
  .controls, .skip-link, .theme-toggle, .lang-picker { display: none !important; }
  details { display: block; }
  details > summary { display: none; }
  a::after { content: " (" attr(href) ")"; font-size: 0.8em; word-break: break-all; }
}

/* src/css/layout.css */
/* Page shell and panels. Exactly two breakpoints: 600px and 1000px. */

.wrap {
  width: 100%;
  max-width: 1120px;
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.masthead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding-block: var(--sp-3);
  min-block-size: 44px;
}

.brand {
  font-size: var(--t-sm);
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--c-ink);
  text-decoration: none;
}
.brand strong { font-weight: 700; }

.masthead__tools {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

main { display: block; }

.section {
  padding-block: var(--sp-6);
  border-block-start: 1px solid var(--c-hairline);
}
.section:first-of-type { border-block-start: 0; }

.section__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-block-end: var(--sp-4);
}

.panel {
  background: var(--c-panel);
  border: 1px solid var(--c-hairline);
  border-radius: var(--r-3);
  padding: var(--sp-4);
}

@media (min-width: 600px) {
  .panel { padding: var(--sp-5); }
  .section { padding-block: var(--sp-7); }
}

/* Collapsible sections. The content inside is baked into the closed element rather than
   built on open, so it is real content for search engines, print and no-JS readers. */
details.disclosure {
  border: 1px solid var(--c-hairline);
  border-radius: var(--r-2);
  background: var(--c-panel);
  padding: var(--sp-3) var(--sp-4);
}
details.disclosure + details.disclosure { margin-block-start: var(--sp-3); }

details.disclosure > summary {
  cursor: pointer;
  font-weight: 600;
  font-size: var(--t-base);
  list-style: none;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
details.disclosure > summary::-webkit-details-marker { display: none; }
details.disclosure > summary::before {
  content: "";
  inline-size: 0.5em;
  block-size: 0.5em;
  border-inline-end: 2px solid var(--c-muted);
  border-block-end: 2px solid var(--c-muted);
  rotate: -45deg;
  transition: rotate 120ms ease;
  flex: none;
}
details.disclosure[open] > summary::before { rotate: 45deg; }
@media (prefers-reduced-motion: reduce) {
  details.disclosure > summary::before { transition: none; }
}

details.disclosure > :not(summary) { margin-block-start: var(--sp-3); }

.prose { color: var(--c-muted); font-size: var(--t-base); }
.prose + .prose { margin-block-start: var(--sp-3); }

.note {
  color: var(--c-muted);
  font-size: var(--t-xs);
  line-height: 1.5;
}

/* --- data table used as the text alternative for every chart --- */

.data-table {
  inline-size: 100%;
  border-collapse: collapse;
  font-size: var(--t-xs);
}
.data-table th, .data-table td {
  padding: var(--sp-1) var(--sp-2);
  text-align: end;
  border-block-end: 1px solid var(--c-hairline);
  white-space: nowrap;
}
.data-table th:first-child, .data-table td:first-child { text-align: start; }
.data-table thead th { color: var(--c-muted); font-weight: 600; }

.table-scroll { overflow-x: auto; max-block-size: 60vh; overflow-y: auto; }

/* src/css/controls.css */
/* Area pills, unit toggle, theme toggle, language picker. */

.controls {
  position: sticky;
  inset-block-start: 0;
  z-index: 10;
  background: color-mix(in srgb, var(--c-bg) 92%, transparent);
  backdrop-filter: blur(8px);
  border-block-end: 1px solid transparent;
  transition: border-color 150ms ease, box-shadow 150ms ease;
}
.controls[data-stuck="true"] {
  border-block-end-color: var(--c-hairline);
  box-shadow: var(--e-1);
}
@media (prefers-reduced-motion: reduce) {
  .controls { transition: none; }
}

@supports not (backdrop-filter: blur(1px)) {
  .controls { background: var(--c-bg); }
}

.controls__inner {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding-block: var(--sp-2);
  min-block-size: 48px;
}

/* Area pills scroll horizontally on a phone and snap, so a thumb can flick through them
   without the row ever wrapping into a second line. */
.pills {
  display: flex;
  gap: var(--sp-2);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  /* min-inline-size:0 is what lets this actually shrink instead of shoving the unit
     toggle off the row and scrolling underneath it. */
  flex: 1 1 auto;
  min-inline-size: 0;
  /* Bleed to the screen edge on the start side only — bleeding on both sides puts the
     tail of the list underneath the unit toggle. */
  margin-inline-start: calc(var(--gutter) * -1);
  padding-inline-start: var(--gutter);
  padding-inline-end: var(--sp-2);
  /* Fade the cut-off edge so it reads as "more to scroll" rather than as a clipped pill. */
  mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent 100%);
}
.pills::-webkit-scrollbar { display: none; }

.pill {
  scroll-snap-align: start;
  flex: none;
  display: inline-grid;
  gap: 0;
  align-content: center;
  justify-items: start;
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--r-pill);
  background: var(--c-chip);
  border: 1px solid transparent;
  color: var(--c-ink);
  font-size: var(--t-sm);
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  /* 44px minimum touch target. */
  min-block-size: 36px;
}
.pill:hover { border-color: var(--c-hairline-strong); }
.pill[aria-current="page"],
.pill[aria-pressed="true"] {
  background: var(--c-accent);
  color: var(--c-on-accent);
}

.pill__code { line-height: 1.2; }
.pill__region {
  font-size: var(--t-2xs);
  font-weight: 500;
  line-height: 1.2;
  opacity: 0.75;
  /* Below 420px, six two-line pills would push the gauge out of the first screenful. The
     map and the area list carry the same information further down the page. */
  display: none;
}
@media (min-width: 420px) {
  .pill__region { display: block; }
  .pill { padding-block: var(--sp-1); }
}

.segmented {
  display: inline-flex;
  flex: none;
  padding: 2px;
  background: var(--c-chip);
  border-radius: var(--r-pill);
}
.segmented button {
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--r-pill);
  font-size: var(--t-sm);
  font-weight: 600;
  color: var(--c-muted);
  min-block-size: 32px;
}
.segmented button[aria-pressed="true"] {
  background: var(--c-bg);
  color: var(--c-ink);
  box-shadow: var(--e-1);
}

.icon-button {
  display: inline-grid;
  place-items: center;
  inline-size: 36px;
  block-size: 36px;
  border-radius: var(--r-1);
  color: var(--c-muted);
}
.icon-button:hover { background: var(--c-chip); color: var(--c-ink); }
.icon-button svg { inline-size: 20px; block-size: 20px; fill: none; stroke: currentColor; stroke-width: 1.75; }

/* The theme toggle only exists with JS; without it the OS preference still applies via
   the prefers-color-scheme block in tokens.css. */
.theme-toggle { display: none; }
.js .theme-toggle { display: inline-grid; }

.theme-toggle .icon-sun { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-sun { display: block; }
:root[data-theme="dark"] .theme-toggle .icon-moon { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle .icon-sun { display: block; }
  :root:not([data-theme="light"]) .theme-toggle .icon-moon { display: none; }
}

/* Language picker: a real <select> wrapping real links is not possible, so it is a
   native select that navigates — which keeps it keyboard- and screen-reader-native, and
   works without any custom listbox code. */
.lang-picker { position: relative; display: inline-flex; align-items: center; }
.lang-picker select {
  appearance: none;
  font: inherit;
  font-size: var(--t-sm);
  font-weight: 600;
  color: var(--c-muted);
  background: transparent;
  border: 1px solid var(--c-hairline-strong);
  border-radius: var(--r-1);
  padding: var(--sp-1) var(--sp-5) var(--sp-1) var(--sp-2);
  min-block-size: 36px;
  cursor: pointer;
}
.lang-picker::after {
  content: "";
  position: absolute;
  inset-inline-end: var(--sp-2);
  inline-size: 0.4em; block-size: 0.4em;
  border-inline-end: 2px solid var(--c-muted);
  border-block-end: 2px solid var(--c-muted);
  rotate: 45deg;
  translate: 0 -0.15em;
  pointer-events: none;
}

/* Status banner, shown only when the pipeline reports something worth saying. */
.banner {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-2);
  background: var(--c-deficit-soft);
  color: var(--c-ink);
  font-size: var(--t-sm);
  border: 1px solid var(--c-hairline-strong);
  margin-block: var(--sp-3);
}
.banner[hidden] { display: none; }

/* src/css/hero.css */
/* The hero: eyebrow, verdict, reservoir gauge, deviation ruler, lede, meta, KPI tiles.
   Budgeted to fit above the fold on a 375x560 viewport (iPhone SE in Safari). Nothing in
   here may grow into the KPI strip's peek — that peek is the scroll affordance. */

.hero {
  padding-block: var(--sp-4) var(--sp-5);
  display: grid;
  gap: var(--sp-4);
}

.hero__eyebrow {
  display: block;
  font-size: var(--t-sm);
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--c-muted);
  margin-block-end: var(--sp-2);
}

.hero__verdict { margin: 0; }

/* The verdict word is never coloured red or green. Only the two genuinely directional
   states get a tint, and it is the deficit/surplus pair, not a traffic light. */
.hero__verdict[data-verdict="unusual-low"],
.hero__verdict[data-verdict="low"] { color: var(--c-ink); }
.hero__verdict[data-verdict="unusual-low"]::after,
.hero__verdict[data-verdict="unusual-high"]::after {
  content: "";
  display: block;
  inline-size: 2.5rem;
  block-size: 3px;
  border-radius: 2px;
  margin-block-start: var(--sp-2);
}
.hero__verdict[data-verdict="unusual-low"]::after { background: var(--c-deficit); }
.hero__verdict[data-verdict="unusual-high"]::after { background: var(--c-surplus); }

.hero__lede {
  font-size: var(--t-md);
  line-height: 1.45;
  color: var(--c-muted);
  max-inline-size: 46ch;
  text-wrap: pretty;
}

.hero__meta {
  font-size: var(--t-sm);
  color: var(--c-muted);
  line-height: 1.45;
}
.hero__meta span { display: block; }

/* --- gauge ---------------------------------------------------------- */

.gauge {
  container-type: inline-size;
  inline-size: min(52vw, 184px);
  justify-self: center;
  position: relative;
}

@media (max-height: 700px) and (max-width: 480px) {
  /* Reclaim ~60px on the shortest phones so the KPI peek survives. */
  .gauge { inline-size: min(44vw, 150px); }
  .hero { gap: var(--sp-3); }
}

.gauge__svg { inline-size: 100%; block-size: auto; }

.gauge__tube-bg { fill: var(--c-gauge-empty); }
.gauge__rim { fill: none; stroke: var(--c-gauge-rim); stroke-width: 2; }

.gauge__water { fill: var(--c-water); }
.gauge__water--back { fill: var(--c-water-2); opacity: 0.55; }

/* Reference chords across the tube. Dashed so they read as annotation, not data. */
.gauge__chord {
  stroke: var(--c-median);
  stroke-width: 1.25;
  stroke-dasharray: 3 3;
  opacity: 0.75;
}
.gauge__chord--median { stroke-dasharray: none; stroke-width: 1.75; opacity: 1; }
.gauge__chord-label {
  fill: var(--c-muted);
  font-size: var(--t-2xs);
  font-family: var(--font);
}

.gauge__readout {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  pointer-events: none;
  font-size: var(--t-hero);
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1;
  color: var(--c-ink);
}

/* The readout sits at the vertical centre of the tube, so whether it lands on water or on
   empty tube is decided entirely by the fill level — which is known at render time. A
   four-way outline that has to survive both cases looks like a mistake where the waterline
   crosses the glyphs, so the renderer stamps which case applies and each gets a treatment
   that suits it. */
.gauge__readout[data-over-water="true"] {
  color: #fff;
  text-shadow: 0 1px 6px rgb(3 26 43 / 0.45);
}
.gauge__readout[data-over-water="false"] {
  color: var(--c-ink);
  text-shadow: 0 1px 8px var(--c-bg);
}

/* The water rises once on load. The level itself is baked into the markup, so if the
   animation never runs the gauge is still correct. */
@media (prefers-reduced-motion: no-preference) {
  .js .gauge__fill { animation: gauge-rise 900ms cubic-bezier(0.22, 1, 0.36, 1) both; }
  .js .gauge__wave { animation: wave-drift var(--wave-duration, 7s) linear infinite; }
  .js .gauge__water--back .gauge__wave {
    animation-duration: calc(var(--wave-duration, 7s) * 1.6);
    animation-direction: reverse;
  }
}

/* Always rises from fully empty, not from "wherever this week's level happens to be".
   A constant keeps the whole animation in the stylesheet, which matters because a
   per-page `style="--gauge-rise-from:…"` would be dropped by `style-src 'self'` — and it
   reads better anyway: the tank fills, rather than nudging up by an arbitrary amount.
   224 is the inner tube height in gauge user units. */
@keyframes gauge-rise {
  from { transform: translateY(224px); }
  to   { transform: translateY(0); }
}

/* One full period of horizontal travel, so the tiled wave loops seamlessly. */
@keyframes wave-drift {
  from { transform: translateX(0); }
  to   { transform: translateX(var(--wave-period, -80px)); }
}

/* --- deviation ruler ------------------------------------------------ */

.ruler {
  display: grid;
  gap: var(--sp-1);
  font-size: var(--t-xs);
  color: var(--c-muted);
}

/* preserveAspectRatio="none" lets the 100-unit track stretch to any column width while
   the marker keeps its drawn size, because the marker is stroked, not scaled. */
.ruler__track {
  inline-size: 100%;
  block-size: 22px;
}
.ruler__line {
  stroke: var(--c-hairline-strong);
  stroke-width: 3;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.ruler__median {
  stroke: var(--c-median);
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
}
/* Outside the 20-year range is the single most newsworthy thing this page can show, so it
   gets a distinct shape as well as a distinct colour — never colour alone. */
.ruler__marker {
  stroke: var(--c-water);
  stroke-width: 9;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.ruler__marker[data-outside] { stroke-linecap: butt; stroke-width: 11; }
.ruler__marker[data-outside="low"] { stroke: var(--c-deficit); }
.ruler__marker[data-outside="high"] { stroke: var(--c-surplus); }

.ruler__labels {
  display: flex;
  justify-content: space-between;
}

/* --- KPI tiles ------------------------------------------------------ */

.tiles {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-2);
}

.tile {
  background: var(--c-panel);
  border: 1px solid var(--c-hairline);
  border-radius: var(--r-2);
  padding: var(--sp-3);
  display: grid;
  gap: 2px;
  align-content: start;
  /* Reserve the tallest label a translation can produce (Spanish runs ~2.3x Norwegian)
     so switching language cannot shift the layout. */
  min-block-size: 5.25rem;
}

.tile__label {
  font-size: var(--t-sm);
  font-weight: 600;
  color: var(--c-muted);
  line-height: 1.25;
}
.tile__value {
  /* "54,9 TWh" is the widest value any tile carries, and at three tiles across a 375px
     screen the column is about 100px. Clamp rather than overflow. */
  font-size: clamp(1.0625rem, 5.2vw, var(--t-tile));
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.01em;
}
.tile__sub {
  font-size: var(--t-xs);
  color: var(--c-muted);
  line-height: 1.3;
}

/* --- reflow --------------------------------------------------------- */

/* On a phone everything is one column, and the order is what matters: headline, then the
   gauge (the reason anyone opened the page), then the ruler and the sentence explaining it. */
.hero__main {
  display: grid;
  gap: var(--sp-4);
  align-content: start;
}

@media (min-width: 600px) {
  /* Wide enough for two columns: the gauge moves to the right and the text, ruler and
     tiles stack down the left. `display: contents` lets the hero__main children join the
     hero's own grid, so the DOM order stays correct for a phone and for a screen reader
     while the layout rearranges around them. */
  .hero__main { display: contents; }

  .hero {
    grid-template-columns: minmax(0, 1fr) 300px;
    grid-template-areas:
      "head  gauge"
      "ruler gauge"
      "lede  gauge"
      "tiles gauge"
      "meta  meta";
    /* The gauge is taller than the text beside it. Letting the tiles row absorb the slack
       keeps that extra space in one place, below the tiles, instead of spreading it as a
       gap between every heading and the sentence under it. */
    grid-template-rows: auto auto auto 1fr auto;
    align-items: start;
    column-gap: var(--sp-6);
  }
  .hero__verdict { grid-area: head; }
  .hero__gaugewrap { grid-area: gauge; align-self: start; }
  .ruler { grid-area: ruler; }
  .hero__lede { grid-area: lede; }
  .tiles { grid-area: tiles; align-self: start; }
  .hero__meta { grid-area: meta; }
  .gauge { inline-size: min(100%, 300px); }
}

@media (min-width: 1000px) {
  .gauge { inline-size: min(100%, 320px); }
}

/* src/css/chart.css */
/* The year curve. */

.chart { margin: 0; }

/* Axis labels live in HTML gutters, not in the SVG.
   The plot is stretched with preserveAspectRatio="none" so it fills whatever column it
   lands in. Stroked lines survive that (vector-effect keeps their width), but text does
   not — it comes out visibly squashed. Putting the labels outside also stops the band's
   fill painting over them, which was hiding the 60 % and 40 % ticks entirely. */
.chart__frame {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: auto auto auto;
  column-gap: var(--sp-2);
}

.chart__ygutter {
  grid-column: 1;
  grid-row: 1;
  position: relative;
  inline-size: 3.1em;
}

.chart__ytick {
  position: absolute;
  inset-inline-end: 0;
  translate: 0 -50%;
  font-size: var(--t-xs);
  color: var(--c-tick);
  white-space: nowrap;
}

/* Percentages of the SVG's full height, derived from yTickPercent() in src/lib/paths.js.
   test/chart.test.js recomputes them and fails if these drift. */
.chart__ytick[data-t="100"] { top: 6.565%; }
.chart__ytick[data-t="80"]  { top: 24.252%; }
.chart__ytick[data-t="60"]  { top: 41.939%; }
.chart__ytick[data-t="40"]  { top: 59.626%; }
.chart__ytick[data-t="20"]  { top: 77.313%; }
.chart__ytick[data-t="0"]   { top: 95.000%; }

.chart__svg {
  grid-column: 2;
  grid-row: 1;
  inline-size: 100%;
  block-size: clamp(200px, 44vw, 320px);
}

.chart__xgutter {
  grid-column: 2;
  grid-row: 2;
  display: flex;
  justify-content: space-between;
  font-size: var(--t-xs);
  color: var(--c-tick);
  margin-block-start: var(--sp-1);
}

.chart__xtitle {
  grid-column: 2;
  grid-row: 3;
  margin: 0;
  text-align: center;
  font-size: var(--t-xs);
  color: var(--c-muted);
}

.chart__grid {
  stroke: var(--c-grid);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}


.chart__band {
  fill: var(--c-band);
  stroke: var(--c-band-edge);
  stroke-width: 1;
  stroke-opacity: 0.55;
  vector-effect: non-scaling-stroke;
}

.chart__median {
  fill: none;
  stroke: var(--c-median);
  stroke-width: 2;
  stroke-dasharray: 6 5;
  vector-effect: non-scaling-stroke;
}

.chart__series {
  fill: none;
  stroke: var(--c-water);
  stroke-width: 3;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* The head of the line: where the data actually stops. Without it a partial year reads as
   a line that trails off rather than one that ends at a known week. */
.chart__last {
  fill: var(--c-water);
  stroke: var(--c-bg);
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
}

.chart__legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  margin-block-start: var(--sp-3);
  font-size: var(--t-xs);
  color: var(--c-muted);
}

.chart__key {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}
.chart__key::before {
  content: "";
  inline-size: 18px;
  block-size: 3px;
  border-radius: 2px;
  flex: none;
}
.chart__key--series::before { background: var(--c-water); }
.chart__key--median::before {
  background: repeating-linear-gradient(90deg,
    var(--c-median) 0 5px, transparent 5px 9px);
}
.chart__key--band::before {
  block-size: 12px;
  border-radius: 2px;
  background: var(--c-band);
  border: 1px solid var(--c-band-edge);
}

.chart__table { margin-block-start: var(--sp-4); }

@media (forced-colors: active) {
  /* Hue carries no information in forced-colors mode, so lean on dash patterns instead. */
  .chart__series { stroke: CanvasText; stroke-dasharray: none; }
  .chart__median { stroke: CanvasText; stroke-dasharray: 6 5; }
  .chart__band { fill: Canvas; stroke: CanvasText; stroke-dasharray: 2 3; }
}

/* --- crosshair (JS only; the chart and its table are complete without it) --- */

.chart__cursor {
  grid-column: 2;
  grid-row: 1;
  position: relative;
  pointer-events: none;
  align-self: stretch;
}
.chart__cursor[hidden] { display: none; }

/* --cursor-x is set through element.style.setProperty, i.e. the CSSOM. That is not the
   same thing as a style attribute in the served markup, and CSP's style-src-attr does not
   apply to it — this is the one sanctioned way to position something at runtime here. */
.chart__cursor-line {
  position: absolute;
  inset-block: 0;
  inset-inline-start: var(--cursor-x, 0);
  inline-size: 2px;
  translate: -50% 0;
  background: var(--c-ink);
  opacity: 0.45;
}

.chart__svg { touch-action: pan-y; }
.chart__svg:focus-visible { outline: var(--focus); outline-offset: 2px; }

.chart__readout {
  margin-block-start: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-1);
  background: var(--c-panel-2);
  border: 1px solid var(--c-hairline);
  font-size: var(--t-xs);
  color: var(--c-ink);
  font-variant-numeric: tabular-nums;
}
.chart__readout[hidden] { display: none; }

/* --- history explorer --- */

.timeline { margin-block: var(--sp-3); }
.timeline__svg { block-size: clamp(160px, 34vw, 260px); }
.timeline__line { stroke-width: 1.5; }
.timeline__yearline {
  stroke: var(--c-grid);
  stroke-width: 1;
  stroke-dasharray: 2 4;
  vector-effect: non-scaling-stroke;
}

.history__body { min-block-size: 3rem; }
.history__body .tiles { margin-block-start: var(--sp-3); grid-template-columns: repeat(2, minmax(0, 1fr)); }
.history__actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-block-start: var(--sp-4); }

/* src/css/map.css */
/* The price-area map and the area list that sits beside it. */

.map-panel {
  display: grid;
  gap: var(--sp-5);
}

.map {
  justify-self: center;
  inline-size: min(100%, 260px);
}

.map__svg {
  inline-size: 100%;
  block-size: auto;
}

.map__zone {
  fill: var(--c-panel-2);
  stroke: var(--c-bg);
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
  cursor: pointer;
  transition: fill 120ms ease;
}
@media (prefers-reduced-motion: reduce) {
  .map__zone { transition: none; }
}

/* Colour encodes one thing only: distance from the 20-year median for this week.
   Amber for a deficit, blue for a surplus — never red/green, because a reservoir drawing
   down in November is not a failure. */
.map__zone[data-dev="--"] { fill: var(--c-dev-low-2); }
.map__zone[data-dev="-"]  { fill: var(--c-dev-low-1); }
.map__zone[data-dev="0"]  { fill: var(--c-dev-mid); }
.map__zone[data-dev="+"]  { fill: var(--c-dev-high-1); }
.map__zone[data-dev="++"] { fill: var(--c-dev-high-2); }

.map__link:hover .map__zone,
.map__link:focus-visible .map__zone {
  stroke: var(--c-ink);
  stroke-width: 2.5;
}
.map__link[aria-current="page"] .map__zone {
  stroke: var(--c-ink);
  stroke-width: 3;
}
.map__link:focus-visible { outline: none; }
.map__link:focus-visible .map__zone { outline: var(--focus); }

/* paint-order puts the stroke behind the glyphs, which gives a clean cut-out label
   without the muddy doubling a CSS text-shadow produces at small sizes. */
.map__label {
  font-family: var(--font);
  font-size: 44px;
  font-weight: 700;
  fill: var(--c-ink);
  stroke: var(--c-bg);
  stroke-width: 5;
  paint-order: stroke fill;
  pointer-events: none;
  text-anchor: middle;
  dominant-baseline: middle;
}

/* The end labels sit under the swatch row rather than beside it: at the map's 260px the
   three-across arrangement wrapped and stranded "Over normalen" on its own line. */
.map__legend {
  display: grid;
  justify-items: center;
  gap: var(--sp-1);
  margin-block-start: var(--sp-2);
  font-size: var(--t-xs);
  color: var(--c-muted);
}
.map__legend-scale {
  display: flex;
  align-items: center;
  gap: 2px;
}
.map__legend-ends {
  display: flex;
  justify-content: space-between;
  inline-size: 100%;
  max-inline-size: 220px;
}
.map__legend-swatch {
  inline-size: 18px;
  block-size: 12px;
  border-radius: 2px;
  border: 1px solid var(--c-hairline-strong);
}
.map__legend-swatch[data-dev="--"] { background: var(--c-dev-low-2); }
.map__legend-swatch[data-dev="-"]  { background: var(--c-dev-low-1); }
.map__legend-swatch[data-dev="0"]  { background: var(--c-dev-mid); }
.map__legend-swatch[data-dev="+"]  { background: var(--c-dev-high-1); }
.map__legend-swatch[data-dev="++"] { background: var(--c-dev-high-2); }

/* --- area list ------------------------------------------------------ */

.area-list { display: grid; gap: var(--sp-2); }

.area-row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  padding: var(--sp-3);
  border: 1px solid var(--c-hairline);
  border-radius: var(--r-2);
  background: var(--c-panel);
  text-decoration: none;
  color: inherit;
}
.area-row:hover { border-color: var(--c-hairline-strong); }
.area-row[aria-current="page"] {
  border-color: var(--c-accent);
  background: var(--c-accent-soft);
}

.area-row__name {
  display: grid;
  gap: 1px;
  min-inline-size: 0;
}
.area-row__code { font-weight: 700; font-size: var(--t-base); }
.area-row__region { font-size: var(--t-xs); color: var(--c-muted); }

.area-row__figures {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  justify-content: flex-end;
}
.area-row__fill { font-size: var(--t-md); font-weight: 700; }
.area-row__delta { font-size: var(--t-xs); }

/* A hairline whose width is the area's share of national capacity. NO2 is 34 TWh and NO1
   is 6 TWh, so comparing their percentages without this is the single most misleading
   thing a reader can do with these numbers.

   SVG rather than a div with a width: a server-rendered style attribute would be dropped
   by `style-src 'self'`, leaving every bar at zero. */
.area-row__capacity {
  grid-column: 1 / -1;
  inline-size: 100%;
  block-size: 3px;
}
.area-row__capacity-track { fill: var(--c-hairline); }
.area-row__capacity-bar { fill: var(--c-axis); }

@media (min-width: 700px) {
  .map-panel {
    grid-template-columns: minmax(0, 260px) minmax(0, 1fr);
    align-items: start;
    gap: var(--sp-6);
  }
  .map { justify-self: start; position: sticky; inset-block-start: 72px; }
}

/* src/css/footer.css */
/* The footer carries three legal obligations and is never collapsed:
   NLOD attribution with a declaration that we derived our own figures, the operator's
   identity under ehandelsloven §8, and an explicit disclaimer of NVE endorsement. */

.footer {
  border-block-start: 1px solid var(--c-hairline);
  padding-block: var(--sp-6) var(--sp-7);
  font-size: var(--t-sm);
  color: var(--c-muted);
  line-height: 1.6;
}

.footer__attribution {
  max-inline-size: 68ch;
}

.footer__owner {
  margin-block-start: var(--sp-4);
  padding-block-start: var(--sp-4);
  border-block-start: 1px solid var(--c-hairline);
  max-inline-size: 68ch;
}

.footer__owner strong { color: var(--c-ink); font-weight: 600; }

/* The independence notice must not read as small print buried under the credit. */
.footer__independent {
  margin-block-start: var(--sp-1);
  color: var(--c-muted);
}

.footer__meta {
  margin-block-start: var(--sp-3);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-3);
  font-size: var(--t-xs);
}
.footer__meta span::after {
  content: "·";
  margin-inline-start: var(--sp-3);
  color: var(--c-hairline-strong);
}
.footer__meta span:last-child::after { content: none; }

.footer a { color: var(--c-accent); }
