/* ============================================================================
   ook.css - the ookhq design system. Tokens, reset, and shared components.

   SOURCE OF TRUTH: site/shared/ook.css. The copies in site/root/,
   site/optimization/ and site/mergerino/ are written by deploy.sh - never edit
   a copy by hand. Every page links this file FIRST, then its section stylesheet
   (e.g. optimization/style.css), then its page stylesheet (e.g. guide/guide.css).

   Zero dependencies: no build step, no web fonts, no external requests.
   ========================================================================== */


/* ============================================================================
   1. TOKENS
   ========================================================================== */

:root {
  color-scheme: dark;
  accent-color: var(--accent-500);
  interpolate-size: allow-keywords;

  /* -- Surfaces: a deliberate ladder, page darkest, cards lifted ---------- */
  --bg-0: #100e0c;   /* page */
  --bg-1: #161412;   /* nav, footer, raised chrome */
  --bg-2: #221e1a;   /* cards - raised in r3 so a card reads lit, not painted on */
  --bg-3: #2a251f;   /* card hover, inset wells, chips, code */

  /* -- Foreground -------------------------------------------------------- */
  --fg-0: #f2ede4;   /* headings */
  --fg-1: #e8e2d8;   /* body */
  --fg-2: #b3aa9b;   /* secondary prose */
  --fg-3: #968c7c;   /* meta; 13px and up only. >=4.5:1 on --bg-2 */
  --fg-4: #7d7365;   /* DECORATIVE micro-caption inside a graphic. Never prose. */

  /* -- Brand ------------------------------------------------------------- */
  --accent-300: #f0b48c;
  --accent-400: #e0956b;   /* links */
  --accent-500: #d8703c;   /* brand, primary action */
  --accent-600: #b05a2e;
  --focus: #f0b48c;

  /* -- Lines: alpha so they compose over any surface --------------------- */
  --line-1: rgba(232, 226, 216, .08);   /* hairline dividers */
  --line-2: rgba(232, 226, 216, .14);   /* visible boundaries */
  --stroke: #7a6f5f;                    /* interactive control boundary, >=3:1 */

  /* -- Feedback ---------------------------------------------------------- */
  --ok: #86c98e;
  --teal: #5cbfa8;
  --warn-bg: #2b120d;
  --warn-fg: #f0b0a4;
  --warn-line: #c2452f;

  /* -- Status ramps: the evidence/impact taxonomy, all AA on their own bg -
     Critical sits at the red end on purpose: it has to be legible as "stop"
     next to the brand orange without borrowing its meaning. */
  --imp-crit-bg: #3a1414;  --imp-crit-fg: #f08a8a;  --imp-crit-line: #6b2222;
  --imp-high-bg: #3a2113;  --imp-high-fg: #eda06c;  --imp-high-line: #66431f;
  --imp-med-bg:  #363011;  --imp-med-fg:  #d9c25e;  --imp-med-line:  #5c531e;
  --imp-low-bg:  #262420;  --imp-low-fg:  #9d9689;  --imp-low-line:  #45403a;

  --dif-easy-bg: #1c2b16;  --dif-easy-fg: #9bc98a;  --dif-easy-line: #33512a;
  --dif-mod-bg:  #2e2a12;  --dif-mod-fg:  #cbb862;  --dif-mod-line:  #55511f;
  --dif-adv-bg:  #33161d;  --dif-adv-fg:  #d98a9d;  --dif-adv-line:  #5c2836;

  --risk-none-bg: #22201c; --risk-none-fg: #8f8779; --risk-none-line: #403b33;
  --risk-low-bg:  #262514; --risk-low-fg:  #b3ab68; --risk-low-line:  #494722;
  --risk-med-bg:  #322014; --risk-med-fg:  #cf9a66; --risk-med-line:  #5c3d22;
  --risk-rp-bg:   #3d1712; --risk-rp-fg:   #f0907f; --risk-rp-line:   #a03626;

  --ev-measured-bg:  #12302a; --ev-measured-fg:  #7cc7b4; --ev-measured-line:  #235c50;
  --ev-consensus-bg: #16283d; --ev-consensus-fg: #8ab3e8; --ev-consensus-line: #2a4a70;
  --ev-vendor-bg:    #271d3a; --ev-vendor-fg:    #b9a1e3; --ev-vendor-line:    #493768;
  --ev-inference-bg: #262420; --ev-inference-fg: #9d9689; --ev-inference-line: #45403a;

  /* -- Ambient light ------------------------------------------------------
     Two sources, not one: a warm key light off the upper left and a cooler,
     weaker fill off the upper right. Layering them is what stops the page
     reading as a single flat fill. Painted on a fixed viewport-wide layer, so
     no container edge can ever cut a visible seam through it. */
  --accent-glow:
    radial-gradient(ellipse 105% 68% at 14% -8%, rgba(216, 112, 60, .13), transparent 62%),
    radial-gradient(ellipse 85% 55% at 82% 4%, rgba(176, 90, 46, .085), transparent 68%);

  /* Film grain over the whole page: breaks up the flat dark fill and hides
     gradient banding on 8-bit panels. Inline SVG, so it costs no request. */
  --noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.75' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");

  /* The card fill: a top-lit sheen over the surface token, not a flat colour. */
  --card-sheen: linear-gradient(180deg, rgba(255, 255, 255, .035), rgba(255, 255, 255, .012));

  /* The tile fill: a real top-to-bottom fall-off rather than a tint over a flat
     colour. Declared once here so the destination card cannot be re-invented. */
  --tile-surface: linear-gradient(180deg, #221e1a 0%, #1b1714 100%);

  /* -- Type -------------------------------------------------------------- */
  --font-sans: system-ui, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, "Cascadia Mono", Consolas, monospace;

  --fs-display: clamp(2.25rem, 1.3rem + 4.2vw, 4.25rem);
  --fs-h1: clamp(2rem, 1.4rem + 2.2vw, 2.75rem);
  --fs-h2: clamp(1.5rem, 1.1rem + 1.3vw, 2rem);
  --fs-h3: 1.125rem;      /* small headings inside prose */
  --fs-h3-lg: 1.375rem;   /* card titles - fills the gap under --fs-h2 */
  --fs-body-lg: 1.125rem;
  --fs-body: 1rem;
  --fs-sm: .875rem;
  --fs-xs: .8125rem;    /* 13px - badges and meta floor */
  --fs-2xs: .75rem;     /* 12px - uppercase micro-labels ONLY */

  --tk-display: -.025em;
  --tk-h1: -.022em;
  --tk-h2: -.02em;
  --tk-h3: -.01em;
  --tk-label: .06em;

  --lh-display: 1.05;
  --lh-heading: 1.15;
  --lh-snug: 1.35;
  --lh-body: 1.6;
  --lh-label: 1.2;

  /* -- Space: 4px grid --------------------------------------------------- */
  --s1: 4px;   --s2: 8px;   --s3: 12px;  --s4: 16px;  --s5: 24px;
  --s6: 32px;  --s7: 48px;  --s8: 64px;  --s9: 96px;  --s10: 128px;

  --section-y: clamp(64px, 8vw, 128px);
  --gutter: clamp(20px, 4vw, 40px);
  --container: 1120px;
  --container-narrow: 780px;
  --measure: 68ch;
  --nav-h: 64px;

  /* THE aside cell. One width for the right-hand column of every hero on every
     page, so the aside's left and right edges land on the same two grid lines
     site-wide (at 1440 that is x776 and x1240). A section that puts a block in
     a right-hand column uses this same track, so its right edge closes on the
     grid line rather than on whatever its content happened to measure. */
  --aside: 464px;

  /* -- Radius ------------------------------------------------------------ */
  --r-sm: 6px;
  --r-md: 10px;
  --r-lg: 14px;
  --r-full: 999px;

  /* -- Elevation: the inset top hairline is what makes a dark card read lit */
  --sh-1: 0 1px 2px rgba(0, 0, 0, .45);
  --sh-2: 0 2px 4px rgba(0, 0, 0, .40), 0 8px 24px -8px rgba(0, 0, 0, .55);
  --sh-3: 0 4px 8px rgba(0, 0, 0, .40), 0 24px 48px -12px rgba(0, 0, 0, .60);
  --hairline: inset 0 1px 0 rgba(255, 255, 255, .05);
  /* Resting card elevation: --sh-1 plus a wide, very soft contact shadow, so a
     card sits on the page instead of being drawn on it. */
  --sh-card: var(--sh-1), 0 12px 32px -14px rgba(0, 0, 0, .75);
  /* Hover ring: a 1px accent halo outside the border, never a colour swap. */
  --ring-accent: 0 0 0 1px rgba(216, 112, 60, .10);

  /* -- Motion: the whole vocabulary. Nothing outside it. ----------------- */
  --dur-fast: 90ms;    /* press */
  --dur-base: 140ms;   /* hover, toggle */
  --dur-slow: 220ms;   /* expand, enter */
  --dur-page: 320ms;   /* view transition */
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --ease-std: cubic-bezier(.2, 0, 0, 1);
  --ease-inout: cubic-bezier(.65, 0, .35, 1);
}


/* ============================================================================
   2. RESET
   ========================================================================== */

*,
*::before,
*::after { box-sizing: border-box; }

html {
  background: var(--bg-0);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
  scrollbar-width: thin;
  scrollbar-color: var(--bg-3) transparent;
  scroll-padding-top: calc(var(--nav-h) + var(--s4));
}

@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

body,
h1, h2, h3, h4, h5, h6,
p, figure, blockquote, dl, dd { margin: 0; }

img, svg, video { max-width: 100%; display: block; }

button, input, select, textarea {
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
}

button {
  background: transparent;
  border: 0;
  padding: 0;
  text-align: inherit;
  cursor: pointer;
}

fieldset { min-inline-size: 0; border: 0; padding: 0; margin: 0; }
legend { padding: 0; }

::placeholder { color: var(--fg-3); opacity: 1; }

summary { cursor: pointer; list-style: none; }
summary::-webkit-details-marker { display: none; }

[hidden] { display: none !important; }

::selection { background: rgba(216, 112, 60, .30); }


/* ============================================================================
   3. PAGE FRAME
   ========================================================================== */

body {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--bg-0);
  color: var(--fg-1);
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Two layers behind everything, both fixed, both free: the light, then the
   grain that sits on top of it. Viewport-wide, so no container edge can cut a
   seam through either one. */
body::before {
  content: "";
  position: fixed;
  inset: 0 0 auto 0;
  height: 88vh;
  z-index: -2;
  pointer-events: none;
  background: var(--accent-glow);
}

body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: .035;
  background-image: var(--noise);
}

