/* labs.css — Principles Lab's own chassis + shell (LUẬT 0: this file
   never pulls in the portfolio's styles.css / case-study.css). */

:root {
  color-scheme: dark;

  /* Tầng A — chassis palette: graphite + one violet accent (bất biến) */
  --ink: #121117;
  --panel: #1b1922;
  --panel-raised: #242131;
  --text-mute: #74708a;
  --text-body: #a9a4bd;
  --text-head: #f1eef7;
  --border: rgba(217, 209, 255, 0.1);
  --border-strong: rgba(217, 209, 255, 0.22);
  /* Brand default — every lesson page overrides this inline with its own
     category color (UI plan §3.2); nothing here needs a var() fallback,
     the cascade already resolves it either way. */
  --accent: #8d7bff;
  --accent-ink: #141022;
  --radius: 3px;
  --radius-pill: 999px;

  /* Category swatches — filter chips only (not a branch-color override;
     a page's own --accent inline on <body> still wins on its own page). */
  --cat-data: #5fd4c7; --cat-security: #ff7b72; --cat-code-design: #e8b04b;
  --cat-api: #79c0ff; --cat-performance: #ffa657; --cat-reliability: #b5e853;

  /* Tầng D — stage palette */
  --stage-bg-deep: #17141c;
  --stage-bg-raised: #211d29;
  --stage-bg-void: #0d0b11;
  --stage-bg-ledger: #1a1620;

  /* Tầng C — signal set (ngữ nghĩa, bất biến toàn site) */
  --sig-ok: #3ecf8e;
  --sig-warn: #e2a63b;
  --sig-bad: #ef5d6f;
  --sig-info: #6fb3ff;
  --sig-ghost: rgba(169, 164, 189, 0.45);
  --sig-focus: #ffffff;

  /* Chữ */
  --font-sans: "Space Grotesk", system-ui, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --fs-hero: clamp(2.2rem, 5.5vw, 3.6rem);
  --fs-h2: 1.1rem;
  --fs-lead: 1.05rem;
  --fs-body: 0.95rem;
  --fs-label: 0.68rem;
  --fs-control: 0.78rem;
  --fs-mono: 0.8rem;
  --fs-mono-sm: 0.72rem;
  --fs-readout: clamp(1.6rem, 4vw, 2.4rem);

  /* Spacing */
  --sp-1: 0.35rem;
  --sp-2: 0.7rem;
  --sp-3: 1.1rem;
  --sp-4: 1.8rem;
  --sp-5: 3rem;
  --sp-6: 5rem;
  --pane-pad: clamp(0.9rem, 2.5vw, 1.4rem);
  --section-pad: clamp(4rem, 9vw, 8rem);
  /* One shared content column for every landing section (hero + catalog) —
     B0: the old mismatch was .lesson-hero (68ch) vs .lesson-block (1100px)
     sharing one max-width keyed off two different rules. The landing no
     longer reuses those lesson-page classes at all (see below). */
  --landing-col: 1100px;

  /* Motion — UI axis only; sim time lives in clock.js, never CSS */
  --t-instant: 90ms;
  --t-ui: 180ms;
  --t-reveal: 320ms;
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  /* Hero loop demo: one shared timeline length so every scene/row/pulse
     keyframe (percentages of this) stays phase-locked without JS driving it. */
  --demo-cycle: 9s;
}

* { box-sizing: border-box; }

/* overflow-x:hidden guards the .hero__rule/.lesson-hero::before 100vw breakout
   below from adding a horizontal scrollbar on pages where the vertical
   scrollbar's own width would otherwise push 100vw past the real viewport. */
html { background: var(--ink); overflow-x: hidden; }

body {
  /* no background here on purpose: html's canvas background already shows
     through (identical ink color on every page), and leaving body itself
     transparent is what lets .labs-bg's fixed, z-index:-1 squares show on
     the landing — a non-positioned body with an opaque fill of its own
     paints over a negative-z-index fixed descendant, same stacking context
     or not. */
  color: var(--text-body);
  font: var(--fs-body) / 1.7 var(--font-sans);
  margin: 0;
  --accent-soft: color-mix(in srgb, var(--accent) 12%, transparent);
  --accent-line: color-mix(in srgb, var(--accent) 38%, transparent);
}

