/* ============================================================
   charts.css — shared SVG / D3 chart styling
   ============================================================ */

/* ============================================================
   [R2 · 1c] LEGACY DEAD STYLES REMOVED.
   A large block used to live here targeting `.chart .axis / .grid / .line / .area /
   .series--* / .seq-* / .ref-line / .zero-line / .annotation / .event-band--* / .marker /
   .end-label / .country / .cell` — but NO element on the page ever has the bare class
   `chart` (the SVG root is `.chart-svg`). So every one of those ~120 rules matched nothing;
   they are exactly why AnnotatedLine's line rendered as a black blob and its event-bands as
   black rectangles in round 1. All styling that is actually needed now lives under
   `.chart-svg` (per chart, below) or is set in JS reading the design tokens. Removed to keep
   the stylesheet honest. RULE: never write a bare `.chart` selector — always `.chart-svg`.
   ============================================================ */

/* Heatmap → Diverging-bar morph (Chapter 7 extension) */

.chart-svg .country-name {
  font-family: var(--font-body);
  font-weight: 500;
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
}

.chart-svg .value-label {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  font-size: 10px;
  fill: var(--ink);
  letter-spacing: 0.01em;
  paint-order: stroke;
  stroke: var(--bg);
  stroke-width: 3px;
  stroke-linejoin: round;
}

/* Cut-chart legacy styles (bar / slope / ridge — bare-.chart dead-root + cut charts)
   removed in the round-5 debug pass. The live SVG root is .chart-svg. */

/* ===== Chart 7 · Heatmap ===== */
.chart-svg .hm-col-head {
  font-family: var(--font-body);
  font-size: 10.5px;
  font-weight: 500;
  fill: var(--ink-soft);
  letter-spacing: 0.02em;
  pointer-events: none;
}

/* [R2] Cell position + fill are now tweened in JS (D3) on year-scrub so the
   matrix morphs as one — no CSS fill-transition (it fought the JS tween and
   lagged the recolour). Theme change does a full instant re-render. */
.chart-svg .hm-cell { cursor: default; }

/* [FULL-SITE AUDIT · heatmap] Hover affordance — the old `.chart .cell:hover`
   rule was dead (SVG root is `.chart-svg`). Outline the focused cell. */
.chart-svg .hm-cell--focus {
  stroke: var(--ink);
  stroke-width: 1.5;
  paint-order: stroke;
}
/* [FULL-SITE AUDIT · heatmap] Decodable color-scale ramp legend. */

.chart-svg .hm-scale-unit {
  font-family: var(--font-body);
  font-size: 9px;
  font-weight: 600;
  fill: var(--ink-soft);
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
.chart-svg .hm-scale-tick {
  font-family: var(--font-mono);
  font-size: 9px;
  fill: var(--ink-soft);
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
}

/* Continuous scale ramp + honest tick marks (replaces the discrete swatches). */
.chart-svg .hm-scale-bar { stroke: var(--rule-soft); stroke-width: 0.5; }
.chart-svg .hm-scale-tickmark { stroke: var(--ink-fainter); stroke-width: 0.75; }
/* Hottest-cell caption: the year's single answer to "where did it land
   hardest", placed under the legend (same tidy left column as the kicker — no
   floating stamp over the matrix). Accent is reserved for the value only. */

.chart-svg .hm-row-label--peak { fill: var(--accent-text); font-weight: 700; }

/* ===== Chart 8 · DivergingBar ===== */
.chart-svg .db-bar { transition: opacity var(--dur-3, 280ms) var(--ease-out); }
/* The zero line is the spine — the whole story is "which side of it". Give it real
   weight so the divide reads before any single bar. */
.chart-svg .db-zero-line { stroke: var(--ink); stroke-width: 2; stroke-opacity: 0.92; }
/* [P8.2] The dumbbell. Two dots and the line between them replace the bar from zero:
   the muted claret dot is where the country FELL to in 2022, the solid dot is where it
   stands in 2025, and the connector is the distance it travelled. Colour keeps the same
   two-camp meaning it had on the bars — jade above the line, claret below — so nothing the
   header teaches has to be re-learned. */
/* [P8.3] Race · under-water count strip — off by default, opened by the "Who fell behind"
   button and closed again by entering any step. Claret because it counts the same thing the
   claret gap above it shades: months when pay sat below prices. Deliberately quiet — it is a
   second reading under the chart, not a second chart. */
/* Same claret tint construction as .rc-gap-claret directly above it, so the strip reads as
   the same colour idea rather than a new one. (--event-energy is mandarin, not claret.) */
.chart-svg .rc-uw-area { fill: var(--accent); fill-opacity: 0.16; stroke: none; }
.chart-svg .rc-uw-line { fill: none; stroke: var(--accent); stroke-width: 1.4; stroke-opacity: 0.85; }
.chart-svg .rc-uw-base { stroke: var(--rule); stroke-width: 1; }
.chart-svg .rc-uw-peak-dot { fill: var(--accent); stroke: var(--bg); stroke-width: 1; }
.chart-svg .rc-uw-peak {
  font-family: var(--font-mono); font-size: 10.5px; fill: var(--accent-text);
  font-variant-numeric: tabular-nums; paint-order: stroke; stroke: var(--bg);
  stroke-width: 2.5px; stroke-linejoin: round;
}
.chart-svg .rc-uw-note {
  font-family: var(--font-ui); font-size: 9.5px; fill: var(--ink-soft);
  letter-spacing: 0.06em; text-transform: uppercase;
}
/* The toggle sits in the same control row as the zoom presets and wears the same chip, so the
   reader does not have to learn a second control shape. */
.scroller__chart .chart-controls .rc-uw-btn,
.scroller__chart .chart-controls .rc-view-btn { margin-inline-start: var(--space-3); }
/* [ROUND-7 P2] The ledger toggle joins that row rather than taking a row of its own. Measured
   before building: at 1440 it lands on the existing second line and costs the chart 0px, which
   matters because the race's breathing sits at 0.841 against a 0.840 floor. It adds a line at
   1024 and 390; at 1024 that line fits inside .panel-foot's 64px reservation and still costs 0px,
   and only 390 pays, 26.1px. Two chips side by side, so the segmented-group divider is off. */
.chart-controls .ac-zoom-btn + .rc-view-btn { border-inline-start: 0; }
/* One view at a time: `hidden` must beat .chart-body's display:flex, or the hidden mount keeps
   its grid slot and the panel sprouts a fourth row under an `auto 1fr auto` template. */
.scroller__chart .chart-body[hidden] { display: none; }

.chart-svg .db-link {
  stroke: var(--ink-fainter); stroke-width: 2; stroke-linecap: round;
}
.chart-svg .db-dot { stroke: var(--bg); stroke-width: 1.2; }
/* The trough is a memory, not a result: same claret, held back so the eye reads the solid
   endpoint dots as the row's answer and the muted ones as where it started. */
.chart-svg .db-dot--trough { fill: var(--db-trough); }
.chart-svg .db-dot--pos { fill: var(--cat-wages); }
.chart-svg .db-dot--neg { fill: var(--accent); }
.chart-svg .db-bar.is-hover .db-link { stroke: var(--ink-soft); stroke-width: 2.6; }
.chart-svg .db-bar.is-hover .db-dot { stroke: var(--ink); stroke-width: 1.4; }
/* Bookends (widest gain / deepest cut) get a thin ink outline — emphasis via craft,
   not a 3rd colour, so the two-camp encoding stays pure. */
.chart-svg .db-dot--extreme { stroke: var(--ink); stroke-opacity: 0.55; }
/* [R5·P11] median reference line (the typical country) — dashed + faint so it never competes
   with the bold ink zero spine; + sphere dots on the two bookends. */
.chart-svg .db-median line { stroke: var(--ink-faint); stroke-width: 1; stroke-dasharray: 3 3; opacity: 0.7; }
.chart-svg .db-median-label {
  font-family: var(--font-mono); font-size: 10.5px; fill: var(--ink-soft); letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums; paint-order: stroke; stroke: var(--bg); stroke-width: 2.5px; stroke-linejoin: round;
}
/* [P8.2] Names now sit in a fixed gutter OUTSIDE the plot, so nothing under them can be
   overdrawn by a trough dot; the hit row spans gutter + plot so the whole line is hoverable. */
.chart-svg .db-hit { cursor: default; }
.chart-svg .db-name {
  font-family: var(--font-ui);          /* [owner D7] Schibsted + larger = legible country labels */
  font-size: 12.5px;
  font-weight: 600;
  fill: var(--ink-soft);
}
.chart-svg .db-name--extreme { font-weight: 700; fill: var(--ink); }
.chart-svg .db-val {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  paint-order: stroke;
  stroke: var(--bg);
  stroke-width: 3px;
  stroke-linejoin: round;
}
.chart-svg .db-val--pos { fill: var(--cat-wages-text); }
.chart-svg .db-val--neg { fill: var(--accent-text); }
.chart-svg .db-val--extreme { font-size: 13px; }
.chart-svg .db-extreme-eyebrow {
  font-family: var(--font-body);
  font-size: 8.5px; font-weight: 600;
  letter-spacing: 0.18em;
  fill: var(--ink-soft);
  paint-order: stroke; stroke: var(--bg); stroke-width: 2.5px; stroke-linejoin: round;
}

/* Top-right unit legend — second line is calmer/smaller */
.chart-svg .db-unit-sub { font-size: 8.5px; fill: var(--ink-soft); letter-spacing: 0.01em; }

/* Two-camp scoreboard (kicker zone) — the headline divide */
.chart-svg .db-head-num {
  font-family: var(--font-display); font-style: normal; font-weight: 700;   /* owner override: upright scoreboard */
  font-size: 36px;
  letter-spacing: -0.01em;
  paint-order: stroke; stroke: var(--bg); stroke-width: 5px; stroke-linejoin: round;
}
.chart-svg .db-head-num--pos { fill: var(--cat-wages); }
.chart-svg .db-head-num--neg { fill: var(--accent); }
.chart-svg .db-head-label {
  font-family: var(--font-display); font-style: normal; font-size: 14.5px;
  fill: var(--ink); letter-spacing: 0;
  paint-order: stroke; stroke: var(--bg); stroke-width: 3.5px; stroke-linejoin: round;
}
.chart-svg .db-head-sub {
  font-family: var(--font-mono); font-size: 9px; fill: var(--ink-soft);
  letter-spacing: 0.04em;
  paint-order: stroke; stroke: var(--bg); stroke-width: 3px; stroke-linejoin: round;
}
.chart-svg .db-head-row { transition: opacity var(--dur-3, 280ms) var(--ease-out); }
/* Phone: one compact line */
.chart-svg .db-head-compact { font-family: var(--font-body); }
.chart-svg .db-head-c {
  font-family: var(--font-display); font-style: normal; font-weight: 700;   /* owner override: upright */
  font-size: 22px;
}
.chart-svg .db-head-c--pos { fill: var(--cat-wages); }
.chart-svg .db-head-c--neg { fill: var(--accent); }
.chart-svg .db-head-cl { font-size: 11px; fill: var(--ink-soft); font-weight: 600; }
.chart-svg .db-head-dot { fill: var(--ink-fainter); }

/* "Broke even" anchor under the zero line */
.chart-svg .db-zero-label {
  font-family: var(--font-body); font-size: 8.5px; font-weight: 600;
  letter-spacing: 0.2em; fill: var(--ink-soft);
  paint-order: stroke; stroke: var(--bg); stroke-width: 3px; stroke-linejoin: round;
}
.chart-svg .db-zero-sub {
  font-family: var(--font-mono); font-size: 8px; fill: var(--ink-soft);
  letter-spacing: 0.02em;
  paint-order: stroke; stroke: var(--bg); stroke-width: 2.5px; stroke-linejoin: round;
}

/* ===== Chart 9 · WaffleChart =====
   [D87] cell stroke/fill semantics now live in one place, further down this file
   (search "waffle-cell--on") — kept + lost tile colours, theme-swapped for contrast.
   The pre-D86 hatch-pattern fallback that used to sit here is gone: no JS ever built
   the #waffle-erode-hatch pattern it pointed to, and .waffle-cell/--on/--off is
   redefined below, so the two definitions would have silently collided. */

#chart-waffle-controls .waffle-ctrl {
  display: inline-flex; gap: var(--space-2); align-items: center;
  margin-inline-end: var(--space-4);
  font-family: var(--font-body); font-size: 12px;
  color: var(--ink-soft);
}
#chart-waffle-controls select {
  font-family: var(--font-body); font-size: 12px;
  padding: 4px 8px;
  border: 1px solid var(--rule);
  background: var(--bg-elev); color: var(--ink);
  border-radius: 3px;
}

