/* ==========================================================================
   Oceantic — Atomist edition
   A bordered editorial grid: every section lives inside one 1280px column
   whose left and right rules never break. Sections are separated by ruled
   spacer blocks rather than whitespace, so the page reads as one continuous
   sheet of drafting paper.

   Two themes, one geometry. `data-theme` is stamped on <html> before paint.
   ========================================================================== */

/* ------------------------------------------------------------------ tokens */
:root {
  /* The beta bundle runs wider than the main site, which holds this at 1180px.
     Prose is capped per-component (56ch, 62ch, 68ch) rather than by the page,
     so the extra width lands on the grids and the figures, not on the measure.
     This is the one deliberate divergence from the root stylesheet: change it
     here and the two files no longer diff clean, which is intended. */
  --maxw: 1270px;
  --gut: 16px;

  --r-sm: 8px;
  --r-md: 10px;
  --r-lg: 14px;
  --r-full: 999px;

  --sans: "Hanken Grotesk", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --dur: 0.3s;

  /* Motion here is two families that never mix, and the comments through this
     file keep saying so — this is where that gets a scale instead of a habit.

     ARRIVALS ease OUT and are over quickly: something appears and settles.
     LOOPS ease IN AND OUT and are slow: something breathes and never lands.
     A third case, constant travel — a dash marching, a packet crossing, a
     conic gradient turning — is `linear` on purpose, because easing a thing
     that never starts or stops just makes it limp.

     Before this there were seven arrival durations (0.32s, 0.4s, 0.42s, 0.5s,
     0.5s, 0.6s, 0.85s) and three arrival distances, none of them chosen
     against each other. */
  --t-ui: 0.3s; /* the interface answering a click */
  --t-in: 0.45s; /* content arriving on screen */
  --t-run: 0.7s; /* a mechanism running, where the duration IS the point */
  --lift: 10px; /* how far anything travels to arrive */
}

/* The product surfaces — console, repository, report — stay dark in both
   themes, the way a screenshot of a dark app does. This is what makes the
   light theme read as paper with a terminal set into it. */
:root {
  --shot-bg: #0a0e18;
  --shot-2: #111827;
  --shot-bd: #1f2937;
  --shot-fg: #f4f4f5;
  --shot-mut: #9ca3af;
  --shot-sub: #6b7280;
}

/* Light — white paper, zinc rules, one warm orange */
[data-theme="light"] {
  --bg: #ffffff;
  --fg: #09090b;
  --muted: #f4f4f5;
  --muted-2: #e4e4e7;
  --muted-fg: #71717a;
  --subtle: #a1a1aa;
  --border: #e4e4e7;
  --border-strong: #d4d4d8;
  --panel: #ffffff;
  --panel-2: #fafafa;
  --primary: #18181b;
  --primary-fg: #fafafa;
  --accent: #d9812f;
  /* The headline gradient's off-scale tail. White on dark, a lighter amber on
     light — where white would simply erase the last few letters. */
  --grad-end: #f2b478;
  --accent-soft: rgba(217, 129, 47, 0.11);
  --accent-line: rgba(217, 129, 47, 0.32);
  --shadow: 0 1px 2px rgba(9, 9, 11, 0.05), 0 18px 40px -16px rgba(9, 9, 11, 0.18);
  /* No dissolve on white — the shot ends on its own rounded edge. */
  --fade: transparent;
  --scrim: #0a0e18;
}

/* Dark — gray-950, zinc rules, the same orange */
[data-theme="dark"] {
  --bg: #030712;
  --fg: #fafafa;
  --muted: #111827;
  --muted-2: #18181b;
  --muted-fg: #a1a1aa;
  --subtle: #71717a;
  --border: #27272a;
  --border-strong: #3f3f46;
  --panel: #090d16;
  --panel-2: #111827;
  --primary: #fafafa;
  --primary-fg: #09090b;
  --accent: #e5a55f;
  --grad-end: #ffffff;
  --accent-soft: rgba(229, 165, 95, 0.13);
  --accent-line: rgba(229, 165, 95, 0.32);
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 24px 60px -24px rgba(0, 0, 0, 0.85);
  --fade: linear-gradient(180deg, rgba(3, 7, 18, 0) 0%, #030712 90%);
  --scrim: #05080f;
}

/* Re-point the tokens inside a product surface; every component below reads
   them, so one block flips the whole panel without a second set of rules. */
.shot {
  --panel: var(--shot-bg);
  --panel-2: var(--shot-2);
  --border: var(--shot-bd);
  --border-strong: #374151;
  --fg: var(--shot-fg);
  --muted-fg: var(--shot-mut);
  --subtle: var(--shot-sub);
  --muted: var(--shot-2);
  color: var(--shot-fg);
}

/* --------------------------------------------------------------- resets */
*,
*::before,
*::after {
  box-sizing: border-box;
}
* {
  margin: 0;
}
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  color-scheme: light;
}
[data-theme="dark"] {
  color-scheme: dark;
}
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}
body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
img,
svg {
  display: block;
  max-width: 100%;
}
a {
  color: inherit;
  text-decoration: none;
}
button,
input,
textarea {
  font: inherit;
  color: inherit;
}
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}
::selection {
  background: var(--accent);
  color: var(--primary-fg);
}

/* ------------------------------------------------------------ primitives */
.wrap {
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gut);
  width: 100%;
}
/* The signature: a column whose side rules run the whole page. */
.rule {
  border-inline: 1px solid var(--border);
}
.rule-t {
  border-top: 1px solid var(--border);
}
.rule-b {
  border-bottom: 1px solid var(--border);
}
.gap {
  height: 80px;
}
.gap > div {
  height: 100%;
  border: 1px solid var(--border);
}
.mono {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}
.muted {
  color: var(--muted-fg);
}

.sec__head {
  padding: 32px 32px;
}
@media (min-width: 640px) {
  .sec__head {
    padding: 40px 32px;
  }
}
@media (min-width: 1024px) {
  .sec__head {
    padding: 80px 48px;
  }
}
.sec__head--split {
  display: grid;
  gap: 24px;
}
@media (min-width: 1024px) {
  .sec__head--split {
    grid-template-columns: 1.1fr 1fr;
    align-items: end;
    gap: 48px;
  }
}

h1,
h2,
h3,
h4 {
  font-weight: 500;
  letter-spacing: -0.025em;
  line-height: 1.08;
}
.h1 {
  font-size: clamp(34px, 6.2vw, 60px);
  /* The headline is a sentence now, not two words — let it break evenly
     rather than leaving one orphaned word on the last line. */
  text-wrap: balance;
}
.h2 {
  font-size: clamp(28px, 4.4vw, 48px);
  line-height: 1.1;
}
.h3 {
  font-size: 24px;
  line-height: 1.25;
}
.lead {
  font-size: 18px;
  color: var(--muted-fg);
  max-width: 36em;
}
@media (max-width: 640px) {
  .lead {
    font-size: 16px;
  }
}
/* The house headline treatment: ink → accent → light, running off-scale. */
.grad {
  background: linear-gradient(90deg, var(--fg) -20%, var(--accent) 94.54%, var(--grad-end) 104.51%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* A marked phrase. The page sets emphasis with <b> about forty times over,
   so a phrase that has to carry more weight than the other forty cannot do
   it with bold — it would land on exactly the same footing.

   This is a marker stroke rather than a filled pill: the tint starts at 56%
   of the line box, so it reads as something swept under the words by hand
   and leaves the ascenders clear. A full-height block behind body copy stops
   looking like a highlight and starts looking like a broken <mark>.

   Named `.marked`, not `.hl`: `hl` is already the filename highlight inside
   `.tree` listings on three other pages, and `.tree .hl` only overrides
   colour and weight — a bare `.hl` here would have swept a marker pen
   through ten filenames in the file trees and nothing would have said so.

   Spend it once a page. The rule the drawings follow — one solid object
   among the outlined ones — is the same rule here. */
.marked {
  color: var(--fg);
  font-weight: 600;
  background-image: linear-gradient(
    to bottom,
    transparent 56%,
    color-mix(in srgb, var(--accent) 30%, transparent) 56%
  );
  padding: 0 0.14em;
  margin: 0 -0.14em;
  border-radius: 2px;
  /* the stroke should break across a wrap the way a real one would, rather
     than boxing each line separately */
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}

/* ---------------------------------------------------------------- buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 13px 24px;
  border-radius: var(--r-md);
  border: 1px solid transparent;
  background: var(--primary);
  color: var(--primary-fg);
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
    border-color var(--dur) var(--ease), transform 0.12s var(--ease);
}
.btn:hover {
  background: color-mix(in srgb, var(--primary) 82%, var(--bg));
}
.btn:active {
  transform: translateY(1px);
}
.btn--sm {
  padding: 9px 18px;
}
.btn--ghost {
  background: var(--panel);
  color: var(--fg);
  border-color: var(--border-strong);
}
.btn--ghost:hover {
  background: var(--muted);
}
.btn svg {
  flex: none;
}

/* Conic sweep border — the reference's "get started" chip. Two counter-phase
   gradients so the light never fully leaves the frame. */
.sweep {
  position: relative;
  display: inline-flex;
  padding: 1px;
  border-radius: calc(var(--r-md) + 1px);
  overflow: hidden;
  isolation: isolate;
}
.sweep::before,
.sweep::after {
  content: "";
  position: absolute;
  inset: -60%;
  z-index: -1;
  animation: spin 4.5s linear infinite;
}
.sweep::before {
  background: conic-gradient(from 0deg, var(--accent) 0deg, var(--accent) 38deg, transparent 62deg);
}
.sweep::after {
  background: conic-gradient(from 180deg, var(--accent) 0deg, var(--accent) 38deg, transparent 62deg);
}
.sweep > .btn {
  border-radius: var(--r-md);
}
@keyframes spin {
  to {
    transform: rotate(1turn);
  }
}

.tlink {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 14px;
  font-weight: 500;
  color: var(--muted-fg);
  transition: color var(--dur) var(--ease);
}
.tlink:hover {
  color: var(--accent);
}
.tlink svg {
  transition: transform var(--dur) var(--ease);
}
.tlink:hover svg {
  transform: translateX(3px);
}

/* ------------------------------------------------------------------- nav */
.nav {
  position: sticky;
  top: 0;
  z-index: 60;
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  backdrop-filter: blur(18px) saturate(140%);
  border-bottom: 1px solid var(--border);
}
.nav__in {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding-block: 14px;
}
@media (min-width: 1024px) {
  .nav__in {
    padding-inline: 32px;
  }
}
.logo {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 21px;
  font-weight: 700;
  letter-spacing: -0.035em;
}
.logo svg {
  width: 26px;
  height: 26px;
  color: var(--accent);
}
.nav__links {
  display: none;
  gap: 32px;
  font-size: 15px;
}
@media (min-width: 1024px) {
  .nav__links {
    display: flex;
  }
}
.nav__links a {
  color: var(--fg);
  transition: color var(--dur) var(--ease);
}
.nav__links a:hover,
.nav__links a[aria-current="true"] {
  color: var(--accent);
}
.nav__cta {
  display: none;
  align-items: center;
  gap: 10px;
}
@media (min-width: 1024px) {
  .nav__cta {
    display: flex;
  }
}
.icon-btn {
  width: 38px;
  height: 38px;
  display: inline-grid;
  place-items: center;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--panel);
  cursor: pointer;
  transition: background var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.icon-btn:hover {
  background: var(--muted);
  transform: scale(1.05);
}
.icon-btn svg {
  width: 18px;
  height: 18px;
}
.icon-btn .moon {
  display: none;
}
[data-theme="dark"] .icon-btn .sun {
  display: none;
}
[data-theme="dark"] .icon-btn .moon {
  display: block;
}
.nav__mob {
  display: flex;
  align-items: center;
  gap: 8px;
}
@media (min-width: 1024px) {
  .nav__mob {
    display: none;
  }
}

/* mobile sheet */
.sheet {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: none;
}
.sheet[data-open="true"] {
  display: block;
}
.sheet__bd {
  position: absolute;
  inset: 0;
  background: var(--scrim);
}
.sheet__panel {
  position: absolute;
  inset: 0 0 0 auto;
  width: min(340px, 86vw);
  background: var(--bg);
  border-left: 1px solid var(--border);
  padding: 22px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  animation: slidein var(--t-ui) var(--ease);
}
@keyframes slidein {
  from {
    transform: translateX(12%);
    opacity: 0;
  }
}
.sheet__panel a {
  padding: 14px 4px;
  border-bottom: 1px solid var(--border);
  font-size: 17px;
}
.sheet__panel .btn {
  margin-top: 18px;
}
.sheet__x {
  align-self: flex-end;
  background: none;
  border: 0;
  cursor: pointer;
  padding: 6px;
  color: var(--muted-fg);
}

/* ------------------------------------------------------------------ hero */
.hero__in {
  border-inline: 1px solid var(--border);
  position: relative;
}
.hero__copy {
  max-width: 42rem;
  margin-inline: auto;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 22px;
  padding: 44px 20px 46px;
}
@media (min-width: 768px) {
  .hero__copy {
    padding: 80px 20px;
    gap: 26px;
  }
}
.badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 5px 14px;
  border: 1px solid var(--border);
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--muted) 60%, transparent);
  font-size: 13.5px;
  color: var(--muted-fg);
}
.badge a {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--accent);
  font-weight: 600;
}
.badge a:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}
.hero__cta {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  justify-content: center;
}
.hero__note {
  font-size: 13.5px;
  color: var(--subtle);
}
.hero__note b {
  color: var(--muted-fg);
  font-weight: 600;
}

/* tabs */
.tabs {
  display: flex;
  border-block: 1px solid var(--border);
  overflow-x: auto;
  scrollbar-width: none;
}
.tabs::-webkit-scrollbar {
  display: none;
}
.tab {
  position: relative;
  flex: 1 0 auto;
  min-width: max-content;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 20px 22px;
  border: 0;
  border-right: 1px solid var(--border);
  background: transparent;
  color: color-mix(in srgb, var(--fg) 68%, transparent);
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  overflow: hidden;
  transition: color var(--dur) var(--ease);
}
@media (min-width: 640px) {
  .tab {
    padding: 26px 34px;
    font-size: 15px;
  }
}
.tab:last-child {
  border-right: 0;
}
.tab:hover {
  color: var(--fg);
}
.tab svg {
  width: 18px;
  height: 18px;
  color: var(--muted-fg);
}
.tab[aria-selected="true"] {
  color: var(--fg);
  background: color-mix(in srgb, var(--muted) 55%, transparent);
}
.tab[aria-selected="true"] svg {
  color: var(--accent);
}
.tab__bar {
  position: absolute;
  top: 0;
  left: 0;
  height: 2px;
  width: 0;
  background: var(--accent);
}
.tab__t {
  display: none;
}
@media (min-width: 640px) {
  .tab__t {
    display: inline;
  }
}

.tabpanes {
  position: relative;
  padding: 20px 20px 0;
  background: radial-gradient(120% 100% at 50% 0%, var(--accent-soft) 0%, transparent 62%);
}
@media (min-width: 768px) {
  .tabpanes {
    padding: 56px 56px 0;
  }
}
.tabpanes::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 26%;
  background: var(--fade);
  pointer-events: none;
}
.pane {
  display: none;
}
.pane[data-on="true"] {
  display: block;
  animation: pane 0.45s var(--ease);
}
@keyframes pane {
  from {
    opacity: 0;
    filter: blur(6px);
    transform: translateY(8px);
  }
}

