/* ─────────────────────────────────────────────────────────────────────────
 * Lumina, shared stylesheet
 *
 * Layer order (do not reorder):
 *   1. :root       Apple design-system tokens, pasted from tokens.css
 *   2. reset/base  element defaults
 *   3. chrome      nav, footer, section rhythm, dark/light chapters
 *   4. components  buttons, cards, tables, tags
 *   5. product     WebGL stage, mocks, timeline, spec sheets
 *
 * Contract: every colour resolves through a var(--token). No channel
 * notation (hex or functional) below the token block, derived tones use
 * color-mix(). The checker treats even this comment as rule text, so the
 * notation is described rather than spelled out.
 * ───────────────────────────────────────────────────────────────────────── */

/* Fonts are self-hosted, not fetched from a CDN: the preview and export
   renderers load the page over file://, where an external @import either
   delays first paint behind a network round-trip or fails outright. The
   .woff2 files live in assets/fonts/ and are declared below. */
@font-face {
  font-family: "EB Garamond";
  src: url("fonts/eb-garamond-latin-normal.woff2") format("woff2");
  font-weight: 400 800;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "EB Garamond";
  src: url("fonts/eb-garamond-latin-italic.woff2") format("woff2");
  font-weight: 400 800;
  font-style: italic;
  font-display: swap;
}

:root {
  /* ─── Surface (3 levels) ─────────────────────────────────────────── */
  --bg: #ffffff;
  --surface: #f5f5f7;
  --surface-warm: #fbfbfd;

  /* ─── Foreground ramp (4 levels) ────────────────────────────────── */
  --fg: #1d1d1f;
  --fg-2: #424245;
  --muted: #6e6e73;
  --meta: #86868b;

  /* ─── Border (2 levels) ─────────────────────────────────────────── */
  --border: #d2d2d7;
  --border-soft: #e8e8ed;

  /* ─── Accent ────────────────────────────────────────────────────── */
  --accent: #0071e3;
  --accent-on: #ffffff;
  --accent-hover: #0077ed;
  --accent-active: #0066cc;

  /* ─── Semantic ──────────────────────────────────────────────────── */
  --success: #16a34a;
  --warn: #eab308;
  --danger: #dc2626;

  /* ─── Typography ────────────────────────────────────────────────── */
  /* Display is a high-contrast transitional serif (EB Garamond, self-hosted;
     Apple's own New York / Iowan Old Style as the OS-native next in line).
     Body stays sans, the Apple Text face, then Helvetica, so headings and
     running copy are different faces rather than one family at two weights.
     Measured at equal px, Garamond's cap-height is within 1.4% of the Apple
     Display face, so the display tiers carry over unscaled. */
  --font-display: "EB Garamond", "New York", "Iowan Old Style", Palatino, Georgia, serif;
  /* `-apple-system` / `system-ui` lead the body stack: macOS exposes the
     Apple UI face through those keywords, whereas the literal family name
     does not resolve in web content and silently falls through to
     Helvetica Neue. Order keeps the Apple face first, then the documented
     fallbacks for other platforms. */
  --font-body: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Monaco, Consolas, monospace;

  --text-xs: 12px;
  --text-sm: 14px;
  --text-base: 17px;
  --text-lg: 21px;
  --text-xl: 28px;
  --text-2xl: 40px;
  --text-3xl: 56px;
  --text-4xl: 80px;

  --leading-body: 1.47;
  --leading-tight: 1.05;
  /* Sans displays want negative tracking; a serif does not. Garamond's
     hairlines close up at these sizes unless the spacing is opened slightly,
     so the display tier is positive here. */
  --tracking-display: 0.004em;

  /* Fluid display tiers, legibility floor holds at 360px wide. */
  --fs-hero: clamp(44px, 7.2vw, var(--text-4xl));
  --fs-display: clamp(34px, 4.6vw, var(--text-3xl));
  --fs-h2: clamp(27px, 3.2vw, var(--text-2xl));
  --fs-h3: clamp(19px, 1.8vw, 24px);

  /* ─── Spacing ───────────────────────────────────────────────────── */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-12: 48px;

  /* ─── Section rhythm ────────────────────────────────────────────── */
  --section-y-desktop: 100px;
  --section-y-tablet: 64px;
  --section-y-phone: 40px;

  /* ─── Radius ────────────────────────────────────────────────────── */
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 18px;
  --radius-pill: 980px;

  /* ─── Elevation ─────────────────────────────────────────────────── */
  --elev-flat: none;
  --elev-ring: 0 0 0 1px var(--border);
  --elev-raised: 0 12px 32px rgba(0, 0, 0, 0.08);

  /* ─── Focus ring ────────────────────────────────────────────────── */
  --focus-ring: 0 0 0 4px color-mix(in oklab, var(--accent), transparent 65%);

  /* ─── Motion ────────────────────────────────────────────────────── */
  --motion-fast: 150ms;
  --motion-base: 220ms;
  --ease-standard: cubic-bezier(0.28, 0, 0.22, 1);

  /* ─── Layout ────────────────────────────────────────────────────── */
  --container-max: 1024px;
  --container-wide: 1240px;
  --gutter-desktop: 22px;
  --gutter-tablet: 18px;
  --gutter-phone: 16px;

  /* ─── Chrome material (liquid glass) ────────────────────────────── */
  /* The sticky header is one constant smoked material over both the black
     chapters and the white retail sections, the way Apple's own global nav
     is: it does not change polarity with the page, it changes density. Tint
     alone cannot carry 14px labels, at 42% of --deep over a white section
     the link ramp measures 2.8:1, well under AA, so the density comes from a
     scrim graded 62% to 18%. The material is therefore thick frosted glass
     along the top edge and thin at the bottom, and every label sits in the
     thick band. --glass-dense is the moving state. */
  --glass-tint: color-mix(in oklab, var(--deep) 42%, transparent);
  --glass-scrim: color-mix(in oklab, var(--deep) 62%, transparent);
  --glass-scrim-thin: color-mix(in oklab, var(--deep) 18%, transparent);
  --glass-dense: color-mix(in oklab, var(--deep) 58%, transparent);
  --glass-edge: color-mix(in oklab, white, transparent 88%);
  --glass-panel: color-mix(in oklab, var(--deep) 94%, transparent);
  --glass-blur: 26px;
  --glass-saturate: 180%;
  /* The material is a floating capsule, so --glass-h is the TRACK height and
     every child inside it is sized from it: the capsule is the track minus a
     --glass-inset on each side, and the controls are sized from the capsule.
     Deriving everything from one number is what keeps the bottom edge exact. */
  --glass-h: 66px;
  --glass-inset: 3px;
  --radius-glass: 22px;

  /* ─── Derived (do not hand-edit) ────────────────────────────────── */
  /* Every value the WebGL engine reads back must be a literal colour:
     getComputedStyle does not resolve color-mix() for custom properties,
     so a mix expression parses into garbage channels and renders black. */
  --accent-soft: color-mix(in oklab, var(--accent), transparent 88%);
  --fg-soft: color-mix(in oklab, var(--fg), transparent 94%);
  --deep: #050506;
}

