/* ==========================================================================
   explorer.css — layout and components for the FleetModel scenario explorer
   --------------------------------------------------------------------------
   Every colour, size, space, radius and duration comes from e3-tokens.css
   (which is itself derived from E3Website/tokens/e3.tokens.json — see that
   file's header). NO raw hex appears below; a structural test greps for it.

   The E3 rules this file implements, in order of appearance:
   - one gutter token governs every section edge (DESIGN_RULES/layout.md)
   - content caps at measure.section (80rem); whitespace grows, not measure
   - hierarchy by size and ink opacity, never by new greys or heavy weights
   - hard offset shadows only — no blur, ever
   - the −3° skewed-block motif is scoped to ONE interactive surface (the
     report CTA): dense repeating chrome (chips, cards, pills) renders flat
   - :focus-visible is 2px ink, offset 2px, never removed
   - prefers-reduced-motion kills every transition and animation
   ========================================================================== */

/* --- Reset / page -------------------------------------------------------- */

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

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

/* Column layout so the footer never floats mid-viewport on a short page
   (the landing): main grows, the footer sits at the bottom edge. */
body {
  margin: 0;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-stack);
  font-size: var(--fs-body);
  font-weight: var(--fw-body);
  line-height: var(--lh-body-zh);
  letter-spacing: var(--track-body);
}

main {
  flex: 1;
}

/* The single source of left/right inset — no inner container adds its own. */
:root {
  --gutter-x: clamp(var(--space-5), 5vw, var(--space-24));
  --section-pad-y: clamp(var(--space-10), 6vw, var(--space-20));

  /* Height of the sticky site header, subtracted from 100svh to size the
     one-screen app area. The value here is the measured resting height of
     the shipped header; explorer.js overwrites it from the real element on
     load and on every resize, so a header that grows a row (wrapped nav on a
     narrow laptop) does not push the charts off screen. */
  --app-offset: 76px;
}

.wrap {
  width: min(100%, var(--measure-section));
  margin-inline: auto;
  padding-inline: var(--gutter-x);
}

/* --- Type primitives ----------------------------------------------------- */

h1,
h2,
h3 {
  margin: 0;
  font-weight: var(--fw-display);
}

h1 {
  font-size: var(--fs-h1);
  line-height: var(--lh-h1);
  letter-spacing: var(--track-h1);
}

h2 {
  font-size: var(--fs-h2);
  line-height: var(--lh-h2);
  letter-spacing: var(--track-h2);
}

h3 {
  font-size: var(--fs-h3);
  line-height: var(--lh-h3);
  letter-spacing: var(--track-h3);
}

p {
  margin: 0;
}

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

/* Eyebrow recipe — DESIGN_RULES/patterns.md § Eyebrow Label, verbatim. */
.eyebrow {
  font-size: var(--fs-eyebrow);
  font-weight: var(--fw-heading);
  text-transform: uppercase;
  letter-spacing: var(--track-eyebrow);
  line-height: var(--lh-flush);
  color: var(--ink);
  opacity: var(--ink-4);
}

/* The zh partner of a Latin line sits smaller and quieter (brand.md). */
.sub-en {
  display: block;
  margin-top: var(--space-2);
  font-size: var(--fs-small);
  font-weight: var(--fw-light);
  letter-spacing: var(--track-body);
  color: var(--ink);
  opacity: var(--ink-3);
}

.meta {
  font-size: var(--fs-meta);
  color: var(--muted);
}

/* --- Header (quiet: the page's boldness is spent on the CTA) ------------- */

/* Sticky: the nav rides along on scroll on every page. Compact on purpose —
   a pinned bar earns its keep by staying small — with an opaque paper
   background so content scrolls under it, and the flat 1px line for an edge
   (no blur shadow, ever). */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--paper);
  border-bottom: 1px solid var(--line);
  padding-block: var(--space-3);
}

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

/* The brand block is a home link; it keeps the ink, not the accent — it is
   identity, not an action. Sized as a wordmark, not a page heading: the h1
   belongs to each page's own content now. */
.brand {
  color: var(--ink);
  text-decoration: none;
  font-size: var(--fs-h3);
  font-weight: var(--fw-display);
  letter-spacing: var(--track-h3);
  line-height: var(--lh-flush);
}

.brand .sub-en {
  margin-top: var(--space-1);
  font-size: var(--fs-meta);
}

