@import "./house-tokens.css";

/* ==========================================================================
   oxagen.sh — shared shell
   Palette: the Oxagen house brand system, so this site and the app
   (@oxagen/ui) are one surface. house-tokens.css above is the kit's own
   generated file, vendored here by tools/scripts/sync-brand-assets.mjs.
   Three layers: the kit's --ox-* table holds every hex, --st-* primitives
   name the ones this site uses, and --<name> semantics alias those.
   Repoint an alias to reskin; never re-hex, and never hardcode a palette value
   in a rule or a page.
   Gold is identity and action only — never a state. States are pass/fail.
   Treatment follows docs.oxagen.sh (apps/docs, on @oxagen/ui): corners are
   rounded on the docs scale (--r / --r-lg / --r-xl), the ONE gradient is the
   kit's sheen and it lands only on headline accents and the primary action,
   and backdrops are soft — a lattice that fades out, the hex constellation
   from the logomark, an ember glow — never a hard-stop texture. Depth comes
   from hairlines and a lit shadow under the windows.
   ========================================================================== */

:root {
  /* ── LAYER 1 · PRIMITIVES ────────────────────────────────────────────────
     The stops this site uses, each a reference into the kit's own --ox-* table
     in house-tokens.css. No hex is written here: a colour that moves in the
     kit moves here on the next sync.
     These are the values on ink. The block after this one repoints them to
     the paper ramp when the page is light, so a browser without light-dark()
     keeps the dark site. Two diff washes have no kit stop, and they are named
     as such. */
  --st-void: var(--ox-void); /* below the canvas */
  --st-bg: var(--ox-ink); /* the canvas */
  --st-panel: var(--ox-panel); /* panels, cards, code blocks */
  --st-hl: var(--ox-hl); /* selected and hover rows */
  --st-border: var(--ox-border); /* hairlines, dividers */
  --st-rule: var(--ox-rule); /* section rules, boundaries */
  --st-gold: var(--ox-gold); /* the brand metal: actions, active, money, the mark */
  --st-gold-bright: var(--ox-gold-bright); /* tiny live indicators only */
  --st-gold-ink: var(--ox-gold-deep); /* gold as TEXT where the metal itself is illegible */
  --st-paper-text: var(--ox-text); /* primary text in dark */
  --st-text: var(--ox-text-body); /* body ink */
  --st-silver-type: var(--ox-muted); /* types, tertiary labels */
  --st-silver: var(--ox-muted); /* secondary emphasis */
  --st-muted: var(--ox-muted); /* secondary text */
  --st-dim: var(--ox-dim); /* hints, captions, line numbers */
  --st-green: var(--ox-st-allowed); /* pass, additive */
  --st-green-ink: var(--ox-st-allowed-ink);
  --st-red: var(--ox-st-failed); /* fail, destructive */
  --st-red-ink: var(--ox-st-failed-ink);
  /* The kit ships two stops per state and both are MARKS: the bare name is the
     mark on ink, the -ink suffix the mark on paper. A mark needs 3:1, so
     neither is promised as a text colour. #C0453C as words on the #18181B
     panel is 3.77:1, under the 4.5:1 body floor, so on ink this is the failed
     state lightened until it clears it, the same derived stop @oxagen/ui
     carries for --error-ink. On paper the -ink stop, #992F28, is 7.9:1 on
     white and serves as words unchanged (see THEMES below). */
  --st-red-type: oklch(0.7 0.16 25); /* failed as TEXT on ink: 6.6:1 canvas, 6.3:1 panel */
  --st-amber: var(--ox-st-denied); /* warning — never the gold, which is not a state */
  --st-amber-ink: var(--ox-st-denied-ink);
  /* The two diff washes the kit has no stop for: its allowed and failed states
     laid over the canvas at low strength, so they move with the ramp. */
  --st-diff-add: color-mix(in srgb, var(--ox-st-allowed) 12%, var(--ox-ink));
  --st-diff-del: color-mix(in srgb, var(--ox-st-failed) 12%, var(--ox-ink));

  /* ── LAYER 2 · SEMANTIC ──────────────────────────────────────────────────
     The names the rest of this stylesheet and every page actually consume.
     Each is an alias onto the table above — repoint here, never re-hex. */
  --ground: var(--st-bg);
  --panel: var(--st-panel);
  --raised: var(--st-hl);
  --line: var(--st-border);
  --rule: var(--st-rule);
  --gold: var(--st-gold);
  --gold-hi: var(--st-gold-bright);
  --gold-ink: var(--st-gold-ink);
  --ink: var(--st-paper-text);
  --ink-2: var(--st-silver-type);
  --ink-3: var(--st-silver);
  --muted: var(--st-muted);
  --faint: var(--st-dim);
  --pass: var(--st-green);
  --fail: var(--st-red); /* the MARK: dots, borders, washes */
  --fail-ink: var(--st-red-type); /* failed as words, the way --gold-ink is gold as words */
  --warn: var(--st-amber);
  --add-bg: var(--st-diff-add);
  --del-bg: var(--st-diff-del);

  /* ---- corners: the docs site's scale, so a button here and a button on
     docs.oxagen.sh are the same shape. --r is @oxagen/ui's --ui-radius
     (0.5rem); the larger steps are Tailwind's xl / 2xl, which the docs pages
     put on windows and frames. ---- */
  --r: 8px; /* controls: buttons, inputs, chips, menu rows */
  --r-lg: 12px; /* windows: the terminal, cards, panels */
  --r-xl: 16px; /* frames: card grids, the demo box */
  --r-pill: 999px; /* the eyebrow pill, tags */
  --nav-h: 60px;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* ---- the metal in motion: the kit's sheen, deep → metal → highlight. The
     one gradient the house allows, laid the way @oxagen/ui lays it
     (--brand-gradient / .lp-grad-text on docs.oxagen.sh). Headline accents
     and the primary button take it; a mark is always one colour. ---- */
  --ember-a: var(--ox-gold-deep);
  --ember-b: var(--ox-gold);
  --ember-c: var(--ox-gold-bright);
  --grad-ember: linear-gradient(
    100deg,
    var(--ember-a) 0%,
    var(--ember-b) 46%,
    var(--ember-c) 100%
  );

  /* ---- backdrops: soft, never hard-stop.
     --tex-grid is the docs landing page's lattice (.lp-grid): a 46px grid at
     7% of the ink, masked to fade out radially wherever it is used.
     --tex-hex is the hex constellation abstracted from the logomark — the
     same nineteen cells, at the same places, as @oxagen/ui's <HexField>, so
     this site and docs.oxagen.sh sit on one field. It is an image, so the
     paper and the gold sheen are written into it; they are the kit's own
     values (--st-paper-text, --st-gold-ink / --st-gold / --st-gold-bright).
     --tex-orb is the ember light behind the hero (.lp-orb). ---- */
  --tex-grid:
    linear-gradient(
      to right,
      color-mix(in srgb, var(--ink) 7%, transparent) 1px,
      transparent 1px
    ),
    linear-gradient(
      to bottom,
      color-mix(in srgb, var(--ink) 7%, transparent) 1px,
      transparent 1px
    );
  --tex-hex: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 600' preserveAspectRatio='xMidYMid slice'><defs><linearGradient id='g' x1='0' y1='0' x2='1' y2='1'><stop offset='0' stop-color='%238B5E1A'/><stop offset='.5' stop-color='%23D6962C'/><stop offset='1' stop-color='%23F1C364'/></linearGradient></defs><g fill='none' stroke='%23F2EEE5' stroke-opacity='.14' stroke-width='1'><polygon points='90.0 80.0 116.0 95.0 116.0 125.0 90.0 140.0 64.0 125.0 64.0 95.0'/><polygon points='150.0 180.0 176.0 195.0 176.0 225.0 150.0 240.0 124.0 225.0 124.0 195.0'/><polygon points='90.0 280.0 116.0 295.0 116.0 325.0 90.0 340.0 64.0 325.0 64.0 295.0'/><polygon points='210.0 80.0 236.0 95.0 236.0 125.0 210.0 140.0 184.0 125.0 184.0 95.0'/><polygon points='210.0 280.0 236.0 295.0 236.0 325.0 210.0 340.0 184.0 325.0 184.0 295.0'/><polygon points='270.0 180.0 296.0 195.0 296.0 225.0 270.0 240.0 244.0 225.0 244.0 195.0'/><polygon points='270.0 380.0 296.0 395.0 296.0 425.0 270.0 440.0 244.0 425.0 244.0 395.0'/><polygon points='330.0 80.0 356.0 95.0 356.0 125.0 330.0 140.0 304.0 125.0 304.0 95.0'/><polygon points='1110.0 98.0 1137.7 114.0 1137.7 146.0 1110.0 162.0 1082.3 146.0 1082.3 114.0'/><polygon points='1110.0 298.0 1137.7 314.0 1137.7 346.0 1110.0 362.0 1082.3 346.0 1082.3 314.0'/><polygon points='990.0 98.0 1017.7 114.0 1017.7 146.0 990.0 162.0 962.3 146.0 962.3 114.0'/><polygon points='930.0 198.0 957.7 214.0 957.7 246.0 930.0 262.0 902.3 246.0 902.3 214.0'/><polygon points='930.0 398.0 957.7 414.0 957.7 446.0 930.0 462.0 902.3 446.0 902.3 414.0'/><polygon points='1050.0 398.0 1077.7 414.0 1077.7 446.0 1050.0 462.0 1022.3 446.0 1022.3 414.0'/><polygon points='600.0 18.0 619.1 29.0 619.1 51.0 600.0 62.0 580.9 51.0 580.9 29.0'/><polygon points='540.0 518.0 559.1 529.0 559.1 551.0 540.0 562.0 520.9 551.0 520.9 529.0'/></g><g fill='url(%23g)' opacity='.45'><polygon points='1050.0 198.0 1077.7 214.0 1077.7 246.0 1050.0 262.0 1022.3 246.0 1022.3 214.0'/><polygon points='990.0 298.0 1017.7 314.0 1017.7 346.0 990.0 362.0 962.3 346.0 962.3 314.0'/><polygon points='660.0 518.0 679.1 529.0 679.1 551.0 660.0 562.0 640.9 551.0 640.9 529.0'/></g></svg>");
  --tex-orb: radial-gradient(
    ellipse 410px 260px at 50% 14%,
    color-mix(in srgb, var(--ember-b) 34%, transparent) 0%,
    color-mix(in srgb, var(--ember-c) 14%, transparent) 40%,
    transparent 70%
  );
}