/* --------------------------------------------------------------- console */
.panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
  overflow: hidden;
}
.panel__bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--panel-2);
  font-size: 12.5px;
  color: var(--muted-fg);
}
.panel__bar .dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 4px var(--accent-soft);
  flex: none;
}
.panel__bar .grow {
  margin-left: auto;
}
.tag {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px;
  border: 1px solid var(--accent-line);
  border-radius: var(--r-full);
  color: var(--accent);
  font-size: 11.5px;
  font-family: var(--mono);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.tag--ok {
  color: var(--accent);
  border-color: var(--accent-line);
}

.brow {
  display: grid;
  grid-template-columns: 18px 92px 1fr auto;
  gap: 12px;
  align-items: baseline;
  padding: 11px 16px;
  border-bottom: 1px solid var(--border);
  font-size: 13.5px;
}
@media (min-width: 768px) {
  .brow {
    padding: 13px 22px;
    font-size: 14.5px;
  }
}
.brow:last-child {
  border-bottom: 0;
}
.brow i {
  color: var(--accent);
  font-style: normal;
  font-family: var(--mono);
}
.brow b {
  font-weight: 500;
  font-family: var(--mono);
  font-size: 0.92em;
  color: var(--muted-fg);
}
.brow span {
  color: var(--fg);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.brow em {
  font-style: normal;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--accent);
}
/* Narrow: the value gets its own line rather than truncating to "desc…". */
@media (max-width: 560px) {
  .brow {
    grid-template-columns: 16px 1fr auto;
    row-gap: 2px;
  }
  /* Explicit placement — auto-flow would drop the status onto a third line. */
  .brow i {
    grid-area: 1 / 1;
  }
  .brow b {
    grid-area: 1 / 2;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .brow em {
    grid-area: 1 / 3;
    justify-self: end;
    white-space: nowrap;
  }
  .brow span {
    grid-area: 2 / 2 / 3 / -1;
    white-space: normal;
  }
}
.brow--final {
  background: var(--accent-soft);
}
.brow--final span {
  color: var(--accent);
}

.tree {
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.95;
  padding: 18px 20px;
  margin: 0;
  overflow-x: auto;
  color: var(--muted-fg);
}
@media (min-width: 768px) {
  .tree {
    font-size: 13.5px;
    padding: 24px 28px;
  }
}
.tree .d {
  color: var(--fg);
  font-weight: 500;
}
.tree .f {
  color: var(--subtle);
}
.tree .hl {
  color: var(--accent);
  font-weight: 500;
}
/* Caption under the tree. The boundary is the page's load-bearing claim, so the
   tree gets one line of prose naming what enforces it rather than leaving the
   reader to infer it from two filenames. */
.tree__n {
  margin: 0;
  padding: 0 20px 18px;
  font-size: 13px;
  line-height: 1.6;
  color: var(--subtle);
  max-width: 62ch;
}
@media (min-width: 768px) {
  .tree__n {
    padding: 0 28px 24px;
  }
}

.rep__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 15px 20px;
  border-bottom: 1px solid var(--border);
  font-size: 14.5px;
}
@media (min-width: 768px) {
  .rep__row {
    padding: 17px 26px;
  }
}
.rep__row b {
  font-family: var(--mono);
  font-size: 12.5px;
  font-weight: 500;
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.rep__sig {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  padding: 15px 20px;
  background: var(--panel-2);
  font-family: var(--mono);
  font-size: 12px;
  color: var(--subtle);
}
.rep__sig b {
  color: var(--accent);
  letter-spacing: 0.02em;
}

.kv {
  display: grid;
  gap: 1px;
  background: var(--border);
}
@media (min-width: 700px) {
  .kv {
    grid-template-columns: 1fr 1fr;
  }
}
.kv > div {
  background: var(--panel);
  padding: 20px 22px;
}
.kv h4 {
  font-size: 15px;
  margin-bottom: 6px;
}
.kv p {
  font-size: 14px;
  color: var(--muted-fg);
}
.kv .mono {
  font-size: 12px;
  color: var(--accent);
  display: block;
  margin-bottom: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* --------------------------------------------------------------- stacks */
.stacks {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  border-block: 1px solid var(--border);
}
@media (min-width: 640px) {
  .stacks {
    grid-template-columns: repeat(3, 1fr);
  }
}
@media (min-width: 1024px) {
  .stacks {
    grid-template-columns: repeat(5, 1fr);
  }
}
.stack {
  position: relative;
  display: grid;
  place-items: center;
  gap: 8px;
  padding: 34px 12px;
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  margin: 0 -1px -1px 0;
  overflow: hidden;
}
.stack__n {
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--muted-fg);
  transition: color var(--dur) var(--ease);
}
.stack__k {
  font-family: var(--mono);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--subtle);
}
.stack:hover .stack__n {
  color: var(--fg);
}
.stack__ov {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: var(--muted);
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
  font-size: 13px;
  color: var(--accent);
  font-family: var(--mono);
}
.stack:hover .stack__ov {
  opacity: 1;
}

/* ----------------------------------------------------------------- split
   The page's organising idea: one repository, two zones. Two cells on the
   same ruled grid as everything else, differentiated by weight rather than
   colour — the compiled side carries the accent rail, because that is the
   half the product is making a claim about. */
.split {
  display: grid;
  grid-template-columns: 1fr;
  border-top: 1px solid var(--border);
}
@media (min-width: 860px) {
  .split {
    grid-template-columns: 1fr 1fr;
  }
}
.split__z {
  padding: 32px 24px;
  border-bottom: 1px solid var(--border);
  position: relative;
}
@media (min-width: 860px) {
  .split__z {
    padding: 44px 48px;
    border-bottom: 0;
  }
  .split__z:first-child {
    border-right: 1px solid var(--border);
  }
}
/* The compiled half is the one under warranty. Mark it. */
.split__z:first-child::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 2px;
  background: var(--accent);
}
@media (max-width: 859px) {
  .split__z:first-child::before {
    inset: 0 0 auto 0;
    width: auto;
    height: 2px;
  }
}
.split__k {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--accent);
}
.split__z--own .split__k {
  color: var(--subtle);
}
.split__t {
  font-size: clamp(24px, 3vw, 32px);
  font-weight: 600;
  letter-spacing: -0.03em;
  margin: 10px 0 24px;
}
.split__list {
  list-style: none;
  /* `list-style: none` hides the markers but leaves the user-agent's
     `padding-inline-start: 40px` behind, which pushed every pill 40px right of
     the heading, the eyebrow and the footnote in the same column. */
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 26px;
}
.split__list li {
  font-size: 14px;
  color: var(--muted-fg);
  padding: 6px 12px;
  border: 1px solid var(--border);
  border-radius: var(--r-full);
  background: var(--panel-2);
}
.split__z:first-child .split__list li {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--fg);
}
.split__f {
  font-size: 14.5px;
  color: var(--muted-fg);
  line-height: 1.65;
}
.split__f b {
  color: var(--fg);
  font-weight: 600;
}

/* ------------------------------------------------------------ pull quote
   One sentence, given the width of the column. Used where an argument needs
   to land on its own rather than inside a paragraph. */
.pq {
  border-top: 1px solid var(--border);
  border-inline: 1px solid var(--border);
  padding: 40px 24px;
  font-size: clamp(19px, 2.4vw, 27px);
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.4;
  text-wrap: balance;
}
@media (min-width: 860px) {
  .pq {
    padding: 52px 48px;
  }
}
.pq span {
  color: var(--muted-fg);
}
.pq--cite {
  font-size: clamp(17px, 2vw, 22px);
  color: var(--muted-fg);
  font-weight: 400;
}
.pq--cite span {
  color: var(--fg);
  font-weight: 500;
}
.pq--cite a {
  display: block;
  margin-top: 18px;
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--subtle);
  border-bottom: 1px solid var(--border-strong);
  width: fit-content;
  padding-bottom: 2px;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.pq--cite a:hover {
  color: var(--accent);
  border-color: var(--accent);
}

/* ------------------------------------------------------------------ trio
   Three bordered cells on the shared grid. Same construction as .rules, but
   each cell carries a heading — used where the three items are arguments
   rather than footnotes. */
.trio {
  display: grid;
  grid-template-columns: 1fr;
  border-top: 1px solid var(--border);
}
@media (min-width: 900px) {
  .trio {
    grid-template-columns: repeat(3, 1fr);
  }
}
.trio > article {
  padding: 32px 24px;
  border-bottom: 1px solid var(--border);
}
@media (min-width: 900px) {
  .trio > article {
    padding: 40px 32px;
    border-bottom: 0;
    border-right: 1px solid var(--border);
  }
  .trio > article:last-child {
    border-right: 0;
  }
}
.trio__k {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--accent);
}
.trio__t {
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.02em;
  margin: 14px 0 12px;
}
.trio > article p:last-child {
  font-size: 15px;
  color: var(--muted-fg);
  line-height: 1.65;
}
/* A trio cell used as a citation needs a link affordance; base `a` inherits
   colour and drops the underline, which leaves a source name looking like
   plain text. Same treatment as `.note a` so the two read as one convention. */