main { flex: 1 0 auto; padding-bottom: var(--section-y); }

.wrap {
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
  width: 100%;
}

.wrap-narrow { max-width: var(--container-narrow); }

/* Chapter rhythm: sections own their spacing, elements never do. */
main > section + section { margin-top: var(--section-y); }


/* ============================================================================
   4. TYPOGRAPHY
   ========================================================================== */

h1, h2, h3, h4, h5, h6 {
  color: var(--fg-0);
  font-weight: 600;
  line-height: var(--lh-heading);
  text-wrap: balance;
}

h1 { font-size: var(--fs-h1); letter-spacing: var(--tk-h1); }
h2 { font-size: var(--fs-h2); letter-spacing: var(--tk-h2); }
h3 { font-size: var(--fs-h3); letter-spacing: var(--tk-h3); }

p { text-wrap: pretty; }

a { color: var(--accent-400); }

/* Prose links: a quiet rule that fills in on hover. Scoped to paragraphs and
   definition bodies so no navigation link is ever caught by it. The plain
   currentColor border is declared first, so an engine without color-mix()
   still shows an underline. */
:where(p, dd) a {
  text-decoration: none;
  border-bottom: 1px solid currentColor;
  border-bottom-color: color-mix(in srgb, currentColor 35%, transparent);
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 0 1px;
  background-position: 0 100%;
  background-repeat: no-repeat;
  transition: background-size var(--dur-base) var(--ease-out),
              color var(--dur-base) var(--ease-std);
}

:where(p, dd) a:hover { background-size: 100% 1px; color: var(--accent-300); }

/* 600, not 700: at display size the heavier cut closes the counters and the
   headline stops reading as one silhouette. The tighter tracking does the work
   the extra weight was doing. */
.display {
  font-size: var(--fs-display);
  line-height: var(--lh-display);
  letter-spacing: var(--tk-display);
  font-weight: 600;
  color: var(--fg-0);
  max-width: 22ch;
  text-wrap: balance;
}

/* Wrap the closing clause of a headline in .nb and the break lands where the
   sentence breaks. text-wrap:balance evens the rag around it; where balance is
   unsupported this alone still prevents an orphaned last word. */
.nb { white-space: nowrap; }

.eyebrow {
  font-size: var(--fs-2xs);
  line-height: var(--lh-label);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--tk-label);
  color: var(--accent-400);
  margin-bottom: var(--s3);
}

/* Hero prose has exactly two tiers, and this is the first. The measure is set
   once by .hero-copy, so inside a hero this cap is released. */
.lede {
  font-size: var(--fs-body-lg);
  line-height: 1.5;
  color: var(--fg-2);
  max-width: 62ch;
  text-wrap: pretty;
}

/* The second and last tier: a byline, a caveat, a provenance line. Anything
   that wants a third tier wants a different component. */
.lede-sub {
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
  color: var(--fg-3);
  margin-top: var(--s4);
}


/* -- The hero: ONE band, ONE light, ONE measure, on every page ------------
   The band is a full-bleed <section class="hero"> with a .wrap inside it -
   never the other way round. That is what makes the light below land in the
   same place on all six pages: its percentages resolve against the viewport
   width every time, instead of against whatever column a page happened to
   nest the hero in. */
.hero {
  position: relative;
  z-index: 0;
  padding-block: clamp(56px, 8vw, 112px) var(--s7);
}

/* Two sources, off-centre and unequal, so the fold is lit rather than tinted:
   a broad warm key at the left third, and a weaker fill centred on the ASIDE
   CELL - 72% across, 320px down is the middle of the 464px right column, so the
   card in it sits inside a halo instead of reading as an unlit box on a flat
   field. Both fade to full transparency well inside the box, so no container
   edge can ever show as a lit rectangle - the usual way this effect fails. */
.hero::before {
  content: "";
  position: absolute;
  inset: -240px 0 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(ellipse 62% 560px at 34% 260px, rgba(216, 112, 60, .20), transparent 72%),
    radial-gradient(ellipse 44% 380px at 72% 320px, rgba(216, 112, 60, .10), transparent 70%);
}

/* One value for the headline-to-deck relationship, everywhere. */
.hero .display,
.hero h1 { margin-bottom: var(--s6); }
.hero .lede { margin-top: 0; }

/* Breadcrumbs open the band on the same rhythm everywhere. No page forks. */
.hero .crumbs { margin-bottom: var(--s5); }

/* No closing rule under the hero: the band ends because the next chapter
   starts a long way below it. A hairline here only ever read as an artifact. */
main > .hero + section { margin-top: var(--s9); }

/* Every hero opens with an action. Buttons wrap before they ever clip. */
.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
  margin-top: var(--s6);
}

/* Stacked buttons of different intrinsic widths read as a stair-step. One
   column, full width, no rag. */
@media (max-width: 39.99em) {
  .hero-actions {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--s3);
  }

  .hero-actions .btn,
  .hero-actions .dl-btn { width: 100%; }
}

/* The hero's two cells. Single column until there is room for a real second
   one. The right cell is a FIXED track, not a ratio: a fraction of the
   container resolves to a different width on every page that wraps its aside
   in different markup, and that is exactly how five heroes ended up with four
   different aside widths. One track, one pair of grid lines, every page.
   Alignment is START by default, so the aside's top edge sits on the same cap
   line as the headline block; a hero whose art is shorter than its copy can
   opt into bottom alignment from its own page layer. */
.hero-grid { display: grid; gap: var(--s6); }

@media (min-width: 64em) {
  .hero-grid {
    grid-template-columns: minmax(0, 1fr) var(--aside);
    gap: var(--s8);
    align-items: start;
  }
}

/* ONE measure for the whole copy column. Every child inherits it, and any
   per-element cap is released - that is what stops the eyebrow, the headline
   and the deck each ragging at a different width. */
.hero-copy { max-width: 34rem; }
.hero-copy > * { max-width: none; }

/* The guide's headline needs one more line's worth of room to sit on one
   line at desktop. Same component, one documented modifier. */
.hero-copy-wide { max-width: 44rem; }

.hero-art { min-width: 0; width: 100%; }

/* Tablet: the hero is still one column here, so a desktop-sized copy measure
   and a capped art frame together leave a quarter-width dead gutter down the
   whole band. Both caps are released for this range only - the column is the
   measure. */
@media (min-width: 48em) and (max-width: 63.99em) {
  .hero-copy { max-width: 40rem; }
  .hero-copy-wide { max-width: 44rem; }
  .hero-art > * { max-width: none; }
}

/* Numbers that update in place must not jitter. */
.tnum { font-variant-numeric: tabular-nums; }


/* ============================================================================
   5. FOCUS, SKIP LINK, SCREEN-READER UTILITIES
   ========================================================================== */

/* Specificity 0 via :where(), so every component keeps its own radius. */
:where(a, button, summary, input, textarea, select, [tabindex]):focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: 4px;
}

:where(a, button, summary, input, textarea, select, [tabindex]):focus:not(:focus-visible) {
  outline: none;
}

/* Custom controls are small and sit inside dense rows, so the generic 2px/2px
   ring lands too close to read as a ring at all. Given its own offset and
   radius here, once, so no page has to restyle focus to make it visible. */
input[type="checkbox"]:focus-visible,
input[type="radio"]:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
  border-radius: 7px;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip {
  position: fixed;
  top: var(--s3);
  left: var(--s3);
  z-index: 100;
  transform: translateY(calc(-100% - var(--s6)));
  background: var(--bg-2);
  color: var(--fg-0);
  border: 1px solid var(--stroke);
  border-radius: var(--r-sm);
  padding: var(--s2) var(--s4);
  font-size: var(--fs-sm);
  font-weight: 500;
  text-decoration: none;
  /* No resting shadow: the offscreen-hidden link's blur would bleed into the nav. */
  transition: transform var(--dur-base) var(--ease-out);
}