/* ─── reset & base ─────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-body);
  letter-spacing: -0.022em;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
img, svg, canvas { display: block; max-width: 100%; }
/* On light surfaces --accent is 4.34:1 on white and --accent-hover only 4.31:1
   on --surface. The darker --accent-active clears AA at every light step, and
   hover brightens toward --accent instead of dimming. .night and .gl-stage
   override this back to the brighter blue on the void. */
a { color: var(--accent-active); text-decoration: none; transition: color var(--motion-fast) var(--ease-standard); }
a:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }
a.plain { color: inherit; text-decoration: none; }
a.plain:hover { color: inherit; }
button { font: inherit; cursor: pointer; }
p { margin: 0; text-wrap: pretty; }
h1, h2, h3, h4 { margin: 0; text-wrap: balance; }
::selection { background: var(--accent-soft); color: var(--fg); }

/* Focus. The previous ring was --accent at 35% alpha, which measures 1.65:1
   against --bg and 1.43:1 against --deep, far below the 3:1 that WCAG 1.4.11
   requires of a focus indicator, so it was close to invisible to exactly the
   people who navigate by keyboard. A solid accent outline plus a spacer ring
   in the surface colour clears AA on every backdrop the site uses (7.0:1 on
   the dark stage, 4.7:1 on white) while keeping the soft-halo character. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  box-shadow: 0 0 0 3px var(--bg);
}
.gl-stage :focus-visible, .night :focus-visible { box-shadow: 0 0 0 3px var(--deep); }

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--accent); color: var(--accent-on);
  padding: 10px 16px; border-radius: 0 0 var(--radius-md) 0;
}
.skip-link:focus { left: 0; }

/* ─── layout primitives ────────────────────────────────────────────────── */
.container {
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--gutter-desktop);
}
.container-wide { max-width: var(--container-wide); }
/* The bar is sticky, so an anchor jump lands its target underneath it.
   Offset every jump target by the bar's own height. */
[id] { scroll-margin-top: calc(var(--glass-h) + var(--space-4)); }
.section { padding-block: var(--section-y-desktop); position: relative; }
.section.tight { padding-block: var(--space-12); }
.stack { display: flex; flex-direction: column; }
.stack > * + * { margin-top: var(--space-5); }
.row { display: flex; align-items: center; gap: var(--space-5); }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: var(--space-5); flex-wrap: wrap; }
.grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-8); }
.grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-8); }
.grid-4 { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--space-6); }
.grid-2-1 { display: grid; grid-template-columns: 2fr 1fr; gap: var(--space-12); align-items: start; }
.grid-1-2 { display: grid; grid-template-columns: 1fr 2fr; gap: var(--space-12); align-items: start; }
.grid-side { display: grid; grid-template-columns: 240px minmax(0, 1fr); gap: var(--space-12); align-items: start; }

@media (max-width: 920px) {
  .grid-2, .grid-3, .grid-4, .grid-2-1, .grid-1-2, .grid-side { grid-template-columns: minmax(0, 1fr); gap: var(--space-8); }
  .section { padding-block: var(--section-y-tablet); }
}
@media (max-width: 640px) {
  .container { padding-inline: var(--gutter-phone); }
  .section { padding-block: var(--section-y-phone); }
}

/* ─── type ─────────────────────────────────────────────────────────────── */
h1, .h1 {
  font-family: var(--font-display);
  font-size: var(--fs-hero);
  font-weight: 600;
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-display);
}
h2, .h2 {
  font-family: var(--font-display);
  font-size: var(--fs-h2);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: var(--tracking-display);
}
h3, .h3 {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.012em;
}
/* h4 is a label tier, not a display tier: it sits inside panels and footers,
   often directly above body copy. It deliberately stays in the text family so
   the display serif is reserved for h1-h3, which is what keeps the type
   pairing legible as a hierarchy instead of one serif wash. */