/* ===== Chart 12 · BoxPlot ===== */
.chart-svg g.box {
  transition: opacity var(--dur-3, 280ms) var(--ease-out);
}
/* During the one-time entrance cascade the per-box opacity is driven by a D3
   transition (BoxPlot._initialReveal). The CSS transition above must stand down
   for that window or the two ease curves convolve into a ~265ms-lagged smear
   (a dual-driver on the same property). The reveal adds .is-revealing, clears it
   on completion, then leaves the box on CSS-class opacity so .is-dim works. */
.chart-svg g.box.is-revealing { transition: none; }
.chart-svg g.box.is-dim   { opacity: 0.22; }
/* Focus emphasis: accent outline + a faint accent wash so even a narrow box
   (e.g. 2024, IQR ≈ 1.1 pp) clearly reads as the highlighted year. */
.chart-svg g.box.is-focus .box-rect { fill: var(--accent-veil); stroke: var(--accent); stroke-width: 1.6; }
.chart-svg g.box.is-focus .box-median { stroke: var(--accent); stroke-width: 2.4; }
.chart-svg g.box.is-focus .box-whisker { stroke: var(--accent); stroke-opacity: 0.75; }
/* Transparent full-column hover target. The cursor rule sits on the COLUMN GROUP, not on the
   hit rect: the rect is drawn behind the marks, so a rule on it alone left the marks inheriting
   the canvas crosshair and the cursor flickered across every box. Default on purpose: hovering a
   year is inspection (tooltip + highlight), not a click, and a pointer hand would promise one. */
.chart-svg g.box { cursor: default; }
.chart-svg .box-whisker { stroke: var(--ink-soft); stroke-width: 1; }
.chart-svg .box-outlier { fill: var(--seq-4); fill-opacity: 0.65; }
/* [Style v95] Per-country jitter strip — show the real distribution under the box */
.chart-svg .box-strip-dot {
  fill: var(--ink);
  fill-opacity: 0.18;
  pointer-events: none;
}
.chart-svg .bp-ref-line {
  stroke: var(--seq-target); stroke-dasharray: 3 4; stroke-width: 1; stroke-opacity: 0.6;
}
.chart-svg .bp-ref-label {
  font-family: var(--font-mono); font-size: 10px;
  fill: var(--seq-target); letter-spacing: 0.04em;
}
.chart-svg .bp-annotation {
  font-family: var(--font-display); font-style: italic;
  font-size: 12.5px; font-weight: 500;
  font-variation-settings: "opsz" 144;
  fill: var(--accent-text);
  letter-spacing: 0.01em;
  paint-order: stroke;
  stroke: var(--bg); stroke-width: 4px; stroke-linejoin: round;
}

/* ============================================================
   [R2 ELEVATE · chart 12 · BoxPlot] — the finale.
   All selectors chart-scoped to .chart-svg .bp-* / .box-* (unique to this
   chart). Adds the three-act arc (median spine + whisker envelope), the
   resting-protagonist treatment, an anchored annotation leader, the 2024
   outlier tag, and the closing stamp. Colours read live from tokens.
   ============================================================ */

/* Median reads FIRST — it is the headline stat of every box. Bump its weight
   above the box stroke and soften the resting box outline so the eye lands on
   the median line, then the IQR, then the dots (clean visual hierarchy). The ONE resting
   declaration for .box-rect / .box-median: an earlier pair above used to be overridden here
   in everything but the rect's fill, so the fill moved down and the pair went (Session 78). */
.chart-svg .box-rect { fill: var(--bg-elev); stroke: var(--ink-faint); stroke-width: 1; }
.chart-svg .box-median { stroke: var(--ink); stroke-width: 2.4; }

/* The arc, drawn under the boxes. Neutral ink washes — never claret — so the
   protagonist box keeps the only accent on the canvas (design system §4). */
.chart-svg .bp-arch { transition: opacity var(--dur-3, 280ms) var(--ease-out); }
.chart-svg .bp-arch.is-dim { opacity: 0.4; }
.chart-svg .bp-envelope {
  fill: var(--ink);
  /* [R4·P15] 0.05 → 0.08: at 5% the "gap" ribbon was near-invisible on cream (and ~0.02 once the
     arch dims during a focus), so the finale's pre-attentive rise→explosion→pinch arc barely read.
     0.08 keeps it a faint context wash (the boxes still carry the detail) but the gap now reads. */
  fill-opacity: 0.08;
  stroke: none;
}
.chart-svg .bp-spine {
  fill: none;
  stroke: var(--ink-soft);
  stroke-width: 1.5;
  stroke-opacity: 0.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Annotation leader — a hairline tying "the gap blew open" to the 2022 box. */
.chart-svg .bp-anno-leader {
  stroke: var(--accent);
  stroke-width: 1;
  stroke-opacity: 0.55;
}

/* Trailing-year outlier tag (Romania again, 2025 — it follows the last year in the stats, D108) —
   small mono label in claret --accent-text (AA). */
.chart-svg .bp-rotag {
  font-family: var(--font-mono);
  font-size: 9.5px;
  fill: var(--accent-text);
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
  paint-order: stroke; stroke: var(--bg); stroke-width: 3px; stroke-linejoin: round;
}

/* [R5·P14] sphere median dot on the 2022 protagonist box */
.chart-svg .bp-median-sphere { stroke: var(--bg); stroke-width: 1.4; pointer-events: none; }
/* [R5·P14 / #12] the closing stamp — "Six years on, your €100 is now €77" (its coin was removed in D111) */
.chart-svg .bp-stamp__bookend {
  font-family: var(--font-display); font-style: normal; font-size: 15px; fill: var(--ink);
  letter-spacing: 0;
  paint-order: stroke; stroke: var(--bg); stroke-width: 4px; stroke-linejoin: round;
}
.chart-svg .bp-stamp__bookend-num { fill: var(--accent-text); font-weight: 500; }

/* Tooltip */
.tooltip {
  /* fixed (not absolute) so that Tooltip.move()'s clientX/clientY viewport coords
     land the tooltip at the cursor regardless of how far the page is scrolled. */
  position: fixed;
  z-index: var(--z-tooltip);
  pointer-events: none;
  background: var(--bg-elev);
  color: var(--ink);
  border: 1px solid var(--rule);
  box-shadow: var(--shadow-2);
  /* [owner review A7] clean compact card: Schibsted text, tight padding, mono numbers (below). */
  font-family: var(--font-ui);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-2);
  font-size: var(--fs-200);
  line-height: 1.45;
  max-inline-size: 248px;
  opacity: 0;
  transition: opacity var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out);
  transform: translateY(4px);
}
.tooltip.visible { opacity: 1; transform: translateY(0); }
.tooltip h5 {
  font-family: var(--font-ui);
  font-weight: 700;
  font-size: var(--fs-200);
  letter-spacing: 0.01em;
  margin: 0 0 5px;
  color: var(--ink);
}
.tooltip .row { display: flex; justify-content: space-between; gap: var(--space-4); align-items: baseline; }
.tooltip .row .key { color: var(--ink-soft); }
.tooltip .row .val { font-family: var(--font-mono); font-weight: 600; font-variant-numeric: tabular-nums; color: var(--ink); }
/* [compare] a group's own member rows nest under its average row — indent + slightly smaller only;
   colour stays the same safe --ink-soft (never dim further into --ink-faint, which fails WCAG AA at
   this text size — see the seq-hi-text/seq-lo-text fix this same session). */
.tooltip .row--member { padding-inline-start: 14px; font-size: 0.92em; }
.tooltip .row--member .ac-sw { opacity: 0.6; }   /* echoes the member line's own thin/pale chart stroke */

/* [AUDIT 08] DivergingBar tooltip — Real Δ coloured by sign
   (replaces an inline style="color:…" in DivergingBar.js). */
.tooltip .db-tip-val--pos { color: var(--cat-wages-text); }
.tooltip .db-tip-val--neg { color: var(--accent-text); }
/* [compare] multi-series crosshair tooltip — a colour swatch per line (EU + compared countries),
   matching the CMP line colours. Inline-block so the .row layout is untouched for other charts. */
.tooltip .ac-sw { display: inline-block; inline-size: 8px; block-size: 8px; border-radius: 2px; margin-inline-end: 6px; vertical-align: middle; }
.tooltip .ac-sw--eu { background: var(--accent); }
.tooltip .ac-sw--c0 { background: var(--cmp-1); }
.tooltip .ac-sw--c1 { background: var(--cmp-2); }
.tooltip .ac-sw--c2 { background: var(--cmp-3); }
.tooltip .ac-sw--c3 { background: var(--cmp-4); }
/* [debug 2026-07-06] c4-c9 were missing entirely — harmless while only individually-added countries
   (max ~4 in practice) used this swatch, but the group-tooltip feature routinely reaches slot 4+
   (e.g. 2 groups + 2 countries already = slots 0-3; one more selection hits the gap), which would
   render a blank/invisible swatch dot despite the line itself being correctly coloured. */
