/* ==============================================================
   SDO Sipalay — Dashboard Management System
   Direction: dark glassmorphism + neon edge glow.

   Surfaces are frosted panels (backdrop-blur) floating over an
   ambient orb field. Depth comes from translucency and light,
   never from heavy borders.

   Chart colors are the VALIDATED categorical palette — see
   --series-*. Do not substitute by eye: the set passed lightness
   band, chroma floor, CVD separation, normal-vision floor, and
   contrast against the --surface-0 base. Worst adjacent CVD pair
   is ΔE 8.4, which sits in the floor band, so every chart MUST
   carry secondary encoding (direct labels + surface gaps).
   ============================================================== */

:root {
  /* --- Field ------------------------------------------------ */
  --surface-0: #0a0e1a;         /* deepest — page base */
  --surface-1: #0f1524;         /* raised */
  --surface-2: #151d30;         /* sunken panel interior */

  /* Glass: alpha tints, lighter at top-left (light source) */
  --glass: rgba(255, 255, 255, 0.045);
  --glass-strong: rgba(255, 255, 255, 0.07);
  --glass-border: rgba(255, 255, 255, 0.1);
  --glass-border-lit: rgba(255, 255, 255, 0.18);
  --glass-blur: 14px;

  /* --- Ink -------------------------------------------------- */
  --ink: #f2f5fb;
  --ink-secondary: #a8b4cc;
  /* Lifted from #6c7a96: against the photo-backed field the old value
     measured 3.6:1 on card surfaces — AA-large only, and this token is
     used for body-sized labels and timestamps. #8b98b4 measures 5.4:1
     and still reads clearly as the quietest of the three inks. */
  --ink-muted: #8b98b4;

  /* --- Validated categorical palette (dark, surface #0a0e1a) -
     PASS: lightness band · chroma floor · CVD sep (8.4 worst)
           · normal-vision floor (17.6) · contrast ≥ 3:1        */
  --series-1: #00a5c4;   /* cyan    */
  --series-2: #8b6ff0;   /* violet  */
  --series-3: #00a86b;   /* green   */
  --series-4: #c78400;   /* amber   */
  --series-5: #e0567a;   /* rose    */
  --series-6: #3d8fd1;   /* steel blue — added for Provident Loan */
  /* terracotta — added for EduTrack. Validated against the other six, not
     picked by eye: L* 55.5 (in band), 5.21:1 on --surface-0, worst normal-
     vision ΔE 28.2, worst CVD ΔE 40.2 deuteranope / 57.1 protanope. */
  --series-7: #c2703f;

  /* Bright variants — glow/accent only, never chart fills.
     These sit above the lightness band on purpose: they are
     light sources, not data marks. */
  --glow-1: #22d3ee;
  --glow-2: #a78bfa;
  --glow-3: #34d399;
  --glow-4: #fbbf24;
  --glow-5: #fb7185;
  --glow-6: #60a5fa;
  --glow-7: #fb923c;

  /* --- Status (reserved — never reused as a series) --------- */
  --ok: #00a86b;
  --ok-bright: #34d399;
  --warn: #c78400;
  --warn-bright: #fbbf24;
  --bad: #e0567a;
  --bad-bright: #fb7185;
  --idle: #6c7a96;

  /* --- Type ------------------------------------------------- */
  --step--2: clamp(0.66rem, 0.64rem + 0.08vw, 0.7rem);
  --step--1: clamp(0.74rem, 0.72rem + 0.1vw, 0.79rem);
  --step-0: clamp(0.86rem, 0.84rem + 0.12vw, 0.92rem);
  --step-1: clamp(1.02rem, 0.97rem + 0.24vw, 1.16rem);
  --step-2: clamp(1.3rem, 1.15rem + 0.6vw, 1.7rem);
  --step-3: clamp(2rem, 1.5rem + 2vw, 3.4rem);
  --step-4: clamp(2.6rem, 1.8rem + 3.4vw, 4.6rem);

  --radius: 12px;
  --radius-lg: 18px;
  --radius-xl: 24px;

  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --dur: 260ms;
  --dur-slow: 520ms;

  --mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;
  --sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;

  color-scheme: dark;
}

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

/* Base colour on <html> and a transparent <body>: the fixed cockpit layer
   below is a body child, so an opaque body background would paint over it. */
html {
  -webkit-text-size-adjust: 100%;
  background: var(--surface-0);
}

body {
  margin: 0;
  min-height: 100vh;
  font-family: var(--sans);
  font-size: var(--step-0);
  line-height: 1.55;
  color: var(--ink);
  background: transparent;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

/* --- Page field ----------------------------------------------
   The flight-deck photo, fixed to the viewport so content scrolls
   "through" the cockpit rather than dragging the aircraft with it.

   The asset is the ORIGINAL artwork, unmodified apart from a resize and
   JPEG encode for weight. Earlier revisions covered the five painted
   instrument screens to stop their invented readouts ("92%", "88%")
   sitting beside live division figures — but that treatment damaged the
   picture, so it was reverted at the user's direction. The scrim below
   is what keeps text legible over it now.

   Centred (50% 50%). The photo is 3:2 while most viewports are wider, so
   `cover` crops top and bottom; centring keeps the instrument panel and
   windscreen in frame instead of pushing the deck to the bottom edge. */
.orbs {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  background:
    url("/img/cockpit.jpg?v=660b7f8c") 50% 50% / cover no-repeat,
    var(--surface-0);
}

/* Legibility scrim. Measured: unscrimmed, light text over the sunset
   in the windscreen bottoms out at 1.0:1 — text would vanish. This
   layer is weighted toward the top (where the bright sky sits) and
   eases off over the darker instrument panel, so the photo keeps its
   depth where it is safe to. With it, the worst point on the whole
   field measures 6.2:1 — above the 4.5:1 AA floor for body text. */
.orbs::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(
      180deg,
      rgba(10, 14, 26, 0.88) 0%,
      rgba(10, 14, 26, 0.72) 26%,
      rgba(10, 14, 26, 0.62) 52%,
      rgba(10, 14, 26, 0.78) 100%
    );
  pointer-events: none;
}

.orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(90px);
  /* Dialled back hard now that the photo carries the depth. At full
     strength these `screen`-blended orbs lifted the blacks in the panel
     and greyed out the cockpit; at 0.16 they only tint the corners. */
  opacity: 0.16;
  will-change: transform;
  mix-blend-mode: screen;
}