/* ── THEMES ────────────────────────────────────────────────────────────────
   The page follows the OS until the footer's theme control pins it. The head
   script writes the resolved theme to <html data-theme>, so a pinned choice
   wins over the OS, and a page without script follows the OS on its own.
   Each primitive above is repointed with light-dark(): the first value is
   the kit's stop on paper, the second its stop on ink. The tokens stay
   unregistered, so a subtree that sets its own color-scheme (the terminal)
   resolves them again for itself. Gold as words on paper is the deep shade,
   because the metal is 2.1:1 on white. The sheen keeps the metal: it is a
   fill, and --ember-* read the kit directly. */
:root {
  color-scheme: light dark;
}
:root[data-theme="light"] {
  color-scheme: light;
}
:root[data-theme="dark"] {
  color-scheme: dark;
}
@supports (color: light-dark(white, black)) {
  :root {
    --st-void: light-dark(var(--ox-paper-void), var(--ox-void));
    --st-bg: light-dark(var(--ox-paper), var(--ox-ink));
    --st-panel: light-dark(var(--ox-paper-panel), var(--ox-panel));
    --st-hl: light-dark(var(--ox-paper-hl), var(--ox-hl));
    --st-border: light-dark(var(--ox-paper-border), var(--ox-border));
    --st-rule: light-dark(var(--ox-paper-rule), var(--ox-rule));
    --st-gold: light-dark(var(--ox-gold-deep), var(--ox-gold));
    --st-gold-bright: light-dark(var(--ox-gold), var(--ox-gold-bright));
    --st-paper-text: light-dark(var(--ox-text-ink), var(--ox-text));
    --st-text: light-dark(var(--ox-text-ink-body), var(--ox-text-body));
    --st-silver-type: light-dark(var(--ox-muted-ink), var(--ox-muted));
    --st-silver: light-dark(var(--ox-muted-ink), var(--ox-muted));
    --st-muted: light-dark(var(--ox-muted-ink), var(--ox-muted));
    --st-dim: light-dark(var(--ox-dim-ink), var(--ox-dim));
    --st-green: light-dark(var(--ox-st-allowed-ink), var(--ox-st-allowed));
    --st-red: light-dark(var(--ox-st-failed-ink), var(--ox-st-failed));
    --st-red-type: light-dark(var(--ox-st-failed-ink), oklch(0.7 0.16 25));
    --st-amber: light-dark(var(--ox-st-denied-ink), var(--ox-st-denied));
    --st-diff-add: color-mix(in srgb, var(--st-green) 12%, var(--st-bg));
    --st-diff-del: color-mix(in srgb, var(--st-red) 12%, var(--st-bg));
  }
}
/* The hex field is an image, so light-dark() cannot reach inside it: on paper
   the outlines are drawn in ink instead of paper. The paper field is held
   once and taken by a pinned light theme, and by a page without script whose
   OS is light (no data-theme is stamped then). */