.tooltip .ac-sw--c4 { background: var(--cmp-5); }
.tooltip .ac-sw--c5 { background: var(--cmp-6); }
.tooltip .ac-sw--c6 { background: var(--cmp-7); }
.tooltip .ac-sw--c7 { background: var(--cmp-8); }
.tooltip .ac-sw--c8 { background: var(--cmp-9); }
.tooltip .ac-sw--c9 { background: var(--cmp-10); }
/* [AMENDMENT-3 §2.3] RaceChart's multi-series hover reuses its own price/pay colours (it has
   no separate cmp-colour system — compared-country lines are dashed, not recoloured). */
.tooltip .ac-sw--wages { background: var(--cat-wages); }

/* Crosshair — AnnotatedLine's vertical hover guide. Scoped to the live .chart-svg root;
   the old bare-.chart selector never matched (root is .chart-svg) so the line was invisible. */
.chart-svg .crosshair {
  stroke: var(--ink);
  stroke-opacity: 0.45;
  stroke-dasharray: 2 3;
  pointer-events: none;
}

/* ============================================================
   RESPIN shared editorial language (brief §5 item 2).
   The kicker = the big italic Source Serif "key number/word" at the chart's
   top-left (current year / country / mode). Reinstates the signature display
   number the round-4 [D34] note had removed — the REBUILD-BRIEF governs the
   respin and explicitly calls for it (logged D47). Shared by the respun charts.
   ============================================================ */
.chart-svg .kick-num {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 600;
  font-size: 46px;
  fill: var(--ink-strong);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.015em;
  line-height: 1;
  paint-order: stroke;
  stroke: var(--bg);
  stroke-width: 5px;
  stroke-linejoin: round;
}
.chart-svg .kick-label {
  font-family: var(--font-ui);
  font-size: 10.5px;
  font-weight: 600;
  fill: var(--ink-soft);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  paint-order: stroke;
  stroke: var(--bg);
  stroke-width: 3px;
  stroke-linejoin: round;
}

/* ===== CH1 · AnnotatedLine (respin) ===== */
/* Whitespace doctrine §2a — kill panel padding + tighten the furniture so the chart body
   (the 1fr grid row → the SVG) fills ≥90% of the sticky panel. The header title + 2-line
   subtitle + source line are the only non-chart rows; drop the space-2 padding + shrink gaps. */
.scroller__chart[data-chart-mount="annotatedLine"] {
  padding-block: 0;
  gap: 2px;
}
.scroller__chart[data-chart-mount="annotatedLine"] header { gap: 1px; }

.scroller__chart[data-chart-mount="annotatedLine"] .chart-source { line-height: 1.15; margin: 0; }
.chart-svg .anno-band-label {
  font-family: var(--font-ui);
  font-size: 9.5px;
  font-weight: 600;
  fill: var(--ink-soft);
  letter-spacing: 0.14em;
  pointer-events: none;
  paint-order: stroke;
  stroke: var(--bg);
  stroke-width: 3px;
  stroke-linejoin: round;
}
.chart-svg .anno-ref-label {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.02em;
}
.chart-svg .anno-tail-label {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 700;
  fill: var(--accent-text);   /* [D95] 12px text: --accent is 3.23:1 on the dark slate; --accent-text is the AA variant */
  font-variant-numeric: tabular-nums;
  paint-order: stroke;
  stroke: var(--bg);
  stroke-width: 3.5px;
  stroke-linejoin: round;
}

/* ===== CH2 · SmallMultiplesLine (respin) — 2×3 shared-scale grid ===== */
.scroller__chart[data-chart-mount="smallMultiples"] { padding-block: 0; gap: 2px; }
.scroller__chart[data-chart-mount="smallMultiples"] header { gap: 1px; }

.scroller__chart[data-chart-mount="smallMultiples"] .chart-source { line-height: 1.15; margin: 0; }
.chart-svg .sm-panel { transition: opacity var(--dur-4) var(--ease-out); }
.chart-svg .sm-panel:focus-visible { outline: 2px solid var(--link); outline-offset: 2px; }
.chart-svg .sm-title {
  font-family: var(--font-ui); font-size: 10.5px; font-weight: 700;
  fill: var(--ink); letter-spacing: 0.08em;
  paint-order: stroke; stroke: var(--bg); stroke-width: 3px; stroke-linejoin: round;
}
.chart-svg .sm-end-tag {
  font-family: var(--font-mono); font-size: 11px; font-weight: 700;
  font-variant-numeric: tabular-nums;
  paint-order: stroke; stroke: var(--bg); stroke-width: 3px; stroke-linejoin: round;
}
.chart-svg .sm-zero { stroke: var(--rule-bold); stroke-width: 1; }
.chart-svg .sm-grid { stroke: var(--rule-soft); stroke-width: 0.75; }
.chart-svg .sm-ytick, .chart-svg .sm-xtick {
  font-family: var(--font-mono); font-size: 9.5px; fill: var(--ink-soft);
  font-variant-numeric: tabular-nums; letter-spacing: 0.01em;
}
.chart-svg .sm-hit { cursor: pointer; }
.chart-svg .sm-scrim { cursor: zoom-out; }
.chart-svg .sm-big-peak {
  font-family: var(--font-display); font-style: italic; font-size: 13px; fill: var(--ink);
  paint-order: stroke; stroke: var(--bg); stroke-width: 3.5px; stroke-linejoin: round;
}
.chart-svg .sm-big-end {
  font-family: var(--font-mono); font-size: 12px; font-weight: 700; fill: var(--ink);
  font-variant-numeric: tabular-nums;
  paint-order: stroke; stroke: var(--bg); stroke-width: 3.5px; stroke-linejoin: round;
}
/* [owner request] Close control for the enlarged small-multiples panel. Quiet at rest — a hairline
   ring on paper, not a filled chip — so it reads as furniture beside the big italic category title
   rather than competing with it; it fills in on hover/focus so the target is unmistakable once the
   pointer is near. The glyph is a stroked path, not a text "x", so it stays optically centred at
   any size and never depends on a font's glyph metrics. */
.chart-svg .sm-close__bg {
  fill: var(--bg-elev);
  stroke: var(--rule);
  stroke-width: 1;
  transition: fill var(--dur-2) var(--ease-out), stroke var(--dur-2) var(--ease-out);
}
.chart-svg .sm-close__x {
  stroke: var(--ink-soft);
  stroke-width: 1.6;
  stroke-linecap: round;
  fill: none;
  transition: stroke var(--dur-2) var(--ease-out);
}
.chart-svg .sm-close:hover .sm-close__bg,
.chart-svg .sm-close:focus-visible .sm-close__bg { fill: var(--ink); stroke: var(--ink); }
.chart-svg .sm-close:hover .sm-close__x,
.chart-svg .sm-close:focus-visible .sm-close__x { stroke: var(--bg); }
.chart-svg .sm-close:focus-visible { outline: 2px solid var(--link); outline-offset: 2px; border-radius: 50%; }

.chart-svg .sm-big-hint { font-family: var(--font-mono); font-size: 10px; fill: var(--ink-soft); letter-spacing: 0.04em; }

/* ===== CH4 · Heatmap (respin) — category × month ===== */
.scroller__chart[data-chart-mount="heatmap"] { padding-block: 0; gap: 2px; }
.scroller__chart[data-chart-mount="heatmap"] header { gap: 1px; }

.scroller__chart[data-chart-mount="heatmap"] .chart-source { line-height: 1.15; margin: 0; }
.chart-svg .hm-cell { cursor: pointer; }
.chart-svg .hm-year-sep { stroke: var(--rule-bold); stroke-width: 1; stroke-opacity: 0.45; pointer-events: none; }
.chart-svg .hm-frame { fill: none; stroke: var(--accent); stroke-width: 2.5; pointer-events: none; }
/* respin row labels: 12px caps (overrides the old country×year 11px style — later rule wins) */
.chart-svg .hm-row-label {
  font-family: var(--font-ui); font-size: 12px; font-weight: 700;
  fill: var(--ink); letter-spacing: 0.05em; text-transform: uppercase;
}
.chart-svg .hm-col-head { font-family: var(--font-mono); font-size: 10px; fill: var(--ink-soft); letter-spacing: 0.02em; }
/* [D85] Event markers — whisper-quiet, claret, entirely below ih (never over the cell grid).
   Distinct from the grey .hm-col-head year row: claret colour + own micro-label voice (matches
   the legend-title convention — 10px, wide tracking, uppercase, --font-ui). */
.chart-svg .hm-event-dot { fill: var(--accent); pointer-events: none; transition: r 150ms var(--ease-out); }
.chart-svg .hm-event-label {
  font-family: var(--font-ui); font-size: 9px; font-weight: 600;
  fill: var(--accent-text); letter-spacing: 0.14em; text-transform: uppercase;
  pointer-events: none;
}
/* Invisible, generously-sized hover/tap target — the visible dot (2-2.4px) is too small to hit
   reliably on its own, especially the narrowViewport dot-only size. */
.chart-svg .hm-event-hit { fill: transparent; }
.chart-svg .hm-event--focus .hm-event-dot { r: 3.6; }
@media (prefers-reduced-motion: reduce) { .chart-svg .hm-event-dot { transition: none; } }
/* [D85] The panel's own block-size (100dvh-derived, shared with every other chart) never grows —
   instead the heatmap's OWN svg is let out of the shared `max-block-size:100%` cap so it can size
   itself by its own (now EVENT_TIER_H-taller) viewBox aspect ratio and overflow the bottom of its
   `1fr` chart-body row by exactly that many px. Same pattern the choropleth chapter already uses
   for its own bleed (scrollytelling.css ~L169) — `.chart-body`'s OWN overflow (computed default:
   hidden, not `.scroller__chart`'s — that one IS visible but is the wrong element) must also be
   freed, or the taller svg just gets clipped back down to the unchanged row height. ih/cell/row
   geometry is untouched because the EXTRA height is pure overflow, never redistributed into the
   grid's own row sizing. At <=768px the tier drops to dot-only and the viewBox no longer grows,
   so this quietly becomes a no-op there (nothing to overflow). */
.scroller__chart[data-chart-mount="heatmap"] .chart-body { overflow: visible; }
.scroller__chart[data-chart-mount="heatmap"] .chart-body > svg.chart-svg { max-block-size: none; }

/* ===== CH5 · RateLevel (respin) — the KEY chart, two stacked panels ===== */
.scroller__chart[data-chart-mount="rateLevel"] { padding-block: 0; gap: 2px; }
.scroller__chart[data-chart-mount="rateLevel"] header { gap: 1px; }

