/* ═══════════════════════════════════════════════════════════════════
   PDI · styles.css — the stack-owned layout layer
   ──────────────────────────────────────────────────────────────────
   This file is the PDI-specific layout/CSS that every page links
   AFTER tokens.css. It reads ONLY Night Shift tokens via var(),
   never hardcodes a hex, rgb(), or named color.

   Rules:
   - Every color is var(--<token>) or color-mix(in srgb, var(--…) …)
   - System fonts only (--font-sans / --font-mono from tokens.css)
   - JS-off legible — no content hidden behind JS-only selectors
   - WCAG AA — measured contrast on every text/background pair
   - Mobile-first — base rules for all screens, progressive enhancement
     at 600px / 900px / 1200px
   ═══════════════════════════════════════════════════════════════════ */

/* ── RESET + BASE ───────────────────────────────────────────────── */
*,
*::before,
*::after {
  box-sizing: border-box;
}

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

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

/* ═══════════════════════════════════════════════════════════════════
   ELEVATION SCALE
   ──────────────────────────────────────────────────────────────────
   Three planes built from Night Shift surface tokens. Every surface
   component selects from this scale — never from the raw tokens
   directly, so the whole stack shifts together if re-tuned.

   plane-base → card at rest (--surface)
   plane-1    → slightly raised card (--raised)
   plane-2    → hover / active lift (--raised + stronger shadow)
   ═══════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════
   ELEVATION SCALE — ported from casa-datos
   ──────────────────────────────────────────────────────────────────
   This block is the FALLBACK — plain token references only, so no
   engine can lose a card to a broken color-mix(). The richer values
   live in the @supports block immediately below.

   Levels: ground (--bg) < band < plane-1 (cards) < plane-2 (hover),
   with --plane-sunk for chips recessed INTO a card.
   ═══════════════════════════════════════════════════════════════════ */

:root {
  /* Fluid section rhythm */
  --rhythm: clamp(48px, 5.6vw, 76px);

  /* ── Plane ladder — base layer (fallback) ─────────────────────── */
  --plane-base: var(--surface);
  --plane-band: var(--bg-alt);
  --plane-1:    var(--raised);
  --plane-2:    var(--raised);
  --plane-sunk: var(--bg);

  /* ── Spectral card edges — base (plain token references) ────────
     Warm→cool gradient borders so each card reads as a distinct frame
     rather than a repeated grey rectangle. */
  --edge-warm:        var(--tp-sun-soft);
  --edge-neutral:     var(--border-strong);
  --edge-cool:        var(--tp-cyan-soft);
  --edge-warm-hot:    var(--tp-cream);
  --edge-neutral-hot: var(--border-strong);
  --edge-cool-hot:    var(--tp-cyan);

  --edge: linear-gradient(148deg,
      var(--edge-warm) 0%,
      var(--edge-neutral) 30%,
      var(--edge-neutral) 62%,
      var(--edge-cool) 100%);
  --edge-hot: linear-gradient(148deg,
      var(--edge-warm-hot) 0%,
      var(--edge-neutral-hot) 28%,
      var(--edge-neutral-hot) 60%,
      var(--edge-cool-hot) 100%);

  /* ── Section band ─────────────────────────────────────────────── */
  --band-line: var(--border-strong);
  --band-atmos: var(--tp-ember-soft);
  --band-spectrum: linear-gradient(90deg,
      transparent 0%,
      var(--tp-sun)   14%,
      var(--tp-ember) 34%,
      var(--tp-red)   50%,
      var(--tp-teal)  72%,
      var(--tp-cyan)  88%,
      transparent 100%);

  /* ── Lift shadows — inset warm highlight + deep ambient shadow ── */
  --lift:
    inset 0 1px 0 0 var(--border-strong),
    0 24px 48px -30px var(--tp-black);
  --lift-hover:
    inset 0 1px 0 0 var(--tp-cream),
    0 30px 60px -28px var(--tp-black);

  /* ── Quiet — between muted and subtle, >4.5:1 on every plane ──── */
  --quiet: var(--muted);

  /* ── Light rig probes (base = -soft tokens) ───────────────────── */
  --rig-1: var(--tp-orange-soft);
  --rig-2: var(--tp-cyan-soft);
  --rig-3: var(--tp-ocean-soft);
  --rig-4: var(--tp-ember-soft);
  --grain-op: 0.34;
  --bloom-cool: var(--tp-ocean-soft);

  /* ── Painted gradients (base = Night Shift --grad-*) ──────────── */
  --brand-paint: var(--grad);
  --title-paint: var(--grad-title);
  --cta-fill:    var(--grad-cta);
  --cta-ink:     var(--bg);
  --cta-glow:    var(--tp-ember-soft);

  /* ── PDI-specific rhythm alias ────────────────────────────────── */
  --section-gap: var(--rhythm);
}

/* ── THE ELEVATION SCALE — ENHANCEMENT LAYER ─────────────────────────
   Everything above is complete and correct on its own. This block makes
   it richer where the engine supports color-mix(). Warmed toward the
   Rainbow ramp (--tp-peach) rather than lifted as flat grey — a
   neutral-grey lift under a warm light reads as a hole.
   ═══════════════════════════════════════════════════════════════════ */
@supports (color: color-mix(in srgb, currentColor 50%, transparent)) {
  :root {
    --plane-base: color-mix(in srgb, var(--surface) 70%, var(--tp-black));
    --plane-band: color-mix(in srgb, var(--tp-peach)  3.5%, var(--bg));
    --plane-1:    color-mix(in srgb, var(--tp-peach)  6.5%, var(--plane-base));
    --plane-2:    color-mix(in srgb, var(--tp-peach) 11.5%, var(--plane-base));
    --plane-sunk: color-mix(in srgb, var(--tp-ember)    3%, var(--tp-black));

    --edge-warm:        color-mix(in srgb, var(--tp-cream) 38%, var(--plane-1));
    --edge-neutral:     color-mix(in srgb, var(--tp-paper) 18%, var(--plane-1));
    --edge-cool:        color-mix(in srgb, var(--tp-cyan)  32%, var(--plane-1));
    --edge-warm-hot:    color-mix(in srgb, var(--tp-cream) 46%, var(--plane-2));
    --edge-neutral-hot: color-mix(in srgb, var(--tp-paper) 30%, var(--plane-2));
    --edge-cool-hot:    color-mix(in srgb, var(--tp-cyan)  50%, var(--plane-2));

    --band-line: color-mix(in srgb, var(--tp-cream) 22%, var(--plane-band));
    --band-atmos: color-mix(in srgb, var(--tp-peach) 26%, transparent);
    --quiet:     color-mix(in srgb, var(--muted) 62%, var(--subtle));

    --lift:
      inset 0 1px 0 0 color-mix(in srgb, var(--tp-cream) 22%, transparent),
      0 24px 48px -30px color-mix(in srgb, var(--tp-black) 96%, transparent);
    --lift-hover:
      inset 0 1px 0 0 color-mix(in srgb, var(--tp-cream) 34%, transparent),
      0 30px 60px -28px color-mix(in srgb, var(--tp-black) 98%, transparent);

    --rig-1: color-mix(in srgb, var(--tp-orange) 30%, transparent);
    --rig-2: color-mix(in srgb, var(--tp-cyan)   26%, transparent);
    --rig-3: color-mix(in srgb, var(--tp-ocean)  32%, transparent);
    --rig-4: color-mix(in srgb, var(--tp-ember)  14%, transparent);
    --bloom-cool: color-mix(in srgb, var(--tp-ocean) 20%, transparent);
    --cta-glow: color-mix(in srgb, var(--tp-ember) 60%, transparent);
  }
}

/* ═══════════════════════════════════════════════════════════════════
   UTILITY CLASSES
   ═══════════════════════════════════════════════════════════════════ */