/* --- Site nav: 報告 (dropdown) + 互動介面 --------------------------------
   The dropdown is a <details>, so it opens and closes without JavaScript —
   the landing page ships none. Items match the chip recipe: flat, 1px line,
   44px targets (semantics-a11y.md § Contrast & Size Floor). */

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

.nav-link,
.nav-dropdown summary {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  padding: var(--space-2) var(--space-4);
  border: 1px solid transparent;
  border-radius: var(--radius);
  color: var(--ink);
  font-size: var(--fs-body);
  font-weight: var(--fw-heading);
  line-height: var(--lh-flush);
  text-decoration: none;
  cursor: pointer;
  transition: background var(--motion-micro) var(--ease),
    border-color var(--motion-micro) var(--ease);
}

.nav-link:hover,
.nav-dropdown summary:hover,
.nav-dropdown[open] summary {
  border-color: var(--structure);
  background: var(--surface);
}

/* The current page: the marker underline bar, not a colour change. */
.nav-link[aria-current="page"] {
  box-shadow: inset 0 calc(-1 * var(--space-1)) 0 var(--marker);
}

.nav-dropdown {
  position: relative;
}

.nav-dropdown summary {
  list-style: none;
}

.nav-dropdown summary::-webkit-details-marker {
  display: none;
}

/* The disclosure glyph: drawn, so it needs no extra font weight. */
.nav-dropdown summary::after {
  content: "";
  margin-left: var(--space-2);
  border: 4px solid transparent;
  border-top-color: var(--ink);
  transform: translateY(2px);
}

.nav-dropdown[open] summary::after {
  border-top-color: transparent;
  border-bottom-color: var(--ink);
  transform: translateY(-2px);
}

/* The panel: flat card + the hard-offset ink shadow (never blur). */
.nav-dropdown ul {
  position: absolute;
  right: 0;
  top: calc(100% + var(--space-1));
  z-index: 10;
  min-width: 14rem;
  margin: 0;
  padding: var(--space-2);
  list-style: none;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper);
  box-shadow: var(--shadow-shift) var(--shadow-shift) var(--shadow-ink);
}

.nav-dropdown ul a {
  display: flex;
  align-items: center;
  min-height: 2.75rem;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius);
  color: var(--ink);
  font-size: var(--fs-small);
  text-decoration: none;
}

.nav-dropdown ul a:hover {
  background: var(--surface);
}

/* --- Hero: the landing statement ------------------------------------------
   Flat by rule — the page's single skewed-block moment is the report CTA
   (layout.md § motif Rule). Hierarchy is size + ink opacity; the only colour
   gesture is the sanctioned --marker underline bar under the statement
   (color.md: marker = underline bars and glyphs, never text). */

.hero .eyebrow {
  display: block;
}

.hero-statement {
  max-width: var(--measure-statement);
  margin-top: var(--space-4);
  text-wrap: balance;
  font-size: var(--fs-display);
  font-weight: var(--fw-display);
  line-height: calc(var(--lh-display) + var(--lh-zh-bonus));
  letter-spacing: var(--track-display);
}

/* The marker underline bar: a drawn stroke, not a text decoration. */
.hero-statement::after {
  content: "";
  display: block;
  width: var(--space-16);
  height: var(--space-1-5);
  margin-top: var(--space-5);
  border-radius: var(--radius);
  background: var(--marker);
}

.hero-lede {
  max-width: var(--measure-prose);
  margin-top: var(--space-6);
  font-size: var(--fs-lede);
  line-height: calc(var(--lh-lede) + var(--lh-zh-bonus));
  color: var(--ink);
  opacity: var(--ink-2);
}

.path-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
  gap: var(--space-5);
  margin-top: var(--space-10);
}

/* Path cards are links, so everything inside stays on the ink ladder —
   accent-text would paint whole paragraphs, which color.md forbids. */
.path-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-6);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink);
  text-decoration: none;
  transition: background var(--motion-micro) var(--ease),
    border-color var(--motion-micro) var(--ease);
}

.path-card:hover {
  border-color: var(--structure);
  background: var(--surface);
}

.path-kicker {
  font-size: var(--fs-eyebrow);
  font-weight: var(--fw-heading);
  text-transform: uppercase;
  letter-spacing: var(--track-eyebrow);
  line-height: var(--lh-flush);
  opacity: var(--ink-4);
}

.path-title {
  font-size: var(--fs-h3);
  font-weight: var(--fw-display);
  line-height: calc(var(--lh-h3) + var(--lh-zh-bonus));
  letter-spacing: var(--track-h3);
}