h1, h2, h3 { color: var(--text-head); font-family: var(--font-sans); line-height: 1.2; margin: 0; }
a { color: var(--accent); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.metric,
.counter,
.log-line {
  font-variant-numeric: tabular-nums;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}

/* ---- Lab header nav / footer (own to the Lab — LUẬT 0: no link leaves /labs/) */
.lab-header { position: sticky; top: 0; z-index: 30; background: var(--ink); border-bottom: 1px solid var(--border); transition: background 160ms ease, box-shadow 160ms ease; }
.lab-header.is-scrolled { background: color-mix(in srgb, var(--ink) 90%, transparent); backdrop-filter: blur(10px); border-color: var(--border-strong); box-shadow: 0 8px 24px -16px rgba(0, 0, 0, 0.6); }
.lab-header__bar { display: flex; align-items: center; gap: var(--sp-4); padding: var(--sp-3) var(--sp-4); max-width: 1200px; margin-inline: auto; }
.lab-nav__wordmark { color: var(--text-head); letter-spacing: 0; text-transform: none; text-decoration: none; flex-shrink: 0; font: 600 0.9rem/1 var(--font-sans); }

.lab-header__primary { display: flex; align-items: center; gap: var(--sp-4); font: var(--fs-label) / 1 var(--font-sans); text-transform: uppercase; letter-spacing: 0.1em; }
.lab-header__primary a { color: var(--text-body); text-decoration: none; }
.lab-header__primary a:hover { color: var(--text-head); }

.lab-header__browse { position: relative; }
.lab-header__browse-toggle { display: inline-flex; align-items: center; gap: 0.35em; background: none; border: none; color: var(--text-body); font: inherit; text-transform: inherit; letter-spacing: inherit; cursor: pointer; padding: 0; }
.lab-header__browse-toggle:hover, .lab-header__browse.is-open .lab-header__browse-toggle { color: var(--text-head); }
.lab-header__browse-toggle svg { width: 8px; height: 8px; transition: transform 140ms ease; }
.lab-header__browse.is-open .lab-header__browse-toggle svg { transform: rotate(180deg); }

.lab-header__browse-menu, .lab-header__search-results {
  position: absolute; background: var(--panel); border: 1px solid var(--border-strong); border-radius: var(--radius);
  padding: 0.3rem; box-shadow: 0 16px 32px -16px rgba(0, 0, 0, 0.55);
}
.lab-header__browse-menu { top: calc(100% + var(--sp-3)); left: 0; width: 15rem; display: none; }
.lab-header__browse.is-open .lab-header__browse-menu { display: block; }
.lab-header__search-results { top: calc(100% + var(--sp-2)); right: 0; width: 17rem; }
.lab-header__search-results[hidden] { display: none; }
.lab-header__mobile-search .lab-header__search-results { left: 0; right: 0; width: auto; }

.lab-header__browse-menu button,
.lab-header__search-results a,
.lab-header__mobile-group a,
.lab-header__mobile-group button {
  display: flex; align-items: center; gap: 0.6rem; width: 100%; padding: 0.55rem 0.6rem;
  border: none; background: none; color: var(--text-body); text-decoration: none; text-align: left; cursor: pointer;
}
.lab-header__browse-menu button, .lab-header__search-results a { border-radius: var(--radius); font: 0.82rem/1 var(--font-sans); }
.lab-header__browse-menu button:hover, .lab-header__browse-menu button:focus-visible,
.lab-header__search-results a:hover, .lab-header__search-results a:focus-visible { background: var(--panel-raised); color: var(--text-head); }
.lab-header__search-results a span { color: var(--text-mute); }
.lab-header__browse-menu .dot, .lab-header__mobile-group .dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.lab-header__browse-menu .count, .lab-header__mobile-group .count { margin-left: auto; color: var(--text-mute); font-family: var(--font-mono); font-size: 0.75rem; }
/* Toggle-filter rows (catalog category dropdown reuses this same menu —
   header's own Browse items never carry aria-pressed, so this is a no-op there). */
.lab-header__browse-menu button[aria-pressed="true"] { background: var(--panel-raised); color: var(--text-head); }
.lab-header__browse-menu button .check { display: none; margin-left: 0.4em; color: var(--accent); font: 700 0.7rem/1 var(--font-mono); flex: none; }
.lab-header__browse-menu button[aria-pressed="true"] .check { display: inline; }

.lab-header__spacer { flex: 1; min-width: 0; }
.lab-header__utility { display: flex; align-items: center; gap: var(--sp-2); flex-shrink: 0; }
.lab-header__stat { display: none; font: var(--fs-label) / 1 var(--font-mono); color: var(--text-mute); letter-spacing: 0.04em; white-space: nowrap; }
@media (min-width: 560px) { .lab-header__stat { display: inline; } }

.lab-header__search { position: relative; }
/* Flowing neon/dark-blue "light transfer" border: a gradient sweeps across
   via background-position only (no rotation/transform), so the ring never
   spins — it just keeps shifting color, slower at rest and quicker on focus. */
.lab-header__search-field {
  padding: 2px;
  border-radius: calc(var(--radius) + 8px);
  background: linear-gradient(115deg, var(--accent) 0%, #0f2e6b 30%, var(--accent) 60%, #0f2e6b 100%);
  background-size: 260% 260%;
  animation: lab-search-flow 10s linear infinite;
  box-shadow: 0 4px 18px -10px color-mix(in srgb, var(--accent) 55%, transparent);
}
.lab-header__search-field:focus-within { animation-duration: 2.6s; box-shadow: 0 4px 20px -8px color-mix(in srgb, var(--accent) 75%, transparent); }
.lab-header__search-inner { display: flex; align-items: center; gap: 0.55em; background: var(--ink); border-radius: calc(var(--radius) + 6px); padding: 0.5rem 0.75rem 0.5rem 0.65rem; }
.lab-header__search-icon { width: 14px; height: 14px; stroke: var(--text-mute); fill: none; stroke-width: 1.7; flex: none; }
.lab-header__search-field:focus-within .lab-header__search-icon { stroke: var(--accent); }
.lab-header__search-input { width: 12.5rem; min-width: 0; flex: 1 1 auto; padding: 0; border: none; background: none; color: var(--text-head); font: 0.84rem/1 var(--font-sans); outline: none; }
.lab-header__search-input::placeholder { color: var(--text-mute); }
.lab-header__mobile-search .lab-header__search-field { width: 100%; }
.lab-header__mobile-search .lab-header__search-inner { padding: 0.6rem 0.75rem; }
.lab-header__mobile-search .lab-header__search-input { width: 100%; }
@keyframes lab-search-flow {
  0% { background-position: 0% 50%; }
  100% { background-position: 100% 50%; }
}

.lab-header__burger { display: none; align-items: center; justify-content: center; width: 2.1rem; height: 2.1rem; border: 1px solid var(--border); border-radius: var(--radius); background: transparent; color: var(--text-head); cursor: pointer; }
.lab-header__burger svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 1.6; }

.lab-header__mobile { display: none; border-top: 1px solid var(--border); padding: var(--sp-3) var(--sp-4) var(--sp-4); max-width: 1200px; margin-inline: auto; }
.lab-header.is-menu-open .lab-header__mobile { display: block; }
.lab-header__mobile-group { position: relative; }
.lab-header__mobile-group + .lab-header__mobile-group { margin-top: var(--sp-3); }
.lab-header__mobile-label { font: var(--fs-label) / 1 var(--font-sans); text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-mute); margin: 0 0 var(--sp-2); }
.lab-header__mobile-group a, .lab-header__mobile-group button { border-bottom: 1px solid var(--border); font-size: 0.92rem; }