.wrap {
  width: 100%;
  max-width: var(--wrap);          /* 1120px from tokens.css */
  margin-inline: auto;
  padding-inline: clamp(16px, 3vw, 32px);
}

.wrap-narrow {
  width: 100%;
  max-width: 72ch;                 /* comfortable reading measure */
  margin-inline: auto;
  padding-inline: clamp(16px, 3vw, 24px);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ═══════════════════════════════════════════════════════════════════
   SKIP-TO-CONTENT LINK
   ──────────────────────────────────────────────────────────────────
   Visually hidden until focused — first focusable element on every
   page. When focused, slides into view at the top of the viewport.
   ═══════════════════════════════════════════════════════════════════ */

.skip-link {
  position: absolute;
  top: -100%;
  left: 0;
  z-index: 100;
  padding: 0.5em 1em;
  background: var(--accent);
  color: var(--bg);
  font-weight: 600;
  font-size: 0.875rem;
  border-radius: 0 0 var(--r-badge) 0;
  text-decoration: none;
  transition: top 120ms ease;
}

.skip-link:focus {
  top: 0;
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ═══════════════════════════════════════════════════════════════════
   TYPOGRAPHY
   ═══════════════════════════════════════════════════════════════════ */

body {
  font-size: clamp(15px, 2.2vw, 17px);
}

h1, h2, h3, h4, h5, h6 {
  margin: 0;
  font-weight: 600;
  line-height: 1.25;
  color: var(--text);
}

h1 { font-size: clamp(1.6rem, 4.5vw, 2.4rem); }
h2 { font-size: clamp(1.3rem, 3.4vw, 1.8rem); }
h3 { font-size: clamp(1.1rem, 2.6vw, 1.3rem); }

p { margin: 0 0 1em; }

a {
  color: var(--accent);
  text-decoration: none;
}
a:hover {
  text-decoration: underline;
}

/* section eyebrow / tagline */
.section-tag {
  display: block;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 0.35em;
}

.section-heading {
  margin-bottom: var(--section-gap);
}

/* ═══════════════════════════════════════════════════════════════════
   SITE HEADER
   ──────────────────────────────────────────────────────────────────
   Sticky, dark ground with backdrop blur. The 1px spectral bottom
   edge is --border-strong at rest, --accent-mix on hover/scroll (the
   stack applies .scrolled via JS).

   LAYOUT — the header has three fixed parts and two registers.

   Parts, left to right:
     .site-logo    the wordmark. Static markup.
     .site-nav     nine peer links. Static markup.
     .lang-pair    ES/EN + the theme flipper. NOT in the HTML —
                   lang-toggle.js appends it to .site-header-inner at
                   runtime and theme-toggle.js appends the button
                   inside it. The layout must reserve room for a third
                   child that is absent at first paint.

   Registers (the flip is at 600px, an existing breakpoint):
     < 600px   one row. The lockup falls back to the PDI monogram,
               the nav is a horizontal rail, toggles pinned right.
               A phone keeps a ~48px bar.
     >= 600px  two rows. Wordmark left + toggles right on row one;
               the nav takes the full content width on row two, where
               nine links fit outright from ~768px up.

   Why two rows rather than one: the row budget does not close. At the
   widest the content box is --wrap (1120px) minus 64px padding =
   1056px, against a 244px wordmark + a 774px nav + 54px of toggles =
   1072px. Nine links have never fit beside the wordmark at ANY width
   — the 800px clipping was the visible half of a defect that also
   wrapped the wordmark at 1920px. Giving the nav its own row closes
   the budget with ~280px to spare at desktop, so the next link added
   costs nothing. (Nav grew 4 -> 9 in one session; assume it grows.)

   Two invariants hold in both registers:
     - the wordmark never wraps and never shrinks (nowrap + flex 0 0);
     - the nav absorbs its own overflow (min-width:0 + overflow-x),
       so it can never push a sibling out of the viewport.
   ═══════════════════════════════════════════════════════════════════ */

/* ── Language toggle ──────────────────────────────────────────── */
.lang-pair {
  display: inline-flex;
  align-items: center;
  /* Injected last and never sacrificed: these two controls are the
     only way to change language or theme, so they hold their width
     while the nav gives ground. */
  flex: 0 0 auto;
  white-space: nowrap;
}

.lang-link {
  display: inline-flex;
  align-items: center;
  padding: 0.35em 0.65em;
  border-radius: var(--r-badge);
  color: var(--muted);
  text-decoration: none;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  transition: background-color 160ms ease, color 160ms ease;
  cursor: pointer;
}
.lang-link:hover {
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  color: var(--text);
  text-decoration: none;
}

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--bg);
  border-bottom: 1px solid var(--border-strong);

  /* backdrop-filter on supported engines — degrades to opaque bg */
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
}
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .site-header {
    background: color-mix(in srgb, var(--bg) 92%, transparent);
  }
}

.site-header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* min-height, never height. A hard box is what turned an overflowing
     nav into a *clipped* wordmark: the bar could not grow, so the
     three-line lockup was cut by the 56px edge. The clamp is unchanged,
     so the bar renders at exactly its old height whenever the content
     fits — but it can no longer cut anything that does not. */
  min-height: clamp(48px, 7vw, 64px);
  column-gap: clamp(10px, 2vw, 20px);
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: clamp(16px, 3vw, 32px);
}

.site-logo {
  /* The wordmark is the one thing that must never give. Before, it was
     the only text-shrinkable child of the row, so it silently absorbed
     the whole of the nav's overflow — squeezed to 115px and wrapped to
     three lines. flex 0 0 auto takes it out of that negotiation;
     nowrap makes a mid-word break impossible at any width. */
  flex: 0 0 auto;
  white-space: nowrap;
  /* Base register (<600px) shows the monogram only — see the register
     note above. font-size:0 collapses the wordmark text node visually
     while leaving it in the DOM and in this link's accessible name, so
     "PDI Policy Data Infrastructure" is still what a screen reader
     announces. The full size is restored at >=600px. */
  font-size: 0;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text);
  text-decoration: none;
}
.site-logo:hover { text-decoration: none; }

.site-logo-accent {
  color: var(--accent);
  font-size: 1.1rem;   /* carries the lockup on its own in the monogram register */
}

.site-nav {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.875rem;
  font-weight: 500;

  /* ── The rail ────────────────────────────────────────────────
     min-width: 0 is the load-bearing declaration. A flex item will
     not shrink below its min-content width unless you say so, and
     this nav's min-content is 774px — wider than the row at every
     viewport. Without it the deficit had to go somewhere, and it went
     to the siblings: the wordmark wrapped and .lang-pair was pushed
     clean off the viewport (measured at x=913 in an 800px window,
     with the document itself scrolling horizontally to 957px).
     With it, the nav takes its own overflow as scroll and no sibling
     ever moves. Nine links stay in the DOM at full size; scrolling
     and Tab both reach every one of them. */
  flex: 1 1 auto;
  min-width: 0;
  overflow-x: auto;
  overflow-y: hidden;
  /* Sized past the 28px trailing fade below, so a link reached by Tab
     is not parked under the gradient. It also softens a Chromium quirk
     rather than curing it: sequential focus navigation into a
     horizontal scroller updates scrollLeft only every other Tab, so a
     focused link can sit partly outside the rail. Reproduced on a bare
     nine-link flex scroller with none of this stylesheet loaded (3 of 9
     clipped, worst 68px at a 181px rail), so it is the engine, not this
     header — and 32px measurably improves it here (worst 41px). Every
     link still receives focus, in DOM order, and activates.
     Not scroll-behavior: motion.css sets `smooth` on html, but
     scroll-behavior does not inherit — a fresh scroll container in this
     header computes `auto`, so there is nothing here to override. */
  scroll-padding-inline: 32px;
  overscroll-behavior-x: contain;     /* a nav swipe must not page-back */
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;              /* a scrollbar here would eat the bar */
  -ms-overflow-style: none;
  /* overflow-y:hidden would crop a focus ring; the padding buys room
     for it and the matching negative margin gives the height back. */
  padding-block: 4px;
  margin-block: -4px;
}
.site-nav::-webkit-scrollbar { width: 0; height: 0; display: none; }