.skip:focus { transform: none; box-shadow: var(--sh-2); }


/* ============================================================================
   6. NAV - identical structure on every page of every subdomain
   ========================================================================== */

.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  height: var(--nav-h);
  background: color-mix(in srgb, var(--bg-1) 78%, transparent);
  backdrop-filter: saturate(180%) blur(12px);
  -webkit-backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--line-1);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .nav { background: var(--bg-1); }
}

.nav-inner {
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
  height: 100%;
  display: flex;
  align-items: center;
  gap: var(--s5);
}

.nav-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  color: var(--fg-0);
  font-size: var(--fs-body);
  font-weight: 600;
  letter-spacing: var(--tk-h3);
  text-decoration: none;
  transition: color var(--dur-base) var(--ease-std);
}

.nav-brand:hover { color: var(--accent-300); }
.mark { flex: none; }

.nav-links {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--s5);
}

.nav-links a {
  position: relative;
  display: inline-flex;
  align-items: center;
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--fg-2);
  text-decoration: none;
  white-space: nowrap;
  transition: color var(--dur-base) var(--ease-std);
}

/* A 44px hit area around a 22px label, without growing the box the active
   underline is measured from. */
.nav-links a::before {
  content: "";
  position: absolute;
  inset: -11px -6px;
}

.nav-links a:hover { color: var(--fg-0); }
.nav-links a[aria-current="page"] { color: var(--fg-0); }

/* Anchored to the link, not to the bar: the mark points at the word it means. */
.nav-links a[aria-current="page"]::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -6px;
  height: 2px;
  border-radius: var(--r-full);
  background: var(--accent-500);
}

/* Cross-subdomain "back to ookhq.com" link. A defined pill, not a trailing
   word after a pipe: the bar has to resolve at its right edge, not fade out. */
/* A ghost pill, deliberately: the primary-action slot at the end of a nav bar
   belongs to an action, and this is a cross-subdomain hop. Visible edge, no
   fill - it must never read as the page's CTA. */
.nav-links .nav-home {
  margin-left: var(--s1);
  padding: 6px 12px;
  background: transparent;
  border: 1px solid var(--line-2);
  border-radius: var(--r-full);
  color: var(--fg-2);
  transition: color var(--dur-base) var(--ease-std),
              border-color var(--dur-base) var(--ease-std);
}

.nav-links .nav-home::before { inset: -6px -2px; }
.nav-links .nav-home:hover { border-color: var(--line-2); color: var(--fg-0); }

/* -- Mobile disclosure: no JavaScript, no dependency -------------------- */
.nav-menu { display: none; margin-left: auto; }

/* The trigger is a bars glyph, never a chevron: a right chevron means
   "navigate forward", which is not what opening a panel does. */
.nav-menu > summary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  min-height: 44px;
  min-width: 44px;
  padding-inline: var(--s3);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  color: var(--fg-1);
  font-size: var(--fs-sm);
  font-weight: 500;
  transition: border-color var(--dur-base) var(--ease-std),
              color var(--dur-base) var(--ease-std);
}

/* Above the scrim, so the trigger stays a close button once the panel is up. */
.nav-menu > summary { position: relative; z-index: 1; }
.nav-menu > summary:hover { border-color: var(--stroke); color: var(--fg-0); }

/* An element with backdrop-filter becomes the containing block for its
   position:fixed descendants - which would collapse the scrim below to the
   height of the bar, i.e. to nothing. So while the panel is open the bar drops
   its blur and goes solid. It is sitting on its own opaque panel at that
   point, so there is nothing left for a translucent bar to reveal. */
.nav:has(.nav-menu[open]) {
  background: var(--bg-1);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* The open panel dims and blurs the page under it, so the menu is a layer
   rather than a box floating on live content. Starts below the bar, so the
   trigger is never covered by its own scrim. */
.nav-menu[open]::before {
  content: "";
  position: fixed;
  inset: var(--nav-h) 0 0 0;
  background: rgba(10, 8, 7, .72);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  pointer-events: none;
}

.nav-menu[open] > summary {
  border-color: var(--accent-500);
  color: var(--fg-0);
}

/* Open state: the two bars rotate into a close cross. Each bar is translated
   onto the centre first, then rotated about it - CSS applies the functions
   right to left, so the order below reads backwards on purpose. */
.menu-bars path {
  transform-box: view-box;
  transform-origin: 12px 12px;
  transition: transform var(--dur-base) var(--ease-std);
}

.nav-menu[open] > summary .menu-bars .bar-1 { transform: rotate(45deg) translate(0, 4px); }
.nav-menu[open] > summary .menu-bars .bar-2 { transform: rotate(-45deg) translate(0, -4px); }

/* Below 400px the word is redundant next to the glyph and costs a quarter of
   the bar. */
@media (max-width: 25em) {
  .nav-menu > summary { padding-inline: var(--s2); }
  .nav-menu > summary > span { display: none; }
}

.nav-menu > nav {
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  z-index: 2;
  display: flex;
  flex-direction: column;
  padding: var(--s2) var(--gutter) var(--s4);
  background: var(--bg-1);
  border-bottom: 1px solid var(--line-1);
  box-shadow: var(--sh-3);
}

.nav-menu > nav a {
  display: flex;
  align-items: center;
  min-height: 48px;
  color: var(--fg-1);
  font-size: var(--fs-body);
  font-weight: 500;
  text-decoration: none;
  border-bottom: 1px solid var(--line-1);
}

.nav-menu > nav a:last-child { border-bottom: 0; }
.nav-menu > nav a[aria-current="page"] { color: var(--accent-300); }

@media (max-width: 45em) {
  .nav-links { display: none; }
  .nav-menu { display: block; }
}

/* A FULL bar collapses earlier, and only a full one. 45em was measured on a bar
   carrying six links plus the cross-domain pill; the optimization subdomain now
   carries seven (Overview, Guide, Config Tuner, PC Audit, Commands, Binds,
   Gallery) plus the search control, and the row runs out of room sooner.

   MEASURED, not computed (2026-08-17, /guide/ in Chromium, this rule suppressed
   and every other rule in place, viewport swept 1440 down to 721): the eight-item
   row overflows neither the document nor .nav-inner at any width in that sweep -
   the search control's own rules (search.css, 45.01-62em) drop the rhythm to
   12px and shrink the control to a 40px icon through the middle of the band, and
   the gaps either side of the links stay positive all the way down (99px at
   980, 62px at 900, 44px at 860, 16px at 800). What DOES give is the brand: it
   holds one line to 792px and wraps to two below that.

   So the line is the brand, at 50em - the last width where the whole bar is one
   line with room to spare. Below it the heavy bar goes to the menu, which
   carries every one of those links at 48px a row, while a three-link bar
   (ookhq.com, Mergerino, the gallery) keeps the wide layout down to 45em.
   :nth-child(8) is the count; :has() is how a stylesheet asks a question about
   it. Where :has() is unsupported the bar falls back to the rule above: tight
   between 721 and 800px, never broken markup, and no route lost.

   An earlier version of this rule collapsed at 64em on a computed estimate that
   the row was ~70px over through that band. It is not; the estimate did not
   account for the rules search.css already applies there, and it cost every
   viewport from 800 to 1024px - landscape tablets among them - its whole route
   bar. */
@media (max-width: 50em) {
  .nav-inner:has(.nav-links > :nth-child(8)) .nav-links { display: none; }
  .nav-inner:has(.nav-links > :nth-child(8)) .nav-menu { display: block; }
}

/* One band above the cap, where the wide search control is back but the
   container has not reached its 1120px maximum: eight items, the brand and the
   116px control inside a content box of 945px at 1025px wide. One rhythm step
   down gives the row about 70px more than it needs; past 72em the designed 24px
   returns. Nothing else in the bar changes. */
@media (min-width: 64.01em) and (max-width: 72em) {
  .nav-inner:has(.nav-links > :nth-child(8)) { gap: var(--s4); }
  .nav-inner:has(.nav-links > :nth-child(8)) .nav-links { gap: var(--s4); }
}

/* -- Identity: the account control at the right end of the bar -----------
   The slot is the LAST flex child of .nav-inner, after .nav-menu, so the
   account control is the last thing in the bar at every width - the wide bar
   ends brand / links / search / account, and the narrow one ends menu /
   account.

   It ships EMPTY (identity.js fills it once /api/me answers, and leaves it
   empty if it never does), so the empty state has to cost nothing: no box, no
   size, and - the part a `padding: 0` alone would miss - no gap. A zero-sized
   flex child still takes one `gap` from .nav-inner and shifts the whole bar
   left, which is why :empty removes it from the flex flow entirely rather
   than just hiding its paint. */
#ook-identity {
  position: relative;
  flex: none;
  display: flex;
  align-items: center;
}

#ook-identity:empty { display: none; }

/* The two states are one control slot: a bordered pill signed out, a 32px
   round avatar signed in. Both are 40px tall, the height the bar's other
   control (the menu trigger) already uses, so the bar's baseline does not
   move when a member signs in. */