.orb--1 {
  width: 48vw;
  height: 48vw;
  top: -14vw;
  left: -10vw;
  background: radial-gradient(circle, #1e6fd9, transparent 68%);
  animation: drift-a 34s var(--ease) infinite alternate;
}

.orb--2 {
  width: 42vw;
  height: 42vw;
  top: 8vw;
  right: -12vw;
  background: radial-gradient(circle, #7c3aed, transparent 68%);
  animation: drift-b 41s var(--ease) infinite alternate;
}

.orb--3 {
  width: 38vw;
  height: 38vw;
  bottom: -14vw;
  left: 28vw;
  background: radial-gradient(circle, #0891b2, transparent 68%);
  animation: drift-c 47s var(--ease) infinite alternate;
}

@keyframes drift-a {
  to { transform: translate3d(8vw, 6vw, 0) scale(1.12); }
}
@keyframes drift-b {
  to { transform: translate3d(-7vw, 9vw, 0) scale(0.9); }
}
@keyframes drift-c {
  to { transform: translate3d(5vw, -7vw, 0) scale(1.1); }
}

/* --- Glass primitive -----------------------------------------
   Reworked from near-transparent film into an instrument screen.

   The old 0.045 alpha let the photo through almost unchanged, which
   measured 1.0:1 for text over the sunset. These panels instead use a
   near-opaque navy matching the screens in the photo (#131a2b): text
   on them measures 12.7:1 regardless of what is behind, so the cockpit
   can stay bright without ever threatening readability.

   The cyan rim and outer glow are what sell "mounted in the panel" —
   real avionics displays spill light onto the bezel around them. */
.glass {
  position: relative;
  background:
    linear-gradient(
      165deg,
      rgba(24, 33, 54, 0.95),
      rgba(14, 20, 35, 0.97)
    );
  backdrop-filter: blur(var(--glass-blur)) saturate(140%);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(140%);
  border: 1px solid rgba(120, 190, 235, 0.22);
  box-shadow:
    0 10px 34px rgba(0, 0, 0, 0.55),
    0 0 22px rgba(56, 189, 248, 0.09),
    inset 0 0 26px rgba(56, 189, 248, 0.045);
}

/* Top edge catches light, like a real glass bevel. */
.glass::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding-top: 1px;
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.22),
    transparent 42%
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
}

/* Fallback for browsers without backdrop-filter. */
@supports not (backdrop-filter: blur(1px)) {
  .glass { background: rgba(15, 21, 36, 0.92); }
}

h1, h2, h3 { margin: 0; font-weight: 600; letter-spacing: -0.02em; }
p { margin: 0; }

/* ================================================== app shell */

/* Content sits above the fixed cockpit layer. Both containers are given an
   explicit positive layer so the order never depends on source position. */
.shell {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-rows: auto 1fr;
  min-height: 100vh;
}

.topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 1.25rem;
  padding: 0.8rem clamp(1rem, 3vw, 2.25rem);
  /* Reads as the glare shield above the panel. Raised from 0.6 to 0.93
     alpha because the secondary text in here (.brand__sub, .who__role,
     .topnav) measured 2.1:1 against the bright windscreen at 0.6 — the
     bar has to carry its own contrast rather than borrow the photo's. */
  background: linear-gradient(
    180deg,
    rgba(8, 12, 22, 0.95),
    rgba(12, 17, 30, 0.93)
  );
  backdrop-filter: blur(18px) saturate(150%);
  -webkit-backdrop-filter: blur(18px) saturate(150%);
  border-bottom: 1px solid rgba(120, 190, 235, 0.18);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.42);
}

.brand {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-width: 0;
}

/* Division seal. The artwork is circular with its own strong colours, so it
   carries no tinted plate behind it — a gradient would fight the seal. A soft
   halo keeps it legible against the cockpit photo. */
.brand__mark {
  position: relative;
  display: block;
  width: 38px;
  height: 38px;
  flex-shrink: 0;
  object-fit: contain;
  border-radius: 50%;
  filter: drop-shadow(0 0 10px rgba(34, 211, 238, 0.35));
}

.brand__text { min-width: 0; }