@media (max-width: 760px) {
  .lab-header__primary, .lab-header__stat, .lab-header__search { display: none; }
  .lab-header__burger { display: inline-flex; }
}

.lab-footer {
  padding: var(--sp-4);
  border-top: 1px solid var(--border);
  font: var(--fs-label) / 1.6 var(--font-sans);
  color: var(--text-mute);
  max-width: 1200px;
  margin-inline: auto;
}
.lab-footer button {
  font: var(--fs-label) / 1 var(--font-sans);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  border: 1px solid var(--border-strong);
  background: none;
  color: var(--text-body);
  border-radius: var(--radius);
  padding: 0.5em 1em;
  cursor: pointer;
  min-height: 40px;
}
.lab-footer button:hover,
.lab-footer button:focus-visible { border-color: var(--accent); color: var(--accent); }

/* ---- Lesson shell: 8 blocks, locked order, lede/content split ---- */
/* Same max-width as .lesson-block's desktop width (below) so the hero's
   left edge lines up with every block under it — they used to be centered
   independently at two different widths (68ch vs 1200px), which put the
   headline and "The situation" at two different left margins. */
.lesson-hero { position: relative; padding: var(--section-pad) var(--sp-4) var(--sp-4); max-width: 68ch; margin-inline: auto; }
@media (min-width: 900px) { .lesson-hero { max-width: 1200px; } }
/* Schematic-grid texture bleeds to the full page width (not just the hero's
   own readable column) via the standard centered-container breakout. */
.lesson-hero::before {
  content: ""; position: absolute; left: 50%; width: 100vw; margin-left: -50vw;
  top: -6%; height: 300px; z-index: -1; pointer-events: none;
  background-image: linear-gradient(var(--border) 1px, transparent 1px), linear-gradient(90deg, var(--border) 1px, transparent 1px);
  background-size: 26px 26px;
  mask-image: radial-gradient(ellipse 30% 70% at 25% 15%, black, transparent 72%);
  -webkit-mask-image: radial-gradient(ellipse 30% 70% at 25% 15%, black, transparent 72%);
}
/* The problem name, first thing read — the symptom headline is support, not the identifier. */
.hero-principle {
  display: block;
  font: 700 var(--fs-h2) / 1.2 var(--font-sans);
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin: 0 0 var(--sp-2);
}
/* Same breakout, but via width+margin (not position:absolute) so this stays
   in normal flow — it still has to occupy its own line to create the gap
   above/below it; only its WIDTH needs to reach the viewport edges. */
.hero__rule { position: relative; height: 1px; overflow: hidden; margin: var(--sp-2) 0 var(--sp-3); width: 100vw; margin-left: calc(50% - 50vw); margin-right: calc(50% - 50vw); }
.hero__rule span { position: absolute; inset: 0; width: 140px; left: -140px; background: linear-gradient(90deg, transparent, var(--accent), transparent); animation: hero-sweep 6s ease-in-out infinite; }
@keyframes hero-sweep { to { left: 100%; } }
.lesson-hero h1 { font-size: var(--fs-hero); letter-spacing: -0.045em; margin: var(--sp-3) 0; max-width: 26ch; }
.pill {
  display: inline-flex;
  font: var(--fs-label) / 1 var(--font-sans);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  border: 1px solid var(--accent-line);
  color: var(--accent);
  border-radius: var(--radius);
  padding: 0.3em 0.8em;
  margin-right: var(--sp-2);
}
/* Difficulty carries no severity signal — stays neutral, unlike category (branch color) and blast-radius (below). */
.pill--difficulty { border-color: var(--border-strong); color: var(--text-body); }
/* Blast-radius IS a severity signal — borrow the site's signal colors instead of the branch accent (RULE: color is data). */
/* Filled, not just outlined — so it reads apart from the category pill even
   when a branch accent (e.g. security's red) is close in hue to a signal color. */