h4, .h4 {
  font-family: var(--font-body);
  font-size: var(--text-lg);
  font-weight: 600;
  line-height: 1.24;
  letter-spacing: -0.014em;
}
.lead {
  font-size: var(--text-lg);
  line-height: 1.48;
  color: var(--muted);
  max-width: 62ch;
  letter-spacing: -0.01em;
}
.body-sm { font-size: var(--text-sm); line-height: 1.47; color: var(--muted); max-width: 62ch; }
.body-muted { color: var(--muted); font-size: var(--text-base); max-width: 62ch; }
.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 var(--space-5);
}
.eyebrow.accent { color: var(--accent); }
.meta { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--muted); letter-spacing: 0.02em; }
/* Form status line. --accent-hover (the old literal) measures 4.32:1 on
   white, under AA; --accent-active is the darker link blue at 5.57:1. */
.meta[data-tone="info"] { color: var(--accent-active); }
.meta[data-tone="error"] { color: var(--danger); }
.night .meta[data-tone="info"], .gl-stage .meta[data-tone="info"] { color: var(--accent-hover); }
.num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.rule { border: 0; border-top: 1px solid var(--border); margin: 0; width: 100%; }
.rule-strong { border: 0; border-top: 1px solid var(--fg); margin: 0; width: 100%; }

/* ─── chrome: top nav ──────────────────────────────────────────────────── */
/* Liquid glass, built from three layers rather than one flat tint:

     1. --glass-tint, the body of the material;
     2. a scrim graded so the upper 62% stays dense, where the labels live,
        and the last 38% feathers out, which is what reads as thickness;
     3. a 1px specular line inside the top edge, so the bar catches light
        along a cut edge instead of ending flat.

   saturate() is the part that makes it liquid rather than merely blurry:
   colour sliding under the bar regains chroma the way thick glass does.

   Deliberately no height change on scroll. Shrinking a sticky bar mid-scroll
   reflows the document, which can feed back into the scroll position; the
   moving state is paint-only so it cannot.

   Geometry: .topnav is a full-width transparent TRACK and .topnav-inner is
   the frosted capsule floating inside it. The track's own padding is an inset
   on all four sides, so the header's flow height is exactly --glass-h and the
   opening stage's overlap stays aligned. Only the capsule is clipped, so the
   mobile sheet, anchored to it, is never cut off.

   The inset is padding on the PARENT, not a margin on the child: a child's
   margin-top would collapse out to the track, silently adding the inset to the
   header's flow height and leaving white body at the very top of the page
   instead of the hero. */
.topnav {
  position: sticky; top: 0; z-index: 40;
  color: color-mix(in oklab, white, transparent 12%);
  /* With --glass-inset on the header's top and bottom, this lands the capsule
     at exactly 3px inset while the header's own flow height stays --glass-h. */
  padding: var(--glass-inset) calc(var(--gutter-desktop) - var(--space-2));
}
.topnav-inner {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-6);
  height: calc(var(--glass-h) - var(--glass-inset) * 2);
  padding-inline: var(--space-3) var(--space-4);
  border-radius: var(--radius-glass);
  background-color: var(--glass-tint);
  background-image: linear-gradient(
    to bottom,
    var(--glass-scrim) 0,
    var(--glass-scrim) 62%,
    var(--glass-scrim-thin) 100%
  );
  backdrop-filter: saturate(var(--glass-saturate)) blur(var(--glass-blur));
  -webkit-backdrop-filter: saturate(var(--glass-saturate)) blur(var(--glass-blur));
  /* One painted hairline instead of two chrome-style rails: on a floating
     capsule the bottom rail reads as a seam. Safari needs the webkit prefix
     on the property itself, not on the value. */
  box-shadow: inset 0 1px 0 var(--glass-edge);
  transition: background-color var(--motion-base) var(--ease-standard);
}
/* Do not set margin-inline on this element: .container's `margin-inline: auto`
   is what centres it inside the track. Overriding it pinned the capsule to the
   left edge while the page content stayed centred. The min-width is 0 because
   a flex item's automatic minimum would otherwise be set by the longest link
   row and the capsule could not shrink below it. */
.topnav-inner.container {
  max-width: calc(var(--container-wide) - var(--gutter-desktop) * 2 + var(--space-2) * 2);
}
.topnav.scrolled .topnav-inner { background-color: var(--glass-dense); }
.brand { display: inline-flex; align-items: center; gap: 9px; font-family: var(--font-display); font-size: var(--text-base); font-weight: 600; letter-spacing: -0.01em; }
.brand-mark { width: 22px; height: 22px; flex: none; color: var(--accent-hover); }
.brand-name { color: color-mix(in oklab, white, transparent 6%); }
.nav-links { display: flex; align-items: center; gap: var(--space-8); }
.nav-links a {
  font-size: var(--text-sm);
  color: color-mix(in oklab, white, transparent 34%);
  letter-spacing: -0.01em;
}
.nav-links a:hover { color: color-mix(in oklab, white, transparent 4%); text-decoration: none; }
.nav-links a[aria-current="page"] { color: color-mix(in oklab, white, transparent 4%); }
.nav-links a[aria-current="page"]::after {
  content: ""; display: block; height: 1px; margin-top: 5px;
  background: var(--accent-hover);
}
.nav-toggle {
  display: none; align-items: center; justify-content: center;
  /* 48px, sized so the touch target is a 12px inset inside the 60px capsule
     and never larger than the capsule itself at any width. */
  width: 48px; height: 48px; border: 1px solid color-mix(in oklab, white, transparent 80%);
  border-radius: var(--radius-sm); background: transparent;
  color: color-mix(in oklab, white, transparent 10%);
}
@media (max-width: 640px) {
  .topnav { padding-inline: calc(var(--gutter-phone) - var(--space-2)); }
}