.brand__title {
  font-size: var(--step-0);
  font-weight: 650;
  letter-spacing: -0.02em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.brand__sub {
  font-size: var(--step--1);
  color: var(--ink-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.topbar__spacer { flex: 1; }

.topnav { display: flex; gap: 0.25rem; }

.topnav a {
  position: relative;
  padding: 0.45rem 0.85rem;
  border-radius: 9px;
  font-size: var(--step--1);
  font-weight: 550;
  color: var(--ink-secondary);
  text-decoration: none;
  transition: color var(--dur) var(--ease), background var(--dur) var(--ease);
}

.topnav a:hover { color: var(--ink); background: var(--glass); }

.topnav a[aria-current="page"] {
  color: var(--ink);
  background: var(--glass-strong);
  box-shadow: inset 0 0 0 1px var(--glass-border);
}

.who {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding-left: 1rem;
  border-left: 1px solid var(--glass-border);
}

.who__name { font-size: var(--step--1); font-weight: 560; line-height: 1.2; }

.who__role {
  font-size: var(--step--2);
  color: var(--ink-muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* ==================================================== buttons */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.45rem;
  padding: 0.52rem 0.95rem;
  border: 1px solid var(--glass-border);
  border-radius: 10px;
  background: var(--glass);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: var(--ink);
  font: inherit;
  font-size: var(--step--1);
  font-weight: 560;
  cursor: pointer;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
}

.btn:hover:not(:disabled) {
  border-color: var(--glass-border-lit);
  background: var(--glass-strong);
  box-shadow: 0 0 18px rgba(34, 211, 238, 0.18);
}

.btn:active:not(:disabled) { transform: translateY(1px); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }

.btn--primary {
  background: linear-gradient(135deg, var(--glow-1), var(--glow-2));
  border-color: transparent;
  color: #05070d;
  font-weight: 680;
  box-shadow: 0 0 22px rgba(34, 211, 238, 0.35);
}

.btn--primary:hover:not(:disabled) {
  filter: brightness(1.1);
  box-shadow: 0 0 32px rgba(34, 211, 238, 0.5);
}

.btn--danger { color: var(--bad-bright); border-color: rgba(224, 86, 122, 0.35); }
.btn--danger:hover:not(:disabled) {
  background: rgba(224, 86, 122, 0.14);
  box-shadow: 0 0 18px rgba(224, 86, 122, 0.22);
}

.btn--sm { padding: 0.32rem 0.62rem; font-size: var(--step--2); }

:where(a, button, input, select, textarea):focus-visible {
  outline: 2px solid var(--glow-1);
  outline-offset: 2px;
}

/* ======================================================= main */

/* The cap scales with the viewport instead of stopping dead at 1620px, which
   left 470px of dead margin each side on a 2560px monitor and 910px on an
   ultrawide. `min(96vw, 2100px)` lets a large display actually use its width
   — one more card column fits — while 2100px still bounds line length so text
   never runs uncomfortably wide. */
.main {
  padding: clamp(1.25rem, 3vw, 2.25rem);
  max-width: min(96vw, 2100px);
  width: 100%;
  margin: 0 auto;
}

/* Sits on the open cockpit field rather than in a card, so it carries a
   soft radial plate of its own — enough to lift the muted meta text off
   the windscreen without drawing a hard box around the heading. */
.page-head {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 1rem;
  margin-bottom: 1.5rem;
}

/* Deliberately NOT z-index:-1 — a negative child paints behind its
   parent's background and, if an ancestor ever gains one, disappears.
   Instead the plate stays at layer 0 and the real content is lifted to
   1, which cannot break no matter what ancestors do. */
/* No plate behind the header — the heading sits directly on the cockpit.
   The plate existed because the sunset used to sit behind this row and put
   the muted timestamp at 1.9:1. Since the panel was reframed, the backdrop
   here is the dark instrument surface: measured on the shipped asset the
   title reads 8.8:1 and the meta line 4.6:1 with nothing behind them, so the
   plate was covering a problem that no longer exists.

   The meta line keeps its own chip (see .page-head__meta) because muted ink
   alone still measures 3.3:1 here — below the 4.5:1 floor. */

.page-head > * {
  position: relative;
  z-index: 1;
}

/* Now that no plate sits behind it, the title carries its own separation.
   `filter: drop-shadow` rather than `text-shadow`: the glyphs are painted by
   a clipped background with a transparent fill, so a text-shadow would draw
   behind the box and smear rather than outline the letterforms. */
.page-head__title {
  font-size: var(--step-2);
  letter-spacing: -0.035em;
  background: linear-gradient(120deg, var(--ink), var(--glow-1) 140%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  /* Stacked drop-shadows, tight to loose: the inner pair darkens the pixels
     against the glyph edges (which is what carries contrast over the lit
     screens in the photo), the outer one gives depth. */
  filter:
    drop-shadow(0 0 3px rgba(4, 8, 16, 0.95))
    drop-shadow(0 2px 8px rgba(4, 8, 16, 0.85));
}

/* Sits bare on the cockpit — no chip, no plate — per the request to leave the
   header transparent.

   With the original photo restored, the five lit instrument screens sit right
   behind this row and the worst backdrop measures 3.35:1 for this text. No
   text colour fixes that: even pure white only reaches 5.5:1 and would look
   harsh. The halo below does the work instead — a tight, opaque dark ring that
   darkens the few pixels immediately around each glyph, so the effective local
   contrast is against near-black rather than against a lit display. It reads
   as a glow, not a box, which is what keeps the header transparent. */
.page-head__meta {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.45rem;
  font-size: var(--step--1);
  color: #eef2f9;
  font-variant-numeric: tabular-nums;
  text-shadow:
    0 0 2px rgba(4, 8, 16, 0.98),
    0 0 4px rgba(4, 8, 16, 0.95),
    0 1px 3px rgba(4, 8, 16, 0.92),
    0 0 12px rgba(4, 8, 16, 0.85);
}

.page-head__actions { margin-left: auto; display: flex; gap: 0.5rem; }

/* Live pulse dot next to the timestamp. */
.pulse {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ok-bright);
  box-shadow: 0 0 0 0 rgba(52, 211, 153, 0.6);
  animation: pulse 2.4s ease-out infinite;
}

@keyframes pulse {
  70% { box-shadow: 0 0 0 8px rgba(52, 211, 153, 0); }
  100% { box-shadow: 0 0 0 0 rgba(52, 211, 153, 0); }
}

/* ============================================== hero KPI strip */

.hero {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  gap: 1rem;
  margin-bottom: 1.5rem;
}

/* `container-type: inline-size` makes this tile the reference for the cqw
   units on .kpi__value below, so the figure scales with the TILE rather than
   the viewport. Those two move in opposite directions: as the window widens
   the grid adds columns, so each tile gets narrower while a vw-based font
   size gets larger. That is why "₱6.04M" was clipped on every desktop width
   and only fitted on phones. */
.kpi {
  position: relative;
  container-type: inline-size;
  padding: 1.15rem 1.25rem 1.2rem;
  border-radius: var(--radius-lg);
  overflow: hidden;
  animation: rise var(--dur-slow) var(--ease) backwards;
  /* Projection cue: a cool inner rim plus an outward bloom, so the tile
     reads as light emitted by the flight-deck screen beneath it rather than
     as a card floating over a photo. */
  box-shadow:
    inset 0 1px 0 rgba(160, 210, 255, 0.14),
    0 0 30px -6px rgba(34, 211, 238, 0.16),
    0 10px 34px rgba(0, 0, 0, 0.42);
}

/* Accent rail — identity for the tile, lit by its own hue. */
.kpi::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 2px;
  background: var(--kpi-accent, var(--glow-1));
  box-shadow: 0 0 14px var(--kpi-accent, var(--glow-1));
}

.kpi__label {
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: 0.11em;
  color: var(--ink-muted);
  margin-bottom: 0.45rem;
}

/* Sized from the tile (cqw), not the viewport.
   19cqw fits the longest value the dashboard produces — "₱6.04M", about
   4.07em with this tracking — inside the tile's inner width at every column
   count, with the clamp holding it readable in a very narrow tile and stopping
   it growing absurd in a very wide one. */
.kpi__value {
  font-size: clamp(1.75rem, 19cqw, 4.6rem);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.045em;
  font-variant-numeric: tabular-nums;
  color: var(--kpi-accent, var(--ink));
  text-shadow: 0 0 26px color-mix(in oklab, var(--kpi-accent, var(--glow-1)) 45%, transparent);
}

/* Last-resort guard: if a future value is longer than anything measured here,
   it shrinks to fit rather than being clipped by the tile's overflow:hidden. */
@supports (text-wrap: balance) {
  .kpi__value {
    min-width: 0;
    overflow-wrap: anywhere;
  }
}

.kpi__sub {
  margin-top: 0.4rem;
  font-size: var(--step--1);
  color: var(--ink-secondary);
}

/* A system that is unreachable or unconfigured still gets a tile, so the
   strip keeps its shape — but it must not read as a real figure. */
.kpi--muted { opacity: 0.55; }
.kpi--muted .kpi__value { color: var(--ink-muted); text-shadow: none; }

@keyframes rise {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: none; }
}

/* ============================================== section titles */

.section-title {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin: 1.9rem 0 0.9rem;
  font-size: var(--step--1);
  font-weight: 660;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink);
  /* Sits directly on the cockpit field with no plate behind it. A tight
     dark halo keeps the strokes readable if the label lands over a
     bright part of the windscreen; --ink-secondary measured 3.1:1 there,
     so the colour is raised to full ink as well. */
  text-shadow:
    0 0 2px rgba(4, 8, 16, 0.98),
    0 0 4px rgba(4, 8, 16, 0.95),
    0 1px 3px rgba(4, 8, 16, 0.92),
    0 0 12px rgba(4, 8, 16, 0.8);
}

/* Hairline rule fills the remaining width — a cockpit panel divider. */
.section-title::after {
  content: "";
  flex: 1;
  height: 1px;
  min-width: 2rem;
  background: linear-gradient(90deg, var(--glass-border), transparent);
}

.section-title__note {
  font-size: var(--step--2);
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: none;
  color: var(--ink-muted);
}

/* ================================== external DepEd link tiles */

.links {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(232px, 100%), 1fr));
  gap: 0.9rem;
}