.ook-id-in,
.ook-id-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  min-height: 40px;
  font-size: var(--fs-sm);
  font-weight: 500;
  white-space: nowrap;
  transition: border-color var(--dur-base) var(--ease-std),
              color var(--dur-base) var(--ease-std);
}

/* 44px of hit area around a 40px control, without growing the box. */
.ook-id-in::before,
.ook-id-btn::before {
  content: "";
  position: absolute;
  inset: -2px -4px;
}

.ook-id-in {
  padding-inline: var(--s3);
  border: 1px solid var(--stroke);
  border-radius: var(--r-full);
  color: var(--fg-1);
  text-decoration: none;
}

.ook-id-in:hover { border-color: var(--accent-500); color: var(--fg-0); }

/* The signed-in trigger admits to being a control at rest: the same 1px
   stroke the sign-in pill and the menu trigger carry, so at 400px - where the
   name is hidden and only the avatar remains - it is a bordered pill beside
   two bordered buttons, not a loose circle. Hover brightens it, as the pill. */
.ook-id-btn {
  padding: 4px;
  padding-inline-end: var(--s2);
  background: transparent;
  border: 1px solid var(--stroke);
  border-radius: var(--r-full);
  color: var(--fg-1);
  cursor: pointer;
}

.ook-id-btn:hover { border-color: var(--accent-500); color: var(--fg-0); }

.ook-id-avatar {
  flex: none;
  width: 32px;
  height: 32px;
  border-radius: var(--r-full);
  background: var(--bg-3);
  object-fit: cover;
}

.ook-id-name,
.ook-id-headname {
  max-width: 14ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Right-anchored under the button, 220px wide, and never wider than the
   viewport allows: at 400px the gutter leaves 360px, so the panel has room
   with the bar's own margin to spare. */
.ook-id-menu {
  position: absolute;
  top: calc(100% + var(--s2));
  right: 0;
  z-index: 3;
  width: 220px;
  max-width: calc(100vw - 2 * var(--gutter));
  display: flex;
  flex-direction: column;
  padding: var(--s1) 0;
  background: var(--bg-1);
  border: 1px solid var(--line-2);
  border-radius: var(--r-md);
  box-shadow: var(--sh-3);
}

/* .ook-id-menu declares a display, so the UA's [hidden] rule loses. */
.ook-id-menu[hidden] { display: none; }

.ook-id-head {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s2) var(--s3);
  color: var(--fg-0);
  font-size: var(--fs-sm);
  font-weight: 600;
  border-bottom: 1px solid var(--line-1);
}

.ook-id-item {
  display: flex;
  align-items: center;
  min-height: 40px;
  padding-inline: var(--s3);
  background: transparent;
  border: 0;
  color: var(--fg-1);
  font-size: var(--fs-sm);
  font-weight: 500;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--dur-base) var(--ease-std),
              color var(--dur-base) var(--ease-std);
}

.ook-id-item:hover { background: var(--bg-3); color: var(--fg-0); }

.ook-id-divider {
  height: 1px;
  margin: var(--s1) 0;
  background: var(--line-1);
}

/* Below 400px the bar is down to glyphs: the avatar carries the identity on
   its own (the button keeps its name in aria-label), and the sign-in control
   drops the service word rather than the verb. */
@media (max-width: 25em) {
  .ook-id-name { display: none; }
  .ook-id-in-svc { display: none; }
  .ook-id-btn { padding-inline-end: 4px; }
}

/* -- Room for the account control on the eight-item bar ------------------
   MEASURED (tools/pw/identity.js sweeps 360-1440 with the widget rendered):
   the optimization row had about 16px of slack left at 800px BEFORE this
   control existed - the note above section 6's 50em rule says so - so an
   account control cannot simply be added to it. Two rules make room, in the
   order that costs least:

   1. Up to 72em the control goes compact on the eight-item bar only - the
      label drops to "Sign in" and the signed-in button to the avatar alone,
      which is the widest of the two states and the one that decides the fit
      (a display name can run to 14ch). The three-link bars (ookhq.com,
      Mergerino, the gallery) have room and keep both at every width.
   2. Up to 64em the eight-item route row goes to the menu, which already
      carries every one of those links at 48px a row - but ONLY while the slot
      actually holds a control. With scripting off, or before /api/me answers,
      the slot is empty, nothing was added to the bar, and the 50em line above
      still governs: a no-script reader loses no route.

   Where :has() is unsupported the fallback is the 45em rule, as it is for the
   50em line: the row is tight through the same band rather than broken. */
@media (max-width: 72em) {
  .nav-inner:has(.nav-links > :nth-child(8)) .ook-id-in-svc { display: none; }
  .nav-inner:has(.nav-links > :nth-child(8)) .ook-id-name { display: none; }
  .nav-inner:has(.nav-links > :nth-child(8)) .ook-id-btn { padding-inline-end: 4px; }
}

@media (max-width: 64em) {
  .nav-inner:has(.nav-links > :nth-child(8)):has(#ook-identity:not(:empty)) .nav-links { display: none; }
  .nav-inner:has(.nav-links > :nth-child(8)):has(#ook-identity:not(:empty)) .nav-menu { display: block; }
}

/* 3. Above 72em the full-width bar needs one rhythm step down as well, and for
   the same reason the 64.01-72em rule above already takes it: MEASURED at
   1366 with the widget rendered, the eight-item row plus the account control
   came to 1061px inside a 1040px content box, and what gave was the brand -
   it wrapped "OOK HQ" onto two lines rather than overflowing, which no
   overflow check would have caught. 24px off .nav-inner's gaps and 8px off
   each of the row's seven puts the bar back at about 1012px with the name and
   the full label both showing. Again only while the slot holds something. */
@media (min-width: 72.01em) {
  .nav-inner:has(.nav-links > :nth-child(8)):has(#ook-identity:not(:empty)) { gap: var(--s4); }
  .nav-inner:has(.nav-links > :nth-child(8)):has(#ook-identity:not(:empty)) .nav-links { gap: var(--s4); }
}


/* ============================================================================
   7. FOOTER
   ========================================================================== */

.site-footer {
  margin-top: auto;
  border-top: 1px solid var(--line-1);
  padding-block: var(--s8);
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
  color: var(--fg-3);
}

.footer-inner {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s5) var(--s6);
  align-items: flex-start;
  justify-content: space-between;
}

/* The prose column is capped so it can never push the nav onto a second row -
   the footer reads side-by-side on every page or on none of them. */
.footer-inner > div {
  flex: 1 1 42ch;
  min-width: 0;
}

/* The footer is the least important region on the page, so it gets the lightest
   colour and the shortest measure - not, as it was, the densest text block set
   at full body weight. */
.footer-note { color: var(--fg-3); font-size: var(--fs-sm); max-width: 52ch; line-height: 1.6; }
.footer-note + .footer-note { margin-top: var(--s3); }
.footer-note + .footer-meta { margin-top: var(--s3); }

/* Two fixed tracks filled COLUMN-FIRST, not an inline row that rags into 3/3/1,
   and not a row-first grid that scrambles the reading order across the gutter
   (Guide / Config Tuner, PC Audit / Mergerino). Four rows = one heading plus
   three links per column; a single heading spans both tracks instead, so both
   footer shapes on the site read top-to-bottom. Tracks are content-sized with a
   floor, so the block hugs its own content and its right edge lands on the
   container's grid line on every page rather than wherever a fr-track happened
   to end. */
.footer-nav {
  display: grid;
  grid-template-columns: repeat(2, max-content);
  grid-template-rows: repeat(4, auto);
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  justify-content: end;
  align-content: start;
  column-gap: var(--s6);
  row-gap: var(--s2);
  margin-left: auto;
}

/* A labelled column, not a loose row of words. One heading serves both tracks;
   two headings take one track each. */
.footer-navlabel {
  margin: 0 0 var(--s1);
  font-size: var(--fs-2xs);
  line-height: var(--lh-label);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--tk-label);
  color: var(--fg-3);
}

.footer-navlabel:only-of-type { grid-column: 1 / -1; grid-row: 1; }

.footer-nav a {
  color: var(--fg-2);
  text-decoration: none;
  transition: color var(--dur-base) var(--ease-std);
}

.footer-nav a:hover { color: var(--fg-0); }
.footer-nav a[aria-current="page"] { color: var(--fg-0); }

.footer-meta { color: var(--fg-3); font-size: var(--fs-xs); max-width: 60ch; }


/* ============================================================================
   8. BUTTONS
   .dl-btn is the tool-page download CTA; it is the same component.
   ========================================================================== */

/* The primary fill is a gradient with a lighter band stacked above the resting
   one; hover slides the window up that band. background-position animates on
   the compositor, so the brightening costs nothing and never blurs text the
   way filter: brightness() does. */
.btn,
.dl-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  min-height: 40px;
  padding-inline: var(--s5);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  background-color: var(--accent-500);
  background-image: linear-gradient(180deg, #f2a061 0%, #e0803f 50%, #c3652c 100%);
  background-size: 100% 200%;
  background-position: 50% 100%;
  background-repeat: no-repeat;
  color: #1b1410;
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: var(--lh-label);
  text-decoration: none;
  cursor: pointer;
  /* At rest: a top highlight and a contact shadow, and nothing else. The warm
     outer halo is a hover reward, not a resting decoration - carrying it all
     the time is what dates a CTA. */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .24),
              0 1px 2px rgba(0, 0, 0, .45);
  transition: background-position var(--dur-base) var(--ease-std),
              background-color var(--dur-base) var(--ease-std),
              border-color var(--dur-base) var(--ease-std),
              color var(--dur-base) var(--ease-std),
              box-shadow var(--dur-base) var(--ease-std),
              transform var(--dur-base) var(--ease-out);
}

.btn:hover,
.dl-btn:hover {
  background-position: 50% 0;
  background-color: var(--accent-400);
  transform: translateY(-1px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .32),
              0 2px 4px rgba(0, 0, 0, .40),
              0 10px 24px -8px rgba(216, 112, 60, .50);
}

.btn:active,
.dl-btn:active {
  transform: translateY(1px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .22), var(--sh-1);
  transition-duration: var(--dur-fast);
}

.btn-secondary,
.dl-btn.secondary {
  background-image: none;
  background-color: var(--bg-2);
  border-color: var(--stroke);
  color: var(--fg-1);
  box-shadow: var(--sh-1), var(--hairline);
}

.btn-secondary:hover,
.dl-btn.secondary:hover {
  background-color: var(--bg-3);
  border-color: var(--fg-3);
  color: var(--fg-0);
  box-shadow: var(--sh-2), var(--hairline);
}

/* Outline-quiet, but never invisible: a secondary action still has an edge. */
.btn-ghost {
  background-image: none;
  background-color: transparent;
  border-color: var(--line-2);
  color: var(--fg-1);
  box-shadow: none;
}

.btn-ghost:hover {
  background-color: var(--bg-2);
  border-color: var(--stroke);
  color: var(--fg-0);
  box-shadow: var(--sh-1);
}

/* A disabled primary stops looking like a primary: no fill to promise a click
   that will not happen. */
.btn:disabled,
.dl-btn:disabled,
.btn[aria-disabled="true"],
.dl-btn[aria-disabled="true"] {
  background-image: none;
  background-color: transparent;
  border: 1px dashed var(--stroke);
  color: var(--fg-3);
  box-shadow: none;
  cursor: not-allowed;
  transform: none;
}

.btn-lg,
.dl-btn.lg {
  min-height: 48px;
  padding-inline: var(--s6);
  font-size: var(--fs-body);
}

@media (pointer: coarse) {
  .btn, .dl-btn { min-height: 44px; }
}


/* ============================================================================
   9. CARDS - one surface, declared once
   Every panel on the site is this surface: a top-lit sheen over --bg-2, a
   visible edge, and a soft contact shadow. The aliases below are the section
   components (hub paths, principles, guide entries, tuner rows, install
   panels, gallery review rows); they add layout and nothing else, so no page
   can invent a second card. Radius/padding variants belong in the section
   stylesheet.
   ========================================================================== */

.card,
.rail-card,
.panel,
.rec-row,
.entry,
.install-how,
.rowitem {
  background-color: var(--bg-2);
  background-image: var(--card-sheen);
  border: 1px solid var(--line-2);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-card), var(--hairline);
}