.site-nav a {
  display: inline-flex;
  align-items: center;
  /* 0 0 auto: links hold their label width rather than squashing into
     unreadable slivers when the rail is narrower than the nav. */
  flex: 0 0 auto;
  white-space: nowrap;
  padding: 0.35em 0.6em;
  border-radius: var(--r-badge);
  color: var(--muted);
  text-decoration: none;
  transition: background-color 160ms ease, color 160ms ease;
}
.site-nav a:hover {
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  color: var(--text);
  text-decoration: none;
}
.site-nav a.active,
.site-nav a[aria-current="page"] {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--text);
}

/* ── Compact register: < 600px ─────────────────────────────────────
   One row, monogram lockup, nav as a rail. The rail is ~205px wide at
   320px against a 704px nav, so it is scrollable by construction at
   every width in this band — which is why the trailing fade is safe to
   state unconditionally here and nowhere else. An always-on fade over a
   nav that actually fits would be a lie about there being more. */
@media (max-width: 599px) {
  .site-nav {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
            mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
  }
}

/* ── Masthead register: >= 600px ───────────────────────────────────
   Row one: wordmark left, toggles right (justify-content: space-between
   already places them). Row two: the nav, full content width.
   flex-basis:100% is what forces the break — the nav cannot share a
   line with anything, so it always lands on its own, whatever else the
   header ends up holding. order: keeps .lang-pair on row one even
   though the JS appends it after the nav in DOM order. */
@media (min-width: 600px) {
  .site-header-inner {
    flex-wrap: wrap;
    row-gap: 4px;
    padding-block: 8px;
  }

  .site-logo  { order: 1; font-size: 1.1rem; }   /* full wordmark returns */
  .lang-pair  { order: 2; }
  .site-nav   { order: 3; flex-basis: 100%; }
}

/* ── >= 1200px: the nav gets its original spacing back ─────────────
   Below this the stock 12px gap and 0.75em pills overrun the row — at
   768px they need 774px against 722px, the "nearly fits" case that
   reads as broken rather than as a rail. The tighter pills buy the fit
   outright through the whole mid-range. From 1200px the row is the full
   --wrap content box and the nav returns to exactly the density it had
   before this fix, with room to spare.

   1200 rather than 900 because of Spanish. The ES labels are 64px wider
   than the EN ones (838px vs 774px at stock spacing), which at 900px
   left 7.9px of slack — a margin thin enough to go negative on any font
   stack that rasterises a percent wider than this one, and it would do
   so silently on someone else's machine. At 1200 the same nav has 218px
   of slack in Spanish. Measured, both languages, not assumed. */
@media (min-width: 1200px) {
  .site-nav   { gap: 12px; }
  .site-nav a { padding: 0.35em 0.75em; }
}

/* ═══════════════════════════════════════════════════════════════════
   CARD SYSTEM
   ──────────────────────────────────────────────────────────────────
   .card          → plane-base bg, --lift shadow, 1px spectral edge
   .card-hover    → adds hover lift to plane-2 + --lift-hover shadow
   .card-raised   → plane-1 bg, for cards that are "up" at rest
   ═══════════════════════════════════════════════════════════════════ */

.card {
  border: 1px solid transparent;
  border-radius: var(--r-card);
  background-image: linear-gradient(var(--plane-1), var(--plane-1)), var(--edge);
  background-origin: border-box;
  background-clip: padding-box, border-box;
  box-shadow: var(--lift);
  padding: clamp(16px, 2.5vw, 24px);
  transition: background-color 200ms ease,
              border-color     200ms ease,
              box-shadow       200ms ease,
              transform        200ms ease;
}

.card-hover:hover {
  background-image: linear-gradient(var(--plane-2), var(--plane-2)), var(--edge-hot);
  box-shadow: var(--lift-hover);
  transform: translateY(-1px);
}

.card-raised {
  background-image: linear-gradient(var(--plane-2), var(--plane-2)), var(--edge-hot);
  box-shadow: var(--lift-hover);
}

/* Spectral top edge — a 1px accent strip above the card */
.card-spectral {
  border-top: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
}

/* ── card grid ─────────────────────────────────────────────────── */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
  gap: clamp(12px, 2vw, 20px);
}

/* ═══════════════════════════════════════════════════════════════════
   DATA TABLES
   ──────────────────────────────────────────────────────────────────
   Dark rows, alternating --bg-alt stripes, sticky header.
   No hex, no named color — every stripe is a token or color-mix().
   ═══════════════════════════════════════════════════════════════════ */

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.875rem;
  font-variant-numeric: tabular-nums;
}

.data-table thead {
  position: sticky;
  top: 0;
  z-index: 1;
}

.data-table th {
  padding: 0.75em 1em;
  text-align: left;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  background: var(--plane-base);
  border-bottom: 1px solid var(--border-strong);
  white-space: nowrap;
}

.data-table td {
  padding: 0.65em 1em;
  border-bottom: 1px solid var(--border);
}

/* Alternating row stripes via --bg-alt */
.data-table tbody tr:nth-child(even) {
  background: color-mix(in srgb, var(--bg-alt) 50%, var(--bg));
}

.data-table tbody tr:hover {
  background: color-mix(in srgb, var(--accent) 4%, var(--surface));
}

/* Numeric columns — right-aligned, monospace */
.data-table .num {
  text-align: right;
  font-family: var(--font-mono);
}

/* Highlight row */
.data-table tbody tr.row-highlight {
  background: color-mix(in srgb, var(--accent) 6%, var(--surface));
}

/* ═══════════════════════════════════════════════════════════════════
   STAT CALLOUT
   ──────────────────────────────────────────────────────────────────
   Big number on clamp font, label below, optional direction arrow.
   ═══════════════════════════════════════════════════════════════════ */

.stat-callout {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.stat-value {
  font-size: clamp(1.8rem, 5.2vw, 3rem);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--text);
}

.stat-label {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 0.25em;
}

/* Direction arrows for deltas */
.stat-delta {
  display: inline-flex;
  align-items: center;
  gap: 0.2em;
  font-size: 0.85rem;
  font-weight: 600;
  margin-top: 0.15em;
}

.stat-delta-up {
  color: var(--live);
}

.stat-delta-down {
  color: var(--gated);
}

/* --quiet, never --subtle for text — see the note above .site-footer
   small. Measured here: --subtle is 3.96:1 on --bg and 3.46:1 on
   --plane-1; --quiet is 5.76:1 and 5.03:1. */
.stat-delta-neutral {
  color: var(--quiet);
}

.stat-delta::before {
  content: "";
  display: inline-block;
  width: 0;
  height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
}
.stat-delta-up::before {
  border-bottom: 6px solid currentColor;
}
.stat-delta-down::before {
  border-top: 6px solid currentColor;
}

/* Compact horizontal stat row */
.stat-row {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(16px, 3vw, 32px);
}

/* ═══════════════════════════════════════════════════════════════════
   RELIABILITY BADGES
   ──────────────────────────────────────────────────────────────────
   High → --live (green), Moderate → --gated (amber),
   Low → red-mix, Unknown → --quiet (grey) for the label, --subtle
   for the tint behind it — a background carries no contrast duty.
   All use color-mix() for the background tint so badges sit on any
   surface without contrast issues.
   ═══════════════════════════════════════════════════════════════════ */

.badge {
  display: inline-flex;
  align-items: center;
  padding: 0.2em 0.6em;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  border-radius: var(--r-badge);
  white-space: nowrap;
}