.scroller__chart[data-chart-mount="rateLevel"] .chart-source { line-height: 1.15; margin: 0; }
.chart-svg .rl-area { fill: var(--accent-veil); }   /* --accent-veil is CSS-only (color-mix); never fed to d3 (D15) */
.chart-svg .rl-ref-label { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.02em; }
.chart-svg .rl-ytitle {
  font-family: var(--font-ui); font-size: 10px; font-weight: 600;
  fill: var(--ink-soft); letter-spacing: 0.13em; text-transform: uppercase;
}
.chart-svg .rl-baseline { stroke: var(--ink-soft); stroke-width: 1.2; stroke-opacity: 0.6; }
.chart-svg .rl-baseline-label { font-family: var(--font-mono); font-size: 10px; fill: var(--ink-soft); letter-spacing: 0.02em; }
.chart-svg .rl-peak-tag {
  font-family: var(--font-mono); font-size: 11px; fill: var(--ink-soft); font-variant-numeric: tabular-nums;
  paint-order: stroke; stroke: var(--bg); stroke-width: 3px; stroke-linejoin: round;
}
.chart-svg .rl-bracket { stroke: var(--accent); stroke-width: 1.4; }
.chart-svg .rl-bracket-label {
  font-family: var(--font-display); font-style: italic; font-size: 12.5px; fill: var(--accent-text);
  paint-order: stroke; stroke: var(--bg); stroke-width: 3.5px; stroke-linejoin: round;
}
/* [P2.4] CH5's thesis number, the big italic claret mark that closes the chart — deliberately
   NOT part of the §D.3 value-tag group further down (see the note there), which had been
   flattening it to 11px mono. */
.chart-svg .rl-end-label {
  font-family: var(--font-display); font-style: italic; font-weight: 600; font-size: 22px; fill: var(--accent);
  font-variant-numeric: tabular-nums;
  paint-order: stroke; stroke: var(--bg); stroke-width: 4px; stroke-linejoin: round;
}
.chart-svg .rl-cursor-line { stroke: var(--ink); stroke-opacity: 0.4; stroke-dasharray: 2 3; }
.chart-svg .rl-cursor-dot { fill: var(--accent); stroke: var(--bg); stroke-width: 1.5; }
.chart-svg .rl-cursor-label {
  font-family: var(--font-mono); font-size: 10.5px; fill: var(--ink); font-variant-numeric: tabular-nums;
  paint-order: stroke; stroke: var(--bg); stroke-width: 3px; stroke-linejoin: round;
}

/* ===== CH6 · WaffleChart / basket (respin) — two halves ===== */
.scroller__chart[data-chart-mount="waffle"] { padding-block: 0; gap: 0; }
.scroller__chart[data-chart-mount="waffle"] header { gap: 1px; }

.scroller__chart[data-chart-mount="waffle"] .chart-source { font-size: 10.5px; line-height: 1.1; margin: 0; letter-spacing: 0.03em; }
/* [D87] waffle colour semantics, corrected: a KEPT euro is the loud one now — a solid warm-gold
   coin-like tile; a LOST euro is a quiet hollow socket (transparent + a thin ink-fainter ring).
   Claret is reserved for THE MOMENT OF TAKING (the erosion's own flash + the step-echo pulse on
   the bars) — never for a resting state. Erosion/refill transitions are driven by JS d3
   transitions (_animateErosion), not this CSS fill-transition, so cutting it here is safe. */
.chart-svg .waffle-cell { stroke: none; cursor: pointer; }
/* --coin-gold itself only measures 2.48:1 against the LIGHT paper (--bg #FFF1E5) -- fails the
   owner's own >=3:1 ask (it was tuned to sit on the hero's darker medallion backdrop, not flat
   paper). --coin-gold-lo (the "darker edge" token) measures 5.72:1 there and reads as a rich,
   still-gold ochre, so light theme's FILL uses -lo instead, with the same -lo doubling as its own
   edge stroke (a crisp same-tone border, not a two-tone bevel, since there's no darker family
   member to draw a genuinely darker edge inside -lo itself). Dark theme is the opposite case --
   --coin-gold reads 5.22:1 there (passes) while --coin-gold-lo would FAIL dark (2.27:1) -- so dark
   gets the token pair the owner's own spec named literally: gold fill, gold-lo edge. Neither coin
   token is redefined per-theme at the root (the hero medallion also reads them and must not
   change) -- both swaps are scoped to .waffle-cell--on only. */
/* settle transition lives on --on/--off only (never the base rule or --flash), so a tile's own
   ENTRY into --flash is a crisp, un-eased class swap (it needs to read instantly, not fade in),
   while its EXIT from --flash into --off (or a refill's --off-to-on) glides. 530ms here must match
   ERODE_SETTLE_MS in WaffleChart.js -- together with the 25ms stagger and 120ms flash hold, that's
   what makes the EU case (23 cells) land on the owner's own "~1.2s" figure. */
.chart-svg .waffle-cell--on {
  fill: var(--coin-gold-lo);
  stroke: var(--coin-gold-lo);
  stroke-width: 1px;
  transition: fill 530ms var(--ease-out), stroke 530ms var(--ease-out);
}
[data-theme="dark"] .chart-svg .waffle-cell--on {
  fill: var(--coin-gold);
  stroke: var(--coin-gold-lo);
}
.chart-svg .waffle-cell--off {
  fill: transparent;
  stroke: var(--ink-fainter);
  stroke-width: 1.5px;
  transition: fill 530ms var(--ease-out), stroke 530ms var(--ease-out);
}
/* the one-shot claret flash a dying tile passes through mid-erosion (see _animateErosion) --
   applied as a class for the flash's brief life, then removed in favour of --off. No transition
   here (or on the base .waffle-cell rule) so gold-to-claret is an instant, crisp cut, not a fade. */
.chart-svg .waffle-cell--flash { fill: var(--accent); stroke: var(--accent); }

/* [D87] waffle's own mini-header, verbatim, same voice as the bars' header. */
.chart-svg .wf-waffle-title { font-family: var(--font-body); font-size: 9.5px; fill: var(--ink-soft); letter-spacing: 0.26em; font-weight: 500; }
/* legend line under the grid -- real 10px swatches (one gold tile, one hollow), not glyphs. */
.chart-svg .wf-legend-text { font-family: var(--font-ui); font-size: 10.5px; fill: var(--ink-soft); }
.chart-svg .wf-legend-swatch--on  { fill: var(--coin-gold-lo); stroke: var(--coin-gold-lo); stroke-width: 1px; }
[data-theme="dark"] .chart-svg .wf-legend-swatch--on { fill: var(--coin-gold); stroke: var(--coin-gold-lo); }
.chart-svg .wf-legend-swatch--off { fill: transparent; stroke: var(--ink-fainter); stroke-width: 1.5px; }
/* kicker sub-line, attached directly under the €77/€61 number. The shared .kick-num rule
   (~L545) already sets font-variant-numeric:tabular-nums, so the countdown's mono-stable width
   requirement needs no addition here. */
.chart-svg .wf-kick-sub { font-family: var(--font-ui); font-size: 11px; fill: var(--ink-soft); letter-spacing: 0.02em; }
/* [D87] step-echo pulse — one rounded-rect outline framing a cited bar pair, expand+fade once
   (_pulseBarPair), no loop. Size/position/opacity are driven entirely by the JS d3 transition;
   this only fixes the stroke voice so every call reads consistently. */
.chart-svg .wf-echo-pulse { fill: none; stroke: var(--accent); stroke-width: 2px; pointer-events: none; }
/* [D86] the right column's own reserved label gutter — left-aligned, lives ENTIRELY inside the
   ~140px gutter reserved by the JS layout, so a label can never cross into the waffle's zone at
   any width (the structural fix for the overlap bug). */
.chart-svg .wf-bar-label { font-family: var(--font-ui); font-size: 11px; font-weight: 600; fill: var(--ink-soft); }
.chart-svg .wf-bar-2019 { fill: var(--ink-fainter); }
/* [D86] the NOW bar locks to its OWN category's --cat-* colour (owner ruling) — keyed on the
   `data-cat` attribute _buildBars() already sets on each row group. Value tags stay a neutral
   ink (not category-coloured): several --cat-* hues (transport cyan, food yellow-green) read
   poorly as small bold text directly on the paper background, unlike a broad bar fill. */
.chart-svg .wf-barrow[data-cat="CP04"]  .wf-bar-2025 { fill: var(--cat-housing); }
.chart-svg .wf-barrow[data-cat="CP01"]  .wf-bar-2025 { fill: var(--cat-food); }
.chart-svg .wf-barrow[data-cat="SERV"]  .wf-bar-2025 { fill: var(--cat-services); }
.chart-svg .wf-barrow[data-cat="CP07"]  .wf-bar-2025 { fill: var(--cat-transport); }
.chart-svg .wf-barrow[data-cat="CP045"] .wf-bar-2025 { fill: var(--cat-energy); }
.chart-svg .wf-barrow[data-cat="CP11"]  .wf-bar-2025 { fill: var(--cat-restaurants); }   /* [D90-I3] was --cat-other */
.chart-svg .wf-bar-v2019 { font-family: var(--font-mono); font-size: 9.5px; fill: var(--ink-soft); font-variant-numeric: tabular-nums; }
.chart-svg .wf-bar-v2025 {
  font-family: var(--font-mono); font-size: 11.5px; font-weight: 700; fill: var(--ink); font-variant-numeric: tabular-nums;
  paint-order: stroke; stroke: var(--bg); stroke-width: 3px; stroke-linejoin: round;
}
.chart-svg .wf-bar-v2019, .chart-svg .wf-bar-v2025 { transition: opacity var(--dur-3) var(--ease-out); }
/* [D86] the whole bars column (title + 6 rows) hides at step 0 (waffle alone) and shows from
   step 1 on — a single opacity/pointer-events toggle the gate can assert on directly. */
.chart-svg .wf-bars-group { opacity: 0; pointer-events: none; transition: opacity 280ms var(--ease-out); }
.chart-svg .wf-bars-group.is-shown { opacity: 1; pointer-events: auto; }
@media (prefers-reduced-motion: reduce) {
  .chart-svg .wf-bars-group, .chart-svg .wf-bar-v2019, .chart-svg .wf-bar-v2025 { transition: none; }
}
.scroller__chart[data-chart-mount="waffle"] .chart-controls { padding: 0; gap: var(--space-2); line-height: 1; }
#chart-waffle-controls .waffle-ctrl {
  display: inline-flex; gap: var(--space-2); align-items: center;
  font-family: var(--font-body); font-size: 11px; color: var(--ink-soft);
}
#chart-waffle-controls select {
  font-family: var(--font-body); font-size: 11px; padding: 2px 6px;
  border: 1px solid var(--rule); background: var(--bg-elev); color: var(--ink); border-radius: 3px;
}

/* ===== CH7 · Housing (respin, NEW) — HPI vs HICP ===== */
.scroller__chart[data-chart-mount="housing"] { padding-block: 0; gap: 2px; }
.scroller__chart[data-chart-mount="housing"] header { gap: 1px; }

.scroller__chart[data-chart-mount="housing"] .chart-source { line-height: 1.15; margin: 0; }
.chart-svg .hs-base { stroke: var(--ink-soft); stroke-width: 1; stroke-opacity: 0.5; stroke-dasharray: 3 3; }
.chart-svg .hs-base-label { font-family: var(--font-mono); font-size: 9.5px; fill: var(--ink-soft); letter-spacing: 0.02em; }
.chart-svg .hs-end-label { font-family: var(--font-mono); font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; paint-order: stroke; stroke: var(--bg); stroke-width: 3px; stroke-linejoin: round; }
/* [D88 R1] explicit per-series colour, in CSS not a JS presentation attribute — an SVG
   presentation attribute (a plain fill="..." set via .attr()) loses to ANY stylesheet rule
   touching the same property, including the broad ".chart-svg text{fill:--ink-soft}"
   default a few rules down; found because the hicp label kept rendering ink-soft instead
   of the --ink 0.75-opacity this ruling asked for, no matter what the JS set inline. */