.path-desc {
  font-size: var(--fs-small);
  line-height: var(--lh-body-zh);
  opacity: var(--ink-2);
}

/* The card's one accent-text element: a short label, an allowed link use. */
.path-go {
  margin-top: auto;
  font-size: var(--fs-small);
  font-weight: var(--fw-heading);
  color: var(--accent-text);
}

.hero-meta {
  margin-top: var(--space-8);
}

/* --- Sections ------------------------------------------------------------ */

main {
  display: block;
}

.section {
  padding-block: var(--section-pad-y);
}

.section--band {
  background: var(--surface);
}

.section-head {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-8);
}

.section-note {
  max-width: var(--measure-prose);
  font-size: var(--fs-small);
  color: var(--muted);
}

/* --- Controls: flat, keyboard-first ------------------------------------- */

.axis-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
  gap: var(--space-4);
}

/* Compact by default, not only in the rail: a slider whose value sat in its
   own block under the track spent a whole line saying one word. Flat 1px
   line, no radius — the axis card is chrome, not a signature surface. */
.axis {
  margin: 0;
  padding: var(--space-3);
  border: 1px solid var(--line);
  background: var(--paper);
}

.axis legend {
  padding-inline: var(--space-2);
  font-size: var(--fs-eyebrow);
  font-weight: var(--fw-heading);
  text-transform: uppercase;
  letter-spacing: var(--track-eyebrow);
  color: var(--ink);
  opacity: var(--ink-4);
}

/* Label left, current value right, on one row. `align-items: baseline` so the
   value sits on the label's first line even when the zh label wraps. */
.axis-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-1);
}

.axis-label {
  display: block;
  min-width: 0;
  font-size: var(--fs-small);
}

/* The Latin partner of a zh axis label: smaller and quieter, never a second
   heading (brand.md § zh-first pairing). */
.axis-label-en {
  display: block;
  font-size: var(--fs-meta);
  font-weight: var(--fw-light);
  color: var(--ink);
  opacity: var(--ink-3);
}

.axis-value {
  flex: 0 0 auto;
  font-size: var(--fs-body);
  font-weight: var(--fw-heading);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--ink);
}

/* Tap targets ≥44px (semantics-a11y.md § Contrast & Size Floor). The 44px
   stays on the INPUT; only the space around it is trimmed. */
.axis input[type="range"] {
  display: block;
  width: 100%;
  min-height: 2.75rem;
  margin: 0;
  accent-color: var(--accent-graphic);
}

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* Dense repeating chrome renders FLAT — the skew is a gesture, and a gesture
   repeated twenty times stops being one (layout.md § motif Rule). */
.chip {
  display: inline-flex;
  align-items: center;
  /* 2.25rem is a POINTER size. The 44px floor is a *touch* floor, so it is
     restored below for coarse pointers rather than dropped. */
  min-height: 2.25rem;
  padding: var(--space-1-5) var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper);
  font-size: var(--fs-meta);
  line-height: var(--lh-flush);
  cursor: pointer;
  transition: background var(--motion-micro) var(--ease),
    border-color var(--motion-micro) var(--ease);
}

/* Touch: back to the 44px floor (semantics-a11y.md § Contrast & Size Floor). */
@media (pointer: coarse) {
  .chip {
    min-height: 2.75rem;
    padding: var(--space-2) var(--space-4);
    font-size: var(--fs-small);
  }
}

.chip[aria-pressed="true"],
.chip:hover {
  border-color: var(--structure);
  background: var(--surface);
}

/* --- Results: flat cards + charts ---------------------------------------- */

.card-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
  gap: var(--space-5);
  margin-bottom: var(--space-12);
}

.card {
  padding: var(--space-5);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper);
}

.card-label {
  font-size: var(--fs-eyebrow);
  font-weight: var(--fw-heading);
  text-transform: uppercase;
  letter-spacing: var(--track-eyebrow);
  color: var(--ink);
  opacity: var(--ink-4);
}

.card-value {
  margin-top: var(--space-2);
  font-size: var(--fs-h2);
  font-weight: var(--fw-display);
  line-height: var(--lh-h2);
  letter-spacing: var(--track-h2);
}

.card-unit {
  margin-top: var(--space-1);
  font-size: var(--fs-meta);
  color: var(--muted);
}