.trio a {
  border-bottom: 1px solid var(--border-strong);
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.trio a:hover {
  color: var(--accent);
  border-color: var(--accent);
}
.trio > article p:last-child b {
  color: var(--fg);
  font-weight: 500;
}

/* ------------------------------------------------------------------- cra
   A dated obligation, set as a notice rather than a feature. The date is the
   whole point, so it leads. */
.cra {
  display: grid;
  gap: 20px;
  padding: 32px 24px;
  align-items: start;
}
@media (min-width: 900px) {
  .cra {
    grid-template-columns: auto 1fr auto;
    gap: 32px;
    padding: 36px 48px;
    align-items: center;
  }
}
.cra__k {
  font-size: 12px;
  letter-spacing: 0.08em;
  color: var(--accent);
  white-space: nowrap;
}
.cra p:nth-child(2) {
  font-size: 15.5px;
  color: var(--muted-fg);
  line-height: 1.65;
  max-width: 68ch;
}
/* Some of these notes need two paragraphs. `.cra` is a three-column grid —
   key | text | action — so a second <p> silently became the ACTION column:
   squeezed into a narrow track at the far right, and unstyled, because the
   rule above only ever matches the first one. A body wrapper keeps however
   many paragraphs there are inside the single text cell. */
.cra__body {
  min-width: 0;
}
.cra__body p {
  font-size: 15.5px;
  color: var(--muted-fg);
  line-height: 1.65;
  max-width: 68ch;
}
.cra__body p + p {
  margin-top: 12px;
}
.cra p b {
  color: var(--fg);
  font-weight: 600;
}
.cra .btn {
  white-space: nowrap;
}

/* Source links inside a .note read as citations, not navigation. */
.note a {
  color: var(--muted-fg);
  border-bottom: 1px solid var(--border-strong);
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.note a:hover {
  color: var(--accent);
  border-color: var(--accent);
}

/* ---------------------------------------------------------- hero deck
   "Not generated. Compiled." demoted from headline to deck — it's the
   mechanism, and it reads better hugging the line it qualifies. */
.hero__deck {
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
  margin-top: -12px;
}

/* ------------------------------------------------------------- statement */
.stmt {
  position: relative;
  overflow: hidden;
  border-inline: 1px solid var(--border);
  padding: 64px 20px;
}
@media (min-width: 768px) {
  .stmt {
    padding: 120px 24px;
  }
}
@media (min-width: 1024px) {
  .stmt {
    padding: 150px 64px;
  }
}
.stmt__glow {
  position: absolute;
  width: 420px;
  height: 420px;
  border-radius: 50%;
  filter: blur(90px);
  background: radial-gradient(circle, var(--accent) 0%, transparent 70%);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s var(--ease);
  transform: translate(-50%, -50%);
}
.stmt__in {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 42px;
}
.stmt__w {
  max-width: 40rem;
  text-align: center;
  font-size: clamp(23px, 3.4vw, 38px);
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.02em;
}
.stmt__w span {
  opacity: 0.18;
  transition: opacity 0.25s linear;
}
.stmt__w span.lit {
  opacity: 1;
}
.stmt__f {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 22px;
  text-align: center;
}

/* ----------------------------------------------------------------- stats */
.stats {
  display: grid;
  grid-template-columns: 1fr;
  border-top: 1px solid var(--border);
}
@media (min-width: 1024px) {
  .stats {
    grid-template-columns: repeat(3, 1fr);
  }
}
.stat {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 32px;
  border-bottom: 1px solid var(--border);
}
@media (min-width: 1024px) {
  .stat {
    padding: 48px;
    border-bottom: 0;
    border-right: 1px solid var(--border);
  }
  .stat:last-child {
    border-right: 0;
  }
}
.stat__n {
  font-size: clamp(34px, 4vw, 50px);
  font-weight: 600;
  letter-spacing: -0.035em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.stat__l {
  color: var(--muted-fg);
  font-size: 17px;
}
.stat__k {
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--subtle);
}
.wide {
  display: grid;
  grid-template-columns: 1fr;
  border-top: 1px solid var(--border);
}
@media (min-width: 1024px) {
  .wide {
    grid-template-columns: 1fr 1fr;
  }
}
.wide > div {
  padding: 32px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 28px;
  min-height: 260px;
  border-bottom: 1px solid var(--border);
}
@media (min-width: 1024px) {
  .wide > div {
    padding: 48px;
    min-height: 320px;
    border-bottom: 0;
  }
  .wide > div:first-child {
    border-right: 1px solid var(--border);
  }
}
.wide__spark {
  position: relative;
}
.wide__spark svg {
  position: absolute;
  inset: auto 0 -12px 0;
  width: 100%;
  height: 130px;
  opacity: 0.55;
  pointer-events: none;
}
.ledger {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}
.ledger span {
  width: 22px;
  height: 4px;
  border-radius: 2px;
  background: var(--border-strong);
}
.ledger span.same {
  background: var(--accent);
}

/* -------------------------------------------------------------- artifact */
.two {
  display: grid;
  grid-template-columns: 1fr;
  border-top: 1px solid var(--border);
}
@media (min-width: 1024px) {
  .two {
    grid-template-columns: 1fr 1fr;
  }
}
.two__vis {
  padding: 20px;
  border-bottom: 1px solid var(--border);
}
@media (min-width: 1024px) {
  .two__vis {
    padding: 24px;
    border-bottom: 0;
    border-right: 1px solid var(--border);
  }
}
.two__vis > div {
  height: 100%;
  border-radius: var(--r-lg);
  background: radial-gradient(120% 120% at 20% 0%, var(--accent-soft), transparent 60%), var(--panel-2);
  border: 1px solid var(--border);
  overflow: hidden;
}

.acc__item {
  border-bottom: 1px solid var(--border);
}
.acc__item[data-open="true"] {
  border-top: 2px solid var(--fg);
}
.acc__btn {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0;
  padding: 24px;
  background: none;
  border: 0;
  cursor: pointer;
  text-align: left;
}
@media (min-width: 1280px) {
  .acc__btn {
    padding: 44px 48px;
  }
}
.acc__head {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
}
.acc__item[data-open="true"] .acc__head {
  flex-direction: column;
  align-items: flex-start;
  gap: 36px;
}
@media (min-width: 1280px) {
  .acc__item[data-open="true"] .acc__head {
    gap: 72px;
  }
}
.acc__head svg {
  width: 22px;
  height: 22px;
  flex: none;
  color: var(--fg);
}
.acc__item[data-open="true"] .acc__head svg {
  color: var(--accent);
}
.acc__t {
  font-size: 20px;
  font-weight: 500;
  letter-spacing: -0.02em;
}
@media (min-width: 1280px) {
  .acc__t {
    font-size: 24px;
  }
}
.acc__item[data-open="true"] .acc__t {
  font-weight: 600;
}
.acc__chev {
  margin-left: auto;
  color: var(--muted-fg);
  transition: transform var(--dur) var(--ease);
}
.acc__item[data-open="true"] .acc__chev {
  display: none;
}
.acc__body {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.42s var(--ease);
}
.acc__item[data-open="true"] .acc__body {
  grid-template-rows: 1fr;
}
.acc__body > * {
  overflow: hidden;
}
.acc__body p {
  font-size: 17px;
  color: var(--muted-fg);
  padding-top: 16px;
}

/* ------------------------------------------------------------- proof/demo */
.proof {
  border-top: 1px solid var(--border);
  display: grid;
  grid-template-columns: 1fr;
}
@media (min-width: 900px) {
  .proof {
    grid-template-columns: 1fr 1fr;
  }
}
.proof__c {
  padding: 32px;
  border-bottom: 1px solid var(--border);
}
@media (min-width: 900px) {
  .proof__c {
    padding: 48px;
    border-bottom: 0;
    border-right: 1px solid var(--border);
  }
}
.proof__run {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-top: 28px;
  flex-wrap: wrap;
}
.proof__count {
  font-size: 12px;
  color: var(--subtle);
  text-transform: uppercase;
  letter-spacing: 0.12em;
}
.vs {
  display: grid;
  grid-template-columns: 1fr;
}
@media (min-width: 560px) {
  .vs {
    grid-template-columns: 1fr 1fr;
  }
}
.vs__card {
  padding: 28px 24px;
  border-bottom: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 18px;
  min-height: 210px;
}
@media (min-width: 560px) {
  .vs__card {
    border-bottom: 0;
    border-right: 1px solid var(--border);
  }
  .vs__card:last-child {
    border-right: 0;
  }
}
@media (min-width: 900px) {
  .vs__card {
    padding: 48px 36px;
  }
}
.vs__card--oceantic {
  background: var(--accent-soft);
}
.vs__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.vs__who {
  font-size: 14px;
  font-weight: 600;
}
.vs__v {
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--subtle);
}
.vs__card--oceantic .vs__v {
  color: var(--accent);
}
.hash {
  font-family: var(--mono);
  font-size: clamp(22px, 3.4vw, 34px);
  letter-spacing: 0.02em;
  display: flex;
  gap: 1px;
}
.hash .sep {
  opacity: 0.4;
  padding-inline: 2px;
}
.hash .hot {
  color: #c0553a;
  animation: pop var(--t-ui) var(--ease);
}
[data-theme="dark"] .hash .hot {
  color: #e0765a;
}
.vs__card--oceantic .hash {
  color: var(--accent);
}
@keyframes pop {
  from {
    transform: translateY(-6px);
    opacity: 0;
  }
}
.trail {
  display: flex;
  gap: 5px;
  margin-top: auto;
}
.trail span {
  width: 26px;
  height: 4px;
  border-radius: 2px;
  background: var(--border-strong);
}
.trail span.diff {
  background: #c0553a;
}
.trail span.same {
  background: var(--accent);
}

/* ----------------------------------------------------------------- steps */
.steps {
  display: grid;
  grid-template-columns: 1fr;
  border-top: 1px solid var(--border);
}
@media (min-width: 768px) {
  .steps {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 1200px) {
  .steps {
    grid-template-columns: repeat(4, 1fr);
  }
}
.step {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 28px;
  padding: 32px;
  min-height: 380px;
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  margin: 0 -1px -1px 0;
  cursor: default;
  overflow: hidden;
  transition: background var(--dur) var(--ease);
}
@media (min-width: 1200px) {
  .step {
    padding: 44px;
    min-height: 440px;
  }
}
.step:hover {
  background: color-mix(in srgb, var(--muted) 65%, transparent);
}
.step__g {
  width: 52px;
  height: 52px;
  color: var(--fg);
}
.step:hover .step__g {
  color: var(--accent);
}
.step__g,
.step__n {
  transition: color var(--dur) var(--ease);
}
.step__n {
  position: absolute;
  top: 32px;
  right: 32px;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--subtle);
  letter-spacing: 0.1em;
}
@media (min-width: 1200px) {
  .step__n {
    top: 44px;
    right: 44px;
  }
}
.step__b {
  display: flex;
  flex-direction: column;
  gap: 14px;
  transition: transform 0.4s var(--ease);
}
.step:hover .step__b,
.step:focus-within .step__b {
  transform: translateY(-6px);
}
.step__b p {
  color: var(--muted-fg);
  font-size: 16.5px;
}
/* The reveal sits in a slot that is always there. Animating max-height instead
   would grow the block inside a space-between column, so the paragraph above it
   lurches upward mid-transition — and most of the duration is spent covering
   the gap between the real height and the max. Reserving the height keeps
   layout fixed, leaving only opacity and transform to move. */
.step__more {
  height: 28px;
  display: flex;
  align-items: center;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.28s var(--ease), transform 0.4s var(--ease);
}
.step:hover .step__more,
.step:focus-within .step__more {
  opacity: 1;
  transform: none;
}

.note {
  padding: 20px 24px 40px;
  font-size: 13px;
  color: var(--subtle);
  max-width: 62ch;
}
/* A ruled note is two different widths: the rule belongs to the column, the
   sentence belongs to a readable measure. `max-width` on the element itself
   gave both to the measure, so the border stopped two-fifths of the way
   across. The span carries the measure and the element carries the rule. */
.note--wide {
  max-width: none;
}
.note--wide > span {
  display: block;
  max-width: 62ch;
}
@media (min-width: 1024px) {
  .note {
    padding: 24px 48px 56px;
  }
}

/* --------------------------------------------------------------- pricing */
.tiers {
  border-top: 1px solid var(--border);
  display: grid;
  grid-template-columns: 1fr;
}
@media (min-width: 900px) {
  .tiers {
    grid-template-columns: 1fr minmax(280px, 380px);
  }
}
.tier {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  width: 100%;
  padding: 26px 24px;
  border: 0;
  border-bottom: 1px solid var(--border);
  background: transparent;
  cursor: pointer;
  text-align: left;
  overflow: hidden;
  transition: background var(--dur) var(--ease);
}
@media (min-width: 900px) {
  .tier {
    padding: 40px 48px;
  }
}
.tier:hover {
  background: color-mix(in srgb, var(--muted) 60%, transparent);
}
.tier[aria-selected="true"] {
  background: var(--muted);
}
.tier[aria-selected="true"]::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 2px;
  background: var(--accent);
}
.tier__l {
  display: flex;
  align-items: center;
  gap: 20px;
  min-width: 0;
}
.tier__g {
  width: 34px;
  height: 34px;
  flex: none;
  color: var(--fg);
}
.tier[aria-selected="true"] .tier__g {
  color: var(--accent);
}
.tier__nm {
  display: block;
  font-size: 19px;
  font-weight: 500;
}
@media (min-width: 900px) {
  .tier__nm {
    font-size: 22px;
  }
}
.tier__who {
  display: block;
  color: var(--muted-fg);
  font-size: 14.5px;
  margin-top: 2px;
}
.tier__p {
  font-size: 26px;
  font-weight: 500;
  white-space: nowrap;
  letter-spacing: -0.03em;
}
@media (min-width: 900px) {
  .tier__p {
    font-size: 30px;
  }
}
.tier__p span {
  font-size: 14px;
  color: var(--muted-fg);
  font-weight: 400;
}
.tier__flag {
  position: absolute;
  top: 12px;
  right: 24px;
  font-family: var(--mono);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--accent);
}
@media (min-width: 900px) {
  .tier__flag {
    right: 48px;
    top: 16px;
  }
}
.incl {
  padding: 32px 24px;
  border-bottom: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 32px;
  justify-content: space-between;
}
@media (min-width: 900px) {
  .incl {
    padding: 40px 40px;
    border-left: 1px solid var(--border);
    border-bottom: 0;
  }
}
.incl h4 {
  font-size: 18px;
  color: var(--muted-fg);
  font-weight: 500;
  margin-bottom: 18px;
}
.incl ul {
  list-style: none;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 13px;
}
.incl li {
  display: flex;
  gap: 10px;
  font-size: 16px;
  line-height: 1.4;
  animation: inrise 0.4s var(--ease) both;
}
.incl li svg {
  width: 17px;
  height: 17px;
  flex: none;
  margin-top: 3px;
  color: var(--accent);
}
.incl li em {
  font-style: italic;
  color: var(--muted-fg);
}
@keyframes inrise {
  from {
    opacity: 0;
    transform: translateX(-8px);
  }
}
.tier__m {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
}
.tier__m span {
  font-family: var(--mono);
  font-size: 11.5px;
  padding: 3px 9px;
  border: 1px solid var(--border);
  border-radius: var(--r-full);
  color: var(--muted-fg);
  background: var(--panel);
}

.rules {
  display: grid;
  grid-template-columns: 1fr;
  border-top: 1px solid var(--border);
}
@media (min-width: 700px) {
  .rules {
    grid-template-columns: 1fr 1fr;
  }
}
.rules > div {
  display: flex;
  gap: 18px;
  padding: 28px 24px;
  border-bottom: 1px solid var(--border);
  border-right: 1px solid var(--border);
  margin: 0 -1px -1px 0;
}
@media (min-width: 1024px) {
  .rules > div {
    padding: 36px 48px;
  }
}
.rules span {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--accent);
  letter-spacing: 0.08em;
}
.rules p {
  font-size: 15.5px;
  color: var(--muted-fg);
}
.rules p b {
  color: var(--fg);
  font-weight: 600;
}

/* ------------------------------------------------------------- assurance */
.rail {
  display: flex;
  gap: 20px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding: 0 0 4px;
  scrollbar-width: none;
}
.rail::-webkit-scrollbar {
  display: none;
}
.card {
  flex: 0 0 min(365px, 84vw);
  scroll-snap-align: start;
  border: 1px solid var(--border);
  background: var(--panel);
  display: flex;
  flex-direction: column;
}
.card__top {
  height: 150px;
  display: grid;
  place-items: center;
  border-bottom: 1px solid var(--border);
  background: radial-gradient(110% 110% at 30% 0%, var(--accent-soft), transparent 65%), var(--panel-2);
  color: var(--accent);
}
.card__top svg {
  width: 42px;
  height: 42px;
}
.card__b {
  padding: 26px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.card__k {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--muted-fg);
}
.card__t {
  font-size: 21px;
  font-weight: 500;
  letter-spacing: -0.02em;
}
.card__b p {
  font-size: 15px;
  color: var(--muted-fg);
}
.dots {
  display: flex;
  gap: 6px;
  justify-content: center;
  padding: 32px 0;
}
.dots button {
  width: 26px;
  height: 3px;
  border: 0;
  padding: 0;
  border-radius: 2px;
  background: var(--border-strong);
  cursor: pointer;
  transition: background var(--dur) var(--ease);
}
.dots button[aria-current="true"] {
  background: var(--accent);
}

/* ------------------------------------------------------------------- FAQ */
.faq {
  display: grid;
  grid-template-columns: 1fr;
  gap: 32px;
  padding: 32px;
}
@media (min-width: 1024px) {
  .faq {
    grid-template-columns: 300px 1fr;
    gap: 80px;
    padding: 80px 48px;
  }
}
.qa {
  border-top: 1px solid var(--border);
  transition: background var(--dur) var(--ease);
}
.qa:last-of-type {
  border-bottom: 1px solid var(--border);
}
.qa[open] {
  background: color-mix(in srgb, var(--muted) 55%, transparent);
}
.qa summary {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 24px;
  padding: 24px 22px;
  font-size: 18px;
  font-weight: 500;
  cursor: pointer;
  list-style: none;
  letter-spacing: -0.015em;
}
@media (min-width: 1024px) {
  .qa summary {
    padding: 30px 32px;
  }
}
.qa summary::-webkit-details-marker {
  display: none;
}
.qa__i {
  position: relative;
  width: 15px;
  height: 15px;
  flex: none;
  margin-top: 6px;
}
.qa__i::before,
.qa__i::after {
  content: "";
  position: absolute;
  background: var(--muted-fg);
  inset: 7px 0 auto 0;
  height: 1.5px;
  transition: transform var(--dur) var(--ease), background var(--dur) var(--ease);
}
.qa__i::after {
  transform: rotate(90deg);
}
.qa[open] .qa__i::before,
.qa[open] .qa__i::after {
  background: var(--accent);
}
.qa[open] .qa__i::after {
  transform: rotate(0deg);
}
.qa p {
  padding: 0 22px 26px;
  color: var(--muted-fg);
  font-size: 16.5px;
  max-width: 68ch;
}
@media (min-width: 1024px) {
  .qa p {
    padding: 0 32px 32px;
  }
}
.qa[open] p {
  animation: pane 0.4s var(--ease);
}

/* ----------------------------------------------------------------- close */
.close {
  position: relative;
  border-inline: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  overflow: hidden;
  padding: 90px 24px;
  text-align: center;
  background: var(--scrim);
}
@media (min-width: 768px) {
  .close {
    padding: 150px 32px;
  }
}
.close::before {
  content: "";
  position: absolute;
  inset: -30% -10% auto -10%;
  height: 140%;
  background: radial-gradient(60% 70% at 50% 100%, var(--accent) 0%, transparent 70%);
  opacity: 0.35;
  pointer-events: none;
}
.close__in {
  position: relative;
  max-width: 56rem;
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  color: #f6f2ea;
}
.close h2 {
  font-size: clamp(30px, 5.4vw, 58px);
}
.close .grad {
  background: linear-gradient(90deg, #f6f2ea -20%, var(--accent) 94.54%, #fff 104.51%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.close p {
  color: rgba(246, 242, 234, 0.72);
  font-size: 17px;
  max-width: 40em;
}
.close .btn--ghost {
  background: transparent;
  color: #f6f2ea;
  border-color: rgba(246, 242, 234, 0.3);
}
.close .btn--ghost:hover {
  background: rgba(246, 242, 234, 0.1);
}
.close .btn:not(.btn--ghost) {
  background: #f6f2ea;
  color: #15130f;
}

/* --------------------------------------------------------------- contact */
.contact {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
  padding: 32px;
}
@media (min-width: 1024px) {
  .contact {
    grid-template-columns: 5fr 7fr;
    gap: 40px;
    padding: 48px;
  }
}
.contact__card {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-lg);
  padding: 28px;
  background: var(--scrim);
  color: #f6f2ea;
  display: flex;
  flex-direction: column;
  gap: 44px;
}
@media (min-width: 1024px) {
  .contact__card {
    padding: 40px;
  }
}
.contact__card::before {
  content: "";
  position: absolute;
  inset: auto -20% -40% -20%;
  height: 90%;
  background: radial-gradient(50% 60% at 50% 100%, var(--accent), transparent 70%);
  opacity: 0.4;
}
.contact__card > * {
  position: relative;
}
.contact__card h3 {
  font-size: 27px;
  margin-bottom: 8px;
}
.contact__card p {
  color: rgba(246, 242, 234, 0.75);
  font-size: 15px;
}
.contact__list {
  display: flex;
  flex-direction: column;
  gap: 22px;
}
.contact__list a,
.contact__list div {
  display: flex;
  align-items: center;
  gap: 18px;
  color: rgba(246, 242, 234, 0.92);
  font-size: 15px;
}
.contact__list a:hover {
  color: #fff;
}
.contact__list svg {
  width: 21px;
  height: 21px;
  flex: none;
  opacity: 0.8;
}
form {
  display: flex;
  flex-direction: column;
  gap: 22px;
}
/* An author `display` beats the user-agent `[hidden] { display: none }`, so a
   form set to hidden in script stays on screen. This is the whole fix, and it
   is easy to lose: the symptom is a submitted form sitting under its own
   confirmation, inviting a second send. */