.chart-svg .hs-end-label--hpi, .chart-svg .hs-end-hpi { fill: var(--accent); }
.chart-svg .hs-end-label--hicp, .chart-svg .hs-end-hicp { fill: var(--ink); fill-opacity: 0.75; }
.chart-svg .hs-end-name { font-family: var(--font-ui); font-size: 10px; fill: var(--ink-soft); }
.chart-svg .hs-end-dot { stroke: var(--bg); stroke-width: 1.4; }
.chart-svg .hs-gap { fill: var(--accent); fill-opacity: 0.11; }   /* the housing-excess gap, faint claret (event-band style) */
.chart-svg .hs-gap-label { font-family: var(--font-display); font-style: italic; font-size: 13px; fill: var(--ink-soft); paint-order: stroke; stroke: var(--bg); stroke-width: 3.5px; stroke-linejoin: round; }
.chart-svg .hs-dot-title { font-family: var(--font-ui); font-size: 10.5px; font-weight: 600; fill: var(--ink-soft); letter-spacing: 0.13em; }
.chart-svg .hs-dot-tick { font-family: var(--font-mono); font-size: 9px; fill: var(--ink-soft); font-variant-numeric: tabular-nums; }
.chart-svg .hs-dot-name { font-family: var(--font-ui); font-size: 11.5px; font-weight: 600; fill: var(--ink); }
.chart-svg .hs-dot-stem { stroke: var(--rule); stroke-width: 1; }
/* [D88 R1] ALL house-price dots are --accent regardless of top/bottom group — the row's own
   position already carries the ranking (top group first, divider, bottom group after);
   colour must not double-encode a signal position already carries. No --top/--bot split. */
.chart-svg .hs-dot { fill: var(--accent); stroke: var(--bg); stroke-width: 1.4; }
.chart-svg .hs-dot-val { font-family: var(--font-mono); font-size: 11px; font-weight: 700; fill: var(--ink); font-variant-numeric: tabular-nums; paint-order: stroke; stroke: var(--bg); stroke-width: 2.5px; stroke-linejoin: round; }
.chart-svg .hs-dot-sep { font-family: var(--font-ui); font-size: 10px; fill: var(--ink-soft); letter-spacing: 0.04em; }
/* [D88 R3] the second marker proving the claim per-country: consumer-price rise, same
   window, same row — a short --ink tick, never competing with the --accent house-price dot. */
.chart-svg .hs-tick { stroke: var(--ink); stroke-width: 2px; }
/* [P2.3] The row hit-rects span the full plot width and their group is appended ABOVE the line
   group, so outside the countries view they swallowed every mousemove: the year crosshair was
   dead across the whole plot and hovering the line chart popped a country ROW's tooltip instead
   (opacity 0 hides a rect; it does not stop it hit-testing). Live only where the rows are — the
   `is-hit-live` class is toggled by Housing._setHitLive on every view change. */
.chart-svg .hs-row-hit { cursor: pointer; pointer-events: none; }
.chart-svg .hs-dotrange.is-hit-live .hs-row-hit { pointer-events: auto; }
/* [D88 R3] legend line under the dot-range's own mini-header — real dot/tick SVG swatches,
   not glyph characters, so they read identically to the marks they explain. */
.chart-svg .hs-legend-dot { fill: var(--accent); }
.chart-svg .hs-legend-tick { stroke: var(--ink); stroke-width: 2px; }
.chart-svg .hs-legend-text { font-family: var(--font-ui); font-size: 10.5px; fill: var(--ink-soft); }

/* ===== CH8 · RaceChart (respin, NEW) — pay vs prices ===== */
.scroller__chart[data-chart-mount="race"] { padding-block: 0; gap: 0; }
.scroller__chart[data-chart-mount="race"] header { gap: 1px; }

.scroller__chart[data-chart-mount="race"] .chart-source { font-size: 10.5px; line-height: 1.1; margin: 0; letter-spacing: 0.03em; }
.chart-svg .rc-base { stroke: var(--ink-soft); stroke-width: 1; stroke-opacity: 0.5; stroke-dasharray: 3 3; }
.chart-svg .rc-base-label { font-family: var(--font-mono); font-size: 9.5px; fill: var(--ink-soft); letter-spacing: 0.02em; }
.chart-svg .rc-gap-jade { fill: var(--cat-wages); fill-opacity: 0.14; }
.chart-svg .rc-gap-claret { fill: var(--accent); fill-opacity: 0.14; }
.chart-svg .rc-end-dot { stroke: var(--bg); stroke-width: 1.4; }
.chart-svg .rc-end-name { font-family: var(--font-mono); font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums; paint-order: stroke; stroke: var(--bg); stroke-width: 3px; stroke-linejoin: round; }
.chart-svg .rc-beat-line { stroke: var(--ink); stroke-opacity: 0.35; stroke-dasharray: 4 3; }

/* ===== CH8b · DivergingBar (respin re-copy) — panel-fill trim for the kept chart ===== */
.scroller__chart[data-chart-mount="divergingBar"] { padding-block: 0; gap: 0; }
.scroller__chart[data-chart-mount="divergingBar"] header { gap: 1px; }

.scroller__chart[data-chart-mount="divergingBar"] .chart-source { font-size: 10.5px; line-height: 1.1; margin: 0; letter-spacing: 0.03em; }

/* ===== CH9b · BoxPlot (respin re-copy) — panel-fill trim for the kept chart ===== */
.scroller__chart[data-chart-mount="boxplot"] { padding-block: 0; gap: 0; }
.scroller__chart[data-chart-mount="boxplot"] header { gap: 1px; }

.scroller__chart[data-chart-mount="boxplot"] .chart-source { font-size: 10.5px; line-height: 1.1; margin: 0; letter-spacing: 0.03em; }

/* ===== [Amendment §C.3] compare-chips + zoom-preset controls (annotatedLine / race / housing) ===== */
.chart-controls .ac-add { display: inline-flex; align-items: center; gap: var(--space-2); }
.chart-controls .ac-add-label { font-family: var(--font-ui); font-size: 10.5px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-soft); }
.chart-controls .ac-select {
  font-family: var(--font-ui); font-size: 12px; text-transform: none; letter-spacing: 0; color: var(--ink);
  background: var(--bg-elev); border: 1px solid var(--rule); border-radius: var(--radius-1); padding: 3px 6px; cursor: pointer;
}
.chart-controls .ac-select:disabled { opacity: 0.5; cursor: not-allowed; }
.chart-controls .ac-chips { display: inline-flex; flex-wrap: wrap; gap: 6px; }
.chart-controls .ac-chip {
  display: inline-flex; align-items: center; gap: 4px;
  font-family: var(--font-ui); font-size: 11.5px; text-transform: none; letter-spacing: 0;
  color: var(--ink); background: var(--bg-sunken); border-radius: 999px; padding: 2px 4px 2px 10px;
}
.chart-controls .ac-chip-x {
  display: inline-grid; place-items: center; inline-size: 16px; block-size: 16px;
  border: 0; border-radius: 50%; background: none; color: var(--ink-soft); cursor: pointer; font-size: 14px; line-height: 1;
}
/* [P4.4] State-changing hovers are gated to devices that actually have a hover. On a touch
   screen a tap synthesises :hover and it STICKS until the next tap elsewhere, so these read as
   a control left switched on. Only the ones that swap colours are gated; opacity-only hovers
   are left alone, since a lingering fade is not misleading. */
@media (hover: hover) and (pointer: fine) {
  .chart-controls .ac-chip-x:hover { background: var(--accent); color: var(--bg); }
}

.chart-controls .ac-reset {
  font-family: var(--font-ui); font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--link); background: none; border: 0; cursor: pointer; padding: 2px 4px;
}
.chart-controls .ac-zoom { display: inline-flex; margin-inline-start: auto; border: 1px solid var(--rule); border-radius: var(--radius-1); overflow: hidden; }
.chart-controls .ac-zoom-btn {
  font-family: var(--font-ui); font-size: 11px; letter-spacing: 0.02em; text-transform: none;
  color: var(--ink-soft); background: var(--bg-elev); border: 0; cursor: pointer; padding: 3px 9px;
}
.chart-controls .ac-zoom-btn + .ac-zoom-btn { border-inline-start: 1px solid var(--rule); }
.chart-controls .ac-zoom-btn.is-on { background: var(--accent); color: var(--bg); }
.chart-controls .ac-zoom-btn:focus-visible,
.chart-controls .ac-chip-x:focus-visible,
.chart-controls .ac-reset:focus-visible,
.chart-controls .ac-select:focus-visible { outline: 2px solid var(--link); outline-offset: 1px; }
.chart-svg .anno-extra-label,
.chart-svg .race-extra-label,
.chart-svg .sm-cmp-label { font-family: var(--font-mono); font-size: 11px; font-weight: 600; }

/* ===== [AMENDMENT-2 §C] InfoPop — clickable-label popover (singleton) ===== */
.infopop {
  position: fixed; z-index: var(--z-popover); max-inline-size: 240px;
  background: var(--bg-elev); color: var(--ink);
  border: 1px solid var(--rule); border-radius: var(--radius-2);
  box-shadow: var(--shadow-pop);
  padding: 12px 32px 12px 14px;
  opacity: 0; transform: translateY(6px);
  transition: opacity var(--dur-3) var(--ease-out), transform var(--dur-3) var(--ease-out);
}
.infopop.is-in { opacity: 1; transform: none; }
.infopop[hidden] { display: none; }
.infopop__text { font-family: var(--font-body); font-size: 13.5px; line-height: 1.5; color: var(--ink); margin: 0; }
.infopop__close {
  position: absolute; inset-block-start: 5px; inset-inline-end: 5px;
  inline-size: 24px; block-size: 24px; display: grid; place-items: center;
  border: 0; border-radius: 50%; background: none; color: var(--ink-soft); cursor: pointer; font-size: 17px; line-height: 1;
}
.infopop__close:hover { background: var(--bg-sunken); color: var(--ink); }
/* clickable SVG labels: cursor help + dotted underline in the label colour + focus ring.
   [AMENDMENT-3 §5.1 real-click fix] SVG text defaults to `pointer-events: visiblePainted` —
   only the glyph INK is a hit target, not the reported bounding box. Short/sparse labels
   (e.g. "COVID LOCKDOWNS") have real gaps between letters where a real click misses every
   glyph and falls through to whatever's underneath. `bounding-box` makes the whole reported
   box clickable, matching what a user visually aims at. */