/* The Δ card. Direction is carried by the ▼/▲ glyph in the text AND by the
   status text tier — colour is never the sole signal of a state
   (semantics-a11y.md § Forms). A Δ of zero keeps the plain ink. */
.delta {
  font-variant-numeric: tabular-nums;
}

.delta.is-better {
  color: var(--status-ok-text);
}

.delta.is-worse {
  color: var(--status-alert-text);
}

/* Two columns at most. ECharts lays a chart out in real pixels, so a track
   narrower than roughly 28rem starts dropping tick labels and crowding the
   legend; capping the track at two keeps every chart at a readable width. */
.chart-grid {
  display: grid;
  /* `min(28rem, 100%)`, not a bare 28rem: a track floor wider than the
     container cannot shrink, and the charts would spill through the gutter
     on a phone. */
  grid-template-columns: repeat(auto-fit, minmax(min(28rem, 100%), 1fr));
  gap: var(--space-10);
}

/* <figure> ships a 40px UA side margin; it would shrink every chart inside
   its own grid track and, with it, the rendered size of the SVG's type. */
.chart {
  margin: 0;
  min-width: 0;
}

.chart h3 {
  margin-bottom: var(--space-3);
}

/* --- Chart containers ----------------------------------------------------
   ECharts needs a box with a resolved height to lay out into; it never reads
   `height: auto`. The chart itself is drawn with the SVG renderer, so the
   result is still vector type at whatever size the container ends up.

   COLOUR NO LONGER LIVES HERE. The hand-rolled SVG charts wrote no colour at
   all (a presentation attribute cannot resolve var() everywhere), so every
   node carried a class and this file mapped it onto a token. ECharts writes
   colours into its option object instead, so charts.js reads the SAME tokens
   at runtime via getComputedStyle(document.documentElement) — chrome from
   structure / line / muted, single series from --accent-graphic, the 基準點
   overlay from --muted plus a dash, and only the four powertrain series from
   the sanctioned --pt-* field palette. Charts stay flat: no border, no
   shadow, no blur, no fill behind the plot area. */

.chart-canvas {
  width: 100%;
  height: 380px;
}

/* 能源需求 stacks three small multiples in one instance; the panel geometry
   is in charts.js (ENERGY_BAND / ENERGY_PLOT_H) and this height must hold all
   three of them plus the bottom panel's year labels. */
.chart-canvas--tall {
  height: 420px;
}

/* The empty state hides the container rather than tearing the instance down,
   so `hidden` must really remove it from layout. */
.chart-canvas[hidden] {
  display: none;
}

/* Empty state where a chart would be — same slot, same rhythm, no numbers. */
.chart-empty {
  padding-block: var(--space-10);
  font-size: var(--fs-small);
  color: var(--muted);
}

/* --- The 150ms result fade ----------------------------------------------
   An animation, not a transition: the node is new on every render, so there
   is no "from" state for a transition to interpolate. The reduced-motion
   kill switch below collapses it to 1ms. */

@keyframes fm-fade-in {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

.fade-in {
  animation: fm-fade-in var(--motion-micro) var(--ease) both;
}

/* Loading / 失敗 state for the current grid point. Flat, quiet, and out of
   the layout entirely when there is nothing to say. */
.point-status {
  margin-top: var(--space-6);
  font-size: var(--fs-small);
  color: var(--muted);
}

.point-status[hidden] {
  display: none;
}

/* Inline annotations use the status pair; colour is never the sole signal —
   the copy itself says what is going on (semantics-a11y.md § Forms). */
.note-alert {
  padding: var(--space-3) var(--space-4);
  border-left: var(--border-w) solid var(--status-alert);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--status-alert) var(--badge-tint), var(--paper));
  color: var(--status-alert-text);
  font-size: var(--fs-small);
}

.note-alert[hidden] {
  display: none;
}

/* --- The one signature moment: the skewed-block report CTA ---------------
   The full motif per DESIGN_RULES/layout.md § The Skewed-Block Motif:
   skew −3°, counter-skewed child so the label reads upright, 2px ink border,
   hard offset ink-alpha shadow (never blur, never grey), and a hover that
   does three things at once — nudge up-left, grow the shadow, DOUBLE the
   radius — all at motion.micro (150ms). Nothing else on this page skews. */

.cta-row {
  margin-top: var(--space-12);
}

/* In its own section below the app area the CTA opens the block, so the
   section padding is already the space above it. */
.app-outro .cta-row {
  margin-top: 0;
}

.cta-note {
  margin-top: var(--space-5);
}