.pill[data-blast="catastrophic"] { border-color: var(--sig-bad); color: var(--sig-bad); background: color-mix(in srgb, var(--sig-bad) 14%, transparent); }
.pill[data-blast="expensive"] { border-color: var(--sig-warn); color: var(--sig-warn); background: color-mix(in srgb, var(--sig-warn) 14%, transparent); }
.pill[data-blast="annoying"] { border-color: var(--border-strong); color: var(--text-body); }
.metric-strip {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  font: var(--fs-readout) / 1 var(--font-mono);
  font-variant-numeric: tabular-nums;
  margin: var(--sp-3) 0;
}
/* Before/after callouts only (.metric, opt-in) — sql-injection's strip is a
   cause->effect pair, not an improvement, and stays neutral on purpose. */
.metric-strip.metric span:first-child { color: var(--sig-bad); }
.metric-strip.metric span:last-child { color: var(--sig-ok); }
.metric-strip small { font: var(--fs-label) / 1 var(--font-sans); color: var(--text-mute); }

body { counter-reset: block-num; }
.lesson-block {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-3);
  padding: var(--section-pad) var(--sp-4);
  max-width: 68ch;
  margin-inline: auto;
}
/* Lab stage only (LUẬT 6a) — the one deliberately edge-to-edge block. */
.lesson-block--full { max-width: none; padding: var(--section-pad) 0; }
@media (min-width: 900px) {
  .lesson-block { grid-template-columns: 0.43fr 1fr; max-width: 1200px; }
}
.lesson-block h2 { font-size: var(--fs-h2); }
/* Chapter marker — a faint auto-numbered index + short rule fills the
   label column instead of leaving it empty (2026-11-01 article revamp). */
.lesson-block:not(.lesson-block--full) { counter-increment: block-num; }
.lesson-block:not(.lesson-block--full) > h2 { position: relative; padding-top: 2.3rem; }
.lesson-block:not(.lesson-block--full) > h2::before {
  content: counter(block-num, decimal-leading-zero);
  position: absolute; top: 0; left: 0;
  font: 700 1.85rem/1 var(--font-mono);
  color: var(--border-strong);
  font-variant-numeric: tabular-nums;
}
.lesson-block:not(.lesson-block--full) > h2::after { content: ""; display: block; width: 26px; height: 2px; background: var(--accent-line); margin-top: 0.7rem; }
.block__body { display: flex; flex-direction: column; gap: var(--sp-3); }
.schematic { position: relative; border: 1px solid var(--border-strong); border-radius: var(--radius); background: var(--panel); padding: 1rem; }
.schematic::before, .schematic::after { content: ""; position: absolute; width: 8px; height: 8px; border: 1px solid var(--accent-line); }
.schematic::before { top: -1px; left: -1px; border-right: none; border-bottom: none; }
.schematic::after { bottom: -1px; right: -1px; border-left: none; border-top: none; }

.tradeoff-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-3);
}
@media (min-width: 720px) {
  .tradeoff-grid { grid-template-columns: 1fr 1fr; }
}
.tradeoff-grid > div { border: 1px solid var(--border-strong); border-radius: var(--radius); border-top-width: 2px; padding: var(--sp-3); }
.tradeoff-grid > div:first-child { border-top-color: var(--sig-ok); }
.tradeoff-grid > div:last-child { border-top-color: var(--sig-warn); }
.tradeoff-grid h3 { display: flex; align-items: center; gap: 0.6em; font-size: 0.95rem; margin-bottom: var(--sp-2); }
.tradeoff-grid h3::before { font: 700 0.8rem var(--font-mono); width: 1.6em; height: 1.6em; border-radius: var(--radius); display: inline-flex; align-items: center; justify-content: center; flex: none; }
.tradeoff-grid > div:first-child h3::before { content: "✓"; color: var(--sig-ok); background: color-mix(in srgb, var(--sig-ok) 15%, transparent); }
.tradeoff-grid > div:last-child h3::before { content: "!"; color: var(--sig-warn); background: color-mix(in srgb, var(--sig-warn) 15%, transparent); }

[data-block="carry-away"] ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
[data-block="carry-away"] li { display: flex; gap: 0.7em; align-items: flex-start; }
[data-block="carry-away"] li::before {
  content: "✓"; flex: none; width: 1.4em; height: 1.4em; margin-top: 0.1em;
  border-radius: var(--radius); background: color-mix(in srgb, var(--sig-ok) 14%, transparent); color: var(--sig-ok);
  font: 700 0.72rem var(--font-mono); display: flex; align-items: center; justify-content: center;
}

.principle-box {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--accent-line);
  border-left-width: 3px;
  border-radius: var(--radius);
  padding: var(--pane-pad);
  background: linear-gradient(180deg, var(--accent-soft), transparent 65%);
}
.principle-box::after { content: "\201D"; position: absolute; top: -0.4rem; right: 1rem; font: 700 4rem/1 var(--font-mono); color: var(--accent-line); pointer-events: none; }
.principle-box > p { position: relative; font-size: 1.12rem; color: var(--text-head); font-weight: 500; max-width: 54ch; }