a.card,
.card-link {
  display: flex;
  flex-direction: column;
  color: inherit;
  text-decoration: none;
  transition: background-color var(--dur-base) var(--ease-std),
              border-color var(--dur-base) var(--ease-std),
              box-shadow var(--dur-base) var(--ease-std),
              transform var(--dur-base) var(--ease-out);
}

/* A linked card lifts, warms its edge and picks up an accent halo. The halo is
   a shadow ring rather than a border swap, so nothing shifts by a pixel. */
a.card:hover,
.card-link:hover,
a.card:focus-visible,
.card-link:focus-visible {
  background-color: var(--bg-3);
  border-color: rgba(216, 112, 60, .38);
  box-shadow: var(--sh-2), var(--hairline), var(--ring-accent);
  transform: translateY(-1px);
}

a.card:active,
.card-link:active {
  transform: translateY(1px);
  transition-duration: var(--dur-fast);
}

/* Card titles sit a full step above body copy - the ramp has a rung here.
   Declared on every card-shaped component at once, so no page can fall through
   to the 18px prose h3 and ship a card title 4px smaller than its neighbours. */
.card > h3,
.tile > h3,
.principle > h3,
.panel > h3,
.feature > h3 {
  font-size: var(--fs-h3-lg);
  letter-spacing: var(--tk-h2);
  color: var(--fg-0);
}

/* -- The rail card: the hero's side panel, on every tool page ------------
   One header shape in this slot: an accent eyebrow over a card-scale heading.
   No page-private micro-label class belongs here. */
.rail-card { padding: var(--s5); }
.rail-card > .eyebrow { margin-bottom: var(--s2); }

.rail-card > h2 {
  font-size: var(--fs-h3-lg);
  letter-spacing: var(--tk-h2);
}

/* -- Card icon canon ----------------------------------------------------
   A bare stroke mark in the accent, stacked ABOVE the title, never inline
   beside the heading. The one documented exception is the principles band,
   where the icon carries no card around it and needs its own anchor - that
   variant lives in the section layer, not here. */
:where(.tile, .principle, .rail-card, .feature, .panel) > .icon {
  flex: none;
  color: var(--accent-400);
  margin-bottom: var(--s2);
}

/* A block that fills a right-hand grid cell closes on the GRID line, not on
   whatever its content happened to measure: the prose cap is a maximum, never
   a target. This is what keeps a column of section blocks from ragging by
   70px band to band. */
:where(.note, .install-how, .rail-card, .panel) { width: 100%; }


/* ============================================================================
   9b. TILE - THE destination card. One component, every subdomain.
   This is the only elevated, clickable surface on the site: the landing page's
   three destinations, the Overview's three paths, Mergerino's features. It is
   deliberately a stronger surface than .card so a destination never reads as
   the same object as a paragraph in a box.
   ========================================================================== */

.tile {
  display: flex;
  flex-direction: column;
  padding: var(--s5);
  background: var(--tile-surface);
  border: 1px solid var(--line-2);
  border-radius: var(--r-lg);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .045),
              0 10px 28px rgba(0, 0, 0, .40);
  color: inherit;
  text-decoration: none;
}

/* A linked tile lifts, warms its edge and picks up an accent halo. The halo is
   a shadow ring rather than a border swap, so nothing shifts by a pixel. */
a.tile {
  transition: border-color var(--dur-base) var(--ease-std),
              box-shadow var(--dur-base) var(--ease-std),
              transform var(--dur-base) var(--ease-out);
}

a.tile:hover,
a.tile:focus-visible {
  border-color: rgba(224, 149, 107, .34);
  transform: translateY(-1px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .06),
              var(--sh-2),
              var(--ring-accent);
}

a.tile:active { transform: translateY(1px); transition-duration: var(--dur-fast); }

/* The eyebrow row: what this destination is for, and the affordance that it
   is one. The chevron is visible AT REST - a link that only admits to being a
   link on hover is a content block to everyone who never hovers. */
.tile-kicker {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s2);
  font-size: var(--fs-2xs);
  line-height: var(--lh-label);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--tk-label);
  color: var(--accent-400);
}

/* At rest it is warm and clearly present, not a grey ghost: this chevron is the
   only thing telling a reader the whole card is a link. */
.tile-kicker .icon {
  flex: none;
  opacity: .75;
  transform: translateX(0);
  color: var(--accent-400);
  transition: opacity var(--dur-base) var(--ease-std),
              transform var(--dur-base) var(--ease-out),
              color var(--dur-base) var(--ease-std);
}

.tile:hover .tile-kicker .icon,
.tile:focus-within .tile-kicker .icon {
  opacity: 1;
  transform: translateX(3px);
  color: var(--accent-300);
}

/* A tile is a designed surface, not a paragraph in a box, so the internal ramp
   is steepened: the title sits a clear step above a body set at small size.
   1.3125rem rather than the --fs-h3-lg rung - a card title should not be within
   a hair of the section heading above it. */
.tile > h3 {
  margin: var(--s3) 0 var(--s2);
  font-size: 1.3125rem;
  letter-spacing: -.012em;
}

.tile p {
  margin: 0 0 var(--s5);
  color: var(--fg-2);
  font-size: var(--fs-sm);
  line-height: 1.6;
}

/* Sunk one step, so the token stays readable against the tile's own fill. */
.tile code { background: var(--bg-1); border-color: var(--line-1); }

/* Chip styling is reserved for STANDALONE copy targets. Inside running prose a
   padded, bordered token opens visible gaps on the line and breaks the
   paragraph's even colour, so a code span in tile copy keeps only its face. */
.tile p code {
  background: none;
  border: 0;
  padding: 0;
  font-size: .92em;
  color: var(--fg-1);
}

/* The compact supporting-facts variant: chips under a hairline, pushed to the
   bottom edge so the rule lands on one baseline across a row. No reserved
   min-height - a reserved line only ever showed as a dead band in the cards
   that did not use it. */
.tile-meta {
  margin-top: auto;
  padding-top: var(--s4);
  border-top: 1px solid var(--line-1);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  font-variant-numeric: tabular-nums;
}