.link-tile {
  position: relative;
  display: grid;
  gap: 0.2rem;
  padding: 0.95rem 1.05rem 1rem;
  border-radius: var(--radius);
  text-decoration: none;
  color: inherit;
  overflow: hidden;
  animation: rise var(--dur-slow) var(--ease) backwards;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
    transform var(--dur) var(--ease);
}

.link-tile::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 2px;
  background: var(--tile-accent, var(--glow-1));
  opacity: 0.75;
}

.link-tile:hover {
  transform: translateY(-2px);
  border-color: var(--glass-border-lit);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4),
    0 0 22px color-mix(in oklab, var(--tile-accent, var(--glow-1)) 26%, transparent);
}

.link-tile__head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.link-tile__name {
  font-size: var(--step-1);
  font-weight: 680;
  letter-spacing: -0.02em;
  color: var(--tile-accent, var(--ink));
}

.link-tile__arrow {
  margin-left: auto;
  font-size: var(--step--1);
  color: var(--ink-muted);
  transition: transform var(--dur) var(--ease), color var(--dur) var(--ease);
}

.link-tile:hover .link-tile__arrow {
  transform: translate(2px, -2px);
  color: var(--tile-accent, var(--ink));
}

.link-tile__full {
  font-size: var(--step--1);
  color: var(--ink-secondary);
  line-height: 1.35;
}

.link-tile__foot {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-top: 0.15rem;
}

.link-tile__host {
  font-family: var(--mono);
  font-size: var(--step--2);
  color: var(--ink-muted);
}

/* Advisory for a host that is not publicly reachable. */
.link-tile__note {
  padding: 0.08rem 0.4rem;
  border-radius: 999px;
  border: 1px solid rgba(251, 191, 36, 0.3);
  background: rgba(199, 132, 0, 0.14);
  font-size: var(--step--2);
  color: var(--warn-bright);
  white-space: nowrap;
}

/* ================================================ system grid */

/* Projection glow.

   The source photo's painted screens sat at x 14.6%–85.3%, y 42%–67%. Those
   are covered now, and the real cards take their place — this is the light
   those cards throw onto the panel around them, which is what makes them read
   as mounted displays rather than rectangles floating over a photograph.

   Anchored to the grid itself rather than to fixed viewport coordinates: the
   band's on-screen position shifts with viewport aspect under `cover`, so a
   hard-coded offset would drift. Following the content keeps the effect
   aligned at every size. */
.grid,
.hero {
  position: relative;
}

.grid::before,
.hero::before {
  content: "";
  position: absolute;
  inset: -2.5rem -3rem;
  background: radial-gradient(
    58% 62% at 50% 45%,
    rgba(56, 189, 248, 0.11) 0%,
    rgba(56, 189, 248, 0.05) 42%,
    transparent 74%
  );
  pointer-events: none;
  /* Layer 0 with children lifted to 1 — never a negative z-index, which
     would paint behind an ancestor background and vanish. */
  z-index: 0;
}

.grid > *,
.hero > * {
  position: relative;
  z-index: 1;
}

/* `stretch` (not `start`) makes every card in a row share the tallest
   height, so the grid reads as a row of equals rather than a ragged stack. */
/* `min(370px, 100%)` rather than a bare 370px: a fixed track floor cannot
   shrink below its minimum, so on a 320–390px phone the cards overflowed the
   viewport by up to 90px and forced a horizontal scroll. Capping the floor at
   the container width lets the track collapse to a single full-width column
   on small screens while keeping the 370px preference everywhere else. */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(370px, 100%), 1fr));
  gap: 1.15rem;
  align-items: stretch;
}

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-lg);
  overflow: hidden;
  animation: rise var(--dur-slow) var(--ease) backwards;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
    transform var(--dur) var(--ease);
}

/* The last chart absorbs leftover height so footers align across a row. */
.card > .viz:last-of-type { flex: 1; }

.card:hover {
  border-color: var(--glass-border-lit);
  transform: translateY(-2px);
  box-shadow: 0 14px 44px rgba(0, 0, 0, 0.45),
    0 0 26px color-mix(in oklab, var(--accent) 22%, transparent);
}