/* ---- lesson reading rail — scroll-tracked, desktop only ---- */
.lesson-rail { position: fixed; top: 50%; right: max(20px, env(safe-area-inset-right, 0px)); transform: translateY(-50%); z-index: 40; display: flex; flex-direction: column; gap: 0.75rem; }
.lesson-rail a { display: flex; align-items: center; gap: 0.6em; justify-content: flex-end; text-decoration: none; }
.lesson-rail .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--border-strong); transition: background 150ms, transform 150ms; }
.lesson-rail .label { font: 600 0.64rem/1 var(--font-mono); text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-mute); opacity: 0; transform: translateX(6px); transition: opacity 150ms, transform 150ms; white-space: nowrap; }
.lesson-rail a:hover .label, .lesson-rail a:focus-visible .label { opacity: 1; transform: none; }
.lesson-rail a[aria-current="true"] .dot { background: var(--accent); transform: scale(1.5); }
.lesson-rail a[aria-current="true"] .label { opacity: 1; transform: none; color: var(--text-head); }
@media (max-width: 1240px) { .lesson-rail { display: none; } }

.lede { font-size: var(--fs-lead); color: var(--text-body); max-width: 56ch; }

.cta-button {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  background: var(--accent);
  color: var(--accent-ink);
  font: 600 0.95rem / 1 var(--font-sans);
  padding: 0.8em 1.5em;
  border-radius: var(--radius);
  text-decoration: none;
  margin-top: var(--sp-3);
  transition: transform var(--t-ui) var(--ease), box-shadow var(--t-ui) var(--ease);
}
.cta-button:hover,
.cta-button:focus-visible { transform: translateY(-1px); box-shadow: 0 8px 24px -8px var(--accent); }

/* ==================================================================
   Landing — hero, loop demo, catalog. Its own section rhythm, not the
   lesson page's label-left prose split: these are heading-above-content
   blocks that scan rather than read (B0).
   ================================================================== */

.landing { position: relative; }
.landing section {
  max-width: var(--landing-col);
  margin-inline: auto;
  padding: var(--sp-5) var(--sp-4);
}
.landing .hero { padding-top: var(--sp-6); padding-bottom: var(--sp-6); }
.hero__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  font: 700 0.75rem / 1 var(--font-sans);
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin-bottom: var(--sp-3);
}
.hero h1 { font-size: var(--fs-hero); letter-spacing: -0.03em; max-width: 24ch; margin: 0; }
.hero p.lead { max-width: 58ch; font-size: var(--fs-lead); margin-top: var(--sp-3); }
.hero__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); margin-top: var(--sp-4); }
[data-hero-stats] { margin: 0; font: 0.85rem / 1 var(--font-mono); color: var(--text-mute); }
[data-hero-stats] strong { color: var(--accent); font-variant-numeric: tabular-nums; }

/* landing-only heading scale (B0: lesson-page tokens stay untouched) */
.landing h2.block-heading { font-size: 1.65rem; margin-bottom: var(--sp-1); }
.landing .block-sub { color: var(--text-mute); max-width: 56ch; margin-bottom: var(--sp-4); }

/* ---- hero loop demo: browse -> open a lab -> Start -> loading -> done -> repeat.
   Pure CSS: every piece's own @keyframes only moves during the slice of
   --demo-cycle where its scene is "on screen", so nothing needs JS to stay
   in sync. Under reduced motion the global catch-all at the end of this
   file already collapses every animation here to a still frame. */
.hero-demo {
  margin-top: var(--sp-5);
  width: 100%;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--panel);
  overflow: hidden;
  box-shadow: 0 32px 64px -36px rgba(0, 0, 0, 0.7);
}
.hero-demo__bar {
  display: flex;
  align-items: center;
  gap: 0.4em;
  height: 2.4rem;
  padding: 0 var(--sp-4);
  background: var(--panel-raised);
  border-bottom: 1px solid var(--border);
}
.hero-demo__dot { width: 6px; height: 6px; border-radius: 50%; background: var(--border-strong); flex: none; }
.hero-demo__url {
  margin-left: var(--sp-2);
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font: clamp(0.65rem, 1.6vw, 0.78rem) / 1 var(--font-mono);
  color: var(--text-mute);
}
.hero-demo__stage { position: relative; aspect-ratio: 16 / 9; min-height: 260px; }
.hero-demo__scene {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  padding: var(--sp-5);
  opacity: 0;
}

.hero-demo__scene--browse { animation: demo-scene-browse var(--demo-cycle) infinite; }
.hero-demo__row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: min(640px, 90%);
  height: 2.6rem;
  padding: 0 1em;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel-raised);
  opacity: 0;
  animation: demo-row-in var(--demo-cycle) infinite;
}
.hero-demo__row span:first-child { width: 9px; height: 9px; border-radius: 50%; background: var(--accent); flex: none; }
.hero-demo__row span:last-child { height: 0.6em; width: 55%; border-radius: 2px; background: var(--border-strong); }
.hero-demo__row--2 { animation-delay: calc(var(--demo-cycle) * -0.04); }
.hero-demo__row--3 { animation-delay: calc(var(--demo-cycle) * -0.08); animation-name: demo-row-in, demo-row-pick; }