.badge-high,
.badge-reliability-high {
  color: var(--live);
  background: color-mix(in srgb, var(--live) 12%, transparent);
}

.badge-moderate,
.badge-reliability-moderate {
  color: var(--gated);
  background: color-mix(in srgb, var(--gated) 12%, transparent);
}

.badge-low,
.badge-reliability-low {
  color: var(--gated);
  background: color-mix(in srgb, var(--gated) 20%, transparent);
}

.badge-unknown,
.badge-reliability-unknown {
  color: var(--quiet);
  background: color-mix(in srgb, var(--subtle) 10%, transparent);
}

/* Info badge variant */
.badge-info {
  color: var(--info);
  background: color-mix(in srgb, var(--info) 12%, transparent);
}

/* ═══════════════════════════════════════════════════════════════════
   CHART CONTAINERS
   ──────────────────────────────────────────────────────────────────
   .chart-card uses --plane-1, spectral top edge, and provides an
   accessible table fallback that's visually hidden but screen-reader
   reachable.
   ═══════════════════════════════════════════════════════════════════ */

.chart-card {
  border: 1px solid transparent;
  border-radius: var(--r-card);
  background-image: linear-gradient(var(--plane-1), var(--plane-1)), var(--edge);
  background-origin: border-box;
  background-clip: padding-box, border-box;
  box-shadow: var(--lift);
  padding: clamp(16px, 2.5vw, 24px);
  position: relative;
  overflow: hidden;
  transition: box-shadow 200ms ease;
}
.chart-card:hover {
  box-shadow: var(--lift-hover);
  background-image: linear-gradient(var(--plane-2), var(--plane-2)), var(--edge-hot);
}

.chart-card-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: clamp(12px, 2vw, 20px);
}

.chart-card-title {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--text);
  margin: 0;
}

.chart-card-subtitle {
  font-size: 0.75rem;
  color: var(--muted);
}

/* Accessible table fallback — hidden visually, present for AT */
.chart-table-fallback {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Bar visualization inside chart card */
.chart-bar-track {
  height: 8px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--text) 8%, transparent);
  overflow: hidden;
}

.chart-bar-fill {
  height: 100%;
  border-radius: 999px;
  background: var(--cta-fill);
  transition: width 400ms ease;
}

.chart-bar-fill--live {
  background: var(--live);
}

.chart-bar-fill--gated {
  background: var(--gated);
}

/* ═══════════════════════════════════════════════════════════════════
   SECTION BAND · atmospheric surface
   ──────────────────────────────────────────────────────────────────
   A full-bleed band that lifts a section off the page with a warm
   atmospheric gradient and a spectral top lip. Uses box-shadow spread
   to extend past the .wrap measure without touching 100vw.
   ═══════════════════════════════════════════════════════════════════ */

.section-band {
  position: relative;
}
.section-band::before,
.section-band::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  z-index: -1;
  pointer-events: none;
  clip-path: inset(0 -100vmax);
}
.section-band::before {
  top: 0;
  bottom: 0;
  background:
    radial-gradient(56% 68% at 20% 30%, var(--band-atmos), transparent 72%),
    var(--plane-band);
  box-shadow: 0 0 0 100vmax var(--plane-band);
}
/* Lit lip — spectral 3px line across the top */
.section-band::after {
  top: 0;
  height: 3px;
  background: var(--band-spectrum);
  box-shadow: 0 0 0 100vmax var(--band-line);
}

/* ═══════════════════════════════════════════════════════════════════
   HERO BLOOM · warm/cool radial glows
   ──────────────────────────────────────────────────────────────────
   .hero-glow places two fixed pseudo-elements behind the hero:
   ::before = warm key light (high left), ::after = cool counter (top right).
   Both are pointer-events: none and carry no information.
   ═══════════════════════════════════════════════════════════════════ */

.hero-glow {
  position: relative;
}
.hero-glow::before,
.hero-glow::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}
.hero-glow::before {
  background:
    radial-gradient(72% 48% at 0% -14%,   var(--rig-1), transparent 66%),
    radial-gradient(58% 42% at 100% -6%,  var(--rig-2), transparent 64%);
}
.hero-glow::after {
  background:
    radial-gradient(92% 50% at 50% 114%,  var(--rig-3), transparent 70%),
    radial-gradient(70% 44% at 96% 78%,   var(--rig-4), transparent 72%);
}

/* Without clip-path: inset() the 100vmax spread on .section-band::before
   is never trimmed back to the section's own top and bottom — it paints
   100vmax in every direction and the WHOLE PAGE turns --plane-band,
   erasing the distinction the band exists to create. Dropping the
   spread degrades the band to the .wrap measure: narrower than
   intended, but a band rather than a flooded page. */
@supports not (clip-path: inset(0 -1px)) {
  .section-band::before,
  .section-band::after {
    box-shadow: none;
  }
}

/* ═══════════════════════════════════════════════════════════════════
   THREAD RIBBON · spectral section divider (ADR-013 §1D)
   ──────────────────────────────────────────────────────────────────
   The line between two sections is the full Rainbow sweep
   (sun→ember→red→teal→cyan), not a faded single-colour hairline. This
   is the one place ADR-013 lets a Rainbow hue carry no data, and the
   sweep still does a job: warm where the section you are leaving ends,
   cool where the next one begins — the same wayfinding the card edges
   run vertically, run horizontally.

   Height is 1px, per §1D. casa-datos runs a 22–60px seeded SVG ribbon
   in this slot, redrawn by its app.js from the same seed as its charts;
   PDI has no such generator, and a tall gradient block without the
   parametric idea behind it is decoration pretending to be an artifact.
   The spectrum and the technique port. The height does not.

   Both markup shapes in the pages are covered:
     <hr class="thread">                          — the rule itself
     <div class="thread"><i class="thread-dot">   — split rule, centre mark
   ═══════════════════════════════════════════════════════════════════ */

.thread {
  display: block;
  width: 100%;
  height: 1px;
  border: 0;
  margin-block: var(--section-gap);
  background: var(--band-spectrum);
}

/* Dot variant — the sweep splits either side of a centre mark, warm
   half leading and cool half trailing, so the divider still reads
   left-to-right the way the single rule does. :has()-guarded: an engine
   without it gets the plain 1px rule above, which is a complete
   divider, not a broken one. */
@supports selector(:has(*)) {
  .thread:has(.thread-dot) {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 16px;
    height: auto;
    background: none;
    max-width: var(--wrap);
    margin-inline: auto;
    padding-inline: clamp(16px, 3vw, 32px);
  }
  .thread:has(.thread-dot)::before,
  .thread:has(.thread-dot)::after {
    content: "";
    flex: 1;
    height: 1px;
  }
  .thread:has(.thread-dot)::before {
    background: linear-gradient(90deg,
      transparent 0%, var(--tp-sun) 42%, var(--tp-ember) 100%);
  }
  .thread:has(.thread-dot)::after {
    background: linear-gradient(90deg,
      var(--tp-red) 0%, var(--tp-teal) 48%, var(--tp-cyan) 80%, transparent 100%);
  }
}

.thread-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  opacity: 0.5;
  flex-shrink: 0;
}

/* ═══════════════════════════════════════════════════════════════════
   SITE FOOTER
   ──────────────────────────────────────────────────────────────────
   Two markup shapes resolve here: PDI's bare centred line
   (<footer class="site-footer">text · link · licence</footer>) and the
   stack's row layout (.site-footer > .wrap holding .footer-links). A
   page can gain the row without gaining a stylesheet.

   The top edge is flat --band-line, not the spectrum. A footer is a
   boundary, not a seam between two peers — ADR-013's colour rule keeps
   the Rainbow for the places that are. --band-line rather than
   --border-strong because the 0.16-alpha white hairline measures about
   1.3:1 against the page: it is declared, and it is not visible.
   ═══════════════════════════════════════════════════════════════════ */