.chart-svg .infopop-trigger { text-decoration: underline dotted var(--ipu, currentColor); text-decoration-thickness: 1px; text-underline-offset: 2px; pointer-events: bounding-box; }
/* [AMENDMENT-3 §5.2] affordance upgrade — the dotted underline alone was too subtle for the
   owner to notice these are clickable. On hover/focus, both the glyph fill and the underline
   switch to claret (never the label's own semantic colour), signalling "interactive" the same
   way everywhere; cursor:help is already set unconditionally in InfoPop.flag(). Focus-visible
   keeps its own ring on top (keyboard-only, unchanged). */
.chart-svg .infopop-trigger:hover, .chart-svg .infopop-trigger:focus-visible { fill: var(--accent); text-decoration-color: var(--accent); }
.chart-svg .infopop-trigger:focus-visible { outline: 2px solid var(--link); outline-offset: 1px; }

/* ===== [AMENDMENT-2 §B.5/§D.3] band labels inside bands + brush overview strip ===== */
.chart-svg .anno-band-label { font-family: var(--font-ui); font-size: 10.5px; letter-spacing: 0.12em; fill: var(--ink-soft); }
.chart-svg .anno-ref-label { font-family: var(--font-ui); font-size: 10.5px; letter-spacing: 0.06em; }
.chart-svg .anno-ov-line { pointer-events: none; }
.chart-svg .anno-brush .selection { fill: var(--accent); fill-opacity: 0.12; stroke: var(--accent); stroke-opacity: 0.55; shape-rendering: crispEdges; }
.chart-svg .anno-brush .handle { fill: var(--accent); fill-opacity: 0.45; }
.chart-svg .anno-brush .overlay { cursor: crosshair; }

/* §B.9 chip × optically centered, hit target ≥ 20px */
.chart-controls .ac-chip-x { inline-size: 20px; block-size: 20px; padding: 0; font-size: 15px; }

/* ===== [AMENDMENT-2 §D.3] label typography unification (all charts) =====
   band/reference labels → --font-ui 10.5px ls 0.12em caps (semantic colour kept);
   value tags → --font-mono 11px. Stamp + axis keep their own patterns. */
.chart-svg .anno-band-label, .chart-svg .anno-ref-label,
.chart-svg .rl-ref-label, .chart-svg .rl-baseline-label,
.chart-svg .rc-base-label, .chart-svg .hs-base-label {
  font-family: var(--font-ui); font-size: 10.5px; letter-spacing: 0.12em; text-transform: uppercase;
}
/* [P2.4] `.rl-end-label` deliberately LEFT OUT of this group. It is not a value tag: it is CH5's
   thesis number, defined at ~L703 as the 22px italic claret mark that closes the chart. Being
   later in the file at equal specificity, this rule silently overrode it to 11px mono — the same
   class of defeat the note at ~L1726 records for `.anno-ref-label`. Only consumer is
   RateLevel.js's end label; nothing else reads the class. */
.chart-svg .sm-end-tag, .chart-svg .rc-end-name,
.chart-svg .hs-end-label {
  font-family: var(--font-mono); font-size: 11px;
}

/* Chart data-load error fallback (replaces the inline style= in main.js + ScrollController). */
.chart-load-error {
  color: var(--seq-hi-text);
  padding: var(--space-5);
  font-family: var(--font-ui);
}

/* ============================================================
   EDITORIAL CHART LANGUAGE (ported from designer reference)
   ============================================================ */
.chart-svg { width: 100%; height: 100%; overflow: visible; font-family: var(--font-ui); cursor: crosshair; }
/* [owner review A5] in-chart labels were too small/faint — lift the shared baselines for legibility. */
.chart-svg text { fill: var(--ink-soft); font-size: 12.5px; }

/* ============================================================
   [D93] SEMANTIC TEXT COLOUR — the D88 family, in one place.
   The rule directly above is why: an SVG presentation attribute (a plain fill="…" written by
   `.attr("fill", …)`) is the LOWEST-priority author style, so ANY stylesheet rule touching the
   same property beats it. Every `.attr("fill", …)` on a <text> inside a `.chart-svg` was
   therefore inert — the charts below all painted ink-soft/ink-strong while their code claimed a
   jade/claret/per-slot colour. Colour for text lives HERE, in classes; the JS only toggles the
   class. Gate: `node tools/check-text-fills.mjs` fails on any new fill attr on a text node.
   Sizes decide which token: >=24px is WCAG "large text" (3:1) and keeps the pure series hue;
   11-12px labels need the text-safe variant (4.5:1) — --accent-text / --cat-wages-text / the
   CMP palette, all measured. ============================================================ */
/* CH8 race — the ±gap kicker flips on sign; the two end names match their own line and dot. */
.chart-svg .kick-num.rc-kick--ahead  { fill: var(--cat-wages); }
.chart-svg .kick-num.rc-kick--behind { fill: var(--accent); }
.chart-svg .rc-end-name--pay,   .chart-svg .race-extra-label--pay   { fill: var(--cat-wages-text); }
.chart-svg .rc-end-name--price, .chart-svg .race-extra-label--price { fill: var(--accent-text); }
/* CH1 / CH2 compare labels — one class per CMP slot, mirroring the .ac-sw--cN tooltip swatches.
   The whole CMP palette is text-safe by construction (4.57:1 light / 4.74:1 dark at worst). */
.chart-svg .anno-extra-label--c0, .chart-svg .sm-cmp-label--c0 { fill: var(--cmp-1); }
.chart-svg .anno-extra-label--c1, .chart-svg .sm-cmp-label--c1 { fill: var(--cmp-2); }
.chart-svg .anno-extra-label--c2, .chart-svg .sm-cmp-label--c2 { fill: var(--cmp-3); }
.chart-svg .anno-extra-label--c3, .chart-svg .sm-cmp-label--c3 { fill: var(--cmp-4); }
.chart-svg .anno-extra-label--c4, .chart-svg .sm-cmp-label--c4 { fill: var(--cmp-5); }
.chart-svg .anno-extra-label--c5, .chart-svg .sm-cmp-label--c5 { fill: var(--cmp-6); }
.chart-svg .anno-extra-label--c6, .chart-svg .sm-cmp-label--c6 { fill: var(--cmp-7); }
.chart-svg .anno-extra-label--c7, .chart-svg .sm-cmp-label--c7 { fill: var(--cmp-8); }
.chart-svg .anno-extra-label--c8, .chart-svg .sm-cmp-label--c8 { fill: var(--cmp-9); }
.chart-svg .anno-extra-label--c9, .chart-svg .sm-cmp-label--c9 { fill: var(--cmp-10); }

/* ---------- CraftFX shared craft layer (Phase 2 · MASTER-PLAN PART 7-PRE) ---------- */
/* Bremer multiply layering — overlaps darken on paper; confine to ONE group; drop on phones. */

.chart-svg .axis path,
.chart-svg .axis line { stroke: var(--rule); shape-rendering: crispEdges; }
.chart-svg .axis text { fill: var(--ink-soft); font-size: 11.5px; }

.chart-svg .gridline { stroke: var(--rule-soft); stroke-dasharray: 2 3; }
/* [AUDIT 05] `.grid` gridlines (D3 axis with tickSize:-width) were styled only by
   the DEAD `.chart .grid line` rule — the SVG root is `.chart-svg`, so the tick
   lines fell back to the inherited near-black text colour and rendered as solid
   black rules. Restyle them here, scoped to the live `.chart-svg`. This also
   corrects the same latent bug in AnnotatedLine / BoxPlot / ConnectedScatter,
   which use the identical `class="grid"`. */
.chart-svg .grid line {
  stroke: var(--rule);
  shape-rendering: crispEdges;
}
.chart-svg .grid .domain { display: none; }
.chart-svg .zero-line { stroke: var(--rule-bold); stroke-width: 1; }

/* ----- Choropleth editorial layer ----- */
.chart-svg.is-pannable { cursor: grab; }
.chart-svg.is-pannable.is-panning { cursor: grabbing; }
.chart-svg .country {
  stroke: var(--bg); stroke-width: 0.6;
  transition: opacity 500ms var(--ease-out), filter 500ms var(--ease-out),
              stroke-width 220ms var(--ease-out), stroke 220ms var(--ease-out);
}
/* Country overlay line on timeline */


.chart-svg .country.is-non-eu { fill: var(--veil-non-eu-map); opacity: 0.32; cursor: default; }
.chart-svg .country.is-hover { stroke: var(--ink); stroke-width: 1.4; }
/* [R2-elevate · keep the map alive] The old focus dim applied a heavy desaturate
   (#country-desat = saturate .18) + opacity .7 to every non-focused country, which
   turned the peak/cooling steps into a grey field with one coloured country — directly
   contradicting the narrative ("the map turns red"). Pixel-confirmed in round-2 before/
   frame 03 (2022 Estonia) + 04 (2024 Spain). We now keep neighbours mostly saturated and
   merely recede them with opacity, so the surrounding reds/golds still read while the
   focused country leads via its stroke + glow. */
.chart-svg .country.is-dim { opacity: 0.62; }
.chart-svg .country.is-focus { stroke: var(--ink); stroke-width: 1.8; filter: url(#country-glow); }

/* Bar-hover tooltip sparkline wrapper (replaces an inline style= in Choropleth). */
.bar-tip-spark { display: block; margin: 2px 0 5px; }





/* [owner review 2026-06-15] Kickers were big faint italic display numbers (58px, weight 200,
   italic) — the owner found them ugly + distracting. Now: SMALL, UPRIGHT, LEGIBLE Schibsted
   labels (A: "no faint italic giant numbers anywhere"). Keeps the paper halo for legibility
   over the chart. Per-chart overrides (waffle hero number, etc.) handled in their own blocks. */
.chart-svg .year-kicker {
  font-family: var(--font-ui); font-style: normal; font-weight: 700;
  font-size: 19px;
  fill: var(--ink);
  letter-spacing: 0.005em;
  transform-origin: left center;
  line-height: 1;
  paint-order: stroke;
  stroke: var(--bg);
  stroke-width: 4px;
  stroke-linejoin: round;
}
.chart-svg .year-kicker-sub {
  font-family: var(--font-ui); font-style: normal;
  font-size: 12px; fill: var(--ink-soft);
  letter-spacing: 0.01em;
  paint-order: stroke;
  stroke: var(--bg);
  stroke-width: 4px;
  stroke-linejoin: round;
}
/* [CH1-R1 / S1-2] story-density extremes — small mono lines under the EU avg */
.chart-svg .year-kicker-extreme {
  font-family: var(--font-mono);
  font-size: 11.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  paint-order: stroke;
  stroke: var(--bg);
  stroke-width: 3px;
  stroke-linejoin: round;
}
/* [debug 2026-07-06] --seq-4/--seq-1 direct read fine as map-fill colour patches but fail WCAG AA as
   small text (measured 2.67:1 / 3.36:1 against --bg at this 11.5px size, need 4.5) — use the
   darker, same-hue text-safe variants instead (mirrors --accent-text's existing pattern). */
.chart-svg .year-kicker-extreme--hi { fill: var(--seq-hi-text); }
.chart-svg .year-kicker-extreme--lo { fill: var(--seq-lo-text); }
.chart-svg .graticule { pointer-events: none; }

.chart-svg .capital-dot {
  fill: var(--ink); fill-opacity: 0.42;
  stroke: var(--bg); stroke-width: 0.5;
  transform-origin: center;
  transform-box: fill-box;
  transition: fill-opacity 280ms var(--ease-out);
}
.chart-svg .capital-dot.is-focus {
  fill: var(--accent); fill-opacity: 1;
  stroke: var(--bg); stroke-width: 1.2;
  animation: capPulse 1.8s ease-in-out infinite;
}
@keyframes capPulse {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.35); }
}