[hidden] {
  display: none !important;
}
.f2 {
  display: grid;
  gap: 22px;
}
@media (min-width: 700px) {
  .f2 {
    grid-template-columns: 1fr 1fr;
  }
}
label {
  display: block;
  font-size: 13.5px;
  color: var(--muted-fg);
  margin-bottom: 8px;
}
label i {
  color: #c0553a;
  font-style: normal;
}
input,
textarea,
select {
  width: 100%;
  padding: 14px 18px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  background: var(--panel);
  font-size: 15px;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
input:focus,
textarea:focus,
select:focus {
  outline: 0;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
textarea {
  min-height: 130px;
  resize: vertical;
}
.form__ok {
  font-size: 14px;
  color: var(--accent);
}

/* ------------------------------------------------------------- turnstile
   The spam check draws itself inside an iframe we do not control, so there
   is nothing here to style — only somewhere for it to stand. It stays empty
   until app.js has a site key, and an empty box must take no room at all,
   or the form carries a gap explained by nothing. */
.turnstile:empty {
  display: none;
}

.turnstile {
  /* Its own iframe is 300×65 and does not scale; below that width it would
     push the column out rather than reflow. */
  max-width: 100%;
  overflow: hidden;
}

/* ------------------------------------------------------------ acceptance
   A tick box, drawn rather than defaulted. The native control keeps all of
   its behaviour — focus, keyboard, `required`, form validation — and is only
   made invisible; the square beside it is what you actually see. That way
   nothing has to be reimplemented in JavaScript, which is how custom
   checkboxes usually end up unreachable.

   It reuses the drawn-check motif from the isometric figures: the same
   accent fill and the same upright tick, so the one deliberate action on
   this page looks like the seal the drawings keep landing. */
.agree {
  /* anchors the visually-hidden input, so the browser's own validation bubble
     points at this row rather than at wherever the last positioned ancestor
     happened to be */
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 12px;
  align-items: start;
  padding: 16px 18px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--muted) 45%, transparent);
  cursor: pointer;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.agree:hover {
  border-color: var(--border-strong);
}
.agree input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  margin: 0;
  pointer-events: none;
}
.agree__box {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  margin-top: 1px;
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  background: var(--bg);
  color: var(--primary-fg);
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.agree__box svg {
  width: 14px;
  height: 14px;
  transform: scale(0.5);
  opacity: 0;
  transition: transform var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
.agree input:checked + .agree__box {
  background: var(--accent);
  border-color: var(--accent);
}
.agree input:checked + .agree__box svg {
  transform: none;
  opacity: 1;
}
/* the focus ring has to live on the drawn box, because the real one is hidden */
.agree input:focus-visible + .agree__box {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* only after a failed submit, so the form does not open in an angry state */
.agree.is-bad {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.agree__t {
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--muted-fg);
}
.agree__t b {
  color: var(--fg);
  font-weight: 600;
}

/* ---------------------------------------------------------- confirmation
   Replaces the form rather than sitting above it. A success message stacked
   on top of a form you can still submit invites a second send and leaves the
   reader unsure which one counted.

   The record block is the part that matters: clickwrap is only worth
   anything if there is evidence of it, so what was accepted and when is
   shown here and written into the message itself. */
.done {
  border: 1px solid var(--accent-line);
  border-radius: var(--r-lg);
  padding: 30px 26px 26px;
  background: radial-gradient(120% 120% at 12% 0%, var(--accent-soft), transparent 62%), var(--panel-2);
}
@media (min-width: 700px) {
  .done {
    padding: 34px 32px 30px;
  }
}
.done__seal {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  margin-bottom: 18px;
  border-radius: var(--r-full);
  background: var(--accent);
  color: var(--bg);
}
.done__seal svg {
  width: 22px;
  height: 22px;
}
.done__k {
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 10px;
}
.done__t {
  font-size: 23px;
  font-weight: 600;
  letter-spacing: -0.022em;
  margin-bottom: 10px;
}
.done__l {
  font-size: 15.5px;
  line-height: 1.66;
  color: var(--muted-fg);
  max-width: 46ch;
}
.done__rec {
  display: grid;
  gap: 0;
  margin: 22px 0 0;
  border-top: 1px solid var(--border);
}
.done__rec > div {
  display: grid;
  grid-template-columns: minmax(0, 130px) minmax(0, 1fr);
  gap: 14px;
  padding: 11px 0;
  border-bottom: 1px solid var(--border);
}
.done__rec dt {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--subtle);
  align-self: center;
}
.done__rec dd {
  margin: 0;
  font-size: 14.5px;
  color: var(--fg);
  overflow-wrap: anywhere;
}
.done__f {
  margin-top: 18px;
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--subtle);
}
.done__f a {
  color: var(--muted-fg);
}

/* it arrives, so an ease-out curve is right here — unlike the drawings,
   which breathe and therefore do not */
.done[data-in="1"] {
  animation: doneIn var(--t-in) var(--ease) both;
}
@keyframes doneIn {
  from {
    opacity: 0;
    transform: translateY(var(--lift));
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  .done[data-in="1"] {
    animation: none;
  }
  .agree__box,
  .agree__box svg,
  .agree {
    transition: none;
  }
}
/* A field-level aside — what we do with the answer, said next to the answer
   rather than only behind a link in the footer. */
label .hint {
  display: block;
  margin-top: 5px;
  font-size: 12.5px;
  color: var(--subtle);
}

/* ---------------------------------------------------------------- footer */
.foot__top {
  display: grid;
  grid-template-columns: 1fr;
  gap: 40px;
  padding: 48px 32px 0;
}
@media (min-width: 700px) {
  .foot__top {
    grid-template-columns: 1.4fr repeat(3, 1fr);
  }
}
@media (min-width: 1024px) {
  .foot__top {
    padding: 80px 48px 0;
    gap: 24px;
  }
}
.foot__brand {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 40px;
}
.foot__blurb {
  color: var(--muted-fg);
  font-size: 15px;
  max-width: 30ch;
  margin-top: 18px;
}
.foot__soc {
  display: flex;
  gap: 22px;
  color: var(--muted-fg);
}
.foot__soc a:hover {
  color: var(--accent);
}
.foot__soc svg {
  width: 19px;
  height: 19px;
}
.foot__col h4 {
  font-size: 15px;
  font-weight: 600;
  margin-bottom: 22px;
}
.foot__col a {
  display: block;
  color: var(--muted-fg);
  font-size: 15px;
  padding-block: 7px;
  transition: color var(--dur) var(--ease);
}
.foot__col a:hover {
  color: var(--accent);
}
.foot__sep {
  height: 1px;
  background: var(--border);
  margin: 48px 32px 0;
}
@media (min-width: 1024px) {
  .foot__sep {
    margin-inline: 48px;
  }
}
.foot__end {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  align-items: center;
  justify-content: space-between;
  padding: 26px 32px;
  color: var(--muted-fg);
  font-size: 14.5px;
}
@media (min-width: 1024px) {
  .foot__end {
    padding: 32px 48px;
  }
}
.foot__end a {
  margin-right: 24px;
}
.foot__end a:hover {
  color: var(--accent);
}
.wordmark {
  border-inline: 1px solid var(--border);
  border-top: 1px solid var(--border);
  overflow: hidden;
  line-height: 0.78;
  padding-top: 10px;
}
.wordmark svg {
  width: 100%;
  height: auto;
}

/* ==========================================================================
   SECTION PLATES
   The page argues from evidence, and evidence read as six paragraphs is
   evidence nobody reads. So each long section earns one *drawn* figure that
   carries its argument without being read — a numbered plate in the same
   language as the rest of the sheet: hairlines, mono figure labels, one
   accent, and marks that are drawn rather than typed.

   Rules that keep these from becoming decoration:
   1. Every figure is made of the section's own numbers or mechanism. If it
      can't be traced back to a sentence in the copy, it doesn't ship.
   2. Motion is the mechanism, not an entrance. The file crosses the fence,
      the request is refused. Nothing fades up for the sake of it.
   3. `[data-fig]` is flipped to "on" by an observer in app.js; every
      keyframe states both ends, so reduced-motion lands on the end state.
   ========================================================================== */
.plate {
  border-top: 1px solid var(--border);
  padding: 30px 24px 34px;
}
@media (min-width: 1024px) {
  .plate {
    padding: 38px 48px 42px;
  }
}
.plate__k {
  display: flex;
  align-items: baseline;
  gap: 10px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--subtle);
}
.plate__k b {
  color: var(--accent);
  font-weight: 400;
}
.plate__k i {
  flex: 1;
  height: 1px;
  background: var(--border);
  font-style: normal;
}
.plate__cap {
  margin-top: 20px;
  font-size: 13px;
  line-height: 1.6;
  color: var(--subtle);
  max-width: 68ch;
}
.plate__cap b {
  color: var(--muted-fg);
  font-weight: 500;
}

/* ------------------------------------------------------- fig 01 · matrix
   Two hundred marks, because the benchmark was two hundred tasks. The eye
   does the division on its own, which is the entire reason this exists next
   to a sentence that says 10.5%. */
.mgrid {
  display: grid;
  gap: 30px;
}
@media (min-width: 900px) {
  .mgrid {
    /* Sized to the sample, not to the column: a key floating in the middle of
       the sheet reads as unrelated to the marks it explains. */
    grid-template-columns: minmax(0, 560px) minmax(0, 224px);
    gap: 44px;
    justify-content: start;
    align-items: start;
  }
}
.matrix {
  position: relative;
  display: grid;
  /* minmax(0,...) or the aspect-ratio items refuse to shrink and the sample
     runs out past the column rule. */
  grid-template-columns: repeat(20, minmax(0, 1fr));
  gap: 4px;
  margin-top: 22px;
  /* A sample, not a tiled floor: capped so the marks stay small enough to be
     read as a quantity rather than counted one by one. */
  max-width: 560px;
}
@media (min-width: 640px) {
  .matrix {
    gap: 6px;
  }
}
/* Revealed by a left-to-right wipe: a scan across the sample, not a fade.
   Only ever applied once script has confirmed it will run the animation —
   the figure in the markup is already the finished one. */
.js-fig .matrix {
  clip-path: inset(0 100% 0 0);
}
[data-fig="on"] .matrix {
  animation: scan 1.1s var(--ease) forwards;
}
@keyframes scan {
  from {
    clip-path: inset(0 100% 0 0);
  }
  to {
    clip-path: inset(0 0 0 0);
  }
}
.matrix i {
  aspect-ratio: 1;
  border-radius: 2px;
  border: 1px solid var(--border-strong);
}
/* functionally correct, but not secure */
.matrix i.c {
  border-color: var(--muted-fg);
}
/* correct *and* secure — the only marks that get the accent, because they
   are the only ones the section is actually about */
.matrix i.s {
  border-color: var(--accent);
  background: var(--accent);
}
.mkey {
  display: grid;
  gap: 0;
  margin-top: 22px;
  border-top: 1px solid var(--border);
}
.mkey div {
  display: grid;
  grid-template-columns: 14px 1fr auto;
  align-items: center;
  gap: 12px;
  padding: 13px 0;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
  color: var(--muted-fg);
}
.mkey span:first-child {
  width: 13px;
  height: 13px;
  border-radius: 2px;
  border: 1px solid var(--border-strong);
}
.mkey .s span:first-child {
  border-color: var(--accent);
  background: var(--accent);
}
.mkey .c span:first-child {
  border-color: var(--muted-fg);
}
.mkey b {
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 400;
  color: var(--subtle);
}
.mkey .s b {
  color: var(--accent);
}

/* -------------------------------------------------------- fig 02 · fence
   Two lanes and a rule between them. A file leaves the zone it belongs to,
   and the build says so by name. The motion *is* the check — there is no
   version of this figure that makes sense standing still. */
.fence {
  margin-top: 24px;
  max-width: 720px;
}
.fence__lane {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  padding: 18px 0;
}
.fence__k {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--subtle);
  width: 100%;
}
.fence__chip {
  font-family: var(--mono);
  font-size: 11.5px;
  padding: 7px 11px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  color: var(--muted-fg);
  background: var(--panel);
}
.fence__chip--core {
  border-color: var(--accent-line);
  color: var(--accent);
}
/* The one that crossed. Dashed and accented in the markup, because in the
   still it has already happened — that is what the caption says. */
.fence__chip--x {
  position: relative;
  border-style: dashed;
  border-color: var(--accent);
  color: var(--accent);
}
/* The chip sits in the core lane in the markup, because that is the state
   the caption describes. Script pulls it back down to where it belongs and
   lets it climb, so the crossing plays without the still ever being wrong. */
.js-fig .fence__chip--x {
  transform: translateY(62px);
}
[data-fig="on"] .fence__chip--x {
  animation: cross 1.5s var(--ease) 0.45s both;
}
@keyframes cross {
  from {
    transform: translateY(62px);
    border-color: var(--border-strong);
  }
  to {
    transform: translateY(0);
    border-color: var(--accent);
  }
}
.fence__rule {
  position: relative;
  height: 1px;
  background: var(--border-strong);
}
.fence__rule::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--accent);
  opacity: 0.55;
}
.js-fig .fence__rule::after {
  opacity: 0;
}
[data-fig="on"] .fence__rule::after {
  animation: flash 1.5s var(--ease) 0.45s both;
}
@keyframes flash {
  0%,
  55% {
    opacity: 0;
  }
  72% {
    opacity: 1;
  }
  100% {
    opacity: 0.55;
  }
}
.fence__seal {
  position: absolute;
  z-index: 1;
  top: 50%;
  right: 0;
  transform: translateY(-50%);
  padding-left: 14px;
  background: var(--bg);
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--subtle);
}
.fence__fail {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 20px;
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--accent);
}
.js-fig .fence__fail {
  opacity: 0;
}
[data-fig="on"] .fence__fail {
  animation: rise 0.5s var(--ease) 1.6s both;
}
@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
.fence__fail svg {
  width: 15px;
  height: 15px;
  flex: none;
}

/* ---------------------------------------------------------- fig 03 · iso
   The tenancy proof, drawn. Worth the space because the accent lands on the
   *refusal* — in this figure the denial is the pass, which is the one thing
   a reader skimming the acceptance list will not work out on their own. */
/* The legacy access-control figure on how-it-works.html. It is a <div>, and it
   claimed the bare `.iso` block name years before the isometric drawings did —
   which meant it was leaking `margin-top`, `display: grid` and, most visibly, a
   `border-top` onto all six <svg class="iso"> illustrations: a stray rule drawn
   above every drawing on the page. Scoped to the element it was written for.
   Its `.iso__row` / `.iso__who` / `.iso__act` children never collided. */
div.iso {
  margin-top: 24px;
  display: grid;
  gap: 0;
  max-width: 760px;
  border-top: 1px solid var(--border);
}
.iso__row {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr) 84px;
  align-items: center;
  gap: 14px;
  padding: 20px 0;
  border-bottom: 1px solid var(--border);
}
@media (max-width: 640px) {
  .iso__row {
    grid-template-columns: 78px minmax(0, 1fr) 64px;
    gap: 10px;
  }
}
.iso__who {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--muted-fg);
}
.iso__act {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
}
.iso__line {
  position: relative;
  flex: 1;
  height: 1px;
  background: var(--border-strong);
}
.iso__row--deny .iso__line {
  background: repeating-linear-gradient(90deg, var(--border-strong) 0 5px, transparent 5px 10px);
}
.iso__line span {
  position: absolute;
  left: 0;
  top: -20px;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--subtle);
}
.iso__box {
  font-family: var(--mono);
  font-size: 11.5px;
  padding: 7px 11px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  color: var(--muted-fg);
  white-space: nowrap;
}
.iso__row--own .iso__box {
  border-color: var(--accent-line);
  color: var(--accent);
}
/* The stop mark: a refusal, drawn where the request dies. */
.iso__stop {
  position: relative;
  width: 26px;
  height: 26px;
  flex: none;
  border-radius: 50%;
  border: 1px solid var(--accent);
  background: var(--accent-soft);
}
.iso__stop::before,
.iso__stop::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 11px;
  height: 1px;
  background: var(--accent);
}
.iso__stop::before {
  transform: translate(-50%, -50%) rotate(45deg);
}
.iso__stop::after {
  transform: translate(-50%, -50%) rotate(-45deg);
}
.iso__code {
  justify-self: end;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--subtle);
}
.iso__row--deny .iso__code {
  color: var(--accent);
}
.js-fig .iso__row {
  opacity: 0;
}
[data-fig="on"] .iso__row {
  animation: rise 0.55s var(--ease) forwards;
}
[data-fig="on"] .iso__row:nth-child(2) {
  animation-delay: 0.18s;
}
[data-fig="on"] .iso__row:nth-child(3) {
  animation-delay: 0.36s;
}

/* --------------------------------------------------- mutants (inline fig)
   Small enough to sit inside a trio cell. Four lines: the guard, then the
   three ways the build breaks it on purpose. */