:root[data-theme="light"] {
  --tex-hex: var(--tex-hex-paper);
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    --tex-hex: var(--tex-hex-paper);
  }
}
:root {
  --tex-hex-paper: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 600' preserveAspectRatio='xMidYMid slice'><defs><linearGradient id='g' x1='0' y1='0' x2='1' y2='1'><stop offset='0' stop-color='%238B5E1A'/><stop offset='.5' stop-color='%23D6962C'/><stop offset='1' stop-color='%23F1C364'/></linearGradient></defs><g fill='none' stroke='%2309090B' stroke-opacity='.10' stroke-width='1'><polygon points='90.0 80.0 116.0 95.0 116.0 125.0 90.0 140.0 64.0 125.0 64.0 95.0'/><polygon points='150.0 180.0 176.0 195.0 176.0 225.0 150.0 240.0 124.0 225.0 124.0 195.0'/><polygon points='90.0 280.0 116.0 295.0 116.0 325.0 90.0 340.0 64.0 325.0 64.0 295.0'/><polygon points='210.0 80.0 236.0 95.0 236.0 125.0 210.0 140.0 184.0 125.0 184.0 95.0'/><polygon points='210.0 280.0 236.0 295.0 236.0 325.0 210.0 340.0 184.0 325.0 184.0 295.0'/><polygon points='270.0 180.0 296.0 195.0 296.0 225.0 270.0 240.0 244.0 225.0 244.0 195.0'/><polygon points='270.0 380.0 296.0 395.0 296.0 425.0 270.0 440.0 244.0 425.0 244.0 395.0'/><polygon points='330.0 80.0 356.0 95.0 356.0 125.0 330.0 140.0 304.0 125.0 304.0 95.0'/><polygon points='1110.0 98.0 1137.7 114.0 1137.7 146.0 1110.0 162.0 1082.3 146.0 1082.3 114.0'/><polygon points='1110.0 298.0 1137.7 314.0 1137.7 346.0 1110.0 362.0 1082.3 346.0 1082.3 314.0'/><polygon points='990.0 98.0 1017.7 114.0 1017.7 146.0 990.0 162.0 962.3 146.0 962.3 114.0'/><polygon points='930.0 198.0 957.7 214.0 957.7 246.0 930.0 262.0 902.3 246.0 902.3 214.0'/><polygon points='930.0 398.0 957.7 414.0 957.7 446.0 930.0 462.0 902.3 446.0 902.3 414.0'/><polygon points='1050.0 398.0 1077.7 414.0 1077.7 446.0 1050.0 462.0 1022.3 446.0 1022.3 414.0'/><polygon points='600.0 18.0 619.1 29.0 619.1 51.0 600.0 62.0 580.9 51.0 580.9 29.0'/><polygon points='540.0 518.0 559.1 529.0 559.1 551.0 540.0 562.0 520.9 551.0 520.9 529.0'/></g><g fill='url(%23g)' opacity='.45'><polygon points='1050.0 198.0 1077.7 214.0 1077.7 246.0 1050.0 262.0 1022.3 246.0 1022.3 214.0'/><polygon points='990.0 298.0 1017.7 314.0 1017.7 346.0 990.0 362.0 962.3 346.0 962.3 314.0'/><polygon points='660.0 518.0 679.1 529.0 679.1 551.0 660.0 562.0 640.9 551.0 640.9 529.0'/></g></svg>");
}