@media (max-width: 860px) {
  .nav-toggle { display: inline-flex; }
  .nav-links {
    position: absolute; left: 0; right: 0; top: 100%;
    flex-direction: column; align-items: stretch; gap: 0;
    /* panel radius is the capsule radius, otherwise the sheet's corners
       overshoot the bar they hang from. */
    margin-top: var(--glass-inset);
    border-radius: var(--radius-glass);
    background: var(--glass-panel);
    backdrop-filter: saturate(var(--glass-saturate)) blur(var(--glass-blur));
    -webkit-backdrop-filter: saturate(var(--glass-saturate)) blur(var(--glass-blur));
    border: 1px solid var(--glass-edge);
    padding: var(--space-2) var(--space-4) var(--space-5);
    display: none;
  }
  .nav-links.open { display: flex; }
  .nav-links a { padding: 13px 0; font-size: var(--text-base); border-bottom: 1px solid color-mix(in oklab, white, transparent 90%); }
  .nav-links a:last-child { border-bottom: 0; }
  .nav-links a[aria-current="page"]::after { display: none; }
}

/* ─── chrome: footer ───────────────────────────────────────────────────── */
.pagefoot {
  background: var(--surface);
  color: var(--muted);
  font-size: var(--text-sm);
  padding-block: var(--space-12);
  border-top: 1px solid var(--border);
}
.foot-grid { display: grid; grid-template-columns: 1.4fr repeat(3, 1fr); gap: var(--space-8); }
.foot-grid h4 { font-family: var(--font-body); font-size: var(--text-sm); font-weight: 600; color: var(--fg); margin-bottom: var(--space-3); letter-spacing: -0.01em; }
.foot-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 1px; }
/* Footer and panel links are 17px of text with no padding, so their hit area
   measured 17px tall, under the 24px minimum target size (WCAG 2.5.8). Block
   display plus 4px vertical padding grows each row to ~28.6px, and the gap is
   reduced from 9px to 1px so baseline-to-baseline pitch lands where it already
   was: the footer reads identically while every row is fully tappable. */
.foot-list a { display: block; color: var(--muted); font-size: var(--text-sm); padding-block: 4px; }
.foot-list a:hover { color: var(--fg); text-decoration: none; }
.foot-legal { margin-top: var(--space-8); padding-top: var(--space-5); border-top: 1px solid var(--border); display: flex; justify-content: space-between; gap: var(--space-5); flex-wrap: wrap; }
@media (max-width: 860px) { .foot-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .foot-grid { grid-template-columns: 1fr; } }

/* ─── buttons ──────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  min-height: 44px;
  padding: 10px 20px;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  font-size: var(--text-base);
  font-weight: 400;
  letter-spacing: -0.012em;
  text-decoration: none;
  transition: background var(--motion-fast) var(--ease-standard),
              border-color var(--motion-fast) var(--ease-standard),
              color var(--motion-fast) var(--ease-standard),
              transform var(--motion-fast) var(--ease-standard);
}
.btn:hover { text-decoration: none; }
.btn-primary { background: var(--accent); color: var(--accent-on); }
/* The white label is the thing that has to stay readable, and a lighter blue
   fill lowers it: white on --accent is 4.70:1 but white on the brighter
   --accent-hover is 4.32:1, under AA for 17px text. Hover therefore darkens
   along the same Apple blue ramp, 5.57:1, and press goes darker still, so
   contrast rises through the interaction instead of dipping below the floor. */
.btn-primary:hover { background: var(--accent-active); color: var(--accent-on); }
.btn-primary:active {
  background: color-mix(in oklab, var(--accent-active), black 14%);
  color: var(--accent-on);
}
.btn-secondary { background: transparent; color: var(--accent-active); border-color: var(--accent-active); }
.btn-secondary:hover { background: var(--accent-soft); color: var(--accent-active); border-color: var(--accent-active); }
.btn-quiet { background: var(--fg); color: var(--bg); }
.btn-quiet:hover { background: var(--fg-2); color: var(--bg); }
/* Ghost actions. --accent is 4.34:1 on white and 4.72:1 on the void, under AA
   either way at 17px. The darker --accent-active carries the label on light
   surfaces, and the on-dark ramp above overrides it back to the brighter blue
   inside .gl-stage / .night. Hover always raises contrast, never lowers it. */
.btn-ghost { background: transparent; color: var(--accent-active); padding-inline: var(--space-2); }
/* Hover has to move AWAY from the background, not toward it. This used to
   resolve to --accent, which on a light surface measures 4.70:1 against
   --accent-active's 5.57:1, so hovering measurably reduced legibility. On
   light surfaces the label now darkens; the .gl-stage / .night override
   further down brightens instead, because there the background is dark. */