.tile-meta span {
  padding: 4px 10px;
  border: 1px solid var(--line-1);
  border-radius: var(--r-full);
  background: var(--bg-3);
  color: var(--fg-1);
  font-size: var(--fs-xs);
  line-height: var(--lh-label);
}

/* One grid for every tile row on the site: one column, then two, then three.
   No full-width-last-child variant - a lone half-width card is the canon, and
   a stretched one always broke its own copy across the gutter. */
.tile-grid {
  display: grid;
  gap: var(--s5);
  grid-template-columns: 1fr;
}

@media (min-width: 40em) {
  .tile-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* A three-up row has no honest two-column form: it lands as 2 + 1 and the
   orphan reads as a mistake. Between the two- and three-column breakpoints a
   three-tile row therefore stays ONE full-width column - the tiles are wider,
   the rhythm is even, and the tablet layout stops looking like an unreflowed
   desktop. Rows of two, four or six are unaffected. */
@media (min-width: 40em) and (max-width: 61.99em) {
  .tile-grid:has(> :nth-child(3):last-child) { grid-template-columns: minmax(0, 1fr); }
}

@media (min-width: 62em) {
  .tile-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }

  /* More room once the row is three-up: a destination card carries the page's
     densest single block of copy and should not wear the same padding as a
     chip strip. */
  .tile { padding: var(--s6); }
}


/* ============================================================================
   9c. SHEAD - THE section header, everywhere
   ========================================================================== */

.shead { margin-bottom: var(--s6); }
.shead h2 { margin-bottom: 0; }
.shead p { color: var(--fg-2); max-width: var(--measure); margin: var(--s2) 0 0; }


/* ============================================================================
   9d. STAT-ROW - THE supporting-facts pattern, big-number variant
   The compact variant is .tile-meta's chips. There is no third form.
   ========================================================================== */

.stat-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s5) var(--s6);
}

.stat-row > * { display: flex; flex-direction: column; gap: 2px; }

.stat-row b {
  font-size: var(--fs-h3-lg);
  font-weight: 600;
  line-height: var(--lh-heading);
  letter-spacing: var(--tk-h2);
  color: var(--fg-0);
  font-variant-numeric: tabular-nums;
}

.stat-row span {
  font-size: var(--fs-2xs);
  line-height: var(--lh-label);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--tk-label);
  color: var(--fg-3);
}


/* ============================================================================
   9e. TRACE - the signature hero graphic
   An honest schematic, not a screenshot and not a fabricated product UI: a
   spiky frame-time trace over a flat one. The frame is the site's deepest
   surface; the drawing inside it is pure design-system colour.
   Canonical markup lives beside this rule in the page sources - copy it, never
   fork it. To make it smaller, cap the FRAME; never edit the SVG.
   ========================================================================== */

.trace {
  position: relative;
  overflow: hidden;
  /* Never drawn larger than it was authored. Above this the strokes thin out
     against the box and the frame reads as an empty panel. Released at tablet
     (below), where the art stacks under the copy and a cap would leave a dead
     gutter beside it. */
  max-width: 540px;
  padding: var(--s4);
  background: var(--tile-surface);
  border: 1px solid var(--line-2);
  border-radius: var(--r-lg);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .05),
              0 1px 2px rgba(0, 0, 0, .5),
              0 24px 60px -20px rgba(0, 0, 0, .85);
}

.trace svg { display: block; width: 100%; height: auto; }

/* Every colour in the drawing is set HERE, never as a var() inside an SVG
   presentation attribute: attribute-level var() substitution is not dependable
   across engines, and a signature graphic cannot be the thing that falls back
   to black. The strokes keep their authored weight at any render size via
   vector-effect, so the trace stays crisp when the cell narrows. */
.trace .t-grid  { stroke: rgba(255, 255, 255, .05); }
/* Faint enough that the area fill dies INTO the baseline instead of stopping
   dead on a hard rule - the tell that gave the old fill its "bar" reading. */
.trace .t-axis  { stroke: rgba(255, 255, 255, .06); }
/* The "before" series is the whole point of the graphic, so it is not allowed
   to be the faintest mark in the card. */
.trace .t-before { stroke: var(--fg-2); }
.trace .t-after { stroke: var(--accent-500); }
/* A vertical fall-off, not a flat slab: the fill carries the accent at the
   line and reaches full transparency at the axis. The literal colour after the
   reference is the SVG paint fallback, so a frame without the gradient def
   still renders warm instead of black. */
.trace .t-area  { fill: url(#t-fill) rgba(216, 112, 60, .18); }
.trace .t-stop  { stop-color: var(--accent-500); }

.trace text {
  fill: var(--fg-3);
  font-family: var(--font-sans);
  font-size: 15px;          /* user units: ~12px once the 540-wide box scales */
  font-weight: 500;
  letter-spacing: .06em;
}

/* The honesty disclaimer is a caveat, not an axis label: same family, one rung
   quieter, so it stops competing with the data it is disclaiming. */
.trace text.t-note {
  font-size: 13px;
  fill: var(--fg-4);
  letter-spacing: .12em;
}

/* Tablet: the art stacks under the copy and owns the full column. */
@media (min-width: 48em) and (max-width: 63.99em) {
  .trace { max-width: none; }
}

/* Mobile keeps the graphic - it is the page's only visual, and dropping it left
   the fold ending on two buttons above a dead band. It is simplified instead:
   tighter frame, the optional axis caption dropped, and the captions sized up
   in user units so they survive the smaller render scale. */
@media (max-width: 47.99em) {
  .trace { padding: var(--s3); }
  .trace .t-opt { display: none; }
  .trace text { font-size: 20px; }
  .trace text.t-note { font-size: 17px; }
}


/* ============================================================================
   9f. NOTE - THE inline caveat. One left rule, one optional leading mark.
   ========================================================================== */

.note {
  display: flex;
  gap: var(--s3);
  border-left: 2px solid var(--accent-500);
  background: var(--bg-2);
  border-radius: 0 var(--r-md) var(--r-md) 0;
  padding: var(--s4) var(--s5);
  color: var(--fg-2);
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
  max-width: var(--measure);
}

.note .icon { flex: none; color: var(--accent-400); margin-top: 1px; }
.note p { margin: 0; }


/* ============================================================================
   10. BADGES - the impact / difficulty / risk / evidence taxonomy
   ========================================================================== */

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  padding: 1px 8px;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  font-size: var(--fs-xs);
  font-weight: 500;
  line-height: var(--lh-label);
  text-transform: uppercase;
  letter-spacing: .04em;
  overflow-wrap: normal;
  hyphens: none;
}

/* Outline variant: the colour lives in a dot so the label stays high-contrast. */
.badge-outline {
  background: transparent;
  border-color: var(--line-2);
  color: var(--fg-2);
}

.badge .dot {
  width: 6px;
  height: 6px;
  flex: none;
  border-radius: var(--r-full);
  background: currentColor;
}

.imp-critical { background: var(--imp-crit-bg); color: var(--imp-crit-fg); border-color: var(--imp-crit-line); }
.imp-high     { background: var(--imp-high-bg); color: var(--imp-high-fg); border-color: var(--imp-high-line); }
.imp-medium   { background: var(--imp-med-bg);  color: var(--imp-med-fg);  border-color: var(--imp-med-line); }
.imp-low      { background: var(--imp-low-bg);  color: var(--imp-low-fg);  border-color: var(--imp-low-line); }

.dif-easy     { background: var(--dif-easy-bg); color: var(--dif-easy-fg); border-color: var(--dif-easy-line); }
.dif-moderate { background: var(--dif-mod-bg);  color: var(--dif-mod-fg);  border-color: var(--dif-mod-line); }
.dif-advanced { background: var(--dif-adv-bg);  color: var(--dif-adv-fg);  border-color: var(--dif-adv-line); }

.risk-none    { background: var(--risk-none-bg); color: var(--risk-none-fg); border-color: var(--risk-none-line); }
.risk-low     { background: var(--risk-low-bg);  color: var(--risk-low-fg);  border-color: var(--risk-low-line); }
.risk-medium  { background: var(--risk-med-bg);  color: var(--risk-med-fg);  border-color: var(--risk-med-line); }
.risk-restore-point { background: var(--risk-rp-bg); color: var(--risk-rp-fg); border-color: var(--risk-rp-line); }

.ev-measured  { background: var(--ev-measured-bg);  color: var(--ev-measured-fg);  border-color: var(--ev-measured-line); }
.ev-consensus { background: var(--ev-consensus-bg); color: var(--ev-consensus-fg); border-color: var(--ev-consensus-line); }
.ev-vendor    { background: var(--ev-vendor-bg);    color: var(--ev-vendor-fg);    border-color: var(--ev-vendor-line); }
.ev-inference { background: var(--ev-inference-bg); color: var(--ev-inference-fg); border-color: var(--ev-inference-line); }

/* IMPACT is a STROKED pill - the ramp lives in the text and the edge, not in a
   filled plate. Only critical keeps its fill: it is the one value that has to
   read as "stop" from across the page. */
.badge-outline.imp-high   { background: transparent; color: var(--imp-high-fg); border-color: var(--imp-high-line); }
.badge-outline.imp-medium { background: transparent; color: var(--imp-med-fg);  border-color: var(--imp-med-line); }
.badge-outline.imp-low    { background: transparent; color: var(--imp-low-fg);  border-color: var(--imp-low-line); }


/* -- THE evidence label. One component, both pages ----------------------
   Evidence is provenance, not status: it is set as a quiet dot plus uppercase
   letter-spaced text, with NO pill around it, so it can never compete with the
   impact pill sitting next to it. The four hues stay distinct on purpose -
   they are semantic colour, the way syntax highlighting is, and a reader
   learns them once and reads them everywhere.

   Two spellings resolve to the same object, so neither page has to re-render
   its markup to adopt it: `.ev-label` on its own, or the legacy
   `.badge.badge-outline.ev-*` the catalog already emits. A child `<span
   class="dot">` is used when present; otherwise the dot is drawn for free. */
.ev-label,
.badge.badge-outline.ev-measured,
.badge.badge-outline.ev-consensus,
.badge.badge-outline.ev-vendor,
.badge.badge-outline.ev-inference {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--fg-2);
  font-size: var(--fs-2xs);
  font-weight: 500;
  line-height: var(--lh-label);
  text-transform: uppercase;
  letter-spacing: var(--tk-label);
  white-space: nowrap;
}