/* The house faces, self-hosted from /fonts/ (vendored by
   tools/scripts/sync-brand-assets.mjs). Space Grotesk sets h1 to h3 and the
   wordmark, Geist sets everything read, and Monaspace Neon sets code, with
   texture healing on. */
@font-face {
  font-family: "Space Grotesk";
  src: url("/fonts/space-grotesk-latin-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Space Grotesk";
  src: url("/fonts/space-grotesk-latin-500.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Space Grotesk";
  src: url("/fonts/space-grotesk-latin-600.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Space Grotesk";
  src: url("/fonts/space-grotesk-latin-700.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Geist";
  src: url("/fonts/geist-latin-wght.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Monaspace Neon";
  src: url("/fonts/monaspace-neon-latin-wght.woff2") format("woff2");
  font-weight: 200 800;
  font-style: normal;
  font-display: swap;
}
:root {
  --font-display: "Space Grotesk", "Helvetica Neue", Arial, sans-serif;
  --font-sans: "Geist", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: "Monaspace Neon", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
}

/* ============ base ============ */
*,
*::before,
*::after {
  box-sizing: border-box;
}
html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}
body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font: 400 16px / 1.62 var(--font-sans);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
::selection {
  background: var(--gold);
  color: var(--ground);
}
h1,
h2,
h3,
h4 {
  margin: 0;
  font-family: var(--font-display);
  /* 600 is the weight the kit sets the wordmark at. */
  font-weight: 600;
  letter-spacing: -0.015em;
  line-height: 1.1;
  text-wrap: balance;
}
p {
  margin: 0;
}
/* The [hidden] attribute must win over any display:grid/flex an author rule
   sets on the same element — the UA's [hidden]{display:none} loses to an
   author declaration, so without this `el.hidden = true` leaves the element
   laid out (see .unlocked and .deck-panel below). */
[hidden] {
  display: none !important;
}
a {
  color: inherit;
  text-decoration: none;
}
img,
svg {
  display: block;
  max-width: 100%;
}
button,
input,
textarea,
select {
  font: inherit;
  color: inherit;
}
hr {
  border: 0;
  height: 1px;
  background: var(--line);
  margin: 0;
}
.mono {
  font-family: var(--font-mono);
  font-feature-settings: "calt", "liga";
}
.wrap {
  width: min(1180px, calc(100% - 40px));
  margin-inline: auto;
}
.gold {
  color: var(--gold);
}
:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
  border-radius: var(--r);
}
.skip {
  position: fixed;
  left: 12px;
  top: -64px;
  z-index: 200;
  background: var(--gold);
  color: var(--ground);
  padding: 10px 16px;
  border-radius: var(--r);
  font-weight: 640;
  transition: top 0.2s;
}
.skip:focus {
  top: 12px;
}

/* ============ backdrops ============
   The docs landing page's layered background, as three classes:
     .tex       the lattice alone, fading out radially (.lp-grid)
     .tex-hex   the hero: lattice + the hex constellation + the ember glow
     .tex-field an alt section: the constellation alone, quieter
   A backdrop always sits *under* the section's content and never behind body
   copy at full strength, so contrast is a property of the layout rather than
   of an opacity guess. */
.tex {
  position: relative;
  isolation: isolate;
}
.tex::before,
.tex::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}
.tex::before {
  background-image: var(--tex-grid);
  background-size: 46px 46px;
  background-position: center;
  -webkit-mask-image: radial-gradient(
    ellipse 80% 60% at 50% 38%,
    #000 0%,
    transparent 78%
  );
  mask-image: radial-gradient(ellipse 80% 60% at 50% 38%, #000 0%, transparent 78%);
}
.tex::after {
  content: none;
}
/* the hero: the constellation floats over the lattice, lit from behind */
.tex-hex::after {
  content: "";
  background-image: var(--tex-hex), var(--tex-orb);
  background-size: cover, auto;
  background-position: center, center;
  background-repeat: no-repeat;
  opacity: 0.7;
  animation: glow 9s ease-in-out infinite;
}
/* an alt section: no lattice, no light, only the field, quieter */
@keyframes glow {
  0%,
  100% {
    opacity: 0.55;
  }
  50% {
    opacity: 0.85;
  }
}

/* ============ the sheen on type ============
   A headline's accent word takes the gradient (docs: .lp-grad-text). Only in
   a heading — inline .gold elsewhere stays the flat metal, because a mark and
   a link are one colour. */
h1 .gold,
h2 .gold,
.grad-text {
  background-image: var(--grad-ember);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

/* ============ eyebrow / section head ============
   The docs .ox-eyebrow: the house face, not a mono; muted, not gold — the
   gold is saved for the headline's accent word. In a hero it sits in a pill
   (docs: rounded-full border bg-card/60 backdrop-blur). The .sec-head form
   is listed so it outranks `.sec-head p`, which sets the lede's size. */
.eyebrow,
.sec-head .eyebrow {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
  display: inline-flex;
  align-items: center;
  gap: 10px;
}
.hero .eyebrow,
.blog-hero .eyebrow {
  font-size: 11px;
  letter-spacing: 0.14em;
  padding: 5px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--panel) 60%, transparent);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}
.sec {
  padding: 96px 0;
}
.sec-tight {
  padding: 64px 0;
}
.sec-head {
  max-width: 740px;
  margin-bottom: 52px;
}
.sec-head h2 {
  font-size: clamp(29px, 4.2vw, 44px);
  margin: 16px 0 14px;
}
.sec-head p {
  color: var(--ink-3);
  font-size: 17.5px;
  max-width: 62ch;
}
.sec-alt {
  background: var(--panel);
  border-block: 1px solid var(--line);
}

/* ============ buttons ============ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: 12px 22px;
  border-radius: var(--r);
  font-weight: 600;
  font-size: 15px;
  border: 1px solid transparent;
  cursor: pointer;
  white-space: nowrap;
  transition:
    transform 0.16s var(--ease),
    border-color 0.16s,
    background-color 0.16s,
    color 0.16s;
}
/* the one action per screen carries the sheen (docs: .lp-grad-surface) */
.btn-primary {
  background-color: var(--gold);
  background-image: var(--grad-ember);
  color: var(--ox-ink); /* ink on the metal in both themes */
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}
.btn-primary:hover {
  transform: scale(1.02);
}
.btn-primary:active {
  transform: scale(1);
}
/* glass over the backdrop, a hairline that warms on hover */
.btn-ghost {
  border-color: var(--line);
  color: var(--ink);
  background: color-mix(in srgb, var(--panel) 60%, transparent);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}
.btn-ghost:hover {
  border-color: color-mix(in srgb, var(--gold) 60%, transparent);
  color: var(--ink);
}
.btn-sm {
  padding: 8px 15px;
  font-size: 13.5px;
}
.btn .arr {
  transition: transform 0.16s var(--ease);
}
.btn:hover .arr {
  transform: translateX(3px);
}
.btn[disabled] {
  opacity: 0.6;
  cursor: progress;
  transform: none;
}

/* ============ nav ============ */
.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  background: color-mix(in srgb, var(--ground) 82%, transparent);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid transparent;
  transition:
    border-color 0.2s,
    background-color 0.2s;
}
.nav.scrolled {
  border-bottom-color: var(--line);
  background: color-mix(in srgb, var(--ground) 94%, transparent);
}
.nav-in {
  display: flex;
  align-items: center;
  gap: 24px;
  width: min(1180px, calc(100% - 40px));
  margin-inline: auto;
}
/* The brand slot holds the WORDMARK and nothing else. Oxagen's logo is the
   word; the `Ox` lettermark is for square slots (the favicon) and is never set
   beside the word — the mark IS the word's first two letters, so the two
   together read as one misspelt word. The mark is sized by height, because a
   wordmark is far wider than it is tall. */
.brand {
  display: flex;
  align-items: center;
  color: var(--ink);
}
.brand svg {
  width: auto;
  height: 22px;
}
.nav-links {
  display: flex;
  align-items: center;
  gap: 22px;
  margin-inline: auto;
}
.nav-links > a {
  font-size: 14.5px;
  color: var(--ink-3);
  transition: color 0.14s;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.nav-links > a:hover {
  color: var(--ink);
}
.nav-cta {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-left: auto;
}
.nav-cta .login {
  font-size: 14.5px;
  color: var(--ink-3);
}
.nav-cta .login:hover {
  color: var(--ink);
}
.ext::after {
  content: "↗";
  font-size: 0.8em;
  color: var(--faint);
  margin-left: 3px;
}
.nav-links a[aria-current="page"] {
  color: var(--gold);
}

/* dropdown */

/* mobile drawer */
.burger {
  display: none;
  background: none;
  border: 1px solid var(--rule);
  border-radius: var(--r);
  width: 36px;
  height: 34px;
  padding: 0;
  cursor: pointer;
  place-items: center;
  color: var(--ink-3);
}
.burger:hover {
  color: var(--ink);
  border-color: var(--gold);
}
.burger i {
  display: block;
  width: 15px;
  height: 1.4px;
  background: currentColor;
  position: relative;
}
.burger i::before,
.burger i::after {
  content: "";
  position: absolute;
  left: 0;
  width: 15px;
  height: 1.4px;
  background: currentColor;
}
.burger i::before {
  top: -5px;
}
.burger i::after {
  top: 5px;
}
.drawer {
  display: none;
}
@media (max-width: 900px) {
  .nav-links {
    display: none;
  }
  .burger {
    display: grid;
  }
  .nav-cta .login {
    display: none;
  }
  .drawer {
    display: block;
    position: fixed;
    top: var(--nav-h);
    left: 0;
    right: 0;
    z-index: 99;
    background: var(--panel);
    border-bottom: 1px solid var(--rule);
    padding: 14px 20px 20px;
    max-height: 0;
    overflow: hidden;
    visibility: hidden;
    transition:
      max-height 0.26s var(--ease),
      visibility 0.26s;
  }
  .drawer[data-open="true"] {
    max-height: 82vh;
    overflow: auto;
    visibility: visible;
  }
  .drawer h5 {
    font-family: var(--font-mono);
  font-feature-settings: "calt", "liga";
    font-size: 10.5px;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--faint);
    margin: 14px 0 8px;
    font-weight: 520;
  }
  .drawer a {
    display: block;
    padding: 9px 0;
    font-size: 16px;
    color: var(--ink-2);
    border-bottom: 1px solid var(--line);
  }
  .drawer a:hover {
    color: var(--gold);
  }
  .drawer .btn {
    margin-top: 16px;
    width: 100%;
  }
}

/* ============ application window ============
   The chrome around every rendering: the docs terminal (.lp-term on
   docs.oxagen.sh) — a rounded window with a light edge along its top and a
   shadow lit by the ember beneath it. */
.app-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 12px;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  font-family: var(--font-mono);
  font-feature-settings: "calt", "liga";
  font-size: 11.5px;
  color: var(--muted);
}
.app-bar .tl {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--line);
  flex: none;
}
/* the three lights are the window's, not the palette's: the docs terminal
   draws them in macOS's own colours, so they are written here once */