.cta {
  display: inline-block;
  padding: var(--space-3) var(--space-6);
  border: var(--border-w) solid var(--ink);
  border-radius: var(--radius);
  background: var(--paper);
  box-shadow: var(--shadow-shift) var(--shadow-shift) var(--shadow-ink);
  color: var(--ink);
  font-size: var(--fs-body);
  font-weight: var(--fw-heading);
  line-height: var(--lh-flush);
  text-decoration: none;
  transform: skewX(var(--block-skew));
  transition: transform var(--motion-micro) var(--ease),
    box-shadow var(--motion-micro) var(--ease),
    border-radius var(--motion-micro) var(--ease);
}

.cta > span {
  display: inline-block;
  transform: skewX(var(--content-skew));
}

.cta:hover,
.cta:focus-visible {
  border-radius: var(--radius-hover);
  box-shadow: var(--shadow-shift-hover) var(--shadow-shift-hover) var(--shadow-ink);
  transform: skewX(var(--block-skew)) translate(-1px, -1px);
}

.cta:active {
  border-radius: var(--radius);
  box-shadow: var(--shadow-shift) var(--shadow-shift) var(--shadow-ink);
  transform: skewX(var(--block-skew)) translate(0, 0);
}

/* --- Footer -------------------------------------------------------------- */

.site-footer {
  border-top: 1px solid var(--line);
  padding-block: var(--space-10);
  font-size: var(--fs-small);
  color: var(--muted);
}

.site-footer p + p {
  margin-top: var(--space-3);
}

/* Legal row: copyright left, the E3 mark right. The logo is the one element
   on the page allowed its own colours — it is the brand mark, not UI. */
.footer-legal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-top: var(--space-6);
  padding-top: var(--space-6);
  border-top: 1px solid var(--line);
}

.site-footer .footer-legal:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

.powered {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 2.75rem;
  color: var(--muted);
  font-size: var(--fs-meta);
  text-decoration: none;
}

.powered:hover span {
  text-decoration: underline;
}

.powered svg {
  height: 2rem;
  width: auto;
}

/* --- The one-screen app layout (laptop and up) ---------------------------
   The requirement: adjusting a scenario axis and reading the result must not
   cost a scroll. So at ≥1025px the controls and the results stop being two
   stacked sections and become two columns of one grid that is exactly the
   viewport minus the sticky header:

     +--------------------------------------------------+
     | sticky header                                    |
     +-------------+------------------------------------+
     | axis rail   | headline strip (5 values, one row) |
     | (scrolls    +------------------------------------+
     |  internally)|  chart   |  chart                  |
     |             |----------+-------------------------|
     |             |  chart   |  chart                  |
     +-------------+------------------------------------+
     |  (scroll)  report CTA · notes · footer           |

   Below 880px this whole block is inert: `.app` is a plain block wrapper and
   the page keeps the stacked flow it has always had. 880px, not the token
   1025px laptop tier: a half-screen window on a 13" laptop lands in the
   900s, and "adjust without scrolling" is exactly the layout that viewer
   needs — the rail clamps down to 16rem so the charts keep a usable share. */