.ev-label > .dot,
.ev-label:not(:has(> .dot))::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: var(--r-full);
  background: var(--fg-3);
}

.ev-measured  > .dot, .ev-measured:not(:has(> .dot))::before  { background: var(--ev-measured-fg); }
.ev-consensus > .dot, .ev-consensus:not(:has(> .dot))::before { background: var(--ev-consensus-fg); }
.ev-vendor    > .dot, .ev-vendor:not(:has(> .dot))::before    { background: var(--ev-vendor-fg); }
.ev-inference > .dot, .ev-inference:not(:has(> .dot))::before { background: var(--ev-inference-fg); }


/* ============================================================================
   11. CHIPS - filter toggles. .on is the legacy hook, aria-pressed the new one.
   ========================================================================== */

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  min-height: 32px;
  padding-inline: var(--s3);
  border: 1px solid var(--stroke);
  border-radius: var(--r-full);
  background: var(--bg-1);
  color: var(--fg-2);
  font-size: var(--fs-sm);
  font-weight: 500;
  line-height: var(--lh-label);
  cursor: pointer;
  transition: background-color var(--dur-base) var(--ease-std),
              border-color var(--dur-base) var(--ease-std),
              color var(--dur-base) var(--ease-std),
              transform var(--dur-fast) var(--ease-std);
}

.chip:hover { background: var(--bg-2); border-color: var(--fg-3); color: var(--fg-0); }

.chip[aria-pressed="true"],
.chip.on {
  background: var(--bg-3);
  border-color: var(--accent-500);
  color: var(--fg-0);
}

.chip:active { transform: translateY(1px); }

/* The read-only variant: the same object, standing still. A fact that happens
   to be shaped like a control must not offer a control's affordances - so it
   loses the pointer, the hover warm-up and the press. Used wherever a page
   shows a value that reads as a chip but does nothing when clicked. */
.chip.is-static {
  cursor: default;
  background: var(--bg-2);
  border-color: var(--line-2);
  color: var(--fg-2);
}

.chip.is-static:hover {
  background: var(--bg-2);
  border-color: var(--line-2);
  color: var(--fg-2);
}

.chip.is-static:active { transform: none; }

@media (pointer: coarse) {
  .chip { min-height: 40px; }
}


/* ============================================================================
   12. CODE AND COPY CHIPS
   ========================================================================== */

code, kbd, samp, pre {
  font-family: var(--font-mono);
  font-variant-ligatures: none;
}

code {
  font-size: .9375em;
  background: var(--bg-3);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  padding: .1em .35em;
  color: var(--accent-300);
  overflow-wrap: anywhere;
  word-break: break-word;
}

/* Copyable tokens are real buttons: focusable, announceable, pressable. */
.copy-code {
  display: inline-flex;
  align-items: baseline;
  gap: var(--s1);
  min-height: 24px;
  font-family: var(--font-mono);
  font-size: .9375em;
  background: var(--bg-3);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  padding: .1em .35em;
  color: var(--accent-300);
  overflow-wrap: anywhere;
  word-break: break-word;
  transition: background-color var(--dur-base) var(--ease-std),
              border-color var(--dur-base) var(--ease-std),
              color var(--dur-base) var(--ease-std),
              transform var(--dur-fast) var(--ease-std);
}

.copy-code:hover { border-color: var(--accent-600); background: var(--bg-2); }
.copy-code:active { transform: translateY(1px); }

/* Fixed-width icon slot: the check swaps in without reflowing the sentence. */
.copy-code .ci {
  position: relative;
  flex: none;
  width: 14px;
  height: 14px;
  align-self: center;
}

.copy-code .ci svg {
  position: absolute;
  inset: 0;
  transition: opacity var(--dur-base) var(--ease-std);
}

.copy-code .ci-copy { opacity: 0; color: var(--fg-3); }
.copy-code .ci-check { opacity: 0; color: var(--ok); }

.copy-code:hover .ci-copy,
.copy-code:focus-visible .ci-copy { opacity: 1; }

.copy-code[data-copied="true"] .ci-copy { opacity: 0; }
.copy-code[data-copied="true"] .ci-check { opacity: 1; }
.copy-code[data-copied="true"] { border-color: var(--ok); }


/* ============================================================================
   13. DISCLOSURE - one marker language for every <details> on the site
   ========================================================================== */

.marker {
  flex: none;
  color: var(--fg-3);
  transform-origin: 50% 50%;
  transition: transform var(--dur-base) var(--ease-std),
              color var(--dur-base) var(--ease-std);
}

/* Two meanings, two directions. A right-pointing chevron says "go there"
   (tile kickers, links); a disclosure marker points DOWN at rest and UP when
   open, so an expand-in-place control can never be misread as navigation. */
summary .marker { transform: rotate(90deg); }
details[open] > summary .marker { transform: rotate(-90deg); }
summary:hover .marker { color: var(--accent-400); }
summary:hover { color: var(--fg-0); }
summary { transition: color var(--dur-base) var(--ease-std); }

/* -- 13b. Q - THE inline "what is this?" popover ----------------------------
   One affordance for a term or line that earns a short aside without renting
   surface space. Native <details>, so keyboard and announcement come free:

     <details class="q"><summary aria-label="What is a claim label?">?</summary>
       <span class="q-pop">One or two sentences. No headings, no lists.</span>
     </details>

   Wide viewports float the note under the mark; narrow viewports let it open
   in flow instead, where a floated card would clip at the screen edge.
   .q-end anchors the note's right edge to the mark - use it when the mark
   sits in a right-hand column or near the container's right edge. */

.q {
  display: inline-block;
  position: relative;
  vertical-align: text-bottom;
  line-height: 1;
}

.q > summary {
  list-style: none;
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border: 1px solid var(--stroke);
  border-radius: var(--r-full);
  color: var(--fg-3);
  font-family: var(--font-sans);
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  cursor: pointer;
  user-select: none;
  transition: color var(--dur-base) var(--ease-std),
              border-color var(--dur-base) var(--ease-std),
              background-color var(--dur-base) var(--ease-std);
}

.q > summary::-webkit-details-marker { display: none; }

/* The mark is 18px so it sits inside a text line; the target is 24px. */
.q > summary::after {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: inherit;
}

.q > summary:hover { color: var(--accent-400); border-color: var(--accent-400); }

.q[open] > summary {
  color: var(--accent-300);
  border-color: var(--accent-400);
  background: var(--bg-3);
}

.q-pop {
  position: absolute;
  z-index: 40;
  top: calc(100% + var(--s2));
  left: 50%;
  transform: translateX(-50%);
  width: max-content;
  max-width: min(38ch, 76vw);
  padding: var(--s3) var(--s4);
  background-color: var(--bg-2);
  background-image: var(--card-sheen);
  border: 1px solid var(--line-2);
  border-radius: var(--r-md);
  box-shadow: var(--sh-2), var(--hairline);
  color: var(--fg-1);
  font-size: var(--fs-sm);
  font-weight: 400;
  line-height: var(--lh-snug);
  letter-spacing: 0;
  text-transform: none;
  text-align: left;
  white-space: normal;
}

.q[open] > .q-pop { animation: q-pop-in var(--dur-slow) var(--ease-out); }
@keyframes q-pop-in { from { opacity: 0; } }

.q-end > .q-pop { left: auto; right: -4px; transform: none; }
.q-start > .q-pop { left: -4px; right: auto; transform: none; }