.app-bar .tl:nth-child(1) {
  background: #ff5f57;
}
.app-bar .tl:nth-child(2) {
  background: #febc2e;
}
.app-bar .tl:nth-child(3) {
  background: #28c840;
  margin-right: 6px;
}
.app-bar .tl.live {
  animation: pulse 2.2s ease-in-out infinite;
}
@keyframes pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.35;
  }
}
.app-bar .app-title {
  color: var(--ink-3);
  letter-spacing: 0.04em;
}
.app-bar .app-meta {
  margin-left: auto;
  color: var(--ink-3);
  letter-spacing: 0.06em;
}
/* a slow sweep, the only motion over the rendering itself */
/* the live strip below the render — where the typing happens */

.caret {
  display: inline-block;
  width: 7px;
  height: 1.05em;
  background: var(--gold);
  vertical-align: text-bottom;
  margin-left: 1px;
  animation: blink 1.05s steps(1) infinite;
}
@keyframes blink {
  50% {
    opacity: 0;
  }
}

figcaption {
  font-family: var(--font-mono);
  font-feature-settings: "calt", "liga";
  font-size: 12px;
  color: var(--ink-3);
  padding-top: 10px;
  letter-spacing: 0.03em;
  line-height: 1.6;
}
figcaption b {
  color: var(--ink-3);
  font-weight: 520;
}