/* Per-system accent, drawn from the validated palette. */
.card[data-accent="violet"] { --accent: var(--series-2); --accent-lit: var(--glow-2); }
.card[data-accent="amber"] { --accent: var(--series-4); --accent-lit: var(--glow-4); }
.card[data-accent="sky"] { --accent: var(--series-1); --accent-lit: var(--glow-1); }
.card[data-accent="emerald"] { --accent: var(--series-3); --accent-lit: var(--glow-3); }
.card[data-accent="rose"] { --accent: var(--series-5); --accent-lit: var(--glow-5); }
.card[data-accent="cyan"] { --accent: var(--series-6); --accent-lit: var(--glow-6); }
.card[data-accent="terracotta"] { --accent: var(--series-7); --accent-lit: var(--glow-7); }

/* Glow bar across the card's top edge. */
.card::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  background: linear-gradient(
    90deg,
    transparent,
    var(--accent-lit, var(--glow-1)),
    transparent
  );
  opacity: 0.85;
}

.card__head {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 1.1rem 1.25rem 0.9rem;
}

.card__titles { min-width: 0; flex: 1; }

.card__name {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  font-size: var(--step-1);
  font-weight: 660;
  letter-spacing: -0.025em;
}

.card__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--accent-lit, var(--glow-1));
  box-shadow: 0 0 12px var(--accent-lit, var(--glow-1));
}

.card__full {
  font-size: var(--step--1);
  color: var(--ink-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.22rem 0.55rem;
  border-radius: 999px;
  font-size: var(--step--2);
  font-weight: 640;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  white-space: nowrap;
  border: 1px solid transparent;
}

.badge--ok {
  background: rgba(0, 168, 107, 0.14);
  border-color: rgba(52, 211, 153, 0.3);
  color: var(--ok-bright);
}

.badge--error {
  background: rgba(224, 86, 122, 0.14);
  border-color: rgba(251, 113, 133, 0.3);
  color: var(--bad-bright);
}

.badge--unconfigured {
  background: rgba(255, 255, 255, 0.05);
  border-color: var(--glass-border);
  color: var(--ink-muted);
}

/* --- metrics ------------------------------------------------- */

.metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(88px, 100%), 1fr));
  gap: 0.9rem 1rem;
  padding: 0.25rem 1.25rem 1.1rem;
}

.metric__label {
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-muted);
  margin-bottom: 0.2rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.metric__value {
  font-size: var(--step-2);
  font-weight: 680;
  line-height: 1.05;
  letter-spacing: -0.04em;
  font-variant-numeric: tabular-nums;
}

.metric--primary { grid-column: span 2; }

.metric--primary .metric__value {
  font-size: var(--step-3);
  color: var(--accent-lit, var(--glow-1));
  text-shadow: 0 0 24px color-mix(in oklab, var(--accent-lit, var(--glow-1)) 42%, transparent);
}

.metric--good .metric__value { color: var(--ok-bright); }
.metric--warn .metric__value { color: var(--warn-bright); }

/* --- charts --------------------------------------------------
   Marks follow the fixed specs: ≤24px bars with 4px rounded
   data-ends, 2px surface gaps between segments, hairline axes,
   and direct labels (required — the palette's worst adjacent CVD
   pair sits in the 6–8 floor band). */

.viz { padding: 0 1.25rem 1.15rem; }

.viz__title {
  font-size: var(--step--2);
  font-weight: 640;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink-muted);
  margin-bottom: 0.7rem;
}

/* Part-to-whole: horizontal stacked bar. */
.stack {
  display: flex;
  width: 100%;
  height: 22px;
  border-radius: 5px;
  overflow: hidden;
  /* The 2px surface-color gap between segments is the separator. */
  gap: 2px;
  background: transparent;
}

.stack__seg {
  position: relative;
  min-width: 3px;
  border-radius: 3px;
  transition: filter var(--dur) var(--ease);
}

.stack__seg:first-child { border-radius: 5px 3px 3px 5px; }
.stack__seg:last-child { border-radius: 3px 5px 5px 3px; }
.stack__seg:hover { filter: brightness(1.25); }

/* Legend — always present for ≥2 series; identity never color-alone. */
.legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 0.9rem;
  margin-top: 0.7rem;
}

.legend__item {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: var(--step--1);
  color: var(--ink-secondary);
}

.legend__swatch {
  width: 9px;
  height: 9px;
  border-radius: 3px;
  flex-shrink: 0;
}