/* .q-term - the same popover, but the TERM is the mark. A jargon word in
   running prose cannot afford an 18px circle beside it: at one per sentence the
   marks read as a rash, and the reader still has to work out which word the
   nearest one belongs to. So the word itself wears a dotted underline and opens
   its own definition. The panel and its narrow-viewport reflow are inherited;
   the trigger is not.

     <span class="q q-term">
       <button type="button" class="q-mark" aria-expanded="false"
               aria-controls="gt-1">VRR<span class="sr-only"> (what does this
         mean?)</span></button>
       <span class="q-pop" id="gt-1">One or two sentences.</span>
     </span>

   A BUTTON, not the <details>/<summary> of the .q variant: <details> is flow
   content and a <p> holds only phrasing content, so a popover built that way
   makes the HTML parser end the paragraph at the first jargon word and drop the
   rest of the sentence out of it. Inline content has to be inline elements.
   The open state is a class, so the closed panel is display:none and stays out
   of the accessibility tree, selection and find-in-page.

   inline, NOT the .q inline-block: "hardware-accelerated GPU scheduling" is
   four words, and an inline-block cannot break across lines - it would jump to
   the next line whole and leave a hole in the paragraph above it. */

.q-term {
  display: inline;
  vertical-align: baseline;
  line-height: inherit;
}

.q-term > .q-mark {
  display: inline;
  margin: 0;
  padding: 0;
  border: 0;
  border-bottom: 1px dotted var(--fg-3);
  border-radius: 0;
  background: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-align: inherit;
  cursor: help;
  transition: color var(--dur-base) var(--ease-std),
              border-color var(--dur-base) var(--ease-std);
}

.q-term > .q-mark:hover,
.q-term.is-open > .q-mark {
  color: var(--accent-300);
  border-bottom-color: var(--accent-400);
}

/* The panel is inline-anchored: under the word's own left edge, rather than
   centred on a word that may sit at the end of a line. */
.q-term > .q-pop {
  display: none;
  left: 0;
  right: auto;
  transform: none;
  font-size: var(--fs-sm);
  font-weight: 400;
}

.q-term.is-open > .q-pop {
  display: block;
  animation: q-pop-in var(--dur-slow) var(--ease-out);
}

/* Narrow: the same decision the .q variant makes, restated at this selector's
   specificity so the floated panel cannot clip off the side of a phone. It
   opens in flow instead, which does split the sentence - a definition you can
   read beats one you cannot reach. */
@media (max-width: 640px) {
  .q-term > .q-pop {
    position: static;
    transform: none;
    width: auto;
    max-width: none;
    margin-top: var(--s2);
  }
}

/* On paper there is no clicking, so a dotted word with a hidden note is just a
   dotted word. Print the definition inline, once, where it was wanted. */
@media print {
  .q-term > .q-mark { border-bottom: 0; }
  .q-term > .q-pop {
    display: inline;
    position: static;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
    max-width: none;
    font-style: italic;
  }
  .q-term > .q-pop::before { content: " ("; }
  .q-term > .q-pop::after { content: ")"; }
}

@media (max-width: 640px) {
  .q-pop,
  .q-end > .q-pop,
  .q-start > .q-pop {
    position: static;
    display: block;
    transform: none;
    width: auto;
    max-width: none;
    margin-top: var(--s2);
  }
}

/* -- 13c. MORE - THE "go deeper" reveal --------------------------------------
   The row a paragraph of depth hides behind. Surface copy states the fact;
   the reveal holds the why, the caveats, the numbers.

   Sanctioned variant: a page whose ONLY disclosure must read as a control on
   first glance (the landing hub's evidence note) may frame the summary as a
   bordered pill page-locally. Everywhere disclosures sit among other rows,
   the bare chevron+label form is the component:

     <details class="more">
       <summary>[chevron svg .marker]<span>Why this works</span></summary>
       <div class="more-body">The paragraph that used to bloat the surface.</div>
     </details> */

.more { margin-top: var(--s3); }

.more > summary {
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  color: var(--fg-3);
  font-size: var(--fs-sm);
  font-weight: 500;
  cursor: pointer;
  user-select: none;
}

.more > summary::-webkit-details-marker { display: none; }

.more-body {
  margin-top: var(--s3);
  max-width: var(--measure);
  color: var(--fg-2);
  font-size: var(--fs-sm);
  line-height: var(--lh-body);
}

.more-body > * + * { margin-top: var(--s3); }

.more[open] > .more-body { animation: more-in var(--dur-slow) var(--ease-out); }
@keyframes more-in { from { opacity: 0; transform: translateY(-2px); } }


/* ============================================================================
   14. BREADCRUMBS
   ========================================================================== */

.crumbs {
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
  color: var(--fg-3);
  margin-bottom: var(--s5);
}

.crumbs ol {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2);
  margin: 0;
  padding: 0;
}

.crumbs li { display: flex; align-items: center; gap: var(--s2); }

/* A slanted hairline instead of a "/" glyph: never announced, never selectable. */
.crumbs li + li::before {
  content: "";
  flex: none;
  width: 0;
  height: 11px;
  border-left: 1px solid var(--line-2);
  transform: rotate(14deg);
}

.crumbs a {
  color: var(--fg-3);
  text-decoration: none;
  transition: color var(--dur-base) var(--ease-std);
}

.crumbs a:hover { color: var(--fg-1); }
.crumbs [aria-current="page"] { color: var(--fg-2); }


/* ============================================================================
   15. SCROLL REVEAL
   Content is VISIBLE BY DEFAULT. The hidden start state exists only when
   scripting has run (html.js) and only when motion is welcome, so a missed
   observer, a disabled script, a print job or an offscreen capture can never
   leave a section blank. Every page's script adds the .js class as its first
   statement and reveals anything already near the fold immediately.
   ========================================================================== */

.reveal { opacity: 1; transform: none; }

@media (prefers-reduced-motion: no-preference) {
  html.js .reveal {
    transition: opacity var(--dur-page) var(--ease-out),
                transform var(--dur-page) var(--ease-out);
  }

  html.js .reveal:not(.in) {
    opacity: 0;
    transform: translateY(12px);
  }

  html.js .reveal:nth-child(2) { transition-delay: 60ms; }
  html.js .reveal:nth-child(3) { transition-delay: 120ms; }
}

@media print {
  .reveal { opacity: 1 !important; transform: none !important; }
}


/* ============================================================================
   15b. STEP NUMBERS - one token for "step N of a procedure", every page
   ========================================================================== */

.stepnum {
  flex: none;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: var(--r-full);
  background: var(--bg-3);
  border: 1px solid var(--accent-600);
  color: var(--accent-300);
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0;
  line-height: 1;
}


/* ============================================================================
   16. USER PREFERENCE BLOCKS
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }

  @view-transition { navigation: none; }
}

@media (prefers-contrast: more) {
  :root {
    --line-1: rgba(232, 226, 216, .25);
    --line-2: rgba(232, 226, 216, .4);
    --fg-2: #cfc6b8;
    --fg-3: #b2a99a;
    --stroke: #9a8f7d;
    --accent-400: #f2b58c;
  }
}

@media (forced-colors: active) {
  body::before,
  body::after,
  .hero::before { display: none; }

  .card, .tile, .rail-card, .panel, .rec-row, .entry, .install-how, .trace,
  .chip, .badge, .btn, .dl-btn, .copy-code, .stepnum, .tile-meta span,
  .q > summary, .q-pop {
    border: 1px solid CanvasText;
  }

  .nav { border-bottom: 1px solid CanvasText; }

  .chip[aria-pressed="true"],
  .chip.on,
  .nav-links a[aria-current="page"] {
    outline: 2px solid Highlight;
    outline-offset: 1px;
  }

  :focus-visible { outline: 3px solid Highlight; outline-offset: 2px; }
}

@media print {
  :root {
    color-scheme: light;
    --bg-0: #fff;  --bg-1: #fff;  --bg-2: #fff;  --bg-3: #f5f3ef;
    --fg-0: #000;  --fg-1: #1a1a1a;  --fg-2: #3d3d3d;  --fg-3: #5a5a5a;
    --line-1: #d5d0c8;  --line-2: #b9b3a9;  --stroke: #8a8378;
    --accent-300: #8a4a1e;  --accent-400: #8a4a1e;
    --accent-500: #a3541f;  --accent-600: #7a3d16;
  }

  body { background: #fff; color: #1a1a1a; }
  body::before,
  body::after,
  .hero::before { content: none; }

  .nav, .skip, .footer-nav, .nav-menu, .trace { display: none !important; }

  .card, .tile, .rail-card, .panel, .rec-row, .entry, .install-how,
  .btn, .dl-btn, .chip, .copy-code { box-shadow: none; background-image: none; background-color: #fff; }
  a { color: #1a1a1a; text-decoration: underline; }
  h1, h2, h3 { break-after: avoid; }
  .card, .tile, .principle, .panel { break-inside: avoid; }
}


/* ============================================================================
   17. CROSS-DOCUMENT VIEW TRANSITIONS
   Same-origin only, so this covers hops inside a subdomain. Unsupported
   browsers ignore the at-rule entirely and get a normal navigation.
   ========================================================================== */

@view-transition { navigation: auto; }

.nav { view-transition-name: site-nav; }

@keyframes ook-fade-in { from { opacity: 0; transform: translateY(6px); } }
@keyframes ook-fade-out { to { opacity: 0; } }

::view-transition-old(root) { animation: ook-fade-out var(--dur-page) var(--ease-std) both; }
::view-transition-new(root) { animation: ook-fade-in var(--dur-page) var(--ease-out) both; }