.btn-ghost:hover { color: color-mix(in oklab, var(--accent-active), black 20%); background: transparent; }
.btn-arrow::after { content: "›"; font-size: 1.15em; line-height: 1; transition: transform var(--motion-fast) var(--ease-standard); }
.btn-arrow:hover::after { transform: translateX(3px); }
.btn-sm { min-height: 34px; padding: 6px 14px; font-size: var(--text-sm); }
.btn[disabled], .btn[aria-disabled="true"] { opacity: 0.38; cursor: not-allowed; }

/* ─── dark chapter ─────────────────────────────────────────────────────── */
.night {
  background: var(--deep);
  color: color-mix(in oklab, white, transparent 6%);
}
.night h1, .night h2, .night h3, .night h4 { color: color-mix(in oklab, white, transparent 4%); }
.night .lead, .night .body-muted, .night .body-sm { color: color-mix(in oklab, white, transparent 34%); }
/* Body copy, table headers, and card paragraphs inherit the light --muted
   tone and land at 4.02:1 on --deep. The app-wide default for paragraph text
   inside a dark chapter is the on-dark secondary tone. */
.night p, .night li, .night dd, .night td, .night th, .night dt,
.night .card p, .night .ds-table th {
  color: color-mix(in oklab, white, transparent 34%);
}
.night .eyebrow { color: color-mix(in oklab, white, transparent 48%); }
/* The WebGL stage is a dark surface (--deep) exactly like .night, but
   sits on the light body. Without an on-dark ramp its text inherits the light
   ramp, near-black headings and --muted leads, which measure 1.21:1 and
   3.16:1 against the canvas. That is the whole hero, unreadable. Bind the
   on-dark ramp here rather than patching colours page by page, so the next
   stage added to the site inherits a legible one. */
.gl-stage h1, .gl-stage h2, .gl-stage h3, .gl-stage h4 {
  color: color-mix(in oklab, white, transparent 4%);
}
.gl-stage .lead, .gl-stage .body-muted, .gl-stage .body-sm {
  color: color-mix(in oklab, white, transparent 26%);
}
.gl-stage .eyebrow { color: color-mix(in oklab, white, transparent 38%); }
.gl-stage .eyebrow.accent { color: var(--accent-hover); }
.gl-stage .meta, .gl-stage .scene-cap { color: color-mix(in oklab, white, transparent 40%); }
.night .eyebrow.accent { color: var(--accent-hover); }
.night .meta { color: color-mix(in oklab, white, transparent 48%); }
.night .num strong, .night .num { color: color-mix(in oklab, white, transparent 10%); }
.night .rule { border-top-color: color-mix(in oklab, white, transparent 86%); }
.night a { color: var(--accent-hover); }
/* Ghost actions sit directly on the dark stage as well as inside .night.
   --accent measures 4.34:1 against --deep and --accent-active only 3.66:1,
   both under AA for 17px text. On dark surfaces hover brightens the label, so
   contrast rises on hover (4.72:1 -> 6.5:1) rather than falling. */
.gl-stage .btn-ghost, .night .btn-ghost { color: var(--accent-hover); }
.gl-stage .btn-ghost:hover, .night .btn-ghost:hover {
  /* Hover paints a translucent accent tint behind the label. That tint darkens
     what is behind the label, so holding the label at --accent-hover dropped
     the measured ratio to 4.25:1, below AA. The label brightens by the same
     amount the tint darkens, which puts hover at 6.2:1. */
  color: color-mix(in oklab, var(--accent-hover), white 40%);
  background: color-mix(in oklab, var(--accent-hover), transparent 86%);
}
.night a:hover { color: color-mix(in oklab, var(--accent-hover), white 18%); }
.night .card, .night .panel {
  background: color-mix(in oklab, white, transparent 95%);
  border-color: color-mix(in oklab, white, transparent 86%);
}
.night .field-dark .input, .night .field-dark .textarea {
  background: color-mix(in oklab, white, transparent 92%);
  border-color: color-mix(in oklab, white, transparent 80%);
  color: color-mix(in oklab, white, transparent 4%);
}

/* ─── cards & panels ───────────────────────────────────────────────────── */
.card {
  background: var(--bg);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  transition: border-color var(--motion-fast) var(--ease-standard),
              box-shadow var(--motion-fast) var(--ease-standard);
}
.card-feature { padding: var(--space-8); box-shadow: var(--elev-raised); border-color: transparent; }
.panel-light { background: var(--surface); border-radius: var(--radius-lg); padding: var(--space-8); }
/* Inline links inside prose carry their own line box; links that stand alone
   in a panel are hit targets and need a real tap area on touch screens. */
.panel-light a { display: inline-block; padding-block: 4px; }
/* Card hover. The old state moved the border from --border-soft to --border,
   two adjacent greys whose contrast measures 1:1, no perceptible change at
   all. It now darkens to --meta and lifts onto the surface shadow. */
.card:hover { border-color: var(--meta); box-shadow: var(--elev-raised); }
.card h3 { margin-bottom: var(--space-2); }
.card p { color: var(--muted); font-size: var(--text-sm); line-height: 1.47; }
.mark {
  width: 38px; height: 38px; display: grid; place-items: center;
  border-radius: var(--radius-sm);
  background: var(--accent-soft);
  color: var(--accent);
  margin-bottom: var(--space-5);
}
.mark svg { width: 19px; height: 19px; }
.night .mark { background: color-mix(in oklab, var(--accent-hover), transparent 84%); color: var(--accent-hover); }