/* Multi-country narrative labels (e.g. step 1: Estonia + Lithuania + Latvia) */
/* Choropleth → diverging-bar morph (Chapter 1, Steps 5–6).
   [debug 2026-07-06 — owner report: "should be in the right side like all charts we have"] The wrap
   used to be a viewport-wide `position:fixed` overlay so the bars got the whole screen, including the
   text column's own width — reported back as reading like a different chart taking over the page
   rather than this chart's own next state. Confined to the SAME box every other chart's own SVG
   lives in: `this.container` (`.chart-body`, already `position:relative` per scrollytelling.css) —
   `position:absolute; inset:0` here fills exactly that box, no more, no less. JS gates visibility to
   "while the chapter is in viewport" via the .is-active class, same as before. */
.choro-bars-wrap {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  /* visibility hidden when opacity 0 so the bars layer never visually blocks the choropleth map
     underneath, even on browsers that paint fully-transparent elements. JS toggles
     visibility:visible when opacity > 0. */
  visibility: hidden;
  z-index: 6;
  background: var(--bg);
  display: flex;
  align-items: stretch;
  justify-content: center;
}
.choro-bars-wrap.is-active { pointer-events: auto; visibility: visible; }
.choro-bars-stage {
  width: 100%;
  height: 100%;
  position: relative;
  display: flex;
  align-items: stretch;
}
.choro-bars-stage > svg.choro-bars-svg {
  width: 100%;
  height: 100%;
  display: block;
}
/* [Morph-v2 · Task 3] Top editorial text box — fades in at p ≥ 0.50 */
.bars-textbox {
  position: absolute;
  z-index: 2;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-4, 420ms) var(--ease-out);
}
/* [debug 2026-07-06 — confined-column redesign] left-anchored + narrower cap instead of
   centered/56ch — a centered title fights the LEFT-aligned bar rows for space in this narrower
   column, and 56ch of wrap room on a ~750-900px stage means 2-3 lines eating the whole top margin
   however tall that margin is set. Left-aligned matches how every other chart's own title reads. */
.bars-textbox--top {
  top: var(--space-3);
  left: var(--space-3);
  right: var(--space-3);
  text-align: left;
  max-inline-size: 34ch;
}

/* [debug 2026-07-06] 28px → 16px: sized for a 1520px-wide full-screen destination before, now
   reads at the same register as this chart's own `.scroller__chart h3` (var(--fs-400)) since the
   bars share that exact column now instead of taking over the page. */
.bars-textbox__title {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 16px;
  font-weight: 400;
  font-variation-settings: "opsz" 32;
  letter-spacing: -0.01em;
  color: var(--ink);
  line-height: 1.25;
}
/* [R2-elevate] Emphasis inside the italic Fraunces title (e.g. "above or below") */
.bars-textbox__title em { font-style: italic; color: var(--accent-text); }
/* [R2-elevate] One-line note under the title stating the EU-27 absolute figure, so the
   deviation bars can't be misread as absolute inflation. Inter, muted, tabular strong. */
.bars-textbox__note {
  margin-top: var(--space-1);
  font-family: var(--font-body);
  font-size: 10.5px;
  line-height: 1.4;
  color: var(--ink-soft);
  letter-spacing: 0.005em;
}
.bars-textbox__note strong { color: var(--ink); font-variant-numeric: tabular-nums; }

/* [user-fix · post-morph] Closing text box — bottom-center, fades in after bars */
.bars-textbox--bottom {
  bottom: var(--space-5);
  left: 50%;
  transform: translateX(-50%);
  text-align: center;
  max-inline-size: 60ch;
}
/* [user-fix · dwell] Empty trailing step gives the reader extra scroll room
   after the bar morph completes so they can absorb the chart + insight before
   the next chapter scrolls into view. Sized as a viewport-height of breathing. */
.scroller__step--dwell {
  min-block-size: 90dvh;
  padding: 0;
  margin: 0;
  pointer-events: none;
}

/* [Morph-v2 · Task 8] Country clones — the path-morphed shapes that travel from map to bar */
.choro-bars-svg .country-clone {
  stroke: var(--bg-elev);
  stroke-width: 0.5;
  paint-order: stroke;
  pointer-events: none;
  /* Subtle drop-shadow so shapes have depth as they fly */
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.12));
}
.choro-bars-svg .bars-title {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 22px;
  font-weight: 400;
  font-variation-settings: "opsz" 144;
  fill: var(--ink);
  letter-spacing: -0.01em;
  paint-order: stroke;
  stroke: var(--bg);
  stroke-width: 4px;
  stroke-linejoin: round;
}

.choro-bars-svg .bar-name {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 500;
  fill: var(--ink);
  letter-spacing: 0.01em;
  text-anchor: start;
}
.choro-bars-svg .bar-flag {
  filter: drop-shadow(0 0.5px 0 rgba(0,0,0,0.12));
}
.choro-bars-svg .bar-rect {
  /* width is interpolated by JS; no CSS transitions to fight */
}
.choro-bars-svg .bar-value {
  /* [D93] readable neutral — the bar colour carries sign/heat; --seq-1 negatives were near-invisible.
     Held in CSS, not a JS fill attr: this svg happens to lack `.chart-svg`, so the attr worked by
     accident and would have died the moment the class was added. */
  fill: var(--ink-soft);
  font-family: var(--font-mono);
  font-size: 11.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
  paint-order: stroke;
  stroke: var(--bg);
  stroke-width: 3px;
  stroke-linejoin: round;
}
.choro-bars-svg .bars-eu-zero { pointer-events: none; }
/* [R2-elevate] Replaces the old top-anchored .bars-eu-label (which bled behind the title).
   Small mono tag at the BOTTOM of the zero rule + directional cues either side of it. */
.choro-bars-svg .bars-eu-tag {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  fill: var(--ink-soft);
  paint-order: stroke;
  stroke: var(--bg);
  stroke-width: 3.5px;
  stroke-linejoin: round;
}
.choro-bars-svg .bars-axis-dir {
  font-family: var(--font-body);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  paint-order: stroke;
  stroke: var(--bg);
  stroke-width: 3px;
  stroke-linejoin: round;
}





.chart-svg .stamp-eyebrow {
  font-family: var(--font-body); font-size: 10px;
  fill: var(--ink-soft); letter-spacing: 0.26em; font-weight: 500;
}
.chart-svg .stamp-num {
  font-family: var(--font-display); font-style: normal;   /* owner override: upright, not faint italic */
  font-size: 34px; fill: var(--accent);
  font-weight: 600; letter-spacing: -0.01em;
}
.chart-svg .stamp-sentence {
  font-family: var(--font-display); font-style: normal;   /* upright for legibility */
  font-size: 13.5px; fill: var(--ink);
  letter-spacing: 0; line-height: 1.32;
}
/* [CH2-R1] Comparison-to-overall line — calmer mono numerals so it doesn't
   compete with the italic Fraunces peak number above it. */


.chart-svg .legend-title {
  font-family: var(--font-body); font-size: 9.5px;
  fill: var(--ink-soft); letter-spacing: 0.26em; font-weight: 500;
}
.chart-svg .legend-tick {
  font-family: var(--font-mono); font-size: 9px;
  fill: var(--ink-soft); letter-spacing: 0.02em;
}

@media (max-width: 768px) {
     /* mobile users discover via touch */
}

/* Legend slot in the choropleth panel-foot: give it a bounded, real width so the
   absolutely-positioned gradient bar + tick track resolve to a finite span. Without
   this the slot is a space-between flex item that shrinks to its (zero-intrinsic,
   absolutely-positioned) content and collapses to 0px — fanning every tick label out
   from one point and spilling the "< 0 %" label past the viewport at ≤768 (A2-F6). */
.choro-legend-slot {
  flex: 0 0 auto;
  inline-size: min(220px, 42vw);
}
.choro-legend-html {
  display: flex;
  flex-direction: column;
  gap: 4px;
  pointer-events: none;
  width: 100%;
}

.choro-legend-bar-wrap {
  position: relative;
  width: 100%;
}
.choro-legend-bar {
  display: block;
  width: 100%;
  height: 8px;
  border-radius: 2px;
}
.choro-legend-ticks {
  position: relative;
  height: 16px;
  margin-top: 2px;
}
.choro-legend-tick-label {
  position: absolute;
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--ink-soft);
  letter-spacing: 0.02em;
  top: 0;
  white-space: nowrap;
}

/* Small multiples */
.chart-svg .mini-panel { fill: var(--bg-elev); stroke: var(--rule-soft); stroke-width: 1; }
.chart-svg .anno-peak-leader {
  stroke: var(--accent); stroke-width: 1; stroke-opacity: 0.7; fill: none;
  pointer-events: none;
}
/* [AUDIT 03] Defensive: the SVG root is .chart-svg, so the global
   `.chart .line { fill:none }` never reaches this chart. The JS sets fill:none
   on the element already; this is a stylesheet backstop so the line can never
   regress to a black filled area. */
.chart-svg .anno-line { fill: none; }
/* [AUDIT 03 motion] No CSS opacity transition here. JS drives this group's
   fade exclusively via d3 `.transition().style("opacity", …)` (first-render
   peak pop + _zoomToYear in/out). A CSS `transition: opacity` on the SAME
   element re-eased every D3 frame, so the painted value lagged the target
   (dual-driver smear). D3 is the single opacity driver. */
.chart-svg .anno-peak-g { /* opacity animated by d3 only */ }
.chart-svg .anno-peak-dot { pointer-events: none; }

/* Reveal helpers */
.chart-svg .reveal { opacity: 0; transition: opacity 700ms var(--ease-out); }
.chart-svg .reveal.is-in { opacity: 1; }

/* Editorial form controls (player, slider, hint) */
.scroller__chart .chart-controls {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding-top: 10px;
  /* No top border — the soft fade mask on the map already creates a clean separation. */
  font-size: var(--fs-200); color: var(--ink-soft);
}
.chapter[data-chart="choropleth"] .scroller__chart .chart-controls { display: block; }
.scroller__chart .chart-controls input[type="range"] {
  width: 180px; -webkit-appearance: none; appearance: none;
  background: transparent; height: 18px;
}
.scroller__chart .chart-controls input[type="range"]::-webkit-slider-runnable-track {
  height: 2px; background: var(--rule); border-radius: 1px;
}
.scroller__chart .chart-controls input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--accent); margin-top: -6px;
  box-shadow: 0 0 0 3px var(--ring-thumb);
  cursor: grab;
  transition: box-shadow var(--dur-2);
}
.scroller__chart .chart-controls input[type="range"]:hover::-webkit-slider-thumb {
  box-shadow: 0 0 0 6px var(--ring-thumb-hover);
}