.hero-demo__scene--lab { animation: demo-scene-lab var(--demo-cycle) infinite; }
.hero-demo__mini-stage { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--sp-3); width: min(560px, 85%); }
.hero-demo__mini-zone { height: 3.4rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--panel-raised); }
.hero-demo__start {
  padding: 0.6em 1.8em;
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  background: none;
  color: var(--accent);
  font: 600 0.85rem / 1 var(--font-sans);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  animation: demo-start-press var(--demo-cycle) infinite;
}

.hero-demo__scene--loading { animation: demo-scene-loading var(--demo-cycle) infinite; }
.hero-demo__rain { position: relative; width: min(640px, 90%); height: 60%; overflow: hidden; }
.hero-demo__rain span {
  position: absolute;
  top: -30%;
  width: 2px;
  height: 22%;
  background: linear-gradient(var(--accent), transparent);
  animation: demo-rain-fall 1.1s linear infinite;
}
.hero-demo__rain span:nth-child(1) { left: 8%; animation-delay: -0.1s; }
.hero-demo__rain span:nth-child(2) { left: 24%; animation-delay: -0.5s; }
.hero-demo__rain span:nth-child(3) { left: 40%; animation-delay: -0.2s; }
.hero-demo__rain span:nth-child(4) { left: 58%; animation-delay: -0.7s; }
.hero-demo__rain span:nth-child(5) { left: 74%; animation-delay: -0.35s; }
.hero-demo__rain span:nth-child(6) { left: 90%; animation-delay: -0.9s; }
.hero-demo__label { font: 0.7rem / 1 var(--font-mono); color: var(--text-mute); text-transform: uppercase; letter-spacing: 0.08em; text-align: center; }

.hero-demo__scene--done { animation: demo-scene-done var(--demo-cycle) infinite; text-align: center; }
.hero-demo__check {
  width: 4.6rem;
  height: 4.6rem;
  border-radius: 50%;
  border: 2px solid var(--sig-ok);
  color: var(--sig-ok);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2.1rem;
  animation: demo-check-pop var(--demo-cycle) infinite;
}
.hero-demo__done-readout { font: 1.5rem / 1 var(--font-mono); color: var(--text-head); font-variant-numeric: tabular-nums; }