.site-footer {
  border-top: 1px solid var(--band-line);
  margin-top: clamp(32px, 5vw, 56px);
  padding-block: clamp(20px, 3vw, 32px);
  padding-inline: clamp(16px, 3vw, 32px);
  text-align: center;
  font-size: 0.8125rem;
  color: var(--quiet);
}

.site-footer a {
  color: var(--accent);
  text-decoration: none;
}
.site-footer a:hover {
  text-decoration: underline;
}

/* --quiet, never --subtle: --subtle cannot reach AA-normal on any plane
   in this stack (3.96:1 at its best on the ground). */
.site-footer small {
  color: var(--quiet);
  font-size: 0.78rem;
}

/* Row layout — engages only when the page supplies the wrapper. */
.site-footer .wrap {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  align-items: center;
  justify-content: space-between;
  text-align: left;
}

.footer-links {
  display: flex;
  gap: 18px;
  list-style: none;
  margin: 0;
  padding: 0;
  flex-wrap: wrap;
}
.footer-links a {
  color: var(--muted);
  font-size: 0.84rem;
}
.footer-links a:hover {
  color: var(--text);
}

/* ═══════════════════════════════════════════════════════════════════
   FORM ELEMENTS
   ──────────────────────────────────────────────────────────────────
   Dark inputs, amber (--accent) focus ring. No Tailwind classes
   required — these are self-contained.
   ═══════════════════════════════════════════════════════════════════ */

input[type="text"],
input[type="search"],
input[type="number"],
input[type="email"],
input[type="url"],
input[type="password"],
select,
textarea {
  width: 100%;
  max-width: 100%;
  padding: 0.6em 0.85em;
  font: inherit;
  font-size: 0.9rem;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-badge);
  outline: none;
  transition: border-color 160ms ease, box-shadow 160ms ease;
}

input:focus,
select:focus,
textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent);
}

/* Placeholder text is text: --quiet, not --subtle. On the --surface
   an input actually sits on, --subtle is 3.76:1 (dark) / 4.95:1
   (light) and --quiet is 5.48:1 / 6.90:1. */
input::placeholder,
textarea::placeholder {
  color: var(--quiet);
}

select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%239a9aa8' fill='none' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.85em center;
  padding-right: 2.4em;
}

/* Dark select dropdown — limited browser support, degrades gracefully */
select option {
  background: var(--surface);
  color: var(--text);
}

/* ── Buttons ───────────────────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4em;
  padding: 0.55em 1.3em;
  font: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.4;
  border: 1px solid transparent;
  border-radius: var(--r-badge);
  cursor: pointer;
  transition: background-color 160ms ease,
              border-color     160ms ease,
              box-shadow       160ms ease,
              opacity          160ms ease;
  text-decoration: none;
}

.btn:hover { text-decoration: none; }

.btn-primary {
  background: var(--cta-fill);
  color: var(--cta-ink);
  border-color: transparent;
  box-shadow: 0 14px 30px -18px var(--cta-glow);
}
.btn-primary:hover {
  filter: brightness(1.06);
}

.btn-secondary {
  background: var(--surface);
  color: var(--text);
  border-color: var(--border-strong);
}
.btn-secondary:hover {
  background: var(--raised);
  border-color: var(--accent);
}

.btn-ghost {
  background: transparent;
  color: var(--muted);
  border-color: transparent;
}
.btn-ghost:hover {
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  color: var(--text);
}

.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
}

/* ── Filter chips / pills ──────────────────────────────────────── */

.filter-chip {
  display: inline-flex;
  align-items: center;
  padding: 0.3em 0.85em;
  font-size: 0.75rem;
  font-weight: 600;
  border: 1px solid var(--border);
  border-radius: var(--r-chip);
  color: var(--muted);
  background: transparent;
  cursor: pointer;
  transition: background-color 160ms ease,
              border-color     160ms ease,
              color            160ms ease;
}
.filter-chip:hover {
  border-color: var(--accent);
  color: var(--text);
}
.filter-chip.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--bg);
}

/* ═══════════════════════════════════════════════════════════════════
   INDICATOR CATEGORY TABS — ADR-013 §2A
   ──────────────────────────────────────────────────────────────────
   The pages' catClass() helper emits cat-demographic / cat-economic /
   cat-education / cat-environment / cat-food / cat-health /
   cat-housing / cat-transportation onto a .cat-tab inside a .cat-bar.
   These are the shared-layer rules those class names resolve against.

   ── WHICH RULE APPLIES, AND WHY NOT THE OTHER ONE ────────────────
   NOT DATAVIZ rule 1. That rule hands --tp-cat-1..8 to series 1..8 of
   a CHART, where the swatch IS the key — nothing else separates
   series 3 from series 5, so ordinal assignment buys maximum
   perceptual distance and is worth spending every hue on. A category
   tab is navigation chrome that prints its own name ("health (8)").
   The label is the key; the hue only confirms it. Spending a
   load-bearing colour here would cost meaning and buy nothing.

   ── THE COLLISION THESE RULES RESOLVE ────────────────────────────
   Three Rainbow hues are already committed platform-wide:
     --tp-cat-4 emerald = better off  (.delta-green, .win-a,
                          .zbar-better, .rank-top, map LL tier)
     --tp-cat-5 red     = worse off   (.delta-red, .zbar-worse,
                          .rank-bottom, map HL tier, error states)
     --tp-cat-6 cyan    = "county B"  (.win-b, .record-bar-b,
                          .dist-strip .ds-b, map LH tier)
   On the compare view all three render on the same screen as the tab
   strip. Assigning categories in ordinal order paints the 4th
   category emerald and the 5th red directly above a table where
   emerald means better and red means worse — ADR-013's Visual
   Principle 2 ("a colour carries ONE meaning") broken in the single
   most misleading place on the platform.

   Two hard rules, both held below:
     1. Category hue comes ONLY from the five non-verdict Rainbow
        hues — sun (1), ocean (2), ember (3), lime (7), violet (8).
        No rule in this block emits cat-4, cat-5 or cat-6.
     2. Category hue never enters the verdict CHANNEL. A verdict is
        text colour or a bar fill; a category is a tab fill or a
        border. Different colours AND different channels, so the two
        can never be read for each other.

   Five hues carry eight categories because exactly one tab is active
   at a time: these are eight mutually exclusive states of one
   control, not eight concurrent series, so two categories sharing a
   hue are never on screen together. The pairings are chosen to read
   as a family rather than as a clash:
     sun    — economic, housing     (what things cost)
     ocean  — education, health     (human services)
     lime   — environment, food     (land, and what grows on it)
     ember  — transportation        (movement)
     violet — demographic           (who)

   ── BOTH THEMES, WITHOUT A THEME BLOCK ───────────────────────────
   --tp-cat-* and --tp-on-* are theme-independent — raw brand hues
   and fixed ink/paper — so an active tab measures identically in
   light and dark: sun/ink 10.99:1, ocean/paper 5.90:1, ember/ink
   4.88:1, lime/ink 7.20:1, violet/paper 6.48:1. All clear AA-normal
   at the 0.72rem these tabs run at. This is the second reason cat-5
   is unusable here even setting the verdict clash aside: tokens.css
   measures --tp-on-red at 3.98:1 and flags it large-text/UI only.

   Colour is not the only signal in either direction: the selected
   tab is a solid fill at weight 700 against an outlined weight-500
   rest state, so the selection survives greyscale and a hue the
   reader cannot distinguish.
   ═══════════════════════════════════════════════════════════════════ */

.cat-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-bottom: 12px;
}

.cat-tab {
  display: inline-flex;
  align-items: center;
  padding: 0.3em 0.7em;
  font: inherit;
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--r-chip);
  cursor: pointer;
  white-space: nowrap;
  transition: background-color 140ms ease,
              border-color     140ms ease,
              color            140ms ease;
}