.legend__value {
  font-weight: 640;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* Ranked horizontal bars. */
.bars { display: grid; gap: 0.55rem; }

.bar-row {
  display: grid;
  /* `min(88px, 30%)` as the floor rather than a flat 88px: inside a narrow
     card the fixed floor could not yield, so the label column kept its width
     while the bar and value were squeezed into what was left. Letting the
     floor fall back to a share of the card keeps all three parts legible. */
  grid-template-columns: minmax(min(88px, 30%), 34%) 1fr auto;
  align-items: center;
  gap: 0.7rem;
  font-size: var(--step--1);
}

.bar-row__label {
  color: var(--ink-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.bar-row__track {
  position: relative;
  height: 9px;
  border-radius: 5px;
  background: rgba(255, 255, 255, 0.055);
  overflow: hidden;
}

.bar-row__fill {
  height: 100%;
  /* Square at the baseline, 4px rounded at the data-end. */
  border-radius: 0 4px 4px 0;
  background: var(--bar-color, var(--series-1));
  box-shadow: 0 0 12px color-mix(in oklab, var(--bar-color, var(--series-1)) 55%, transparent);
  transform-origin: left center;
  animation: grow var(--dur-slow) var(--ease) backwards;
}

@keyframes grow {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

.bar-row__value {
  font-weight: 640;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  min-width: 3ch;
  text-align: right;
}

/* Donut for a small part-to-whole set. */
.donut-wrap {
  display: flex;
  align-items: center;
  gap: 1.15rem;
  flex-wrap: wrap;
}

.donut { position: relative; flex-shrink: 0; }
.donut svg { display: block; transform: rotate(-90deg); }

.donut__center {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  text-align: center;
  pointer-events: none;
}

.donut__total {
  font-size: var(--step-1);
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.donut__caption {
  font-size: var(--step--2);
  color: var(--ink-muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.donut__seg {
  transition: opacity var(--dur) var(--ease);
  cursor: default;
}

.donut:hover .donut__seg { opacity: 0.45; }
.donut .donut__seg:hover { opacity: 1; }

/* Empty state — a system that is up but holds no records yet. Stated
   plainly, because a grid of zeros reads as a fault. */
.empty-state {
  display: grid;
  gap: 0.3rem;
  padding: 1.4rem 1rem;
  text-align: center;
  border: 1px dashed var(--glass-border);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.018);
}

.empty-state__title { font-size: var(--step--1); color: var(--ink-secondary); font-weight: 560; }
.empty-state__hint { font-size: var(--step--2); color: var(--ink-muted); }

.card__note {
  padding: 0.25rem 1.25rem 1.15rem;
  font-size: var(--step--1);
  color: var(--ink-secondary);
}

.card__foot {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: auto; /* pin to the bottom of the stretched card */
  padding: 0.6rem 1.25rem;
  border-top: 1px solid var(--glass-border);
  background: rgba(255, 255, 255, 0.02);
  font-size: var(--step--2);
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}

.card__foot a {
  margin-left: auto;
  color: var(--ink-secondary);
  text-decoration: none;
  font-weight: 560;
  transition: color var(--dur) var(--ease);
}

.card__foot a:hover { color: var(--accent-lit, var(--glow-1)); }

/* --- skeleton ------------------------------------------------ */

.skeleton {
  height: 260px;
  border-radius: var(--radius-lg);
  border: 1px solid var(--glass-border);
  background: linear-gradient(
    100deg,
    rgba(255, 255, 255, 0.03) 30%,
    rgba(255, 255, 255, 0.075) 50%,
    rgba(255, 255, 255, 0.03) 70%
  );
  background-size: 240% 100%;
  animation: shimmer 1.4s linear infinite;
}

@keyframes shimmer {
  from { background-position: 200% 0; }
  to { background-position: -40% 0; }
}

/* ======================================================= auth */

.auth {
  position: relative;
  z-index: 1;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 1.5rem;
}

.auth__card {
  width: 100%;
  max-width: 410px;
  padding: clamp(1.6rem, 4vw, 2.4rem);
  border-radius: var(--radius-xl);
  animation: rise var(--dur-slow) var(--ease) backwards;
}

.auth__mark {
  display: block;
  width: 56px;
  height: 56px;
  margin-bottom: 1.2rem;
  object-fit: contain;
  border-radius: 50%;
  filter: drop-shadow(0 0 14px rgba(34, 211, 238, 0.35));
}

.auth__title { font-size: var(--step-2); letter-spacing: -0.035em; }

.auth__sub {
  margin-top: 0.35rem;
  margin-bottom: 1.6rem;
  color: var(--ink-muted);
  font-size: var(--step--1);
}

.field { margin-bottom: 1rem; }

.field label {
  display: block;
  margin-bottom: 0.4rem;
  font-size: var(--step--2);
  font-weight: 620;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-secondary);
}

.input,
select.input {
  width: 100%;
  padding: 0.65rem 0.8rem;
  border: 1px solid var(--glass-border);
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.25);
  color: var(--ink);
  font: inherit;
  font-size: var(--step-0);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

.input:focus {
  border-color: var(--glow-1);
  outline: none;
  box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.16);
}

.input::placeholder { color: var(--ink-muted); }

.alert {
  padding: 0.65rem 0.85rem;
  margin-bottom: 1rem;
  border-radius: 10px;
  font-size: var(--step--1);
  border: 1px solid transparent;
}

.alert--error {
  background: rgba(224, 86, 122, 0.13);
  border-color: rgba(251, 113, 133, 0.32);
  color: #ffc2ce;
}

.alert--ok {
  background: rgba(0, 168, 107, 0.13);
  border-color: rgba(52, 211, 153, 0.32);
  color: #a9f5d4;
}

.alert[hidden] { display: none; }

/* ====================================================== table */

.panel { border-radius: var(--radius-lg); overflow: hidden; }

.panel__head {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.95rem 1.25rem;
  border-bottom: 1px solid var(--glass-border);
}

.panel__title { font-size: var(--step-1); font-weight: 640; letter-spacing: -0.025em; }

.table-wrap { overflow-x: auto; }

table { width: 100%; border-collapse: collapse; font-size: var(--step--1); }

th {
  padding: 0.62rem 1rem;
  text-align: left;
  font-size: var(--step--2);
  font-weight: 640;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--ink-muted);
  background: rgba(0, 0, 0, 0.22);
  border-bottom: 1px solid var(--glass-border);
  white-space: nowrap;
}

td {
  padding: 0.68rem 1rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.055);
  vertical-align: middle;
}

tbody tr:last-child td { border-bottom: 0; }
tbody tr { transition: background var(--dur) var(--ease); }
tbody tr:hover { background: rgba(255, 255, 255, 0.035); }

.pill {
  display: inline-block;
  padding: 0.16rem 0.5rem;
  border-radius: 999px;
  font-size: var(--step--2);
  font-weight: 640;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  border: 1px solid transparent;
}

.pill--admin {
  background: rgba(139, 111, 240, 0.16);
  border-color: rgba(167, 139, 250, 0.34);
  color: var(--glow-2);
}

.pill--viewer {
  background: rgba(255, 255, 255, 0.05);
  border-color: var(--glass-border);
  color: var(--ink-secondary);
}

.pill--active {
  background: rgba(0, 168, 107, 0.15);
  border-color: rgba(52, 211, 153, 0.32);
  color: var(--ok-bright);
}

.pill--inactive {
  background: rgba(224, 86, 122, 0.14);
  border-color: rgba(251, 113, 133, 0.3);
  color: var(--bad-bright);
}

.mono { font-family: var(--mono); font-size: 0.84em; }
.muted { color: var(--ink-muted); }
.empty { padding: 2.6rem 1rem; text-align: center; color: var(--ink-muted); }

/* ====================================================== modal */

.modal[hidden] { display: none; }

.modal {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  padding: 1.5rem;
  background: rgba(5, 7, 13, 0.68);
  backdrop-filter: blur(5px);
  -webkit-backdrop-filter: blur(5px);
}

.modal__box {
  width: 100%;
  max-width: 470px;
  max-height: 88vh;
  overflow-y: auto;
  padding: 1.6rem;
  border-radius: var(--radius-lg);
  animation: rise 220ms var(--ease);
}

.modal__title { font-size: var(--step-1); margin-bottom: 1.2rem; }

.checks { display: grid; gap: 0.4rem; }

.check {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.45rem 0.65rem;
  border: 1px solid var(--glass-border);
  border-radius: 9px;
  font-size: var(--step--1);
  cursor: pointer;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}

.check:hover { border-color: var(--glass-border-lit); background: var(--glass); }
.check input { accent-color: var(--glow-1); }

.modal__actions {
  display: flex;
  gap: 0.6rem;
  justify-content: flex-end;
  margin-top: 1.5rem;
}

/* ================================================= responsive */

@media (max-width: 760px) {
  .topbar { flex-wrap: wrap; gap: 0.7rem; }
  .topbar__spacer { display: none; }
  .topnav { order: 3; width: 100%; }
  .who { margin-left: auto; }
  .orb { filter: blur(70px); opacity: 0.4; }
  .bar-row { grid-template-columns: minmax(min(76px, 34%), 42%) 1fr auto; }
}

/* Reclaim horizontal space inside cards on small screens.
   At 320–430px a card has only ~240–330px of inner width, and the desktop
   paddings were eating 60–80px of that before any content was drawn — which
   is what squeezed the charts rather than resizing them. */
@media (max-width: 560px) {
  .card__head,
  .metrics { padding-inline: 0.85rem; }

  .metrics {
    grid-template-columns: repeat(auto-fit, minmax(min(76px, 100%), 1fr));
    gap: 0.7rem 0.75rem;
  }

  .viz { padding-inline: 0.85rem; }

  th,
  td { padding: 0.5rem 0.6rem; }

  .demo-panel { padding: 0.95rem 0.9rem 1.05rem; }

  /* Long category names get the room instead of the value column. */
  .gbar { grid-template-columns: minmax(min(72px, 38%), 34%) 1fr; }

  .kpi__value { font-size: var(--step-2); }
}

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

/* ==================================================================
   DEMOGRAPHICS PAGE
   Static division-profile figures rendered as charts. Panels reveal
   and bars grow on scroll; every animation is opt-out under
   prefers-reduced-motion (handled by the global rule at the end of
   this file plus explicit checks in demographics.js).
   ================================================================== */

/* Explicit column counts, not `auto-fit`.

   `repeat(auto-fit, minmax(340px, 1fr))` resolved to 3 columns at 1280–1440px
   and 4 at 1620px+, while `--wide` always claimed exactly 2. A section
   budgeted for 4 columns therefore left a 2-cell hole in a 3-column grid, and
   the 6-column Facilities section left holes at every width. Fixing the count
   per breakpoint makes the arithmetic knowable: every section is built to a
   4-column budget, so it fills 4-up and 2-up rows exactly, and 3 columns is
   never used. */
.demo-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.15rem;
  align-items: stretch;
  margin-bottom: 0.5rem;
}

@media (min-width: 760px) {
  .demo-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1500px) {
  .demo-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* Stretch, so every panel in a row ends on the same baseline. `align-items:
   start` previously let each panel size to its own content, which is what
   produced the ragged bottoms in each row. */
.demo-panel {
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-lg);
  padding: 1.15rem 1.25rem 1.3rem;
  overflow: hidden;
}

/* A wide panel spans two columns so dense charts are not squeezed into one
   narrow column. At the 2-column breakpoint that is a full-width row, which
   is the intended behaviour for these chart-heavy panels. */
@media (min-width: 760px) {
  .demo-panel--wide { grid-column: span 2; }
}

/* Facilities carries six columns of content in a four-column grid, which
   always leaves a two-cell remainder on the second row. Rather than shrink a
   panel that needs its width, the two light panels on that row stretch to
   share the space — both are short stat lists, so the extra width costs
   nothing and the row ends flush. */
@media (min-width: 1500px) {
  .demo-panel--half { grid-column: span 2; }
}

/* On very wide screens `auto-fit` would fit 8–9 KPI tiles and 8 link tiles per
   row, slicing each one thin enough that the labels wrap. The dashboard only
   ever has 7 KPIs and 4 external links, so cap the track count at what the
   content actually needs and let the tiles grow instead of multiply. */
@media (min-width: 2000px) {
  .hero { grid-template-columns: repeat(7, minmax(0, 1fr)); }
  .links { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

.demo-panel__head { margin-bottom: 1rem; }

.demo-panel__title {
  font-size: var(--step-1);
  font-weight: 640;
  letter-spacing: -0.025em;
}

.demo-panel__note {
  margin-top: 0.2rem;
  font-size: var(--step--1);
  color: var(--ink-muted);
}

/* Footnote under a chart — carries caveats and source corrections.
   `margin-top: auto` pushes it to the foot of the flex column, so a note in a
   short panel still aligns with the note in the tall panel beside it instead
   of floating mid-card. */
.demo-note {
  margin-top: auto;
  /* `auto` collapses to zero when the panel is already full, so the spacing
     above the rule comes from padding rather than margin. */
  padding-top: 1.65rem;
  border-top: 1px solid var(--glass-border);
  font-size: var(--step--2);
  line-height: 1.5;
  color: var(--ink-muted);
}

/* --- scroll reveal ------------------------------------------------ */

.reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 620ms var(--ease), transform 620ms var(--ease);
}

.reveal.is-visible {
  opacity: 1;
  transform: none;
}

/* --- grouped bars ------------------------------------------------- */

.gbars { display: grid; gap: 0.85rem; }

.gbar {
  display: grid;
  /* Same collapsible floor as .bar-row — see the note there. */
  grid-template-columns: minmax(min(90px, 32%), 26%) 1fr;
  align-items: center;
  gap: 0.85rem;
}

.gbar__label {
  font-size: var(--step--1);
  color: var(--ink-secondary);
  line-height: 1.25;
}

.gbar__tracks { display: grid; gap: 0.3rem; }

.gbar__line {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 0.6rem;
}

.gbar__track {
  position: relative;
  height: 10px;
  border-radius: 5px;
  background: rgba(255, 255, 255, 0.055);
  overflow: hidden;
}

/* Square at the baseline, 4px rounded at the data end. Width is held at 0
   until the chart scrolls into view, then transitions to its real value. */
.gbar__fill {
  height: 100%;
  width: 0;
  border-radius: 0 4px 4px 0;
  transition: width 900ms var(--ease);
}

.gbars.is-drawn .gbar__fill { width: var(--w); }

.gbar__value {
  min-width: 4.5ch;
  text-align: right;
  font-size: var(--step--1);
  font-weight: 640;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* --- meters ------------------------------------------------------- */

.meters { display: grid; gap: 0.9rem; }

.meter__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.75rem;
  margin-bottom: 0.35rem;
}

.meter__label { font-size: var(--step--1); color: var(--ink-secondary); }

.meter__value {
  font-size: var(--step--1);
  font-weight: 660;
  font-variant-numeric: tabular-nums;
}

.meter__track {
  height: 8px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.055);
  overflow: hidden;
}

.meter__fill {
  height: 100%;
  width: 0;
  border-radius: 0 4px 4px 0;
  transition: width 950ms var(--ease);
}

.meter.is-drawn .meter__fill { width: var(--w); }

/* --- stat rows ---------------------------------------------------- */

.stat-rows { display: grid; gap: 0.1rem; }

.stat-row {
  display: flex;
  align-items: baseline;
  gap: 0.85rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
  font-size: var(--step--1);
}

.stat-row:last-child { border-bottom: 0; }
.stat-row__label { color: var(--ink-secondary); }

.stat-row__value {
  margin-left: auto;
  font-weight: 640;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* Tables inside a panel sit flush and get a little breathing room above. */
.demo-panel .table-wrap { margin-top: 1rem; }
.demo-panel td.num,
.demo-panel th:not(:first-child) {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* --- source footer ------------------------------------------------ */

.demo-footer {
  margin-top: 1.75rem;
  padding: 1.15rem 1.35rem 1.25rem;
  border-radius: var(--radius-lg);
  font-size: var(--step--1);
  color: var(--ink-secondary);
  display: grid;
  gap: 0.4rem;
}

.demo-footer__title {
  font-size: var(--step--2);
  font-weight: 640;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ink-muted);
}

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

@media print {
  /* Re-point the design tokens instead of overriding individual rules.
     46 rules set text colour via --ink*, so patching them one by one is how
     the first attempt printed white-on-white; redefining the variables makes
     every descendant inherit dark ink automatically. */
  :root {
    --ink: #111827;
    --ink-secondary: #374151;
    --ink-muted: #4b5563;
    --glass: #ffffff;
    --glass-strong: #ffffff;
    --glass-border: #9ca3af;
    --glass-border-lit: #6b7280;
    --surface-0: #ffffff;
    --surface-1: #ffffff;
    --surface-2: #f3f4f6;
  }

  html, body {
    background: #fff !important;
    color: #111827 !important;
  }

  /* Chrome and friends drop backgrounds by default; charts are the content
     here, so ask for them to be kept. */
  * {
    -webkit-print-color-adjust: exact !important;
    print-color-adjust: exact !important;
  }

  .orbs,
  .topbar,
  .page-head__actions,
  .demo-footer__toggle {
    display: none !important;
  }

  .shell,
  .main {
    display: block;
    max-width: none;
    padding: 0;
  }

  /* Glass effects do not survive print: the blur is dropped and the
     translucent fill renders as a grey wash. Flatten to plain panels. */
  .glass,
  .demo-panel,
  .kpi,
  .demo-footer {
    background: #fff !important;
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
    border: 1px solid #9ca3af !important;
    box-shadow: none !important;
    break-inside: avoid;
    page-break-inside: avoid;
  }

  .glass::after { display: none !important; }

  /* Cockpit-field affordances that only make sense on screen: the projection
     glow and the text halos would print as grey boxes or muddy text. */
  .grid::before,
  .hero::before {
    display: none !important;
  }

  .page-head__meta {
    color: #374151 !important;
    text-shadow: none !important;
  }

  /* The gradient-clipped title is re-filled solid for print elsewhere in this
     block; its drop-shadow has to go too or it prints as a grey blur. */
  .page-head__title { filter: none !important; }

  .section-title {
    color: #111827 !important;
    text-shadow: none !important;
  }

  /* Text that carried an accent colour or glow on screen. */
  .page-head__title {
    -webkit-text-fill-color: #111827 !important;
    background: none !important;
    color: #111827 !important;
  }

  .kpi__value,
  .metric__value,
  .donut__total,
  .stat-row__value,
  .gbar__value,
  .meter__value {
    color: #111827 !important;
    text-shadow: none !important;
  }

  .kpi__label,
  .demo-panel__note,
  .demo-note,
  .section-title__note,
  .donut__caption,
  .kpi__sub {
    color: #4b5563 !important;
  }

  .section-title { color: #111827 !important; }

  /* Reveal animations must not leave content hidden on paper. */
  .reveal,
  .reveal.is-visible {
    opacity: 1 !important;
    transform: none !important;
  }

  /* Bars and meters are drawn by a class the observer adds on scroll; a
     print may happen before that fires, so force the final width. */
  .gbar__fill,
  .meter__fill {
    width: var(--w) !important;
    transition: none !important;
  }

  .gbar__track,
  .meter__track {
    background: #e5e7eb !important;
    border: 1px solid #d1d5db;
  }

  /* Donut arcs are animated from a zero dasharray — reset so a pre-scroll
     print still shows the full ring. */
  .donut__seg {
    transition: none !important;
    stroke-dasharray: var(--print-dash, none);
  }

  table { border-collapse: collapse; }

  th {
    background: #f3f4f6 !important;
    color: #111827 !important;
    border-bottom: 1px solid #9ca3af !important;
  }

  td {
    color: #111827 !important;
    border-bottom: 1px solid #e5e7eb !important;
  }

  tbody tr:hover { background: none !important; }

  .demo-grid {
    display: block;
  }

  /* Panels are flex columns on screen so rows end level. In print they are
     stacked blocks, so the flex context is dropped — otherwise `margin-top:
     auto` on a footnote would push it to the foot of a full page. */
  .demo-panel {
    display: block;
    margin-bottom: 0.6rem;
  }

  .demo-note {
    margin-top: 0.9rem;
    padding-top: 0.75rem;
  }

  .hero {
    display: grid !important;
    grid-template-columns: repeat(3, 1fr) !important;
    gap: 0.5rem !important;
  }

  a { color: #111827 !important; text-decoration: none; }

  @page {
    margin: 14mm;
  }
}