/* ============ terminal (typed, no image) ============ */
/* A terminal is dark in both themes, the way a real one is: it sets its own
   color-scheme, so every token inside it resolves to its value on ink. */
.term {
  color-scheme: dark;
  color: var(--ink);
  background: var(--ground);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: var(--r-lg);
  overflow: hidden;
  font-family: var(--font-mono);
  font-feature-settings: "calt", "liga";
  font-size: 13px;
  line-height: 1.75;
  display: flex;
  flex-direction: column;
  box-shadow:
    0 1px 0 0 rgba(255, 255, 255, 0.06) inset,
    0 30px 80px -28px color-mix(in srgb, var(--ember-c) 42%, transparent),
    0 18px 50px -30px rgba(0, 0, 0, 0.7);
}
.term-body {
  padding: 16px 18px;
  flex: 1;
}
.term-line {
  white-space: pre-wrap;
  word-break: break-word;
  min-height: 1.75em;
}
.t-prompt {
  color: var(--gold);
}
.t-cmd {
  color: var(--ink);
}
.t-dim {
  color: var(--faint);
}
.t-note {
  color: var(--ink-3);
}
.t-diff {
  color: var(--ink-2);
}
.t-ok {
  color: var(--pass);
}
.t-bad {
  color: var(--fail-ink);
}

/* ============ cards ============ */
.grid-2 {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 20px;
}
@media (max-width: 660px) {
  .grid-2 {
    grid-template-columns: 1fr;
  }
}
.card {
  position: relative;
  isolation: isolate;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 24px;
  overflow: hidden;
  transition:
    border-color 0.2s,
    transform 0.2s var(--ease);
}
.card:hover {
  border-color: color-mix(in srgb, var(--gold) 60%, transparent);
  transform: translateY(-2px);
}
/* on hover the constellation surfaces through the card (docs: HexField at
   opacity-30 on group-hover), under the copy */
.card::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: var(--tex-hex) center / cover no-repeat;
  opacity: 0;
  transition: opacity 0.5s;
}
.card:hover::before {
  opacity: 0.3;
}
.card h3 {
  font-size: 18px;
  margin: 14px 0 8px;
  font-weight: 620;
}
.card p {
  color: var(--ink-3);
  font-size: 14.8px;
}
.card-icon {
  width: 40px;
  height: 40px;
  border-radius: var(--r);
  display: grid;
  place-items: center;
  background: var(--raised);
  border: 1px solid var(--line);
}
.card-icon svg {
  width: 20px;
  height: 20px;
  stroke: var(--gold);
  fill: none;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ============ spec list — a machine-readable row ============ */
.spec {
  display: grid;
  gap: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--panel);
}
.spec > div {
  display: grid;
  grid-template-columns: minmax(150px, 0.32fr) 1fr;
  gap: 18px;
  padding: 15px 18px;
  border-bottom: 1px solid var(--line);
  font-size: 15px;
}
.spec > div:last-child {
  border-bottom: 0;
}
.spec dt,
.spec .k {
  font-family: var(--font-mono);
  font-feature-settings: "calt", "liga";
  font-size: 11.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold);
  padding-top: 3px;
}
.spec dd,
.spec .v {
  margin: 0;
  color: var(--ink-3);
}
.spec .v b {
  color: var(--ink);
  font-weight: 560;
}
@media (max-width: 620px) {
  .spec > div {
    grid-template-columns: 1fr;
    gap: 6px;
  }
}

/* ============ two-up layout ============ */
.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.06fr);
  gap: 48px;
  align-items: center;
}
.split-flip > *:first-child {
  order: 2;
}
@media (max-width: 960px) {
  .split {
    grid-template-columns: 1fr;
    gap: 36px;
  }
  .split-flip > *:first-child {
    order: 0;
  }
}