.mut {
  margin: 16px 0 4px;
  border-top: 1px solid var(--border);
  font-family: var(--mono);
  font-size: 11.5px;
}
.mut div {
  display: grid;
  grid-template-columns: 46px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid var(--border);
  color: var(--muted-fg);
}
.mut div:first-child {
  color: var(--fg);
}
.mut em {
  font-style: normal;
  color: var(--accent);
}
.mut i {
  font-style: normal;
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}
.mut span:first-child {
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--subtle);
}

/* ---------------------------------------------------------- fig 04 · dest
   Where it runs, and the line that isn't there. The severed return path is
   the figure — everything else on it is context for that one gap. */
.dest {
  margin-top: 26px;
  max-width: 760px;
}
.dest__art {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 9px 14px;
  border: 1px solid var(--accent-line);
  border-radius: var(--r-sm);
  background: var(--accent-soft);
  font-family: var(--mono);
  font-size: 12px;
  color: var(--accent);
}
.dest__stem {
  width: 1px;
  height: 26px;
  margin-left: 34px;
  background: var(--border-strong);
}
.dest__row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  border-top: 1px solid var(--border-strong);
  border-left: 1px solid var(--border);
}
@media (min-width: 700px) {
  .dest__row {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }
}
.dest__row div {
  position: relative;
  padding: 26px 12px 16px;
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--muted-fg);
  text-align: center;
}
/* each destination hangs off the shared rule by its own drop line */
.dest__row div::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  width: 1px;
  height: 14px;
  background: var(--border-strong);
}
.dest__back {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 22px;
  padding-top: 20px;
  border-top: 1px dashed var(--border-strong);
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--subtle);
}
.dest__back b {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--accent);
  font-weight: 400;
}
.dest__back svg {
  width: 14px;
  height: 14px;
}

@media (prefers-reduced-motion: reduce) {
  /* Script only adds `.js-fig` when it intends to animate, so this is a
     belt-and-braces reset for a preference toggled after load. */
  .js-fig .matrix {
    clip-path: none;
  }
  .js-fig .iso__row,
  .js-fig .fence__fail {
    opacity: 1;
  }
  .js-fig .fence__chip--x {
    transform: none;
  }
  .js-fig .fence__rule::after {
    opacity: 0.55;
  }
  [data-fig="on"] .matrix,
  [data-fig="on"] .iso__row,
  [data-fig="on"] .fence__chip--x,
  [data-fig="on"] .fence__rule::after,
  [data-fig="on"] .fence__fail {
    animation: none;
  }
}

/* ----------------------------------------------------------- close · zbar
   The closing line is the thesis in eight words, so it gets the thesis as a
   drawing: one bar cut in two, the compiled side carrying the accent. It is
   the same boundary as Fig. 02, reduced to the only thing left to say. */
.zbar {
  display: grid;
  grid-template-columns: 1fr;
  /* The gap is the point: two rules that do not meet, which is the boundary
     the whole page is about. */
  gap: 22px;
  margin: 30px auto 4px;
  max-width: 620px;
  text-align: left;
}
@media (min-width: 700px) {
  .zbar {
    grid-template-columns: 1fr 1fr;
  }
}
.zbar__z {
  padding-top: 12px;
  border-top: 1px solid var(--border-strong);
}
.zbar__z + .zbar__z {
  padding-top: 12px;
}
@media (max-width: 699px) {
  .zbar__z + .zbar__z {
    margin-top: 16px;
  }
}
.zbar__z--core {
  border-top-color: var(--accent);
}
.zbar__z span {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--subtle);
}
.zbar__z--core span {
  color: var(--accent);
}

/* ==========================================================================
   ICONS, POINTS, AIR
   The page was carrying its argument entirely in prose, and prose at this
   density is prose nobody finishes. Three devices fix it without losing a
   single claim: an icon plate so a card can be identified before it is read,
   a points list so a three-paragraph explanation becomes three statements,
   and more air between the blocks so the eye has somewhere to rest.
   ========================================================================== */

/* An icon in a bordered plate — the same square the figures are drawn in, at
   card scale. Line weight matches the nav and the accordion so the whole page
   reads as one set of drawings. */
.ico {
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  color: var(--accent);
  margin-bottom: 20px;
}
.ico svg {
  width: 20px;
  height: 20px;
}
.split__z .ico {
  margin-bottom: 18px;
}

/* A claim, then one line under it. Replaces the stacked <p class="lead"> runs
   that made every section look the same from ten feet away. */
.pts {
  display: grid;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--border);
}
.pts li {
  display: grid;
  grid-template-columns: 21px minmax(0, 1fr);
  gap: 18px;
  align-items: start;
  padding: 22px 0;
  border-bottom: 1px solid var(--border);
  list-style: none;
}
.pts svg {
  width: 20px;
  height: 20px;
  margin-top: 1px;
  color: var(--accent);
}
.pts b {
  display: block;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.012em;
  color: var(--fg);
  margin-bottom: 6px;
}
.pts span {
  display: block;
  font-size: 14.5px;
  line-height: 1.62;
  color: var(--muted-fg);
}

/* Air. The spacer blocks are the page's punctuation, so on a wide screen they
   get to be a real pause rather than a gap you scroll past.

   Scoped to `.p-beta` on purpose: these are density decisions made for the
   beta page's copy, and index.html / why-oceantic.html carry longer text that
   was composed against the old rhythm. Widen the scope deliberately, not by
   inheriting it. */
/* The ruled spacer blocks are GONE from these two pages — the empty
   `<div class="wrap gap">` between every section went with them.

   The reference uses them as punctuation, and shrinking them from 116px to
   40px did not fix the actual problem: an empty bordered box is an empty
   bordered box, and a reader inspecting the page finds a div that holds
   nothing and wonders what broke. Sections now separate with the one thing
   that was always doing the work — a single rule at the end of each — which
   is the same idea with nothing hollow in it.

   `.gap` itself stays in the stylesheet: index.html and why-oceantic.html
   are still built on it. */
.p-beta main .rule,
.p-beta .hero__in {
  border-bottom: 1px solid var(--border);
}
/* Nothing on this page should run to 90 characters. */
.p-beta .acc__body p {
  max-width: 62ch;
}


/* ------------------------------------------------------------- sources
   Three cited cards became one block. Provenance is a footnote on a landing
   page — it has to be checkable, not prominent. */
.src {
  padding: 30px 24px 34px;
}
@media (min-width: 1024px) {
  .src {
    padding: 34px 48px 38px;
  }
}
.src__k {
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 8px;
}
.src ul {
  margin: 0;
  padding: 0;
}
.src li {
  display: grid;
  gap: 4px 26px;
  padding: 16px 0;
  border-bottom: 1px solid var(--border);
  list-style: none;
}
@media (min-width: 760px) {
  .src li {
    grid-template-columns: 200px minmax(0, 1fr);
    align-items: baseline;
  }
}
.src b {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 400;
  color: var(--accent);
}
.src span {
  font-size: 14px;
  line-height: 1.6;
  color: var(--muted-fg);
  max-width: 62ch;
}
/* Citations need a link affordance, same convention as `.note a`. */
.src a {
  color: var(--muted-fg);
  border-bottom: 1px solid var(--border-strong);
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.src a:hover {
  color: var(--accent);
  border-color: var(--accent);
}

/* ==========================================================================
   INTERACTION
   The page is now short enough that the remaining detail can be asked for
   rather than printed. Every interaction below reveals something real — a
   capability's definition, a subset of the sample, the row of a figure a
   sentence is talking about. None of them animate for the sake of it, and
   every one has a resting state that already reads correctly.
   ========================================================================== */

/* ------------------------------------------------- fig 01 · legend filter
   Hover or click a row and the other two groups drop back, so "10.5%" stops
   being a number and becomes a shape you picked out of the sample yourself. */
.mkey button {
  display: grid;
  grid-template-columns: 14px 1fr auto;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 13px 8px 13px 0;
  border: 0;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  background: none;
  font: inherit;
  font-size: 13px;
  color: var(--muted-fg);
  text-align: left;
  cursor: pointer;
  transition: color var(--dur) var(--ease), padding-left var(--dur) var(--ease),
    background var(--dur) var(--ease);
}
.mkey button:hover,
.mkey button:focus-visible,
.mkey button[aria-pressed="true"] {
  color: var(--fg);
  padding-left: 10px;
  background: linear-gradient(90deg, var(--accent-soft), transparent 70%);
}
.mkey button span:first-child {
  width: 13px;
  height: 13px;
  border-radius: 2px;
  border: 1px solid var(--border-strong);
  transition: transform var(--dur) var(--ease);
}
.mkey button:hover span:first-child,
.mkey button[aria-pressed="true"] span:first-child {
  transform: scale(1.25);
}
.mkey .s span:first-child {
  border-color: var(--accent);
  background: var(--accent);
}
.mkey .c span:first-child {
  border-color: var(--muted-fg);
}
.matrix i {
  transition: opacity 0.35s var(--ease), transform 0.35s var(--ease);
}
.matrix[data-hl] i {
  opacity: 0.14;
}
.matrix[data-hl="s"] i.s,
.matrix[data-hl="c"] i.c,
.matrix[data-hl="n"] i:not(.s):not(.c) {
  opacity: 1;
  transform: scale(1.12);
}

/* --------------------------------------------------- split · capabilities
   Each chip is a definition nobody had to read until they wanted it. The
   zone's own closing line doubles as the readout, so hovering swaps one
   true sentence for another rather than opening an empty panel. */
.cap {
  font: inherit;
  font-size: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  text-align: left;
}
.split__list li {
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease),
    transform var(--dur) var(--ease);
}
.split__list li:hover,
.split__list li:has(.cap:focus-visible) {
  border-color: var(--accent);
  background: var(--accent-soft);
  transform: translateY(-1px);
}
.split__f {
  transition: color var(--dur) var(--ease);
  min-height: 3.2em;
}
.split__f[data-live="true"] {
  color: var(--accent);
}
.split__f[data-live="true"] b {
  color: inherit;
  font-weight: 400;
}

/* ------------------------------------------------- gates · point ↔ figure
   The sentence and the row it describes are 400px apart. Hovering one marks
   the other, which is cheaper than repeating the sentence in the caption. */
.pts li {
  transition: background var(--dur) var(--ease), padding-left var(--dur) var(--ease);
}
.pts li[data-hl] {
  cursor: default;
}
.pts li[data-hl]:hover {
  background: linear-gradient(90deg, var(--accent-soft), transparent 70%);
  padding-left: 10px;
}
.iso__row {
  transition: background var(--dur) var(--ease);
}
.iso[data-lit] .iso__row {
  opacity: 0.4;
}
.iso[data-lit="own"] .iso__row[data-row="own"],
.iso[data-lit="deny"] .iso__row[data-row="deny"] {
  opacity: 1;
  background: linear-gradient(90deg, var(--accent-soft), transparent 78%);
}