@keyframes demo-scene-browse { 0%, 21% { opacity: 1; } 24%, 100% { opacity: 0; } }
@keyframes demo-scene-lab { 0%, 21% { opacity: 0; } 24%, 45% { opacity: 1; } 48%, 100% { opacity: 0; } }
@keyframes demo-scene-loading { 0%, 45% { opacity: 0; } 48%, 70% { opacity: 1; } 73%, 100% { opacity: 0; } }
@keyframes demo-scene-done { 0%, 70% { opacity: 0; } 73%, 97% { opacity: 1; } 100% { opacity: 0; } }
@keyframes demo-row-in { 0% { opacity: 0; transform: translateX(-6px); } 3%, 21% { opacity: 1; transform: none; } 24%, 100% { opacity: 0; } }
@keyframes demo-row-pick {
  0%, 15% { border-color: var(--border); box-shadow: none; }
  18%, 21% { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
  24%, 100% { border-color: var(--border); box-shadow: none; }
}
@keyframes demo-start-press {
  0%, 38% { transform: none; box-shadow: none; }
  41% { transform: scale(0.92); background: var(--accent); color: var(--accent-ink); }
  44%, 47% { transform: scale(1); background: var(--accent); color: var(--accent-ink); box-shadow: 0 0 20px -4px var(--accent); }
  48%, 100% { transform: none; background: none; color: var(--accent); box-shadow: none; }
}
@keyframes demo-rain-fall { 0% { top: -30%; opacity: 0; } 15% { opacity: 1; } 85% { opacity: 1; } 100% { top: 115%; opacity: 0; } }
@keyframes demo-check-pop {
  0%, 72% { transform: scale(0.4); opacity: 0; }
  76% { transform: scale(1.08); opacity: 1; }
  80%, 97% { transform: scale(1); opacity: 1; }
  100% { transform: scale(0.4); opacity: 0; }
}

/* ---- full-screen background squares (mounted by scripts/labs-bg.js) ---- */
.labs-bg {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
}
.labs-bg__square {
  position: absolute;
  border-radius: var(--radius);
  background: var(--accent);
  opacity: 0;
  animation: labs-bg-fade 7s var(--ease) infinite;
}
@keyframes labs-bg-fade {
  0% { opacity: 0; transform: scale(0.92); }
  12% { opacity: var(--peak, 0.14); transform: scale(1); }
  55% { opacity: var(--peak, 0.14); }
  100% { opacity: 0; transform: scale(0.92); }
}
.labs-bg__square--static { animation: none; opacity: var(--peak, 0.08); }

/* ---- catalog: category filter dropdown, "more filters" menu, view dial, cards ---- */
.catalog-head { display: flex; flex-direction: column; gap: var(--sp-3); margin-bottom: var(--sp-4); }

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

/* Category filter — a dropdown built from the exact same component as the
   header's Browse-by-category menu (.lab-header__browse*), not a bespoke
   one, just dressed as a standalone toolbar button instead of a nav link. */
.cat-filter__toggle {
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 0.6em 0.9em;
  min-height: 40px;
  text-transform: none;
  letter-spacing: normal;
  font: 600 0.78rem/1 var(--font-sans);
}
.cat-filter__toggle:hover, .lab-header__browse.is-open .cat-filter__toggle { border-color: var(--accent); color: var(--text-head); }
.cat-filter__toggle .count-badge { font: 700 0.64rem var(--font-mono); background: var(--accent); color: var(--accent-ink); border-radius: var(--radius-pill); padding: 0.1em 0.5em; }
.cat-filter__menu { width: 17rem; z-index: 20; }

.more-filters { position: relative; }
.more-filters__trigger {
  display: inline-flex; align-items: center; gap: 0.5em; white-space: nowrap; border: 1px solid var(--border-strong); background: none; color: var(--text-body);
  border-radius: var(--radius); padding: 0.6em 0.9em; cursor: pointer; font: 600 0.78rem var(--font-sans); min-height: 40px;
}
/* Every inline SVG icon in this file needs an explicit size: a bare viewBox
   with no width/height defaults to the browser's intrinsic SVG size
   (300x150px) — the bug behind the oversized "More filters" icon. */
.more-filters__trigger svg { width: 14px; height: 14px; stroke: var(--text-mute); fill: none; stroke-width: 1.6; flex: none; }
.more-filters__trigger:hover, .more-filters__trigger[aria-expanded="true"] { border-color: var(--accent); color: var(--text-head); }
.more-filters__trigger:hover svg, .more-filters__trigger[aria-expanded="true"] svg { stroke: var(--text-head); }
.more-filters__trigger .count-badge { font: 700 0.64rem var(--font-mono); background: var(--accent); color: var(--accent-ink); border-radius: var(--radius-pill); padding: 0.1em 0.5em; }
.more-filters__panel {
  position: absolute; top: calc(100% + 0.5rem); left: 0; width: 260px; z-index: 20;
  background: var(--panel-raised); border: 1px solid var(--border-strong); border-radius: var(--radius);
  box-shadow: 0 24px 48px -20px rgba(0,0,0,.6); padding: 0.9rem;
}
.more-filters__panel.is-hidden { display: none; }
.filter-search { display: flex; align-items: center; gap: 0.5em; border: 1px solid var(--border-strong); border-radius: var(--radius); padding: 0.5em 0.7em; margin-bottom: 0.8rem; background: var(--ink); }
.filter-search svg { width: 13px; height: 13px; stroke: var(--text-mute); fill: none; stroke-width: 1.8; flex: none; }
.filter-search input { border: none; background: none; outline: none; color: var(--text-head); font: 0.82rem var(--font-sans); width: 100%; }
.filter-search input::placeholder { color: var(--text-mute); }
.more-filters__group + .more-filters__group { margin-top: 0.8rem; padding-top: 0.8rem; border-top: 1px solid var(--border); }
.more-filters__group-label { font: 700 0.62rem var(--font-mono); text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-mute); margin-bottom: 0.4rem; }
.more-filters__panel button[data-filter-axis] {
  display: block; width: 100%; text-align: left; border: none; background: none; color: var(--text-body);
  border-radius: var(--radius); padding: 0.4em 0.5em; cursor: pointer; font: 0.84rem var(--font-sans); min-height: 36px;
}
.more-filters__panel button[data-filter-axis]:hover { background: var(--ink); color: var(--text-head); }
.more-filters__panel button[data-filter-axis][aria-pressed="true"] { color: var(--accent); }
.more-filters__panel button[data-filter-axis][aria-pressed="true"]::before { content: "✓ "; }
.more-filters__panel button[data-filter-axis].is-search-hidden { display: none; }

.surprise-btn {
  display: inline-flex; align-items: center; gap: 0.5em; white-space: nowrap; border: 1px dashed var(--accent-line); background: none; color: var(--accent);
  border-radius: var(--radius); padding: 0.6em 0.9em; cursor: pointer; font: 600 0.78rem var(--font-sans); min-height: 40px;
}
.surprise-btn svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 1.6; flex: none; }
.surprise-btn:hover { background: var(--accent-soft); }

/* Deliberately NOT chip-shaped — a sunken icon-only dial, so it never reads as another filter. */
.view-dial { margin-left: auto; display: inline-flex; align-items: center; gap: 2px; padding: 3px; border-radius: var(--radius-pill); background: var(--panel-raised); border: 1px solid var(--border); flex: none; }
.view-dial button { width: 34px; height: 32px; display: flex; align-items: center; justify-content: center; border: none; background: none; border-radius: var(--radius-pill); color: var(--text-mute); cursor: pointer; }
.view-dial button svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 1.7; }
.view-dial button[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); }