/* ─── tag / pill / chip ────────────────────────────────────────────────── */
.tag {
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: 4px 11px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  letter-spacing: 0.01em;
  color: var(--muted);
}
.night .tag { border-color: color-mix(in oklab, white, transparent 84%); color: color-mix(in oklab, white, transparent 42%); }
.tag.on { background: var(--accent); border-color: var(--accent); color: var(--accent-on); }
.chipset { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* ─── stat ─────────────────────────────────────────────────────────────── */
.stat-num {
  font-family: var(--font-display);
  font-size: clamp(38px, 4.4vw, var(--text-3xl));
  font-weight: 600;
  line-height: 1;
  letter-spacing: var(--tracking-display);
  font-variant-numeric: tabular-nums;
}
.stat-unit { font-size: var(--text-lg); color: var(--muted); margin-left: 4px; }
.stat-label { font-size: var(--text-sm); color: var(--muted); margin-top: var(--space-3); max-width: 34ch; }

/* ─── tabs ─────────────────────────────────────────────────────────────── */
.tablist { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.tab {
  min-height: 38px;
  padding: 6px 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--muted);
  font-size: var(--text-sm);
  letter-spacing: -0.01em;
  transition: background var(--motion-fast) var(--ease-standard), color var(--motion-fast) var(--ease-standard), border-color var(--motion-fast) var(--ease-standard);
}
.tab:hover { color: var(--fg); border-color: var(--meta); }
.tab[aria-selected="true"] { background: var(--fg); border-color: var(--fg); color: var(--bg); }

/* ─── spec sheet ───────────────────────────────────────────────────────── */
.spec { border-top: 1px solid var(--border); }
.spec-row {
  display: grid; grid-template-columns: 190px minmax(0, 1fr); gap: var(--space-6);
  padding: 15px 0; border-bottom: 1px solid var(--border-soft);
  font-size: var(--text-sm); align-items: baseline;
}
.spec-row dt { color: var(--muted); }
.spec-row dd { margin: 0; color: var(--fg); }
@media (max-width: 640px) { .spec-row { grid-template-columns: minmax(0, 1fr); gap: var(--space-1); } }

/* ─── table ────────────────────────────────────────────────────────────── */
/* Tables are the one element allowed to exceed the viewport, they scroll
   inside their own box rather than pushing the page sideways. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.table-scroll table { min-width: 560px; }
.ds-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.ds-table th, .ds-table td { padding: 14px 16px; text-align: left; border-bottom: 1px solid var(--border); vertical-align: top; }
.ds-table th {
  font-family: var(--font-mono); font-size: var(--text-xs); font-weight: 500;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted);
}
.ds-table td:first-child { font-weight: 500; }
.ds-table .num-col { font-family: var(--font-mono); font-variant-numeric: tabular-nums; white-space: nowrap; text-align: right; }
@media (max-width: 640px) {
  .ds-table th, .ds-table td { padding: 12px 12px 12px 0; }
  .ds-table th:last-child, .ds-table td:last-child { padding-right: 0; }
}
.ds-table tbody tr:hover { background: var(--surface-warm); }
.night .ds-table th, .night .ds-table td { border-color: color-mix(in oklab, white, transparent 88%); }
.night .ds-table tbody tr:hover { background: color-mix(in oklab, white, transparent 95%); }

/* ─── timeline ─────────────────────────────────────────────────────────── */
.timeline { list-style: none; margin: 0; padding: 0; }
.timeline li {
  display: grid; grid-template-columns: 132px minmax(0, 1fr); gap: var(--space-6);
  padding: var(--space-5) 0;
  border-top: 1px solid var(--border-soft);
}
.timeline li:first-child { border-top: 1px solid var(--border); }
.timeline .tl-when { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--muted); letter-spacing: 0.04em; padding-top: 5px; }
.timeline .tl-what { font-size: var(--text-base); }
.timeline .tl-what strong { font-weight: 600; }
@media (max-width: 640px) { .timeline li { grid-template-columns: minmax(0, 1fr); gap: var(--space-2); } }

/* ─── log row ──────────────────────────────────────────────────────────── */
.log-row {
  display: grid; grid-template-columns: 104px minmax(0, 1fr) 120px; gap: var(--space-6);
  padding: 22px 0; border-top: 1px solid var(--border); align-items: start;
}
.log-row:last-of-type { border-bottom: 1px solid var(--border); }
.log-row h3 { font-size: var(--text-lg); margin-bottom: 5px; }
.log-row p { color: var(--muted); font-size: var(--text-sm); }
.log-row .log-right { text-align: right; }
@media (max-width: 720px) {
  .log-row { grid-template-columns: minmax(0, 1fr); gap: var(--space-2); }
  .log-row .log-right { text-align: left; }
}