/* The hue previews on hover and focus — a border, never the label,
   so it stays out of the verdict channel even here. --cat-hue is
   unset on the "All" tab, which has no category; it falls back to
   --accent, the platform's action colour. */
.cat-tab:hover {
  color: var(--text);
  border-color: var(--cat-hue, var(--accent));
}
.cat-tab:focus-visible {
  outline: 2px solid var(--cat-hue, var(--accent));
  outline-offset: 2px;
}

.cat-tab.active {
  font-weight: 700;
  color: var(--cat-ink, var(--bg));
  background: var(--cat-hue, var(--accent));
  border-color: var(--cat-hue, var(--accent));
}

/* The mapping — the one place a category name and a hue meet. Kept
   as custom properties rather than eight background declarations so
   a page can adopt the palette on a non-tab element (a legend swatch,
   a row rule) without restating the table. */
.cat-economic,
.cat-housing        { --cat-hue: var(--tp-cat-1); --cat-ink: var(--tp-on-sun);   }
.cat-education,
.cat-health         { --cat-hue: var(--tp-cat-2); --cat-ink: var(--tp-on-ocean); }
.cat-environment,
.cat-food           { --cat-hue: var(--tp-cat-7); --cat-ink: var(--tp-on-lime);  }
.cat-transportation { --cat-hue: var(--tp-cat-3); --cat-ink: var(--tp-on-ember); }
/* Violet is the bridge hue and has no --tp-on-* helper; paper on it
   measures 6.48:1, the same footing the others sit on. */
.cat-demographic    { --cat-hue: var(--tp-cat-8); --cat-ink: var(--tp-paper);    }

/* ═══════════════════════════════════════════════════════════════════
   SKELETON LOADING
   ──────────────────────────────────────────────────────────────────
   Shimmer animation over --surface. Respects prefers-reduced-motion.
   ═══════════════════════════════════════════════════════════════════ */

.skeleton {
  background: linear-gradient(
    90deg,
    var(--surface) 25%,
    color-mix(in srgb, var(--text) 5%, var(--surface)) 50%,
    var(--surface) 75%
  );
  background-size: 200% 100%;
  animation: skeleton-shimmer 1.6s ease-in-out infinite;
  border-radius: var(--r-badge);
  color: transparent !important;
  user-select: none;
  pointer-events: none;
}

/* Inline-text skeleton — use on spans, headings, paragraphs */
.skeleton-text {
  display: inline-block;
  height: 0.85em;
  vertical-align: middle;
  border-radius: 4px;
}

/* Skeleton variants for common shapes.

   ADR-013 §3B: a loading state is atmosphere, not a spinner. The card
   and row variants keep the spectral edge and pulse warm rather than
   grey, so the page's lighting model is present before its content is.

   These re-state the fill because .skeleton's `background` SHORTHAND
   (above) resets background-image and would otherwise clobber the
   --edge layer that .card puts on the same element — index.html marks
   its placeholders `class="card skeleton skeleton-card"`, and without
   this the card silently loses its edge exactly while loading.

   The pulse is --accent, not a Rainbow hue: "the system is working" is
   a system signal, and a categorical colour here would claim a
   meaning the placeholder does not have. */
.skeleton-card {
  min-height: 120px;
  border: 1px solid transparent;
  border-radius: var(--r-card);
  background-image:
    linear-gradient(90deg,
      var(--plane-1) 24%,
      color-mix(in srgb, var(--accent) 9%, var(--plane-1)) 50%,
      var(--plane-1) 76%),
    var(--edge);
  background-origin: border-box;
  background-clip: padding-box, border-box;
  background-size: 200% 100%, auto;
  background-repeat: no-repeat, no-repeat;
  box-shadow: var(--lift);
  animation: skeleton-spectral 1.9s ease-in-out infinite;
}

/* Rows sit INSIDE a card, so they recess to --plane-sunk and take no
   lift — depth is earned, and a placeholder row has earned none. */
.skeleton-row {
  height: 44px;
  margin-bottom: 8px;
  border: 1px solid transparent;
  border-radius: var(--r-badge);
  background-image:
    linear-gradient(90deg,
      var(--plane-sunk) 24%,
      color-mix(in srgb, var(--accent) 8%, var(--plane-sunk)) 50%,
      var(--plane-sunk) 76%),
    var(--edge);
  background-origin: border-box;
  background-clip: padding-box, border-box;
  background-size: 200% 100%, auto;
  background-repeat: no-repeat, no-repeat;
  animation: skeleton-spectral 1.9s ease-in-out infinite;
}

.skeleton-circle {
  border-radius: 50%;
}

@keyframes skeleton-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* Two layers, two positions — the sweep travels, the --edge stays put.
   A single-value keyframe would drag the spectral border along with
   the shimmer and the card's warm corner would migrate. */
@keyframes skeleton-spectral {
  0%   { background-position: 200% 0, 0 0; }
  100% { background-position: -200% 0, 0 0; }
}

/* ═══════════════════════════════════════════════════════════════════
   LOADING SPINNER
   ═══════════════════════════════════════════════════════════════════ */

.loader {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(32px, 6vw, 64px) 0;
}

.loader-spinner {
  width: 32px;
  height: 32px;
  border: 3px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: loader-spin 0.7s linear infinite;
}

@keyframes loader-spin {
  to { transform: rotate(360deg); }
}

/* ═══════════════════════════════════════════════════════════════════
   ERROR + EMPTY STATES
   ═══════════════════════════════════════════════════════════════════ */

.state-error {
  padding: 1em 1.25em;
  border: 1px solid color-mix(in srgb, var(--gated) 35%, transparent);
  border-radius: var(--r-badge);
  background: color-mix(in srgb, var(--gated) 8%, transparent);
  color: var(--gated);
  font-size: 0.875rem;
}

.state-error .btn {
  margin-left: 0.75em;
}

.state-empty {
  padding: clamp(32px, 6vw, 64px) 0;
  text-align: center;
  color: var(--muted);
  font-size: 0.95rem;
}

/* ═══════════════════════════════════════════════════════════════════
   PAGE TRANSITIONS
   ──────────────────────────────────────────────────────────────────
   Minimal fade for SPA page swaps. Gated behind .js on <html> so
   JS-off visitors see content immediately with no flash.
   ═══════════════════════════════════════════════════════════════════ */

html.js .page-view {
  animation: page-enter 180ms ease;
}

@keyframes page-enter {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* ═══════════════════════════════════════════════════════════════════
   RESPONSIVE BREAKPOINTS
   ──────────────────────────────────────────────────────────────────
   Mobile-first: everything above is the base (mobile) layout.
   These are purely additive — wider viewports get more columns,
   larger type, bigger gaps.

   NOT the only breakpoints in this file. The site header carries its
   own 600px and 900px queries, kept beside the header rules rather
   than here on purpose: its layout flip is a single component's
   behaviour, and splitting it a thousand lines from the rules it
   modifies is how a header that clipped its own wordmark at every
   width from 320 to 1920 went unnoticed. Same breakpoints, stated
   where they can be read against what they change.
   ═══════════════════════════════════════════════════════════════════ */

/* ── ≥600px (tablet portrait / large phone landscape) ──────────── */
@media (min-width: 600px) {
  .card-grid {
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 280px), 1fr));
  }

  .stat-row {
    gap: clamp(24px, 4vw, 40px);
  }

  .data-table th,
  .data-table td {
    padding: 0.75em 1.15em;
  }
}

/* ── ≥900px (tablet landscape / small desktop) ─────────────────── */
@media (min-width: 900px) {
  .card-grid {
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 320px), 1fr));
  }

  /* Two-column layout utility */
  .two-col {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: clamp(20px, 3vw, 32px);
  }
}