/* one full-width card per lab, background + overview content, height hugs its own content */
.catalog-list { display: flex; flex-direction: column; gap: var(--sp-3); }
.catalog-card {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: var(--sp-2);
  width: 100%;
  min-height: 15rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--sp-5);
  background: var(--panel);
  text-decoration: none;
  color: inherit;
  transition: border-color var(--t-ui) var(--ease), box-shadow var(--t-ui) var(--ease);
}
.catalog-card:hover,
.catalog-card:focus-visible { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), 0 16px 32px -20px var(--accent); }
.catalog-card__content { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-2); }
.catalog-card__meta { display: flex; align-items: center; gap: var(--sp-3); }
.catalog-card__title { color: var(--text-head); font-size: 1.3rem; line-height: 1.3; margin: 0; }
.catalog-card__overview { color: var(--text-body); max-width: 72ch; margin: 0; }
.catalog-card__cta {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  margin-top: var(--sp-2);
  color: var(--accent);
  font: 600 0.78rem / 1 var(--font-sans);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.catalog-list.is-hidden { display: none; }

/* per-card architecture/flow animation — a default template, overridable per
   lab slug (see scripts/lab-card-anim.js). Anchored right, masked toward the
   left so it never fights the title/overview text. */
.catalog-card__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  opacity: 0.55;
  -webkit-mask-image: linear-gradient(to left, black 38%, transparent 82%);
  mask-image: linear-gradient(to left, black 38%, transparent 82%);
}
.catalog-card__bg svg { position: absolute; right: 0; top: 50%; transform: translateY(-50%); width: min(380px, 65%); height: auto; }
.catalog-card__bg .flow-node { fill: var(--border-strong); }
.catalog-card__bg .flow-node--end { fill: var(--accent); }
.catalog-card__bg .flow-line { stroke: var(--border-strong); stroke-width: 1.5; fill: none; }
.catalog-card__bg .flow-pulse {
  fill: var(--accent);
  offset-path: path("M20,45 L220,45");
  animation: flow-pulse var(--speed, 2.6s) linear infinite;
  animation-delay: var(--delay, 0s);
}
.catalog-card__bg .scatter-node {
  fill: var(--accent);
  animation: scatter-blink var(--speed, 2.6s) ease-in-out infinite;
  animation-delay: var(--delay, 0s);
  transform-origin: center;
  transform-box: fill-box;
}
.catalog-card__bg .flow-node--glow { animation: node-glow 2s ease-in-out infinite; }
/* Lesson pages' hand-authored "Under the hood" SVGs: same flow-pulse keyframe
   as the catalog cards, but each diagram's line geometry differs, so
   offset-path/fill/--speed/--delay are set inline per circle. */
[data-block="under-the-hood"] svg .flow-pulse {
  animation: flow-pulse var(--speed, 2.6s) linear infinite;
  animation-delay: var(--delay, 0s);
}
@keyframes flow-pulse {
  0% { offset-distance: 0%; opacity: 0; }
  8% { opacity: 1; }
  92% { opacity: 1; }
  100% { offset-distance: 100%; opacity: 0; }
}
@keyframes scatter-blink {
  0%, 100% { opacity: 0.22; transform: scale(0.85); }
  50% { opacity: 0.95; transform: scale(1); }
}
@keyframes node-glow {
  0%, 100% { filter: drop-shadow(0 0 0 transparent); }
  50% { filter: drop-shadow(0 0 6px var(--glow-color, var(--accent))); }
}

/* severity badge — a signal-strength meter, not a text pill: the bar count
   itself carries the severity, color reinforces it (semantic, not accent). */
.badge-severity {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  padding: 0.3em 0.6em 0.3em 0.5em;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  font: 600 0.65rem / 1 var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-body);
  flex: none;
}
.badge-severity__bars { display: inline-flex; align-items: flex-end; gap: 2px; height: 11px; }
.badge-severity__bars span { width: 3px; border-radius: 1px; background: var(--border-strong); }
.badge-severity__bars span:nth-child(1) { height: 4px; }
.badge-severity__bars span:nth-child(2) { height: 7px; }
.badge-severity__bars span:nth-child(3) { height: 11px; }
.badge-severity--catastrophic { border-color: var(--sig-bad); color: var(--sig-bad); }
.badge-severity--catastrophic .badge-severity__bars span.is-active { background: var(--sig-bad); }
.badge-severity--expensive { border-color: var(--sig-warn); color: var(--sig-warn); }
.badge-severity--expensive .badge-severity__bars span.is-active { background: var(--sig-warn); }
.badge-severity--annoying { color: var(--text-mute); }
.badge-severity--annoying .badge-severity__bars span.is-active { background: var(--text-mute); }

/* provenance badge — a dot style per source, not just a label */
.badge-provenance { display: inline-flex; align-items: center; gap: 0.5em; font: 0.72rem / 1 var(--font-sans); color: var(--text-mute); flex: none; }
.badge-provenance__dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.badge-provenance--classic .badge-provenance__dot { background: var(--accent); }
.badge-provenance--field-report .badge-provenance__dot { background: none; border: 1.5px solid var(--accent); }
.badge-provenance--derived .badge-provenance__dot { background: none; border: 1.5px dashed var(--border-strong); }

/* slide view: native scroll-snap track, one full-width card per slide */
.catalog-track {
  display: none;
  gap: var(--sp-3);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  padding-bottom: var(--sp-2);
}
.catalog-track.is-active { display: flex; }
.catalog-track .catalog-card { flex: 0 0 100%; width: auto; scroll-snap-align: center; }
.track-controls { display: flex; justify-content: flex-end; gap: var(--sp-2); margin-top: var(--sp-2); }
.track-controls.is-hidden { display: none; }
.track-controls button {
  width: 2.2rem;
  height: 2.2rem;
  border-radius: var(--radius);
  border: 1px solid var(--border-strong);
  background: var(--panel);
  color: var(--text-body);
  cursor: pointer;
  font-size: 1rem;
  font-family: var(--font-mono);
}
.track-controls button:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.track-controls button:disabled { opacity: 0.35; cursor: default; }

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}