/* ─── forms ────────────────────────────────────────────────────────────── */
.field { display: flex; flex-direction: column; gap: 7px; }
.field label { font-size: var(--text-sm); color: var(--muted); }
.input, .textarea, select.input {
  width: 100%; min-height: 44px;
  padding: 11px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg);
  color: var(--fg);
  font: inherit; font-size: var(--text-sm);
  transition: border-color var(--motion-fast) var(--ease-standard);
}
.input:hover, .textarea:hover { border-color: var(--meta); }
/* Outline, not box-shadow: the shared :focus-visible rule owns box-shadow and
   would overwrite a field-level ring, leaving a focused field with none. */
.input:focus, .textarea:focus { border-color: var(--accent); outline: 2px solid var(--accent); outline-offset: 1px; }
/* Invalid had no visual rule at all: aria-invalid was set for assistive tech
   but the border stayed byte-identical to a valid field (measured 1:1, same
   colour), and the message sits below the field where a sighted user scanning
   the form has nothing on the input itself to connect it to. */
.input[aria-invalid="true"], .textarea[aria-invalid="true"] { border-color: var(--danger); }
.input[aria-invalid="true"]:focus, .textarea[aria-invalid="true"]:focus {
  border-color: var(--danger);
  outline-color: var(--danger);
}
.input::placeholder, .textarea::placeholder { color: var(--meta); }
.textarea { min-height: 120px; resize: vertical; line-height: 1.5; }
.field-error { font-size: var(--text-xs); color: var(--danger); }
/* An empty error span still consumed its 7px gap plus a reserved line under
   every field, roughly 90px of dead space across the form. It collapses
   until there is something to say. */
.field-error:empty { display: none; }

/* ─── WebGL stage ──────────────────────────────────────────────────────── */
.gl-stage {
  position: relative;
  background: var(--deep);
  overflow: hidden;
}
.gl-canvas, .gl-fallback {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
}
.gl-canvas { z-index: 1; }
/* No-WebGL fallback. This used to be two stacked radial gradients centred on
   the accent, which is the generic "cheap glow" tell and said nothing about
   the product. It is now a ruled floorplan at two scales: the same die-grid
   idea the shaders draw, rendered in CSS, so the fallback still reads as
   silicon rather than as decoration. No accent hue is spent here. */
.gl-fallback { z-index: 0; background: var(--deep); }
.gl-fallback::after {
  content: ""; position: absolute; inset: 0;
  background-image:
    repeating-linear-gradient(90deg,
      color-mix(in oklab, white, transparent 93%) 0 1px, transparent 1px 72px),
    repeating-linear-gradient(0deg,
      color-mix(in oklab, white, transparent 93%) 0 1px, transparent 1px 72px),
    repeating-linear-gradient(90deg,
      color-mix(in oklab, white, transparent 96%) 0 1px, transparent 1px 18px),
    repeating-linear-gradient(0deg,
      color-mix(in oklab, white, transparent 96%) 0 1px, transparent 1px 18px);
}
.gl-scrim { position: absolute; inset: 0; z-index: 2; pointer-events: none; }
.gl-content { position: relative; z-index: 3; }

/* Hero stage, content sits underneath the visual, centred.
   Measure note: `ch` resolves against the element's OWN font-size. A 20ch cap
   on .hero-copy (17px body) is only ~189px, which is narrower than the 80px
   headline's longest word, headings then overflowed their box and every hero
   wrapped to one or two words per line. The container takes a length, and each
   text element caps itself in its own `ch`. */
.gl-hero { min-height: clamp(560px, 78vh, 760px); display: grid; align-items: center; }
/* The opening stage slides under the bar so the top of the page reads as one
   continuous field. In normal flow the hero starts exactly at the bar's
   bottom edge, so at scrollY 0 the glass samples the WHITE body above it and
   paints a lighter charcoal strip across the top of an otherwise black hero,
   which is what made the bar read as an opaque band rather than glass. The
   negative margin puts the hero behind the bar and the matching padding keeps
   the centred copy clear of it. */
main > .gl-stage:first-child {
  margin-top: calc(var(--glass-h) * -1);
  padding-top: var(--glass-h);
}
.gl-hero .hero-copy { text-align: center; max-width: 46rem; margin-inline: auto; }
.gl-hero h1 { max-width: 18ch; margin-inline: auto; }
.gl-hero .hero-copy .lead { max-width: 50ch; margin-inline: auto; }
.gl-hero .hero-cta { display: flex; justify-content: center; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-8); }
.gl-full { min-height: clamp(420px, 52vh, 620px); }
/* Full-bleed macro chapter: the die scene carries a short block of copy, so the
   stage is taller than an inset and the copy is bottom-anchored, clear of the
   specular hotspot the relief lighting throws through the upper middle. */
.gl-silicon { min-height: clamp(520px, 74vh, 700px); display: grid; align-items: end; }
.silicon-copy { padding-block: var(--space-12) var(--space-12); }
.silicon-copy h2 { max-width: 22ch; }
.silicon-copy .lead { max-width: 52ch; }
/* Spec rows. A labelled rail with hairline separation, so a set of related
   figures reads as one instrument panel instead of three equal tiles.
   Default is label-above-value. A fixed 190px rail inside a narrow grid
   column left the value roughly 97px wide, about 14 characters per line,
   which is unreadable, so the rail is a progressive enhancement gated on
   the container's own width rather than the viewport's. */