/* ── ≥1200px (desktop) ─────────────────────────────────────────── */
@media (min-width: 1200px) {
  .card-grid {
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 340px), 1fr));
  }

  /* Three-column layout utility */
  .three-col {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: clamp(20px, 2.5vw, 32px);
  }
}

/* ═══════════════════════════════════════════════════════════════════
   REDUCED MOTION
   ──────────────────────────────────────────────────────────────────
   Kill all animation/transition durations when the visitor has
   requested reduced motion via their OS. Skeleton becomes a static
   placeholder; reveals snap to finished state; page transitions
   skip the fade.
   ═══════════════════════════════════════════════════════════════════ */

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

  html {
    scroll-behavior: auto !important;
  }

  .skeleton {
    background: var(--surface);
    animation: none;
  }

  /* ADR-013 §3B: "section atmosphere holds during loading" — the sweep
     stops, the spectral edge and the plane do not. Stated after the
     .skeleton rule above on purpose: elements carry both classes, and
     that rule's `background` shorthand would otherwise flatten these. */
  .skeleton-card,
  .skeleton-row {
    animation: none;
    background-size: auto, auto;
    background-position: 0 0, 0 0;
  }
  .skeleton-card {
    background-image: linear-gradient(var(--plane-1), var(--plane-1)), var(--edge);
  }
  .skeleton-row {
    background-image: linear-gradient(var(--plane-sunk), var(--plane-sunk)), var(--edge);
  }

  .card-hover:hover {
    transform: none;
  }

  /* Reveal effects — snap to finished state; content must never
     depend on an animation completing for a reduced-motion visitor. */
  .reveal,
  .reveal.in {
    opacity: 1 !important;
    transform: none !important;
  }

  /* Skip-link — snap, don't slide */
  .skip-link {
    transition: none !important;
  }
}

/* ═══════════════════════════════════════════════════════════════════
   PRINT STYLES
   ──────────────────────────────────────────────────────────────────
   Hide navigation, go full-width, black text on white background.
   Remove shadows, borders, and dark surface colors — optimize for
   the printed page.
   ═══════════════════════════════════════════════════════════════════ */

@media print {
  *,
  *::before,
  *::after {
    background: #fff !important;
    color: #000 !important;
    box-shadow: none !important;
    text-shadow: none !important;
  }

  body {
    font-size: 11pt;
    line-height: 1.5;
  }

  .site-header,
  .site-nav,
  footer,
  .filter-chip,
  .btn,
  .loader,
  .skeleton {
    display: none !important;
  }

  .card,
  .chart-card,
  .data-table {
    border: 1px solid #ccc !important;
    border-radius: 0 !important;
    break-inside: avoid;
    page-break-inside: avoid;
  }

  .data-table {
    width: 100% !important;
    font-size: 9pt;
  }

  .data-table th,
  .data-table td {
    padding: 4px 8px !important;
    border-bottom: 1px solid #ddd !important;
  }

  .data-table thead {
    display: table-header-group;
  }

  .wrap,
  .wrap-narrow {
    max-width: 100% !important;
    padding: 0 !important;
  }

  /* Show URLs after links */
  a[href]::after {
    content: " (" attr(href) ")";
    font-size: 0.8em;
    font-weight: normal;
  }

  /* Don't show hash-only or JS links */
  a[href^="#"]::after,
  a[href^="javascript:"]::after {
    content: "";
  }

  /* Ensure chart fallback tables are visible in print */
  .chart-table-fallback {
    position: static !important;
    width: auto !important;
    height: auto !important;
    clip: auto !important;
    clip-path: none !important;
    margin: 0 !important;
    overflow: visible !important;
    white-space: normal !important;
  }

  h1, h2, h3, h4 {
    page-break-after: avoid;
  }

  img {
    max-width: 100% !important;
    page-break-inside: avoid;
  }

  @page {
    margin: 1.5cm;
  }
}

/* ══════════════════════════════════════════════════════════════════════
   THE THEME LAYER — light
   ──────────────────────────────────────────────────────────────────────
   tokens.css re-binds canonical tokens (--bg, --text, --accent …).
   This block re-derives what this stack composes ON TOP of them — the
   elevation ladder, edges, shadows, light rig, and painted gradients.

   Gated on color-mix(). An engine without it gets the dark theme in
   light mode: a complete, measured page rather than a broken
   approximation.

   Two identical blocks by CSS necessity — a selector list cannot cross
   an @media boundary.

   ── AUDITED 2026-07-28, measured on rendered pixels ───────────────────
   casa-datos warns that the SIGN of an elevation step flips between
   themes. Checked here, in ΔL* against each theme's own ground:
     card (--plane-1)  dark +6.79  light +5.33   both RAISED — correct
     skeleton pulse    dark +7.44  light −5.63   flips, and must: a
                       pulse brightens on near-black and darkens on
                       cream. It flips on its own because --accent does.
     hero rig 1-4      dark  bloom light −1.5…−3.5  same story, correct;
                       ink over the warmest wash still reads 12.59:1.
     --edge            darker than the card on light, lighter on dark —
                       right polarity both ways (1.81:1 / 2.97:1 at the
                       warm corner). --lift is a drop shadow on light,
                       an inset-cream + drop on dark. Correct.
     band (--plane-band) dark +2.10  light −3.46   THIS ONE FLIPS and is
                       left alone deliberately: --plane-band is
                       --bg-alt, whose sign relative to --bg is
                       tokens.css's own decision, and a band is a TINT
                       not an elevation — a slightly deeper cream strip
                       is what a band looks like on paper. Text on it
                       holds (ink 12.06:1, --quiet 5.70:1). The header
                       comment on .section-band says it "lifts a section
                       off the page"; on light it insets it. The words
                       are wrong, the rendering is not.
   Known and NOT fixed: .section-band::after's 3px spectral lip washes
   out on cream exactly like .thread did (--tp-sun 1.10:1 against the
   band). It is not given the same multiply because that pseudo also
   carries a 100vmax box-shadow AND z-index: -1, and mix-blend-mode on
   a negative-z pseudo changes which stacking context it blends into —
   too much risk for a decorative 3px lip. Revisit only with a full
   z-index audit.
   ══════════════════════════════════════════════════════════════════════ */