/* ============================================================
   CompareMap — before/after swipe-compare map (owner review D1 stage 5)
   ============================================================ */
.chapter--compare { padding-block: var(--space-7); }


#chart-compareMap-controls {
  display: flex; justify-content: center; align-items: center; flex-wrap: wrap;
  gap: var(--space-4); margin-block: var(--space-1);
}
.cmp-ctrl { display: inline-flex; align-items: center; gap: 4px; font-family: var(--font-ui); font-size: var(--fs-200); color: var(--ink-soft); }
.cmp-ctrl select {
  font-family: var(--font-ui); font-size: var(--fs-200); font-weight: 600;
  color: var(--ink); background: var(--bg-elev);
  border: 1px solid var(--rule); border-radius: var(--radius-2);
  padding: 3px 8px; cursor: pointer;
}
.cmp-ctrl select:focus-visible { outline: 2px solid var(--link); outline-offset: 1px; }
.cmp-ctrl select:hover { border-color: var(--link); }
.cmp-ctrl-vs { font-family: var(--font-ui); font-size: var(--fs-100); color: var(--ink-soft); text-transform: uppercase; letter-spacing: 0.12em; }
#chart-compareMap {
  position: relative;
  inline-size: 100%;
  /* [scroll-fix §4c] Cap the WIDTH so the height (= width / 1.42) plus the header + year-pickers +
     source line all fit inside ONE viewport. Before this the map was ~1248x879 and overflowed the
     screen (owner: "the map is larger than the screen") — the title and pickers scrolled off-top. */
  max-inline-size: min(100%, calc((100dvh - 210px) * 1.42));
  aspect-ratio: 1.42 / 1;        /* matches the CompareMap BaseChart aspect → no letterbox */
  margin-inline: auto;
}
#chart-compareMap .chart-svg { width: 100%; height: 100%; display: block; }
.cmp-map .cmp-country { stroke: var(--bg); stroke-width: 0.5; }
.cmp-map .cmp-country.is-non-eu { fill: var(--rule-soft); opacity: 0.30; }
.cmp-year-label {
  font-family: var(--font-ui); font-weight: 700; font-size: 22px;   /* upright, NOT a faint italic giant number (owner override) */
  fill: var(--ink-strong);
  paint-order: stroke; stroke: var(--bg); stroke-width: 4px; stroke-linejoin: round;
}
.cmp-divider-line { stroke: var(--bg-elev); stroke-width: 3; pointer-events: none; }
.cmp-handle {
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  inline-size: 34px; block-size: 34px; border-radius: 50%;
  /* [D95] the border is a 2px graphic (3:1 bar, --accent clears it); the `‹ ›` grip is 14px TEXT
     and --accent measures 2.85:1 on --bg-elev in dark, so the glyph takes the AA variant. */
  background: var(--bg-elev); border: 2px solid var(--accent); color: var(--accent-text);
  display: grid; place-items: center; cursor: ew-resize;
  box-shadow: var(--shadow-2); z-index: 3; touch-action: none;
  font-family: var(--font-ui); font-size: 14px; font-weight: 700; line-height: 1;
  transition: box-shadow var(--dur-2) var(--ease-out);
}
.cmp-handle.is-dragging { box-shadow: var(--shadow-3); }
.cmp-handle:focus-visible { outline: 2px solid var(--link); outline-offset: 2px; }
.cmp-handle__grip { letter-spacing: -2px; }
/* [D83] One-shot invite pulse once the enter-compare sweep settles — must keep the handle's own
   translate(-50%,-50%) centering (not just scale) or the pulse would visibly re-center the handle. */
@keyframes cmp-handle-pulse {
  0%   { transform: translate(-50%, -50%) scale(1); }
  50%  { transform: translate(-50%, -50%) scale(1.15); }
  100% { transform: translate(-50%, -50%) scale(1); }
}
.cmp-handle.is-pulsing { animation: cmp-handle-pulse var(--dur-5) var(--ease-spring); }
@media (prefers-reduced-motion: reduce) { .cmp-handle.is-pulsing { animation: none; } }




/* (Ridgeline chart cut — its .ridge-* / .rdg-* styles removed in the round-5 debug pass.) */

/* ============================================================
   [Fable D54] CH3 compare finale + camera polish
   ============================================================ */
/* Graticule hidden while the camera flies — the hairline grid re-rasterises every zoom frame
   (the owner's "vertical and horizontal lines appear for a moment") and costs paint time. */
.chart-svg.is-camming .graticule { opacity: 0; }
/* [D77 · owner "still not fixed" — the actual seam trigger] `will-change: transform` was REMOVED from
   `.map-layer` here. D70 added it to "smooth" the camera, but on a large SVG group it does the OPPOSITE
   of what you want: it forces Chrome to promote the layer to a persistent GPU texture and SCALE THAT
   BITMAP during the zoom (tiled once the layer exceeds texture size — ~6300px at scale 2 × DPR 2 even
   after the D76 graticule fix), and the tile boundaries leak the paper background as the white
   horizontal/vertical grid the owner filmed (plus the bitmap upscaling reads as the "shake"/blur).
   WITHOUT `will-change`, Chrome re-renders the map's VECTORS crisply every frame instead of scaling a
   cached bitmap — no bitmap means no tiles means no seams, and it stays sharp at any zoom. The D76
   graticule bound keeps that per-frame vector re-render cheap (27 countries + a bounded grid, not a
   383-million-unit path). This is the real fix; the earlier is-camming graticule-hide (D70), mask-drop
   (D75), and even the graticule-size fix (D76) all reduced the tiling without removing the one property
   that FORCED bitmap-tiling in the first place. */
/* [D75] The vignette mask is also dropped while `.is-camming` — see the co-located rule + rationale in
   scrollytelling.css. Kept as defense-in-depth for extreme Alt+wheel zoom (scale 8). */

/* The embedded drag-to-compare overlay — fills chapter 3's chart-body like the bars wrap does. */
.choro-cmp-wrap {
  position: absolute; inset: 0;
  background: var(--bg);
  opacity: 0;
  transition: opacity var(--dur-4) var(--ease-out);
  z-index: 4;
}
.choro-cmp-wrap.is-in { opacity: 1; }
.choro-cmp-wrap .chart-svg { width: 100%; height: 100%; display: block; }
@media (prefers-reduced-motion: reduce) { .choro-cmp-wrap { transition: none; } }

/* [D82] Cross-fade partner — the map's own <svg> fades opacity over the SAME duration/easing as
   .choro-cmp-wrap above instead of an instant visibility cut, so entering/leaving compare reads as
   one cross-fade (map fades out while the compare overlay fades in) rather than a cut-then-fade-up.
   Scoped to the direct SVG child only — nothing else here ever drives this element's own opacity. */
#chart-choropleth > svg { transition: opacity var(--dur-4) var(--ease-out); }
@media (prefers-reduced-motion: reduce) { #chart-choropleth > svg { transition: none; } }

/* Year pickers now live in the compare STEP CARD (left column), not above a full-bleed map. */
.cmp-card-controls {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--space-3);
  margin-block-start: var(--space-3);
}
/* [D84] Country before/after card under the pickers — filled by Choropleth._renderCmpDetail on
   click. Two distinct motion cases, both class-toggle + CSS-transition driven (the line draw/morph
   itself is d3-attr-transition driven in JS — see _cmpDetailReveal/_cmpDetailSwitch):
   CASE A (first selection, panel not yet open) — .cmp-card-detail is a grid-template-rows 0fr->1fr
   reveal container (the "animate to auto height" technique — no JS height measurement, no layout
   jump). .cmp-detail__inner is the required overflow:hidden child the technique clips against, and
   also carries the divider border, which fades in on its own delayed timing alongside the name.
   CASE B/C (switching country, or a year-dropdown change while one is selected) — the panel does
   NOT re-collapse/re-reveal; .is-swap/.is-swap-in on the SAME elements drive a fast, undelayed
   crossfade (150ms out, swap text, 150ms in) — see the JS for why these need their OWN transition
   duration distinct from the one-time reveal's delayed 180ms (a bare class-removal back to the
   `.is-in` state would otherwise inherit the base rule's 168ms delay, wrong for a crossfade). */
.cmp-card-detail {
  margin-block-start: var(--space-3);
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 280ms var(--ease-out);
}
.cmp-card-detail.is-in { grid-template-rows: 1fr; }
@media (prefers-reduced-motion: reduce) { .cmp-card-detail { transition: none; } }

.cmp-detail__inner {
  overflow: hidden;
  min-height: 0;
  border-block-start: 1px solid transparent;
  padding-block-start: var(--space-3);
  transition: border-color 180ms var(--ease-out) 168ms;
}
.is-in .cmp-detail__inner { border-color: var(--rule); }
@media (prefers-reduced-motion: reduce) { .cmp-detail__inner { transition: none; } }

.cmp-detail__name {
  font-family: var(--font-ui); font-size: 11px; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink);
  margin-block-end: 6px;
  opacity: 0; transform: translateY(4px);
  transition: opacity 180ms var(--ease-out) 168ms, transform 180ms var(--ease-out) 168ms;
}
.is-in .cmp-detail__name { opacity: 1; transform: translateY(0); }
.is-in .cmp-detail__name.is-swap { opacity: 0; transition: opacity 150ms var(--ease-out); }
.is-in .cmp-detail__name.is-swap.is-swap-in { opacity: 1; }

.cmp-detail__mini { display: block; width: 100%; height: 64px; overflow: visible; }
.cmp-detail__mini path { fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.cmp-detail__mini .cmp-mini-a { stroke: var(--seq-1); }
.cmp-detail__mini .cmp-mini-b { stroke: var(--seq-4); }

.cmp-detail__rows { display: flex; gap: var(--space-4); margin-block-start: 6px; }
.cmp-detail__row {
  font-family: var(--font-mono); font-size: 11.5px; font-variant-numeric: tabular-nums;
  opacity: 0; transform: translateY(4px);
  transition: opacity 180ms var(--ease-out), transform 180ms var(--ease-out);
}
.cmp-detail__row.is-shown { opacity: 1; transform: translateY(0); }
.cmp-detail__row.is-shown.is-swap { opacity: 0; transition: opacity 150ms var(--ease-out); }
.cmp-detail__row.is-shown.is-swap.is-swap-in { opacity: 1; }
.cmp-detail__row--a { color: var(--seq-lo-text); }
.cmp-detail__row--b { color: var(--seq-hi-text); }
@media (prefers-reduced-motion: reduce) {
  .cmp-detail__name, .cmp-detail__row { transition: none; }
}