/* ------------------------------------------------------- fig 04 · destinations */
.dest__row div {
  transition: color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.dest__row div:hover {
  color: var(--accent);
  background: var(--accent-soft);
}

/* ----------------------------------------------------------- card polish
   A hairline that warms to the accent, and a millimetre of lift. Enough to
   say "this is a thing", not enough to look like a SaaS template. */
.p-beta .stat,
.p-beta .src li {
  transition: background var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.p-beta .stat:hover {
  background: var(--muted);
  box-shadow: inset 0 1px 0 var(--accent-line);
}
.p-beta .src li:hover {
  background: var(--muted);
}
.p-beta .stat__n {
  transition: color var(--dur) var(--ease);
}
.p-beta .stat:hover .stat__n {
  color: var(--accent);
}
.split__z {
  transition: background var(--dur) var(--ease);
}

/* One focus treatment for everything this page made interactive. */
.mkey button:focus-visible,
.cap:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

@media (prefers-reduced-motion: reduce) {
  .mkey button,
  .matrix i,
  .split__list li,
  .pts li,
  .iso__row,
  .dest__row div,
  .p-beta .stat {
    transition: none;
  }
  .matrix[data-hl="s"] i.s,
  .matrix[data-hl="c"] i.c,
  .matrix[data-hl="n"] i:not(.s):not(.c) {
    transform: none;
  }
}

/* ==========================================================================
   COMPOSITION  (`.p-beta`)
   Every section on this page used the same skeleton: headline left, lead
   right, then a bordered grid of two or three cells — nine times in a row.
   No amount of copy editing fixes that, because the sameness is structural.
   This layer gives sections different *compositions*: a numbered rail, a
   sticky column that holds while its evidence scrolls past, stacked rows
   instead of a third three-up card grid, and one visual stage driven by the
   accordion beside it.
   ========================================================================== */

/* ------------------------------------------------------------- section head
   A masthead line, then the argument.

   The rail this replaces put the number and the label in a 160px gutter and
   pushed the headline into two thirds of the column — which left the head
   tall, the right of the sheet empty, and the label reading as a margin note
   rather than as a heading.

   This is the same label idiom the figures already use (`.plate__k`): a name
   on the left, a hairline across, a value on the right. Sections and figures
   now announce themselves the same way, and the rule runs the full width of
   the column instead of stopping in a gutter. The value is the section's
   position, so the reader always knows how much page is left.

   Under it, the headline and its lead share one row and end on the same
   baseline. That is the whole head in about 180px. */
/* The top padding carries the separation the spacer blocks used to: one
   rule closes the section above, and the air belongs to the head below it. */
.p-beta .shead {
  display: grid;
  gap: 22px;
  padding: 48px 24px 30px;
}
@media (min-width: 1024px) {
  .p-beta .shead {
    gap: 30px;
    padding: 60px 48px 36px;
  }
}

/* the masthead line */
.p-beta .shead__n {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0;
  border: 0;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted-fg);
}
.p-beta .shead__n i {
  flex: 1;
  height: 1px;
  min-width: 24px;
  background: var(--border);
  font-style: normal;
}
.p-beta .shead__n b {
  font-weight: 400;
  font-size: inherit;
  color: var(--accent);
  white-space: nowrap;
}

/* the argument: headline and lead, ending together */
.p-beta .shead > div {
  display: grid;
  gap: 16px;
}
@media (min-width: 1024px) {
  .p-beta .shead > div {
    grid-template-columns: minmax(0, 1.12fr) minmax(0, 1fr);
    gap: 0 56px;
    align-items: end;
  }
}
/* The column is the measure now, not a character count — 20ch inside a
   578px cell wrapped every headline to three lines with 120px spare beside
   it. Let it use the cell it was given and balance the two lines. */
.p-beta .shead h2 {
  font-size: clamp(27px, 3.5vw, 46px);
  letter-spacing: -0.03em;
  line-height: 1.06;
  text-wrap: balance;
  max-width: 26ch;
}
.p-beta .shead .lead {
  margin-top: 0;
  font-size: 17px;
  max-width: 46ch;
}
@media (min-width: 1024px) {
  /* Optical, not mathematical: the lead's descenders sit a touch below the
     headline's, and matching the boxes exactly reads as misaligned. */
  .p-beta .shead .lead {
    padding-bottom: 4px;
  }
}

/* ------------------------------------------------------------ sticky column
   The claim holds still while the evidence for it scrolls past. Only kicks
   in where there is height to spare, and never on a touch-sized screen. */
@media (min-width: 1024px) {
  .p-beta .dock {
    display: grid;
    grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
    border-top: 1px solid var(--border);
  }
  .p-beta .dock__hold {
    position: sticky;
    top: 92px;
    align-self: start;
    padding: 48px;
    border-right: 1px solid var(--border);
  }
  .p-beta .dock__flow > * + * {
    border-top: 1px solid var(--border);
  }
}
@media (max-width: 1023px) {
  .p-beta .dock__hold {
    padding: 32px 24px;
    border-top: 1px solid var(--border);
  }
  .p-beta .dock__flow > * {
    border-top: 1px solid var(--border);
  }
}
.p-beta .dock__hold .stat__l {
  display: block;
  margin-bottom: 20px;
}

/* --------------------------------------------------------------- row stack
   Three cards side by side is the shape the page already used twice. Set as
   numbered rows instead: the eye reads down a list far faster than it reads
   across three equal columns, and each row can breathe. */
.p-beta .rows {
  display: grid;
}
.p-beta .rows > article {
  display: grid;
  gap: 8px 32px;
  padding: 30px 24px;
  border-bottom: 1px solid var(--border);
  transition: background var(--dur) var(--ease);
}
@media (min-width: 860px) {
  .p-beta .rows > article {
    /* number and icon share the first column so the title gets real width —
       a two-word heading wrapping to three lines is what a cramped column
       looks like, and it made the rows read as an afterthought. */
    grid-template-columns: 56px minmax(0, 260px) minmax(0, 1fr);
    align-items: start;
    padding: 36px 48px 40px;
  }
}
.p-beta .rows__n {
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.p-beta .rows .ico {
  margin-bottom: 0;
  width: 36px;
  height: 36px;
}
.p-beta .rows .ico svg {
  width: 18px;
  height: 18px;
}
.p-beta .rows > article:last-child {
  border-bottom: 0;
}
.p-beta .rows > article:hover {
  background: var(--muted);
}
.p-beta .rows__n {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--accent);
  padding-top: 2px;
}
.p-beta .rows h3 {
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.018em;
}
.p-beta .rows p {
  font-size: 15px;
  line-height: 1.65;
  color: var(--muted-fg);
  max-width: 54ch;
}

/* ------------------------------------------------------------- accordion stage
   The accordion stops being a list of paragraphs and becomes a control: open
   a row and the panel beside it changes to the thing that row describes. */
@media (min-width: 1024px) {
  .p-beta .stage {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
    border-top: 1px solid var(--border);
  }
  .p-beta .stage__list {
    padding: 40px 40px 40px 48px;
    border-right: 1px solid var(--border);
  }
  .p-beta .stage__view {
    position: sticky;
    top: 92px;
    align-self: start;
    padding: 40px 48px 40px 40px;
  }
}
@media (max-width: 1023px) {
  .p-beta .stage__list {
    padding: 28px 24px;
    border-top: 1px solid var(--border);
  }
  .p-beta .stage__view {
    padding: 8px 24px 32px;
  }
}
.p-beta .view {
  display: none;
}
.p-beta .view[data-on="true"] {
  display: block;
  animation: viewin var(--t-in) var(--ease) both;
}
@keyframes viewin {
  from {
    opacity: 0;
    transform: translateY(var(--lift));
  }
  to {
    opacity: 1;
    transform: none;
  }
}
.p-beta .view__k {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 18px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--subtle);
}
.p-beta .view__k b {
  color: var(--accent);
  font-weight: 400;
}
/* the component grid */
.p-beta .comps {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  border-top: 1px solid var(--border);
  border-left: 1px solid var(--border);
}
@media (min-width: 560px) {
  .p-beta .comps {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}
.p-beta .comps span {
  padding: 15px 12px;
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--muted-fg);
  text-align: center;
  transition: color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.p-beta .comps span:hover {
  color: var(--accent);
  background: var(--accent-soft);
}

@media (prefers-reduced-motion: reduce) {
  .p-beta .view[data-on="true"] {
    animation: none;
  }
}

/* The reference accordion stacks its head vertically when open — a 72px gap
   between icon and title, which reads as deliberate at full width and as a
   bug in the narrower stage column. Inside the stage it stays a row. */
.p-beta .stage .acc__item[data-open="true"] .acc__head {
  flex-direction: row;
  align-items: center;
  gap: 12px;
}
.p-beta .stage .acc__btn {
  padding: 22px 4px;
}
.p-beta .stage .acc__t {
  font-size: 18px;
}
@media (min-width: 1280px) {
  .p-beta .stage .acc__t {
    font-size: 19px;
  }
}
.p-beta .stage .acc__body p {
  font-size: 14.5px;
  line-height: 1.65;
}
.p-beta .stage .acc__item[data-open="true"] {
  border-top-color: var(--accent);
}


/* ==========================================================================
   THE SHEET  (`.p-beta`)
   An earlier pass lifted this page onto cards — rounded, shadowed, floating
   on a ground. It was wrong for the argument: the whole page is about a
   boundary you can read, drawn as a line, and a drawing needs a sheet to be
   drawn on. So the page keeps the house geometry — one 1280px column whose
   left and right rules never break, sections separated by ruled spacer
   blocks, square corners, one hairline weight — and the compositions above
   (`.shead`, `.dock`, `.rows`, `.stage`) are cells cut into it.

   What stays from the card pass is the *composition*, not the surface.
   ========================================================================== */

/* ------------------------------------------------------------------ limits
   Six admissions read as six equal cards is the wrong shape: the reader
   wants to know *how* missing a thing is before reading a word about it.
   Set as a ledger with the status in its own column, sorted by it —
   `not built`, then `partial`, then the one that is a decision. */
.p-beta .lim {
  display: grid;
  border-top: 1px solid var(--border);
}
.p-beta .lim > div {
  display: grid;
  gap: 8px 32px;
  padding: 28px 24px;
  border-bottom: 1px solid var(--border);
  transition: background var(--dur) var(--ease);
}
@media (min-width: 900px) {
  .p-beta .lim > div {
    grid-template-columns: 118px minmax(0, 280px) minmax(0, 1fr);
    align-items: baseline;
    padding: 30px 48px;
  }
}
.p-beta .lim > div:hover {
  background: color-mix(in srgb, var(--muted) 60%, transparent);
}
.p-beta .lim > div:last-child {
  border-bottom: 0;
}
.p-beta .lim__s {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--subtle);
}
.p-beta .lim__s--part {
  color: var(--muted-fg);
}
/* The only one that is a choice rather than a gap, so it is the only one
   that gets the accent. */
.p-beta .lim__s--design {
  color: var(--accent);
}
.p-beta .lim h3 {
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.018em;
}
.p-beta .lim p:last-child {
  font-size: 15px;
  line-height: 1.65;
  color: var(--muted-fg);
  max-width: 58ch;
}

/* --------------------------------------------------------------- the rows
   The kicker sits above the row's heading in the middle column; it was
   written for a trio cell where it had one to itself. */
.p-beta .rows .trio__k {
  margin-bottom: 8px;
}

/* ------------------------------------------------------------- the compile
   The console, the tree and the receipt used to hang off the hero. They are
   the product, not a decoration behind a headline, so they get a section —
   which means the strip is a full-width segmented control across the column
   again, the way the rest of the sheet is built. */
.p-beta .tabpanes {
  padding-bottom: 20px;
}
@media (min-width: 768px) {
  .p-beta .tabpanes {
    padding-bottom: 56px;
  }
}
/* Nothing below it to dissolve into — the section ends on the column rule. */
.p-beta .tabpanes::after {
  display: none;
}
/* Behind a headline the strip could drop to three icons and still be read as
   decoration. As the section's own control it can't: the labels stay, and the
   padding comes in far enough that all three still fit a phone. */
@media (max-width: 639px) {
  .p-beta .tab__t {
    display: inline;
  }
  .p-beta .tab {
    gap: 7px;
    padding: 16px 10px;
    font-size: 13px;
  }
  .p-beta .tab svg {
    width: 16px;
    height: 16px;
  }
}

/* ------------------------------------------------------------ fine tuning
   Four joins that only show up once the compositions are on the sheet. */

/* Fig. 01 — `correct, but not secure` was a hairline a shade lighter than
   `neither`, which is not a difference at 13px. The middle group gets a
   wash: solid accent > tinted > empty, so 101 and 78 read apart. */
.p-beta .matrix i.c {
  background: color-mix(in srgb, var(--muted-fg) 16%, transparent);
}

/* The figure is 828px of sample-and-key sitting in a 1,150px cell, and it
   was packed to the left with a third of the plate empty beside it. It is a
   composed figure, so it gets centred as one — caption included, aligned to
   the sample's own left edge rather than to the page's. */
.p-beta .plate .mgrid {
  justify-content: center;
}
.p-beta .plate .mgrid + .plate__cap {
  max-width: 828px;
  margin-inline: auto;
}

/* A source's key figure is prose, not a label — `.src b` is the mono
   citation key in the left column and shouldn't reach into the sentence. */
.p-beta .src span b {
  font-family: inherit;
  font-size: inherit;
  font-weight: 600;
  color: var(--fg);
}

/* The zbar is a figure, not a caption: it needs air before the buttons. */
.p-beta .close .zbar {
  margin-bottom: 18px;
}

/* The card beside the form is stretched to the form's height, so its
   contents should use it — the address block sits on the bottom edge where
   the card's own glow is, rather than floating mid-panel. */
.p-beta .contact__card {
  justify-content: space-between;
}

/* ------------------------------------------------------- fig 03 on narrow
   The request label is drawn *above* the line it belongs to, which needs a
   line long enough to hold it. On a phone the line is 90px and the label
   wrapped straight through the response chip. Below 640 the row stacks —
   who and status on the first line, the exchange on the second — and the
   label goes static, keeping the solid/dashed signal as its own underline
   rather than as a rule it no longer has room for. */
@media (max-width: 640px) {
  .p-beta .iso__row {
    grid-template-columns: minmax(0, 1fr) auto;
    row-gap: 12px;
  }
  .p-beta .iso__who {
    grid-column: 1;
    grid-row: 1;
  }
  .p-beta .iso__code {
    grid-column: 2;
    grid-row: 1;
  }
  .p-beta .iso__act {
    grid-column: 1 / -1;
    grid-row: 2;
    flex-wrap: wrap;
  }
  .p-beta .iso__line {
    flex: 0 1 auto;
    height: auto;
    background: none;
    border-bottom: 1px solid var(--border-strong);
    padding-bottom: 3px;
  }
  .p-beta .iso__row--deny .iso__line {
    background: none;
    border-bottom-style: dashed;
  }
  .p-beta .iso__line span {
    position: static;
    white-space: nowrap;
  }
}

@media (prefers-reduced-motion: reduce) {
  .p-beta .lim > div {
    transition: none;
  }
}

/* ==========================================================================
   THE PUBLIC PAGE
   `beta.html` is read by people who are not going to compile anything: a
   founder, an agency lead, someone who runs a small team and has been told
   they need a security review. The mechanism moved to `how-it-works.html`;
   what stays here has to carry the argument in plain words and in pictures
   that need no caption.

   Every component below follows the house rules: a bordered cell on the
   ruled column, square corners, one hairline, one accent. Motion is either
   the mechanism (the three steps complete in order) or a resting-state
   reveal, and the markup is always the finished picture.
   ========================================================================== */

/* -------------------------------------------------------------- the flow
   Describe → build → prove. Three cells, each topped by an accent bar that
   completes in sequence — the process, drawn as the process. */
.flow {
  display: grid;
  grid-template-columns: 1fr;
  border-top: 1px solid var(--border);
}
@media (min-width: 900px) {
  .flow {
    grid-template-columns: repeat(3, 1fr);
  }
}
.flow__s {
  position: relative;
  padding: 34px 24px 36px;
  border-bottom: 1px solid var(--border);
}
@media (min-width: 900px) {
  .flow__s {
    padding: 42px 40px 46px;
    border-bottom: 0;
    border-right: 1px solid var(--border);
  }
  .flow__s:last-child {
    border-right: 0;
  }
}
/* The bar is drawn complete in the markup; script winds it back only when
   it intends to animate it. */
.flow__s::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  background: var(--accent);
  transform-origin: left;
}
.js-fig .flow__s::before {
  transform: scaleX(0);
}
[data-fig="on"] .flow__s::before {
  animation: grow var(--t-run) var(--ease) forwards;
}
[data-fig="on"] .flow__s:nth-child(2)::before {
  animation-delay: 0.4s;
}
[data-fig="on"] .flow__s:nth-child(3)::before {
  animation-delay: 0.8s;
}
@keyframes grow {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}
.flow__n {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
}
/* The step glyph — the same icon language as `index.html`'s "How Oceantic
   works": a 52px *solid* geometric mark, not a line icon in a plate.
   Filled with `currentColor`, secondary shapes at 45%, and anything knocked
   out of a filled shape uses the page colour rather than white, so it works
   in both themes. It warms to the accent with the rest of the cell. */
.flow__g {
  width: 52px;
  height: 52px;
  color: var(--fg);
  margin: 20px 0 18px;
  transition: color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.flow__s:hover .flow__g {
  color: var(--accent);
  transform: translateY(-2px);
}

@media (prefers-reduced-motion: reduce) {
  .flow__g {
    transition: none;
  }
  .flow__s:hover .flow__g {
    transform: none;
  }
}
.flow h3 {
  font-size: 21px;
  font-weight: 600;
  letter-spacing: -0.022em;
  margin-bottom: 10px;
}
.flow p {
  font-size: 15.5px;
  line-height: 1.65;
  color: var(--muted-fg);
  max-width: 40ch;
}
.flow p b {
  color: var(--fg);
  font-weight: 600;
}

/* ----------------------------------------------------------- the compare
   Two ways to get an app built, on the same six questions. Oceantic's
   column is one continuous tinted band rather than six tinted cells, so it
   reads as a position rather than as six coincidences. */
.vsx {
  border-top: 1px solid var(--border);
}
.vsx__h {
  display: none;
}
@media (min-width: 860px) {
  .vsx__h,
  .vsx__r {
    display: grid;
    grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.05fr) minmax(0, 1.05fr);
  }
  .vsx__h {
    border-bottom: 1px solid var(--border);
  }
}
.vsx__h > * {
  padding: 18px 24px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--subtle);
}
.vsx__h > :last-child {
  color: var(--accent);
  background: var(--accent-soft);
}
.vsx__r {
  border-bottom: 1px solid var(--border);
  transition: background var(--dur) var(--ease);
}
.vsx__r:hover {
  background: color-mix(in srgb, var(--muted) 55%, transparent);
}
.vsx__q {
  padding: 22px 24px 6px;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.015em;
}
.vsx__a,
.vsx__b {
  display: grid;
  grid-template-columns: 18px minmax(0, 1fr);
  gap: 12px;
  align-items: start;
  padding: 10px 24px;
  font-size: 15px;
  line-height: 1.6;
  color: var(--muted-fg);
}
.vsx__b {
  color: var(--fg);
  background: var(--accent-soft);
  padding-bottom: 22px;
}
@media (min-width: 860px) {
  .vsx__q,
  .vsx__a,
  .vsx__b {
    padding: 26px 28px;
  }
  .vsx__q {
    padding-left: 24px;
  }
}
.vsx__a svg,
.vsx__b svg {
  width: 17px;
  height: 17px;
  margin-top: 2px;
}
.vsx__a svg {
  color: var(--subtle);
}
.vsx__b svg {
  color: var(--accent);
}
/* Each answer says whose column it is, as real text rather than a CSS
   `content: attr(data-c)`. Generated content is not in the accessibility
   tree, so the old version told sighted phone readers which column an answer
   belonged to and told a screen reader nothing — on the one table where that
   is the entire point. Visible below the breakpoint, available to assistive
   technology at every width. */