@supports (color: color-mix(in srgb, currentColor 50%, transparent)) {
  @media (prefers-color-scheme: light) {
    :root:not([data-theme="dark"]) {
      --plane-base: var(--surface);
      --plane-band: var(--bg-alt);
      --plane-1:    var(--raised);
      --plane-2:    var(--tp-paper);
      --plane-sunk: color-mix(in srgb, var(--tp-ember) 4%, var(--bg));

      --edge-warm:        color-mix(in srgb, var(--tp-ember) 52%, var(--plane-1));
      --edge-neutral:     color-mix(in srgb, var(--tp-ink)   22%, var(--plane-1));
      --edge-cool:        color-mix(in srgb, var(--tp-ocean) 32%, var(--plane-1));
      --edge-warm-hot:    color-mix(in srgb, var(--tp-ember) 62%, var(--plane-2));
      --edge-neutral-hot: color-mix(in srgb, var(--tp-ink)   34%, var(--plane-2));
      --edge-cool-hot:    color-mix(in srgb, var(--tp-ocean) 48%, var(--plane-2));

      --band-line: color-mix(in srgb, var(--tp-ink) 20%, var(--plane-band));
      --band-atmos: color-mix(in srgb, var(--tp-peach) 18%, transparent);
      --quiet:     color-mix(in srgb, var(--tp-ink) 76%, var(--tp-paper));

      --lift:
        0 1px 2px -1px   color-mix(in srgb, var(--tp-ink) 20%, transparent),
        0 16px 30px -20px color-mix(in srgb, var(--tp-ink) 38%, transparent);
      --lift-hover:
        0 2px 5px -2px   color-mix(in srgb, var(--tp-ink) 26%, transparent),
        0 24px 44px -20px color-mix(in srgb, var(--tp-ink) 46%, transparent);

      --rig-1: color-mix(in srgb, var(--tp-orange) 7%, transparent);
      --rig-2: color-mix(in srgb, var(--tp-cyan)   6%, transparent);
      --rig-3: color-mix(in srgb, var(--tp-ocean)  7%, transparent);
      --rig-4: color-mix(in srgb, var(--tp-ember)  4%, transparent);
      --grain-op: 0.065;
      --bloom-cool: color-mix(in srgb, var(--tp-ocean) 13%, transparent);

      --brand-paint: linear-gradient(135deg, var(--tp-red-deep) 0%, var(--tp-teal-deep) 52%, var(--tp-ocean-deep) 100%);
      --title-paint: linear-gradient(120deg, var(--tp-ink) 0%, var(--tp-teal-deep) 46%, var(--tp-ocean-deep) 100%);
      --cta-fill:    linear-gradient(135deg, var(--tp-ember-deep) 0%, var(--tp-red-deep) 45%, var(--tp-ocean-deep) 100%);
      --cta-ink:     var(--tp-paper);
      --cta-glow:    color-mix(in srgb, var(--tp-ink) 34%, transparent);
    }
  }

  :root[data-theme="light"] {
    --plane-base: var(--surface);
    --plane-band: var(--bg-alt);
    --plane-1:    var(--raised);
    --plane-2:    var(--tp-paper);
    --plane-sunk: color-mix(in srgb, var(--tp-ember) 4%, var(--bg));

    --edge-warm:        color-mix(in srgb, var(--tp-ember) 52%, var(--plane-1));
    --edge-neutral:     color-mix(in srgb, var(--tp-ink)   22%, var(--plane-1));
    --edge-cool:        color-mix(in srgb, var(--tp-ocean) 32%, var(--plane-1));
    --edge-warm-hot:    color-mix(in srgb, var(--tp-ember) 62%, var(--plane-2));
    --edge-neutral-hot: color-mix(in srgb, var(--tp-ink)   34%, var(--plane-2));
    --edge-cool-hot:    color-mix(in srgb, var(--tp-ocean) 48%, var(--plane-2));

    --band-line: color-mix(in srgb, var(--tp-ink) 20%, var(--plane-band));
    --band-atmos: color-mix(in srgb, var(--tp-peach) 18%, transparent);
    --quiet:     color-mix(in srgb, var(--tp-ink) 76%, var(--tp-paper));

    --lift:
      0 1px 2px -1px   color-mix(in srgb, var(--tp-ink) 20%, transparent),
      0 16px 30px -20px color-mix(in srgb, var(--tp-ink) 38%, transparent);
    --lift-hover:
      0 2px 5px -2px   color-mix(in srgb, var(--tp-ink) 26%, transparent),
      0 24px 44px -20px color-mix(in srgb, var(--tp-ink) 46%, transparent);

    --rig-1: color-mix(in srgb, var(--tp-orange) 7%, transparent);
    --rig-2: color-mix(in srgb, var(--tp-cyan)   6%, transparent);
    --rig-3: color-mix(in srgb, var(--tp-ocean)  7%, transparent);
    --rig-4: color-mix(in srgb, var(--tp-ember)  4%, transparent);
    --grain-op: 0.065;
    --bloom-cool: color-mix(in srgb, var(--tp-ocean) 13%, transparent);

    --brand-paint: linear-gradient(135deg, var(--tp-red-deep) 0%, var(--tp-teal-deep) 52%, var(--tp-ocean-deep) 100%);
    --title-paint: linear-gradient(120deg, var(--tp-ink) 0%, var(--tp-teal-deep) 46%, var(--tp-ocean-deep) 100%);
    --cta-fill:    linear-gradient(135deg, var(--tp-ember-deep) 0%, var(--tp-red-deep) 45%, var(--tp-ocean-deep) 100%);
    --cta-ink:     var(--tp-paper);
    --cta-glow:    color-mix(in srgb, var(--tp-ink) 34%, transparent);
  }
}

/* ── THE THEME LAYER — light · thread ribbon + select chevron ────────
   ADR-013's Consequences flag the ribbon case explicitly. The ribbon is
   raw Rainbow hues laid straight onto the ground, and on cream the warm
   half all but disappears — --tp-sun over --tp-paper is a near-white on
   a near-white. Multiply is the physically right correction: it is what
   translucent ink on paper actually does, subtracting the channels the
   hue lacks (sun's blue) instead of averaging toward white. Measured on
   the light ground it roughly doubles every stop's separation
   (ember 2.71 → 3.25, red 3.33 → 3.95, teal 3.85 → 4.57). The sun end
   stays faint at 1.49 and is meant to — the sweep fades from
   transparent at both ends by design, and nothing here is load-bearing.

   INSIDE the color-mix() @supports gate, unlike the version this
   replaces. The reasoning for keeping it outside was inverted: it is
   true that an engine without color-mix() has no light ground to wash
   against, but that is exactly the problem. tokens.css gates its whole
   light palette on color-mix(), so on such an engine a reader who
   FORCES data-theme="light" gets the dark palette — and multiply
   against #0a0a0f drives every stop of the spectrum to 1.01–1.04:1
   against its own ground. The ribbon does not wash out there; it
   disappears completely. Gating it here ties the blend to the palette
   it was measured for.

   Not gated to the dot variant's pseudo-elements separately —
   mix-blend-mode on the host blends everything it renders, pseudos
   included.

   The select chevron below is the file's one unavoidable literal: an
   SVG data URI cannot read a CSS custom property, so the stroke has to
   be written out. The dark value (#9a9aa8) is --muted's dark literal
   and measures 6.77:1 on --surface; on the light --surface it drops to
   2.45:1, under the 3:1 WCAG 1.4.11 floor for a UI component, and the
   chevron is the only affordance saying "this is a dropdown". #3C4D4B
   is not an invented colour — it is what the light theme's own
   --muted (--tp-ink 80% into --tp-paper) resolves to, and it measures
   7.91:1 there.

   Two identical halves by CSS necessity — a selector list cannot cross
   an @media boundary. Keep them in step.
   ─────────────────────────────────────────────────────────────────── */
@supports (color: color-mix(in srgb, currentColor 50%, transparent)) {
  @media (prefers-color-scheme: light) {
    :root:not([data-theme="dark"]) .thread {
      mix-blend-mode: multiply;
    }
    :root:not([data-theme="dark"]) select {
      background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%233C4D4B' fill='none' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
    }
  }

  :root[data-theme="light"] .thread {
    mix-blend-mode: multiply;
  }
  :root[data-theme="light"] select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%233C4D4B' fill='none' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
  }
}

/* ═══════════════════════════════════════════════════════════════════
   COPY LINK BUTTON — ADR-012 I4 deep-link
   ──────────────────────────────────────────────────────────────────
   Shared button style for copying shareable URLs. Used by the
   PDIDeepLink utility in /static/lib/deeplink.js.
   ═══════════════════════════════════════════════════════════════════ */

.copy-link-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: var(--raised);
  border: 1px solid var(--border-strong);
  color: var(--muted);
  font-family: var(--font-sans);
  font-size: 0.75rem;
  font-weight: 600;
  padding: 5px 12px;
  border-radius: var(--r-badge);
  cursor: pointer;
  transition: background-color 150ms ease, color 150ms ease, border-color 150ms ease;
  white-space: nowrap;
}
.copy-link-btn:hover {
  background: var(--surface);
  color: var(--text);
  border-color: var(--accent);
}
.copy-link-btn svg {
  flex-shrink: 0;
}
.copy-link-btn--copied {
  background: color-mix(in srgb, var(--live) 12%, transparent) !important;
  color: var(--live) !important;
  border-color: color-mix(in srgb, var(--live) 35%, transparent) !important;
}