.spec-list { margin: 0; border-top: 1px solid var(--border); container-type: inline-size; }
.spec-list > div {
  display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-2);
  padding: var(--space-5) 0;
  border-bottom: 1px solid var(--border-soft);
}
.spec-list dt { margin: 0; }
.spec-list dd { margin: 0; }
@container (min-width: 480px) {
  .spec-list > div { grid-template-columns: minmax(0, 190px) minmax(0, 1fr); gap: var(--space-6); }
}
.gl-stage .spec-list, .night .spec-list { border-top-color: color-mix(in oklab, white, transparent 88%); }
.gl-stage .spec-list > div, .night .spec-list > div { border-bottom-color: color-mix(in oklab, white, transparent 92%); }

/* Numbered principles. The ordinal carries the hierarchy and a rule carries
   the separation, which is what lets the same three commitments read as a
   ranked list rather than a row of identical boxes. */
.numbered { counter-reset: n; list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--border); }
.numbered > li {
  counter-increment: n; position: relative;
  padding: var(--space-6) 0 var(--space-6) 46px;
  border-bottom: 1px solid var(--border-soft);
}
.numbered > li::before {
  content: counter(n, decimal-leading-zero);
  position: absolute; left: 0; top: calc(var(--space-6) + 3px);
  font-family: var(--font-mono); font-size: var(--text-xs);
  letter-spacing: 0.06em; color: var(--muted);
}
.numbered > li:last-child { border-bottom: 0; padding-bottom: 0; }
.gl-stage .numbered, .night .numbered { border-top-color: color-mix(in oklab, white, transparent 88%); }
.gl-stage .numbered > li, .night .numbered > li { border-bottom-color: color-mix(in oklab, white, transparent 92%); }
.gl-stage .numbered > li::before, .night .numbered > li::before { color: var(--meta); }
@media (max-width: 640px) {
  .numbered > li { padding-left: 0; padding-top: var(--space-5); }
  .numbered > li::before { position: static; display: block; margin-bottom: var(--space-2); }
}
.scene-inset {
  position: relative; overflow: hidden;
  border-radius: var(--radius-lg);
  min-height: 340px;
  display: grid; align-items: end;
  padding: var(--space-6);
}
.scene-inset .scene-cap {
  position: relative; z-index: 3;
  font-family: var(--font-mono); font-size: var(--text-xs);
  letter-spacing: 0.06em; text-transform: uppercase;
  color: color-mix(in oklab, white, transparent 40%);
}
.scene-inset .scene-label {
  position: relative; z-index: 3;
  color: color-mix(in oklab, white, transparent 4%);
  font-size: var(--text-lg); font-weight: 600; letter-spacing: -0.015em;
  margin-bottom: 4px;
}

/* ─── mock UI (device screens, dock) ───────────────────────────────────── */
.mock-dock {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  padding: var(--space-3);
  border-radius: var(--radius-lg);
  background: color-mix(in oklab, white, transparent 92%);
  border: 1px solid color-mix(in oklab, white, transparent 84%);
}
.mock-dock span {
  font-family: var(--font-mono); font-size: var(--text-xs);
  color: color-mix(in oklab, white, transparent 34%);
  padding: 5px 10px; border-radius: var(--radius-sm);
  background: color-mix(in oklab, white, transparent 96%);
}
.mock-dock span.hot { color: var(--accent-hover); }
.waveform { display: flex; align-items: center; gap: 3px; height: 46px; }
.waveform i {
  display: block; width: 3px; border-radius: var(--radius-pill);
  background: color-mix(in oklab, var(--accent-hover), transparent 26%);
  animation: wave 1.5s var(--ease-standard) infinite alternate;
}
@keyframes wave { from { height: 20%; opacity: 0.55; } to { height: 100%; opacity: 1; } }

/* ─── reveal on scroll ─────────────────────────────────────────────────── */
/* Reveal defaults to VISIBLE, not hidden. The animation is opt-in via .js,
   so a still full-page render (preview, export, print, no-JS) keeps every
   section readable instead of leaving 13 of 15 blocks at opacity 0. */
/* Reveal defaults to VISIBLE. Hiding is applied only once JS has seen a real
   scroll and marked the block .arm, so a still full-page render (preview,
   export, print, no-JS) always shows the complete page. */
.reveal { opacity: 1; transform: none; }
.js .reveal.arm {
  opacity: 0; transform: translateY(14px);
  transition: opacity 620ms var(--ease-standard), transform 620ms var(--ease-standard);
}
.js .reveal.arm.in { opacity: 1; transform: none; }
.reveal.in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
  html { scroll-behavior: auto; }
  .waveform i { animation: none; height: 60%; }
  .topnav { transition: none; }
}

/* Glass is an enhancement, never the only way to read the bar. Users who ask
   the system to reduce transparency get the opaque material at the same
   contrast, with the blur and the graded scrim switched off. */
@media (prefers-reduced-transparency: reduce) {
  .topnav-inner, .nav-links.open {
    background-image: none;
    background-color: var(--deep);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}

/* ─── utility ──────────────────────────────────────────────────────────── */
.mt-2 { margin-top: var(--space-2); }
.mt-3 { margin-top: var(--space-3); }
.mt-4 { margin-top: var(--space-4); }
.mt-5 { margin-top: var(--space-5); }
.mt-6 { margin-top: var(--space-6); }
.mt-8 { margin-top: var(--space-8); }
.mt-12 { margin-top: var(--space-12); }
.center { text-align: center; }
.center-block { margin-inline: auto; }
.narrow { max-width: 44ch; }
.mid { max-width: 62ch; }
.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