.vsx__c {
  grid-column: 1 / -1;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--subtle);
}
.vsx__b .vsx__c {
  color: var(--accent);
}
@media (min-width: 860px) {
  .vsx__c {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* ------------------------------------------------------------------ who
   Four readers, four sentences. The grid is the page's own bordered grid;
   the only interaction is the cell warming under the cursor, which is
   enough to say these are four separate answers. */
.who {
  display: grid;
  grid-template-columns: 1fr;
  border-top: 1px solid var(--border);
}
@media (min-width: 700px) {
  .who {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 1100px) {
  .who {
    grid-template-columns: repeat(4, 1fr);
  }
}
.who > article {
  padding: 30px 24px 34px;
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  margin: 0 -1px -1px 0;
  transition: background var(--dur) var(--ease);
}
@media (min-width: 1100px) {
  .who > article {
    padding: 36px 30px 40px;
  }
}
.who > article:hover {
  background: color-mix(in srgb, var(--muted) 60%, transparent);
}
.who__k {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--subtle);
}
.who > article:hover .who__k {
  color: var(--accent);
}
.who h3 {
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.022em;
  margin: 14px 0 10px;
}
.who p {
  font-size: 15px;
  line-height: 1.65;
  color: var(--muted-fg);
}

/* ------------------------------------------------------------ hand-off
   One row, pointing the technical reader at the page that answers them.
   Deliberately quiet: it is a door, not an argument. */
.tech {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 26px 24px;
  border-top: 1px solid var(--border);
}
@media (min-width: 1024px) {
  .tech {
    padding: 30px 48px;
  }
}
.tech p {
  font-size: 15px;
  color: var(--muted-fg);
  max-width: 62ch;
}
.tech p b {
  color: var(--fg);
  font-weight: 600;
}

/* --------------------------------------------------------------- reveal
   The one piece of motion that is not a mechanism. It sequences the reading
   of a grid rather than announcing itself: 10px and 0.5s, once.

   The waiting state is `.is-wait`, and the ONLY thing that ever sets it is
   the script, on elements that are below the fold at the moment it runs.
   That distinction is the whole rule:

     · nothing is hidden in the stylesheet, so with no JavaScript — or with
       reduced motion, where the script returns early — every row is simply
       there. A reveal that can strand content is a bug with a nice name.
     · nothing already ON SCREEN is ever hidden. The previous version hid
       whatever it was about to animate, including rows the reader was
       looking at, so content blinked out and faded back. An animation that
       takes something away before giving it back is worse than no animation:
       the reader saw it, and then it left.

   So a row is only ever hidden while it is somewhere nobody can see it. */
[data-reveal].is-wait {
  opacity: 0;
}
[data-reveal].is-in {
  animation: revealin var(--t-in) var(--ease) both;
}
/* The rows here reveal with the same single movement as everything else on
   the page. An earlier cut staggered each row into three parts — question,
   then one answer, then the other — and wiped the accent band in underneath.
   It read as choreography on a table you are supposed to be reading, which is
   the wrong job for motion in a comparison: the eye should be going down the
   column, not waiting for it. One row, one movement. */
@keyframes revealin {
  from {
    opacity: 0;
    transform: translateY(var(--lift));
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .js-fig .flow__s::before {
    transform: none;
  }
  [data-fig="on"] .flow__s::before {
    animation: none;
  }
  [data-reveal].is-in {
    animation: none;
  }
  .vsx__r,
  .who > article {
    transition: none;
  }
}

/* ------------------------------------------------------- fig 01 · the cut
   The 200-square sample this replaces was honest but inert: a scatter of
   marks where two of the three groups differed by a hairline, next to a key
   you had to look back and forth to use. It asked the reader to do the
   counting, and on a page written for someone deciding rather than
   reviewing, that is one job too many.

   The benchmark is really two cuts through the same 200 tasks — did it
   work, and was it also safe — so it is drawn as two cuts. Three bars on
   one scale, each starting at the same edge, so the third is visibly a
   slice of the second and the shortfall is the empty part of the track.
   The numbers are the study's own: 200 attempted, 61% correct, 10.5% both. */
.funl {
  display: grid;
  margin: 26px 0 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--border);
}
.funl li {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 10px;
  /* set in from the figure's own key line above, so the three rows read as
     items under a heading rather than as three more full-width rules */
  padding: 20px 0 20px 18px;
  border-bottom: 1px solid var(--border);
  transition: background var(--dur) var(--ease);
}
@media (min-width: 760px) {
  .funl li {
    grid-template-columns: minmax(0, 210px) minmax(0, 1fr) 128px;
    align-items: center;
    gap: 0 28px;
    padding-left: 26px;
  }
}
.funl li:hover {
  background: linear-gradient(90deg, var(--accent-soft), transparent 72%);
}
.funl__l {
  font-size: 15.5px;
  color: var(--muted-fg);
}
.funl__l b {
  color: var(--fg);
  font-weight: 600;
}

/* the track: a measured length, and the part of it that is missing */
.funl__t {
  position: relative;
  display: block;
  height: 34px;
  border: 1px solid var(--border-strong);
  border-radius: 2px;
  overflow: hidden;
}
.funl__t i {
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--w);
  background: var(--muted-fg);
  opacity: 0.5;
}
/* the row the section is actually about */
.funl__hit .funl__t {
  border-color: var(--accent-line);
}
.funl__hit .funl__t i {
  background: var(--accent);
  opacity: 1;
}
.funl__hit .funl__l {
  color: var(--fg);
}

/* Motion is the mechanism: the bars are cut down in sequence, which is what
   the benchmark did. The still is the finished figure — script only winds
   the fills back once it has committed to animating them. */
.js-fig .funl__t i {
  width: 0;
}
[data-fig="on"] .funl__t i {
  animation: cut var(--t-run) var(--ease) forwards;
}
[data-fig="on"] .funl li:nth-child(2) .funl__t i {
  animation-delay: 0.2s;
}
[data-fig="on"] .funl li:nth-child(3) .funl__t i {
  animation-delay: 0.45s;
}
@keyframes cut {
  from {
    width: 0;
  }
  to {
    width: var(--w);
  }
}

.funl__v {
  display: flex;
  align-items: baseline;
  gap: 10px;
  font-family: var(--mono);
  font-size: 21px;
  color: var(--muted-fg);
  font-variant-numeric: tabular-nums;
}
@media (min-width: 760px) {
  .funl__v {
    justify-content: flex-end;
  }
}
.funl__v em {
  font-style: normal;
  font-size: 12px;
  letter-spacing: 0.06em;
  color: var(--subtle);
}
.funl__hit .funl__v {
  color: var(--accent);
}
.funl__hit .funl__v em {
  color: var(--accent);
}

@media (prefers-reduced-motion: reduce) {
  .js-fig .funl__t i {
    width: var(--w);
  }
  [data-fig="on"] .funl__t i {
    animation: none;
  }
  .funl li {
    transition: none;
  }
}

/* A figure label is `NAME ——————— VALUE`, which needs a line wide enough to
   hold all three. On a phone it squeezed into four, so the trailing value —
   always the least load-bearing part, a sample size or a build number —
   steps out and lets the name and its rule have the line. */
@media (max-width: 640px) {
  .p-beta .plate__k {
    flex-wrap: wrap;
  }
  .p-beta .plate__k > span:last-of-type {
    display: none;
  }
  .p-beta .plate__k i {
    min-width: 16px;
  }
  /* With the value gone the label's rule runs the full width, which is
     already the top edge of whatever it labels — a second one 26px below it
     is the same line drawn twice. */
  .p-beta .funl {
    margin-top: 14px;
    border-top: 0;
  }
}

/* ==========================================================================
   THE GLYPH SET  (`.gly`, `.flow__g`)
   One icon language for every mark that *identifies* a block on the public
   page — the three steps, the two zones, the four readers, the three things
   you get. Taken from `index.html`'s "How Oceantic works" plates:

     · a 60×60 field, and every shape square or circular within it. This is
       the rule an earlier pass broke — 26×32 rectangles and 60×14 bars read
       as stretched, because they are. Primitives are 26×26 with `rx 6`
       (rounded square) or `rx 13` (circle), 16×16 on the 3×3, or true
       circles. Nothing is scaled on one axis only.
     · solid fills in `currentColor`, secondary shapes at 45%
     · anything cut out of a filled shape is knocked out in `var(--bg)`, not
       white, so it holds in both themes

   UI affordances — the chevrons, the nav, the arrows, the marks inside the
   comparison — stay line icons. `index.html` runs the same two sets side by
   side: solid glyphs identify, strokes operate. */
.gly {
  flex: none;
  color: var(--fg);
  transition: color var(--dur) var(--ease);
}
.split__z .gly {
  display: block;
  width: 46px;
  height: 46px;
  margin-bottom: 18px;
}
.who .gly {
  display: block;
  width: 42px;
  height: 42px;
  margin-bottom: 20px;
}
.who > article:hover .gly {
  color: var(--accent);
}
/* The accordion row's mark sits inline with its title, so it is sized to the
   text rather than to the plate. `.acc__head svg` already colours it, and
   already turns it accent when the row is open. */
.p-beta .acc__head .gly {
  width: 26px;
  height: 26px;
}

@media (prefers-reduced-motion: reduce) {
  .gly {
    transition: none;
  }
}

/* ==========================================================================
   THE CLOSE  (`.p-beta`)
   The closing block was a headline, a line, two hairline-topped columns and
   two buttons of equal weight — which read as a footer, not as a close. It
   is the last thing on the page, so it gets a structure: a kicker to say
   what the block is, the thesis, and then the trade itself drawn as one
   bordered pair with the accent rail on the half we stand behind — the same
   device as the split section, so the page ends where it started.

   One primary action. The second was competing with it; it is a text link
   now, which is what a secondary action should have been all along.
   ========================================================================== */
.p-beta .close {
  padding-block: 74px;
}
@media (min-width: 768px) {
  .p-beta .close {
    padding-block: 104px;
  }
}
.p-beta .close__in {
  max-width: 44rem;
  gap: 0;
}
.p-beta .close__k {
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 22px;
}
.p-beta .close__lead {
  margin-top: 20px;
  max-width: 34em;
}

/* the trade, as one object */
.p-beta .czone {
  display: grid;
  grid-template-columns: 1fr;
  width: 100%;
  max-width: 34rem;
  margin: 38px 0 34px;
  border: 1px solid rgba(246, 242, 234, 0.16);
  text-align: left;
}
@media (min-width: 640px) {
  .p-beta .czone {
    grid-template-columns: 1fr 1fr;
  }
}
.p-beta .czone__c {
  position: relative;
  padding: 20px 22px 22px;
}
@media (min-width: 640px) {
  .p-beta .czone__c:first-child {
    border-right: 1px solid rgba(246, 242, 234, 0.16);
  }
}
@media (max-width: 639px) {
  .p-beta .czone__c:first-child {
    border-bottom: 1px solid rgba(246, 242, 234, 0.16);
  }
}
/* the accent rail, same as the compiled half of the split */
.p-beta .czone__c:first-child::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 2px;
  background: var(--accent);
}
.p-beta .czone__k {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(246, 242, 234, 0.5);
}
.p-beta .czone__c:first-child .czone__k {
  color: var(--accent);
}
.p-beta .czone__l {
  margin-top: 10px;
  font-size: 15px;
  line-height: 1.55;
  color: rgba(246, 242, 234, 0.88);
}