/* ============ forms ============ */
.lead-form {
  display: grid;
  gap: 13px;
  max-width: 520px;
}
.f-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 13px;
}
@media (max-width: 560px) {
  .f-row {
    grid-template-columns: 1fr;
  }
}
.field {
  display: grid;
  gap: 6px;
}
.field label {
  font-size: 12.5px;
  color: var(--ink-3);
  font-family: var(--font-mono);
  font-feature-settings: "calt", "liga";
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.field label .opt {
  color: var(--ink-3);
}
.field input,
.field textarea {
  background: var(--ground);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  padding: 11px 13px;
  font-size: 15px;
  color: var(--ink);
  width: 100%;
  transition: border-color 0.14s;
}
.field input::placeholder,
.field textarea::placeholder {
  color: var(--muted);
}
.field input:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--gold);
}
.field textarea {
  resize: vertical;
  min-height: 88px;
}
.hp-wrap {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}
.form-foot {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}
.form-status {
  font-size: 14px;
  color: var(--ink-3);
  min-height: 1.5em;
}
.form-status.err {
  color: var(--fail-ink);
}
.form-status a {
  color: var(--gold);
}
.unlocked {
  display: grid;
  gap: 14px;
  max-width: 520px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 24px;
}
.unlocked .ok-line {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 620;
  font-size: 17px;
}
.unlocked .ok-line .ok-dot {
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
  color: var(--pass);
  font-size: 13px;
  border: 1px solid var(--pass);
  border-radius: var(--r);
}
.unlocked p {
  color: var(--ink-3);
  font-size: 14.5px;
}

/* ============ footer ============ */
footer {
  border-top: 1px solid var(--line);
  background: var(--panel);
  padding: 60px 0 36px;
}
/* The brand block, then one column per link group, side by side. Each group
   gets its own column however many a page carries (three on the home page,
   four on the blog), because the brand sets the first track and every group
   after it lands in an implicit one. Below 860px the brand takes its own row
   above the groups. */
.foot-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr);
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 38px;
}
@media (max-width: 860px) {
  .foot-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-auto-flow: row;
    gap: 32px 24px;
  }
  .foot-brand {
    grid-column: 1 / -1;
  }
}
@media (max-width: 560px) {
  .foot-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
.foot-brand p {
  color: var(--ink-3);
  font-size: 14px;
  margin-top: 13px;
  max-width: 30ch;
}
.foot-col h4 {
  font-family: var(--font-mono);
  font-feature-settings: "calt", "liga";
  font-size: 10.5px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin: 0 0 15px;
  font-weight: 520;
}
.foot-col ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 10px;
}
.foot-col a,
.foot-col li {
  font-size: 14.5px;
  color: var(--ink-3);
}
.foot-col a:hover {
  color: var(--gold);
}
.foot-col address {
  font-style: normal;
  font-size: 14.5px;
  color: var(--ink-3);
  line-height: 1.7;
}
.socials {
  display: flex;
  gap: 12px;
  margin-top: 16px;
}
.socials a {
  width: 34px;
  height: 34px;
  border-radius: var(--r);
  border: 1px solid var(--line);
  display: grid;
  place-items: center;
  color: var(--muted);
  transition:
    color 0.14s,
    border-color 0.14s;
}
.socials a:hover {
  color: var(--gold);
  border-color: var(--gold);
}
.socials svg {
  width: 15px;
  height: 15px;
  fill: currentColor;
}
.foot-base {
  margin-top: 44px;
  padding-top: 20px;
  border-top: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: space-between;
  align-items: center;
  font-size: 13px;
  color: var(--ink-3);
}
.foot-base .mono {
  font-size: 12px;
}
.foot-base a {
  color: var(--gold);
}
.foot-end {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px;
}

/* ============ theme control ============
   Three icon buttons in one pill, the way vercel.com's footer carries it:
   System, Light, Dark. The chosen one sits on a raised chip; the others are
   muted until hovered. It is a radio group, so one tab stop reaches it and
   the arrow keys move the choice (oxagen.js). */
.theme-switch {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--ground);
}
.theme-switch button {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  transition:
    color 0.14s,
    background-color 0.14s;
}
.theme-switch button:hover {
  color: var(--ink);
}
.theme-switch button[aria-checked="true"] {
  background: var(--raised);
  color: var(--ink);
}
.theme-switch svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* Without script the buttons could not do anything, and the page already
   follows the OS, so the control is not drawn. */
html:not(.js) .theme-switch {
  display: none;
}
/* Nothing fades while the theme swaps (oxagen.js adds this for one frame). */
.theme-swap,
.theme-swap *,
.theme-swap *::before,
.theme-swap *::after {
  transition: none !important;
}

/* ============ diagrams ============
   The home and product pages explain the product with figures drawn in HTML
   and inline SVG from the product's own vocabulary: no screenshots, and one
   terminal on the whole site (the installer's output on the home page, where
   the output is the thing being shown).
   State is carried by border shape, so it survives grayscale and print:
     .is-ok    double: allowed, held, matched, proven
     .is-wait  dashed: pending, waiting on a person
     .is-no    single, in the fail ink: denied, broken, an exception
   The dot beside a label is a second signal, never the only one. A piece
   marked .dg-in arrives in order (--i) once its .reveal figure is in view.
   Anything that loops is a .dg-loop and runs only while its [data-live] host
   is on screen (.playing, toggled in oxagen.js). Without script, nothing is
   hidden and nothing moves. */