@media (min-width: 880px) {
  .app {
    display: grid;
    grid-template-columns: clamp(16rem, 24vw, 21.5rem) minmax(0, 1fr);
    height: calc(100svh - var(--app-offset));
  }

  /* Each column owns its own overflow; neither can push the other off. */
  .app > .section {
    min-height: 0;
    padding-block: var(--space-3);
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  /* Inside the app the page gutter is replaced by column padding: a 5vw
     inset on a 320px rail would leave nothing for the slider to live in. */
  .app > .section > .wrap {
    width: 100%;
    max-width: none;
    padding-inline: var(--space-5);
  }

  /* --- Left rail ------------------------------------------------------ */

  .app-rail {
    border-right: 1px solid var(--line);
  }

  /* The h1 drops a step in SIZE only — the ink tier is untouched, so the
     hierarchy still reads the way the ladder says it should. */
  .app-rail h1 {
    font-size: var(--fs-h3);
    line-height: var(--lh-h3);
    letter-spacing: var(--track-h3);
  }

  .app-rail .section-head {
    gap: var(--space-1-5);
    margin-bottom: var(--space-4);
  }

  /* Standing prose in the rail drops to the meta tier: it is a standing note,
     read once, and every line it keeps is a line of controls pushed under
     the fold. --fs-meta is the floor and it stops there. */
  .app-rail .section-note {
    font-size: var(--fs-meta);
    line-height: var(--lh-body);
  }

  .app-rail .axis-grid {
    grid-template-columns: 1fr;
    gap: var(--space-2);
  }

  /* Left flowing rather than forced into two rigid columns: the three short
     population chips share one line and the two 壽命 chips the next, so the
     block costs two lines in the rail instead of five. */
  .app-rail .chips {
    gap: var(--space-1-5);
  }

  /* The Latin subtitle stays in the DOM for assistive tech and is taken off
     the screen: four of them cost four lines the rail does not have. */
  .app-rail .axis-label-en {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    white-space: nowrap;
    clip-path: inset(50%);
  }

  .app-rail .point-status {
    margin-top: var(--space-4);
  }

  /* The §11.5 FCV-floor annotation appears at the bottom of a rail that may
     already be scrolled, and it explains why a slider the reader just moved
     did nothing — it cannot be allowed to appear below the fold. Pinned to
     the rail's bottom edge; its tinted background is a color-mix over
     --paper, so it is fully opaque and content scrolls cleanly under it. */
  .app-rail .note-alert {
    position: sticky;
    bottom: 0;
    z-index: 1;
    margin-top: var(--space-3);
  }

  /* --- Right: headline strip over a 2×2 chart grid --------------------- */

  .app-results > .wrap {
    display: flex;
    flex-direction: column;
    height: 100%;
  }

  /* One line instead of three: the eyebrow sits beside the heading. */
  .app-results .section-head {
    flex-direction: row;
    align-items: baseline;
    gap: var(--space-3);
    margin-bottom: var(--space-3);
  }

  .app-results h2 {
    font-size: var(--fs-h3);
    line-height: var(--lh-h3);
    letter-spacing: var(--track-h3);
  }

  /* Five values in one row. Still `auto-fit`, so a genuinely narrow laptop
     wraps the strip to two lines rather than crushing the numbers. */
  /* Five fixed columns, never a wrap: a card row that wraps to a second line
     steals its height straight from the chart grid below. minmax(0, 1fr)
     lets a column shrink under its content instead of forcing the wrap. */
  .app-results .card-row {
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: var(--space-2);
    margin-bottom: var(--space-3);
  }

  .app-results .card {
    padding: var(--space-3);
  }

  .app-results .card-value {
    margin-top: var(--space-1);
    font-size: var(--fs-h3);
    line-height: var(--lh-h3);
    letter-spacing: var(--track-h3);
  }

  /* The grid takes every pixel the strip did not. `minmax(0, 1fr)` rows, not
     `1fr`: a `1fr` row refuses to go below its content's natural height, and
     the grid would grow straight back through the bottom of the viewport. */
  .app-results .chart-grid {
    flex: 1 1 auto;
    min-height: 0;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-rows: repeat(2, minmax(0, 1fr));
    gap: var(--space-4);
  }

  .app-results .chart {
    display: flex;
    flex-direction: column;
    min-height: 0;
  }

  .app-results .chart h3 {
    margin-bottom: var(--space-1);
    font-size: var(--fs-small);
    line-height: var(--lh-h3);
    letter-spacing: var(--track-body);
  }

  /* The cell sizes the chart now, not a fixed 380/420px — including the
     three-panel energy chart, whose panel geometry is expressed in PERCENT
     in charts.js precisely so that it follows whatever height it is given. */
  .app-results .chart-canvas,
  .app-results .chart-canvas--tall {
    flex: 1 1 auto;
    height: auto;
    min-height: 8rem;
  }

  .app-results .chart-empty {
    flex: 1 1 auto;
    padding-block: var(--space-6);
  }

  .app-results figcaption {
    margin-top: var(--space-1);
    line-height: var(--lh-body);
  }
}

/* --- Focus: 2px ink, offset 2px, never removed --------------------------- */

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

/* --- Progressive enhancement -------------------------------------------- */

body:not(.js-ready) .js-only {
  display: none;
}

.no-js-note {
  display: none;
}

body:not(.js-ready) .no-js-note {
  display: block;
}

/* --- Reduced motion: the kill switch ------------------------------------
   The global rule collapses animation; transition-based states need the
   explicit reduce variant too (semantics-a11y.md § Motion). The skew, the
   border and the shadow stay — only their movement in time goes away. */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }

  .cta:hover,
  .cta:focus-visible {
    transform: skewX(var(--block-skew));
  }
}