.p-beta .close__cta {
  display: flex;
  align-items: center;
  gap: 14px 26px;
  flex-wrap: wrap;
  justify-content: center;
}
.p-beta .close__alt {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 14.5px;
  color: rgba(246, 242, 234, 0.7);
  border-bottom: 1px solid rgba(246, 242, 234, 0.25);
  padding-bottom: 2px;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.p-beta .close__alt:hover {
  color: #fff;
  border-color: rgba(246, 242, 234, 0.6);
}
.p-beta .close__alt svg {
  transition: transform var(--dur) var(--ease);
}
.p-beta .close__alt:hover svg {
  transform: translateX(3px);
}

@media (prefers-reduced-motion: reduce) {
  .p-beta .close__alt,
  .p-beta .close__alt svg {
    transition: none;
  }
  .p-beta .close__alt:hover svg {
    transform: none;
  }
}

/* ==========================================================================
   THE ISOMETRIC SET  (`.iso`)
   The page's illustration system, in the language of the reference film:
   true 30° isometric line drawings, drawn as if on a drafting sheet.

   What changed from the first cut: THICKNESS. Every plate used to be a face
   with a second copy offset behind it, and the sliver that showed was called
   an extrusion. It reads flat, because it is flat — there is no side wall,
   only a second top. Here the wall is the real swept silhouette of the
   rounded face, computed and emitted as one path (see `scripts` in the repo
   notes), so a plate has a lit top, a shaded side and one continuous outline
   around the whole solid. That single change is most of the difference
   between "some rectangles at an angle" and "an object".

   Seven devices carry the look, and all seven are in the reference:

     1. ROUNDED faces. Rounded-corner squares seen in isometric, never sharp
        rhombi. Every face is a plain `<rect rx>` inside
        `matrix(.866 .5 -.866 .5 cx cy)`, so the radius comes along for free.
     2. REAL WALLS. The extrusion path above. Inside the iso matrix a local
        translate of (D, D) is exactly a screen translate of (0, D), which is
        what makes the sweep expressible without leaving the matrix.
     3. ECHOES. Faded duplicates below a plate — the same object, previous
        passes. On this page that is not decoration: it is the claim.
     4. RECESSED WELLS holding bar groups and slider tracks, so a face reads
        as an instrument rather than a tile.
     5. ONE SOLID OBJECT among the outlined ones. Black in the reference, the
        accent here — the same rule the glyph set follows.
     6. UPRIGHT marks. A check sheared 30° stops being a check, so checks,
        badges and probes are drawn in screen space over the plates.
     7. SHEET FURNITURE. Faint ground guides and four corner pins, the way a
        drawing tool shows a selected object. Cheap, and it is what stops the
        drawings reading as clip art.

   Colour is carried on `.iso` itself as its own small token set, so one
   block re-tints every drawing and neither theme has to be special-cased
   further down.
   ========================================================================== */
.iso {
  --iso-ink: #35353c;
  --iso-ink-2: #131317;
  --iso-face: #ffffff;
  --iso-wall: #eceae5;
  --iso-well: #f5f4f1;
  --iso-ghost: #c6c5c8;

  width: 100%;
  height: auto;
  overflow: visible;
  color: var(--iso-ink);
}
/* Dark is not an inversion — a white line drawing on near-black reads as
   neon. It is the same drawing in blueprint: faces lift off the page rather
   than glowing, and the ink drops to a cool grey. */
[data-theme="dark"] .iso {
  --iso-ink: #8794a8;
  --iso-ink-2: #d5dde9;
  --iso-face: #121b2b;
  --iso-wall: #090f1c;
  --iso-well: #0d1524;
  --iso-ghost: #384355;
}

/* ----------------------------------------------------------- the surfaces */
.iso__face,
.iso__wall {
  stroke: var(--iso-ink);
  stroke-width: 1.25;
  stroke-linejoin: round;
}
.iso__face {
  fill: var(--iso-face);
}
.iso__wall {
  fill: var(--iso-wall);
}
/* the foundation reads heavier than what rests on it, because it is */
.iso__face--base,
.iso__wall--base {
  stroke: var(--iso-ink-2);
  stroke-width: 1.6;
  stroke-linejoin: round;
}
.iso__face--base {
  fill: var(--iso-face);
}
.iso__wall--base {
  fill: var(--iso-wall);
}
/* a wall with nothing inside it. The face above is identical to its
   neighbour's — that is the whole point of the figure it appears in. */
.iso__wall--hollow {
  fill: none;
  stroke-dasharray: 4 4.5;
  opacity: 0.8;
}
/* Sits over the hollow wall and is indistinguishable from a sound one. It is
   the only thing standing between the two plates looking identical and looking
   different, which is exactly the figure's argument — so it defaults to
   invisible (the still frame must tell the truth) and only the running
   animation puts it back for the first half of each pass. */
.iso__wall--cover {
  fill: var(--iso-wall);
  stroke: var(--iso-ink);
  stroke-width: 1.25;
  stroke-linejoin: round;
  opacity: 0;
}

/* recessed detail: a well cut into a face, and what sits in it */
.iso__well {
  fill: var(--iso-well);
  stroke: var(--iso-ink);
  stroke-width: 0.9;
  opacity: 0.95;
}
.iso__bar {
  fill: var(--iso-ink);
  opacity: 0.45;
}
.iso__track {
  fill: var(--iso-well);
  stroke: var(--iso-ink);
  stroke-width: 0.9;
}
.iso__knob {
  fill: var(--iso-face);
  stroke: var(--iso-ink-2);
  stroke-width: 1.2;
}
.iso__mark {
  fill: var(--iso-ink);
  opacity: 0.3;
}
/* an outline with nothing in it yet — the reference uses these constantly */
.iso__ghost {
  fill: none;
  stroke: var(--iso-ghost);
  stroke-width: 1.05;
}
.iso__ghost--near {
  stroke: var(--iso-ink);
  opacity: 0.55;
}

/* --------------------------------------------------------- the one solid */
.iso__solid {
  fill: var(--accent);
}
.iso__solid-e {
  fill: color-mix(in srgb, var(--accent) 62%, #000);
}
.iso__tick {
  fill: none;
  stroke: var(--bg);
  stroke-width: 3.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.iso__ring {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.5;
  opacity: 0;
}

/* --------------------------------------------------- ground and annotation */
.iso__guide {
  stroke: var(--border);
  stroke-width: 1;
  opacity: 0.75;
}
.iso__pin {
  fill: var(--bg);
  stroke: var(--border-strong);
  stroke-width: 1.1;
  opacity: 0.8;
}
.iso__dot {
  stroke: var(--accent);
  stroke-width: 1.4;
  stroke-dasharray: 2 5;
  stroke-linecap: round;
  opacity: 0.7;
  fill: none;
}
.iso__packet {
  fill: var(--accent);
  opacity: 0;
}
.iso__fence {
  stroke: var(--accent);
  stroke-width: 1.4;
  stroke-dasharray: 5 7;
  opacity: 0.5;
}
/* the line down the middle, where it is load-bearing enough to be a wall.
   A dashed rule with end caps reads as a measurement; a filled quad reads as
   a hole punched in the page. Hatched, it reads as a section plane — the one
   drafting convention for "a boundary you can see through". */
.iso__hatch {
  stroke: var(--accent);
  stroke-width: 1;
  opacity: 0.24;
}
.iso__shadow {
  fill: var(--fg);
  opacity: 0.07;
}
.iso__echo {
  opacity: 0.55;
}
.iso__echo--far {
  opacity: 0.28;
}

/* the refusal, drawn upright over the plates — a cross sheared 30° into the
   ground plane stops looking like a cross. It appears in exactly one figure.
   It is the loudest mark in the set, so it is spent where a refusal is the
   finding, and never where the reader is meant to come away reassured. */
.iso__deny {
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
}
.iso__deny-o {
  fill: var(--bg);
  stroke: var(--accent);
  stroke-width: 1.5;
}
.iso__deny-x {
  stroke: var(--accent);
  stroke-width: 1.8;
  stroke-linecap: round;
}

/* something passing over two things that look the same from above */
.iso__lens-o {
  fill: var(--iso-face);
  fill-opacity: 0.35;
  stroke: var(--iso-ink-2);
  stroke-width: 1.5;
}
.iso__lens-h {
  stroke: var(--iso-ink-2);
  stroke-width: 3.4;
  stroke-linecap: round;
}
.iso__lens-g {
  stroke: var(--iso-ink-2);
  stroke-width: 1.2;
  stroke-linecap: round;
  opacity: 0.5;
}

/* the severed return path — drawn as a rule that has been cut, because the
   absence is the point and absence has to be drawn to be noticed */
.iso__cut {
  stroke: var(--border-strong);
  stroke-width: 1.3;
  stroke-dasharray: 6 7;
  opacity: 0.65;
}
.iso__cut-x {
  stroke: var(--accent);
  stroke-width: 1.8;
  stroke-linecap: round;
}

/* ==========================================================================
   THE LOOPS
   Everything here is `ease-in-out` and slow. The reference never accelerates
   into a stop — an ease-out curve is right for a thing that arrives and
   wrong for a thing that breathes, and all of these breathe. Several small
   motions run at once at different periods so the drawing never appears to
   restart: that is what "smooth" is made of, not longer durations.

   Every animated class is on a wrapper `<g>` that carries no `transform`
   attribute of its own. A CSS transform replaces the attribute outright, so
   animating the group that holds the isometric matrix would flatten the
   drawing the moment the animation started.
   ========================================================================== */
[data-fig="on"] .iso__float--a { animation: isoFloatA 7.5s ease-in-out infinite; }
[data-fig="on"] .iso__float--b { animation: isoFloatB 9.5s ease-in-out infinite; }
[data-fig="on"] .iso__float--c { animation: isoFloatA 11s ease-in-out -3s infinite; }
@keyframes isoFloatA {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-7px); }
}
@keyframes isoFloatB {
  0%, 100% { transform: translateY(-5px); }
  50%      { transform: translateY(3px); }
}

/* a handle travelling its track, and back, without a visible turn */
[data-fig="on"] .iso__knob--live { animation: isoSlide 9s ease-in-out infinite; }
@keyframes isoSlide {
  0%, 100% { transform: translate(0, 0); }
  50%      { transform: translate(50px, 0); }
}

/* the foundation being laid, one piece at a time, always in the same order */
[data-fig="on"] .iso__bar--live { animation: isoLay 7s ease-in-out infinite; }
[data-fig="on"] .iso__bar--live:nth-of-type(2) { animation-delay: 0.34s; }
[data-fig="on"] .iso__bar--live:nth-of-type(3) { animation-delay: 0.68s; }
[data-fig="on"] .iso__bar--live:nth-of-type(4) { animation-delay: 1.02s; }
[data-fig="on"] .iso__bar--live:nth-of-type(5) { animation-delay: 1.36s; }
[data-fig="on"] .iso__bar--live:nth-of-type(6) { animation-delay: 1.7s; }
@keyframes isoLay {
  0%        { fill: var(--iso-ink); opacity: 0.45; }
  6%        { fill: var(--accent);  opacity: 1; }
  18%       { fill: var(--accent);  opacity: 1; }
  34%, 100% { fill: var(--iso-ink); opacity: 0.45; }
}

/* the proof lands after the rows, never with them */
[data-fig="on"] .iso__ring {
  animation: isoProve 7s ease-in-out 2.1s infinite;
  transform-box: fill-box;
  transform-origin: center;
}
@keyframes isoProve {
  0%   { opacity: 0;    transform: scale(0.72); }
  9%   { opacity: 0.85; transform: scale(0.88); }
  32%  { opacity: 0;    transform: scale(1.16); }
  100% { opacity: 0;    transform: scale(1.16); }
}

/* it lifts, thins, and settles back into the same place — replaced by an
   identical one, which is the claim. An earlier cut had it vanish entirely,
   which read as "your work disappears for a moment": the opposite. */
[data-fig="on"] .iso__relay { animation: isoRelay 6.5s ease-in-out infinite; }
@keyframes isoRelay {
  0%, 38%   { transform: translateY(0); opacity: 1; }
  56%       { transform: translateY(-14px); opacity: 0.3; }
  78%, 100% { transform: translateY(0); opacity: 1; }
}
/* the hero's foundation is the largest object on the page; at full strength
   the relay reads as the whole drawing blinking */
[data-fig="on"] .iso__relay--soft { animation: isoRelaySoft 9s ease-in-out infinite; }
@keyframes isoRelaySoft {
  0%, 44%   { transform: translateY(0); opacity: 1; }
  60%       { transform: translateY(-9px); opacity: 0.7; }
  80%, 100% { transform: translateY(0); opacity: 1; }
}

/* echoes breathe a beat behind the object that casts them */
[data-fig="on"] .iso__echo { animation: isoEcho 6.5s ease-in-out infinite; }
[data-fig="on"] .iso__echo--far { animation: isoEchoFar 6.5s ease-in-out 0.3s infinite; }
@keyframes isoEcho {
  0%, 100% { opacity: 0.55; }
  55%      { opacity: 0.2; }
}
@keyframes isoEchoFar {
  0%, 100% { opacity: 0.28; }
  55%      { opacity: 0.09; }
}

/* ask twice and it lands somewhere else. Three periods that share no common
   multiple, so the stack never once resolves into alignment. */
[data-fig="on"] .iso__drift--0 { animation: isoDrift0 8s ease-in-out infinite; }
[data-fig="on"] .iso__drift--1 { animation: isoDrift1 11s ease-in-out infinite; }
[data-fig="on"] .iso__drift--2 { animation: isoDrift2 13s ease-in-out infinite; }
[data-fig="on"] .iso__drift--3 { animation: isoDrift3 9.5s ease-in-out infinite; }
@keyframes isoDrift0 {
  0%, 100% { transform: translate(0, 0); }
  34%      { transform: translate(-11px, -3px); }
  67%      { transform: translate(8px, 2px); }
}
@keyframes isoDrift1 {
  0%, 100% { transform: translate(7px, 2px); }
  40%      { transform: translate(-9px, -2px); }
  72%      { transform: translate(12px, 4px); }
}
@keyframes isoDrift2 {
  0%, 100% { transform: translate(-8px, -2px); }
  45%      { transform: translate(13px, 4px); }
  78%      { transform: translate(-4px, 3px); }
}
@keyframes isoDrift3 {
  0%, 100% { transform: translate(10px, 3px); }
  38%      { transform: translate(-6px, 2px); }
  70%      { transform: translate(-12px, -3px); }
}

/* a dotted path drawing itself along its own length */
[data-fig="on"] .iso__dot--draw {
  stroke-dasharray: 3 6;
  animation: isoTravel 5s linear infinite;
}
@keyframes isoTravel {
  to { stroke-dashoffset: -36; }
}

/* what leaves, leaving. Three lines, three packets, one period each so they
   never form a marching row. */
[data-fig="on"] .iso__packet--1 { animation: isoPacket1 3.6s linear infinite; }
[data-fig="on"] .iso__packet--2 { animation: isoPacket2 4.3s linear 0.5s infinite; }
[data-fig="on"] .iso__packet--3 { animation: isoPacket3 3.9s linear 1.1s infinite; }
@keyframes isoPacket1 {
  0%   { transform: translate(0, 0); opacity: 0; }
  12%  { opacity: 1; }
  86%  { opacity: 1; }
  100% { transform: translate(66px, -32px); opacity: 0; }
}
@keyframes isoPacket2 {
  0%   { transform: translate(0, 0); opacity: 0; }
  12%  { opacity: 1; }
  86%  { opacity: 1; }
  100% { transform: translate(66px, 0); opacity: 0; }
}
@keyframes isoPacket3 {
  0%   { transform: translate(0, 0); opacity: 0; }
  12%  { opacity: 1; }
  86%  { opacity: 1; }
  100% { transform: translate(66px, 34px); opacity: 0; }
}

/* the lens crosses to the second plate, and only then does the refusal show
   — the figure's whole argument is that you cannot tell until you look */
[data-fig="on"] .iso__lens { animation: isoLens 11s ease-in-out infinite; }
@keyframes isoLens {
  0%, 10%   { transform: translate(0, 0); }
  38%, 68%  { transform: translate(216px, 0); }
  96%, 100% { transform: translate(0, 0); }
}
/* The reveal is the whole figure. The cover holds while the lens is still over
   the sound plate, drops as it arrives — a beat before the refusal badge, so
   you read "it's hollow" and then "that's a fail", not both at once — and
   returns once the lens leaves, because the plate never actually changed. */
[data-fig="on"] .iso__wall--cover { animation: isoUncover 11s ease-in-out infinite; }
@keyframes isoUncover {
  0%, 33%   { opacity: 1; }
  41%, 64%  { opacity: 0; }
  72%, 100% { opacity: 1; }
}
[data-fig="on"] .iso__lens-in { animation: isoFloatA 5.5s ease-in-out infinite; }
[data-fig="on"] .iso__deny--late { animation: isoDenyLate 11s ease-in-out infinite; }
@keyframes isoDenyLate {
  0%, 40%   { opacity: 0; transform: scale(0.6); }
  47%       { opacity: 1; transform: scale(1.12); }
  53%, 66%  { opacity: 1; transform: scale(1); }
  74%, 100% { opacity: 0; transform: scale(1); }
}

/* A drawing that has scrolled well clear of the viewport is paused rather than
   left running. `animation-play-state` freezes each loop where it stands, so
   scrolling back resumes the stroke instead of snapping every plate back to
   its first frame — and while parked the compositor has nothing to do.

   Scoped to `svg.iso`, and that scoping is load-bearing. `[data-fig]` does not
   only wrap drawings: it also wraps the funnel in Fig. 01 and the three flow
   steps, whose bars run ONCE and hold with `forwards`. Parking those froze a
   one-shot part-way, so scrolling past the funnel left it permanently drawn at
   whatever fraction it had reached. Only endless things may be paused; a thing
   that was going to finish must be allowed to finish. */
[data-fig="on"].is-parked svg.iso,
[data-fig="on"].is-parked svg.iso * {
  animation-play-state: paused !important;
}

@media (prefers-reduced-motion: reduce) {
  [data-fig="on"] .iso__float--a,
  [data-fig="on"] .iso__float--b,
  [data-fig="on"] .iso__float--c,
  [data-fig="on"] .iso__knob--live,
  [data-fig="on"] .iso__bar--live,
  [data-fig="on"] .iso__ring,
  [data-fig="on"] .iso__relay,
  [data-fig="on"] .iso__relay--soft,
  [data-fig="on"] .iso__echo,
  [data-fig="on"] .iso__echo--far,
  [data-fig="on"] .iso__drift--0,
  [data-fig="on"] .iso__drift--1,
  [data-fig="on"] .iso__drift--2,
  [data-fig="on"] .iso__drift--3,
  [data-fig="on"] .iso__dot--draw,
  [data-fig="on"] .iso__packet--1,
  [data-fig="on"] .iso__packet--2,
  [data-fig="on"] .iso__packet--3,
  [data-fig="on"] .iso__deny,
  [data-fig="on"] .iso__lens,
  [data-fig="on"] .iso__lens-in,
  [data-fig="on"] .iso__deny--late,
  [data-fig="on"] .iso__wall--cover {
    animation: none;
  }
  /* the still frame still has to make the point, so the things that are
     invisible until their cue arrives are simply left visible */
  .iso__deny { opacity: 1; }
  .iso__packet { opacity: 1; }
}

/* ==========================================================================
   FIGURE PLACEMENT  (`.figrow`, `.hero__in`)
   An illustration centred in an otherwise empty band is not placed, it is
   parked. Every drawing on this page now sits in a two-column row with the
   sentence it illustrates — the reference does exactly this, alternating
   which side the drawing takes so the page does not develop a gutter.
   ========================================================================== */
.figrow {
  display: grid;
  grid-template-columns: 1fr;
  gap: 26px;
  align-items: center;
  border-top: 1px solid var(--border);
  padding: 34px 24px 38px;
}
@media (min-width: 900px) {
  .figrow {
    grid-template-columns: minmax(0, 1.06fr) minmax(0, 0.94fr);
    gap: 0 54px;
    padding: 40px 48px 44px;
  }
  /* the drawing takes the other side, so consecutive figures alternate */
  .figrow--flip .figrow__art {
    order: 2;
  }
}
.figrow__art {
  display: grid;
  place-items: center;
}
.figrow__art .iso {
  max-width: 500px;
}
.figrow__say .plate__k {
  margin-bottom: 16px;
}
.figrow__say h3 {
  font-size: 21px;
  font-weight: 600;
  letter-spacing: -0.022em;
  margin-bottom: 12px;
}
.figrow__say p {
  font-size: 15.5px;
  line-height: 1.66;
  color: var(--muted-fg);
  max-width: 46ch;
}
.figrow__say p b {
  color: var(--fg);
  font-weight: 600;
}

/* ------------------------------------------------------------------- hero
   Copy left, drawing right, at the width where both get room. Below that
   the drawing goes under the copy and keeps its own band. */
@media (min-width: 1024px) {
  .p-beta .hero__in {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.92fr);
    gap: 0 48px;
    align-items: center;
  }
  .p-beta .hero__copy {
    text-align: left;
    align-items: flex-start;
    margin-inline: 0;
    max-width: none;
    padding: 64px 0 64px 48px;
  }
  .p-beta .hero__cta,
  .p-beta .hero__in .badge {
    justify-content: flex-start;
  }
  .p-beta .hero__art {
    border-top: 0;
    border-left: 1px solid var(--border);
    align-self: stretch;
    padding: 30px 40px;
    /* the cell is stretched to the copy's height, so the drawing has to be
       centred inside it — otherwise it hangs from the top with the slack
       collecting underneath */
    display: grid;
    place-items: center;
  }
}
.hero__art .iso {
  max-width: 520px;
}