.dg {
  position: relative;
  margin: 0;
  min-width: 0;
  background: var(--ground);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow:
    0 1px 0 0 rgba(255, 255, 255, 0.06) inset,
    0 30px 80px -28px color-mix(in srgb, var(--ember-c) 30%, transparent),
    0 18px 50px -30px rgba(0, 0, 0, 0.7);
}
.dg-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 16px;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  font-size: 13px;
  color: var(--ink-3);
}
.dg-bar b {
  color: var(--ink);
  font-weight: 560;
}
.dg-bar .end {
  margin-left: auto;
  font-family: var(--font-mono);
  font-feature-settings: "calt", "liga";
  font-size: 11.5px;
  text-align: right;
}
@media (max-width: 520px) {
  .dg-bar .end {
    display: none;
  }
}
.dg-body {
  padding: 18px;
  display: grid;
  gap: 14px;
}
.dg-foot {
  padding: 11px 16px;
  border-top: 1px solid var(--line);
  background: var(--panel);
  font-size: 13px;
  color: var(--ink-3);
}
.dg-k {
  font-family: var(--font-mono);
  font-feature-settings: "calt", "liga";
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
}
.dg-m {
  font-family: var(--font-mono);
  font-feature-settings: "calt", "liga";
  font-size: 12.5px;
}
.dg-tag {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 3px 10px;
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  background: var(--panel);
  font-family: var(--font-mono);
  font-feature-settings: "calt", "liga";
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--ink-2);
  white-space: nowrap;
}
.is-ok {
  border: 3px double var(--ink-3);
}
.dg-tag.is-ok {
  padding: 1px 8px;
}
.is-wait {
  border-style: dashed;
  border-color: var(--ink-3);
}
.is-no {
  border-color: var(--fail);
}
.dg-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--faint);
  flex: none;
}
.dg-dot.ok {
  background: var(--pass);
}
.dg-dot.wait {
  background: var(--warn);
}
.dg-dot.no {
  background: var(--fail);
}
.dg-track {
  height: 6px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--raised);
  overflow: hidden;
}
.dg-fill {
  height: 100%;
  width: calc(var(--w, 0.5) * 100%);
  background: var(--ink-3);
  transform-origin: left;
}
/* a share split into parts: shades of the ink, labelled in a legend, so the
   parts never need a hue */
.dg-stack {
  display: flex;
  height: 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  overflow: hidden;
}
.dg-stack > span {
  width: calc(var(--w) * 100%);
  transform-origin: left;
}
.dg-legend {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 5px 18px;
  font-family: var(--font-mono);
  font-feature-settings: "calt", "liga";
  font-size: 12px;
  color: var(--ink-3);
}
.dg-legend > li {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.dg-legend > li::before {
  content: "";
  width: 9px;
  height: 9px;
  border-radius: 2px;
  flex: none;
}
.dg-legend b {
  margin-left: auto;
  color: var(--ink-2);
  font-weight: 500;
}
.dg-stack > [data-s="1"],
.dg-legend > [data-s="1"]::before {
  background: color-mix(in srgb, var(--ink) 80%, var(--ground));
}
.dg-stack > [data-s="2"],
.dg-legend > [data-s="2"]::before {
  background: color-mix(in srgb, var(--ink) 54%, var(--ground));
}
.dg-stack > [data-s="3"],
.dg-legend > [data-s="3"]::before {
  background: color-mix(in srgb, var(--ink) 32%, var(--ground));
}
.dg-stack > [data-s="4"],
.dg-legend > [data-s="4"]::before {
  background: color-mix(in srgb, var(--ink) 18%, var(--ground));
}
.in-view .dg-in {
  animation: dgIn 0.5s var(--ease) calc(var(--i, 0) * 110ms + 0.15s) both;
}
.in-view .dg-fill,
.in-view .dg-stack > span {
  animation: dgGrow 1s var(--ease) calc(var(--i, 0) * 110ms + 0.35s) both;
}
@keyframes dgIn {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
}
@keyframes dgGrow {
  from {
    transform: scaleX(0);
  }
}
/* !important because each loop sets its own `animation` shorthand, which
   resets the play state, and a page's own \3c style> comes after this file */
[data-live]:not(.playing) .dg-loop {
  animation-play-state: paused !important;
}

/* the copy column beside a figure */
.dg-copy h2 {
  font-size: clamp(28px, 3.8vw, 40px);
  margin: 14px 0;
}
.dg-copy .lede {
  color: var(--ink-3);
  font-size: 17px;
  max-width: 56ch;
}
.dg-copy ul {
  list-style: none;
  margin: 22px 0 0;
  padding: 0;
  display: grid;
  gap: 11px;
}
.dg-copy li {
  position: relative;
  padding-left: 20px;
  color: var(--ink-2);
  font-size: 15.5px;
  max-width: 60ch;
}
.dg-copy li::before {
  content: "";
  position: absolute;
  left: 1px;
  top: 0.62em;
  width: 7px;
  height: 7px;
  border: 1px solid var(--ink-3);
  border-radius: 2px;
}

/* ============ reveal ============ */
/* Gated on .js: without the head script (blocked, failed, or an old browser)
   nothing is hidden, so a broken script costs the animation, not the content. */
.js .reveal {
  opacity: 0;
  transform: translateY(18px);
  transition:
    opacity 0.6s var(--ease),
    transform 0.6s var(--ease);
}
.js .reveal.in-view {
  opacity: 1;
  transform: none;
}
.js .reveal-d1 {
  transition-delay: 0.07s;
}
.js .reveal-d2 {
  transition-delay: 0.14s;
}
.js .reveal-d3 {
  transition-delay: 0.21s;
}

/* ============ reduced motion ============ */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .tex-hex::after {
    animation: none;
    opacity: 0.7;
  }
  .js .reveal {
    opacity: 1;
    transform: none;
  }
  /* the stagger's delay is not a duration, so the rule above leaves it; drop
     the arrival animations so every figure is simply there */
  .in-view .dg-in,
  .in-view .dg-fill,
  .in-view .dg-stack > span {
    animation: none;
  }
  .caret {
    animation: none;
  }
}

/* h4 is small: the text face carries it, heavier than body. */
h4 {
  font-family: var(--font-sans);
  letter-spacing: 0;
}
