/* ═══════════════════════════════════════════════════════════════
   SIBYL v3 — "instrument"
   A layer ON TOP of v1. Two files, zero markup changes: the v1
   document is untouched, this wraps it in an instrument reading it.

   The colour rule that makes the two layers legible as two layers:
     gold  #c4a862  — the document. never changes.
     jade  #2ea9a1  — the machine. every hacker element is jade.

   Almost everything here is gated on --vel (scroll velocity, 0..1,
   written per frame by v3.js). At rest the page is v1 exactly.
   ═══════════════════════════════════════════════════════════════ */

:root {
  --instr:        #2ea9a1;
  --instr-a10: rgba(46,169,161,.10);
  --instr-a22: rgba(46,169,161,.22);
  --instr-a40: rgba(46,169,161,.40);
  --instr-a70: rgba(46,169,161,.70);

  --vel:   0;   /* scroll velocity, 0..1 — decays to 0 when still */
  --sprog: 0;   /* scroll progress, 0..1 */

  --status-h: 26px;
  --rail-x: clamp(.625rem, 1.6vw, 1.75rem);

  /* build.py swaps this for a data: URI in the single-file preview */
  --plate-poster: url('/sibyl-plate-poster.webp');
}

/* ═══════════════ ambient: only exists while you're moving ═══════════════ */

/* The ambient layer sits BEHIND the document, not over it.

   To get a fixed layer underneath the content, the page ground moves to
   <html>: a background on <body> would propagate to the canvas and bury
   anything at a negative z-index. Then the full-width surfaces take a little
   alpha so the layer reads through them. Cards, panels and the leaderboard
   stay fully opaque on purpose — text keeps its own ground, and nothing
   drifts underneath a paragraph. */
html { background: var(--bg); }
body { background: transparent; }

.rain {
  position: fixed; inset: 0; z-index: -1;
  width: 100%; height: 100%;
  pointer-events: none;
  opacity: calc(var(--vel) * .95);
}

/* full-width grounds: let the layer through */
.section--surface { background: rgba(11, 14, 20, .82); }
.ticker           { background: rgba(11, 14, 20, .82); }
.foot             { background: rgba(11, 14, 20, .88); }
/* this one only ever repainted the page ground — now it just blocks the layer */
.section--proof   { background: transparent; }

/* the wordmark splits under speed and re-registers when you stop */
.hero__word {
  text-shadow:
    calc(var(--vel) * -3px) 0 0 rgba(46,169,161, calc(var(--vel) * .50)),
    calc(var(--vel) *  3px) 0 0 rgba(196,168,98, calc(var(--vel) * .34));
}

/* the ticker gets shoved by the scroll and springs back */
.ticker { transform: translateX(calc(var(--vel) * -34px)); will-change: transform; }

/* ═══════════════ left rail — position in the descent ═══════════════ */
.rail {
  position: fixed; z-index: 40;
  left: var(--rail-x); top: 50%; transform: translateY(-50%);
  display: none;
}
@media (min-width: 1200px) { .rail { display: block; } }

.rail__stops { display: grid; gap: 11px; position: relative; }
.rail__stops::before,
.rail__stops::after {
  content: ''; position: absolute; left: 0; top: -7px; width: 1px;
}
.rail__stops::before { bottom: -7px; background: var(--border-strong); }
.rail__stops::after {
  /* was: height: calc((100% + 14px) * var(--sprog)) — animating HEIGHT is a
     layout property, so the rail forced a layout on every scroll frame.
     Full height + scaleY is the identical visual on the compositor.
     (perf panel, 2026-09-12) */
  height: calc(100% + 14px);
  transform: scaleY(var(--sprog, 0));
  transform-origin: 0 0;
  will-change: transform;
  background: linear-gradient(180deg, var(--instr), var(--gold));
}

.rail a { position: relative; display: block; height: 9px; }
.rail__tick {
  position: absolute; left: 0; top: 4px;
  display: block; width: 11px; height: 1px;
  background: var(--border-strong);
  transition: width var(--base) var(--ease), background var(--base);
}
.rail a:hover .rail__tick { width: 20px; background: var(--instr); }
.rail .is-on .rail__tick { width: 23px; background: var(--gold); box-shadow: 0 0 9px var(--gold-a30); }

/* the name is a transient tooltip — it never sits over the content column */
.rail__name {
  position: absolute; left: 31px; top: 50%;
  transform: translateY(-50%) translateX(-5px);
  padding: .25rem .5rem;
  border: 1px solid var(--border); border-radius: 3px;
  background: rgba(6,7,10,.94);
  font-family: var(--font-mono); font-size: .625rem;
  letter-spacing: .18em; text-transform: uppercase;
  color: var(--instr); white-space: nowrap;
  opacity: 0; pointer-events: none;
  transition: opacity var(--fast) var(--ease), transform var(--fast) var(--ease);
}
.rail a:hover .rail__name,
.rail a:focus-visible .rail__name { opacity: 1; transform: translateY(-50%); }

/* ═══════════════ status bar — the instrument's readout ═══════════════ */
.statusbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 48;
  height: var(--status-h);
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: 0 clamp(.75rem, 2vw, 1.25rem);
  border-top: 1px solid var(--border);
  background: rgba(6,7,10,.9);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  font-family: var(--font-mono); font-size: .625rem;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--text-faint);
  pointer-events: none; user-select: none;
}
@media (max-width: 900px) { .statusbar { display: none; } }

.sb__l { display: flex; align-items: center; gap: .5rem; min-width: 0; }
.sb__cursor {
  width: 6px; height: 11px; flex: none;
  background: var(--instr);
  animation: sbBlink 1.15s steps(1) infinite;
}
@keyframes sbBlink { 0%, 55% { opacity: 1; } 56%, 100% { opacity: .12; } }
.sb__log { color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.sb__r { display: flex; align-items: center; gap: clamp(.75rem, 1.6vw, 1.5rem); flex: none; }

/* five blocks, each lighting at its own velocity threshold — pure CSS */
.sb__vel { display: flex; gap: 2px; }
.sb__vel i { display: block; width: 5px; height: 8px; background: var(--instr); }
.sb__vel i:nth-child(1) { opacity: clamp(.10, calc(var(--vel) * 5),     1); }
.sb__vel i:nth-child(2) { opacity: clamp(.10, calc(var(--vel) * 5 - 1), 1); }
.sb__vel i:nth-child(3) { opacity: clamp(.10, calc(var(--vel) * 5 - 2), 1); }
.sb__vel i:nth-child(4) { opacity: clamp(.10, calc(var(--vel) * 5 - 3), 1); }
.sb__vel i:nth-child(5) { opacity: clamp(.10, calc(var(--vel) * 5 - 4), 1); }

.sb__pct { color: var(--text-muted); font-variant-numeric: tabular-nums; }
.sb__live { display: flex; align-items: center; gap: .4rem; color: var(--instr); }
.sb__live i {
  width: 5px; height: 5px; border-radius: 50%; background: var(--instr);
  box-shadow: 0 0 0 0 rgba(46,169,161,.55);
  animation: pulse 2.6s infinite;
}

/* the chrome must not sit on top of the document's last lines */
.foot { padding-bottom: calc(2rem + var(--status-h)); }
.hero__scroll { bottom: calc(1.75rem + var(--status-h)); }
@media (max-width: 900px) {
  .foot { padding-bottom: 2rem; }
  .hero__scroll { bottom: 1.75rem; }
}

/* ═══════════════ hero plate — footage, read by the instrument ═══════════════ */
/* Layering inside .hero (which is `isolation: isolate`, so this stack is sealed):
     -5  ::before   poster   — no-JS / reduced-motion / mobile still
     -4  .hero__plate        — the footage
     -3  .hero__canvas       — v1's memory graph, drawn OVER the footage
     -2  .hero__grid
     -1  .hero__veil         — the ground that protects the wordmark
   The plate goes under the graph on purpose: the instrument reads the footage,
   it does not sit behind it. */

.hero::before {
  content: ''; position: absolute; inset: 0; z-index: -5;
  background: var(--plate-poster) center / cover no-repeat;
  opacity: .30;
  filter: saturate(.72) contrast(1.06);
}
/* once the video is in, its own poster attribute covers loading — a second
   copy underneath would only double-expose the plate */
.hero.has-plate::before { display: none; }

.hero__plate {
  position: absolute; inset: 0; z-index: -4;
  width: 100%; height: 100%;
  object-fit: cover;
  border: 0; pointer-events: none;
  /* barely present at rest; the instrument waking lifts it */
  opacity: calc(.30 + var(--vel) * .17);
  filter: saturate(calc(.72 + var(--vel) * .26)) contrast(1.06);
  transform: scale(calc(1.03 + var(--vel) * .018));
  transform-origin: 50% 45%;
}

/* v1's veil is deliberately transparent through the middle band, which is
   exactly where the wordmark sits. With footage behind it that band has to
   come back or the 14rem type loses its contrast — the same lesson
   /framework's stylesheet already records about its own hero footage. */
.hero__veil {
  background:
    radial-gradient(ellipse 55% 45% at 50% 42%, rgba(107,139,165,.10), transparent 70%),
    radial-gradient(ellipse 72% 58% at 50% 46%, rgba(6,7,10,.58), transparent 74%),
    linear-gradient(180deg, rgba(6,7,10,.74) 0%, rgba(6,7,10,.34) 26%,
                    rgba(6,7,10,.38) 62%, var(--bg) 100%);
}

/* ═══════════════ hero: crop marks around the identity block ═══════════════ */
.hero__title { position: relative; }
.reticle { position: absolute; inset: -1.5rem -.5rem; pointer-events: none; }
.reticle i {
  position: absolute; width: 17px; height: 17px;
  border: 1px solid var(--instr-a40);
  animation: retIn 620ms var(--ease) backwards;
}
.reticle i:nth-child(1) { top: 0;    left: 0;  border-right: 0; border-bottom: 0; animation-delay: 520ms; }
.reticle i:nth-child(2) { top: 0;    right: 0; border-left: 0;  border-bottom: 0; animation-delay: 600ms; }
.reticle i:nth-child(3) { bottom: 0; left: 0;  border-right: 0; border-top: 0;    animation-delay: 680ms; }
.reticle i:nth-child(4) { bottom: 0; right: 0; border-left: 0;  border-top: 0;    animation-delay: 760ms; }
@keyframes retIn { from { opacity: 0; transform: scale(1.7); } to { opacity: 1; transform: none; } }

/* ═══════════════ section address ═══════════════ */
/* the number is the rail's address for this section, not decoration —
   hover the matching rail tick and it names the same stop */
.label .lbl3 {
  font-style: normal;
  font-size: .6875rem; letter-spacing: .2em;
  color: var(--instr);
}

/* ═══════════════ entry beats ═══════════════ */

/* one jade line passes down each card as it lands */
.cell::after {
  content: ''; position: absolute; inset: 0;
  pointer-events: none; opacity: 0;
  background: linear-gradient(180deg, transparent, var(--instr-a40), transparent);
  background-size: 100% 46px;
  background-repeat: no-repeat;
  background-position: 0 -46px;
}
.cell.is-in::after { animation: cellScan 1000ms var(--ease) 150ms 1; }
@keyframes cellScan {
  0%   { opacity: 0; background-position: 0 -46px; }
  14%  { opacity: 1; }
  100% { opacity: 0; background-position: 0 calc(100% + 46px); }
}

/* The two entry beats below are TRANSITIONS, not animations, and both hold
   their hidden state behind `.js` — the same bargain v1 already makes for
   .reveal. An animation with `backwards` fill parks content at opacity 0
   during its delay, so anything that stops the animation clock leaves the
   leaderboard and the memory tree blank. A transition that never fires just
   lands on its end state: visible. Content this important fails visible. */

/* the memory tree prints itself, one line at a time, like real tree output */
.js .cell .tree__row {
  opacity: 0; transform: translateX(-9px);
  transition: opacity 340ms var(--ease), transform 340ms var(--ease);
}
.cell.is-in .tree__row { opacity: 1; transform: none; }
.cell.is-in .tree__row:nth-child(1) { transition-delay: 340ms; }
.cell.is-in .tree__row:nth-child(2) { transition-delay: 440ms; }
.cell.is-in .tree__row:nth-child(3) { transition-delay: 540ms; }
.cell.is-in .tree__row:nth-child(4) { transition-delay: 640ms; }
.cell.is-in .tree__row:nth-child(5) { transition-delay: 740ms; }

/* leaderboard rows land top-down, the way a query returns */
.js .board .row {
  opacity: 0; transform: translateX(-11px);
  transition: opacity 500ms var(--ease), transform 500ms var(--ease),
              background var(--base);
}
.board.is-in .row { opacity: 1; transform: none; }
.board.is-in .row:nth-child(1) { transition-delay:  60ms; }
.board.is-in .row:nth-child(2) { transition-delay: 150ms; }
.board.is-in .row:nth-child(3) { transition-delay: 240ms; }
.board.is-in .row:nth-child(4) { transition-delay: 330ms; }
.board.is-in .row:nth-child(5) { transition-delay: 420ms; }

/* ═══════════════ motion / print ═══════════════ */
@media (prefers-reduced-motion: reduce) {
  /* the poster stands in — v3.js never injects the video at all */
  .hero__plate { display: none; }
  /* style.css collapses durations but not delays — a staggered row would
     still arrive most of a second late with motion turned off */
  .cell.is-in .tree__row, .board.is-in .row { transition-delay: 0ms !important; }
  .rain { display: none; }
  .hero__word { text-shadow: none; }
  .ticker { transform: none; }
  .reticle i { opacity: 1; animation: none; }
  .sb__cursor, .sb__live i { animation: none; }
}

@media print {
  .rail, .statusbar, .rain, .reticle, .hero__plate { display: none !important; }
  .hero::before { display: none !important; }
}

/* ── operator iteration 2026-09-10: header logo + hero badge ── */

/* The nav emblem is now the lab's own mark: beige artwork on transparency,
   so it carries its own colour and must not be tinted by currentColor or
   squared off by the 26px .brand__mark box it replaces. Own class, own rules. */
.brand__logo {
  display: block;
  flex: none;
  height: 35px;   /* 48px -> 41px -> 35px, two 15% reductions (operator 2026-09-11) */
  width: auto;
  transition: transform 600ms var(--ease), opacity var(--base) var(--ease);
}
.brand:hover .brand__logo { transform: scale(1.05); }

/* the row was proportioned around a 26px mark — the taller logo buys its height
   from the whitespace, not from the nav bar. The block padding that used to sit
   here is gone: buttons.css is the single owner of the bar's vertical rhythm
   (padding-block: 0 + min-height: calc(var(--nav-h) - 2px)), 2026-09-12. */

/* the hero badge is gone; the wordmark block is the first thing in the hero
   now, and it wants the top space the badge used to occupy */
.hero__title { margin-top: clamp(2rem, 6vh, 4rem); }

/* ── section subtitle: the machine naming what the section is ────────────── */
/* mono + jade, matching the rail and the status bar — the gold .label above
   states the address, this states the job. Lowercase as written. */
.sec__sub {
  margin: clamp(.75rem, 1.6vw, 1.125rem) 0 0;
  font-family: var(--font-mono);
  font-size: clamp(.8125rem, 1.15vw, .9375rem);
  font-weight: 400;
  letter-spacing: .11em;
  line-height: 1.5;
  color: var(--instr);
  text-wrap: balance;
}

@media (max-width: 720px) {
  /* a phone nav has no room to spare — the logo still reads at 38px */
  .brand__logo { height: 27px; }   /* 38px -> 32px -> 27px, keeps the desktop ratio */
  /* padding-block removed — buttons.css owns the bar's vertical rhythm */
  .nav__inner { gap: .75rem; }
  .sec__sub { letter-spacing: .08em; }

  /* Measured at 390px: the wordmark's right edge landed 4px INSIDE the
     $SIBYL chip once the mark grew (and 33px inside at 360px, which was
     already true of the old 26px mark). The logo is the brand now, so the
     repeated word is what gives — the chip and STAKE keep their room. */
  .brand__word { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .brand:hover .brand__logo { transform: none; }
}

/* ── primitives: the multi-link flagship card ─────────────────────────────── */
/* PING left the grid, so the memory card leads and anchors a clean 4x2 block.
   It carries three outbound receipts, which an <a class="cell"> could never
   hold — nested anchors are invalid and the browser unnests them. So this one
   cell is a <div>, its primary link stretches over the card, and the receipts
   sit above that overlay. Every .cell hover/glow/scan rule still binds. */
.cell--multi { position: relative; }

.cell__foot { margin-top: auto; padding-top: .75rem; display: grid; gap: .625rem; }
.cell__go--main { margin-top: 0; padding-top: 0; }
.cell__go--main::after {
  content: ''; position: absolute; inset: 0; z-index: 1;
  border-radius: var(--radius);
}

.cell__links {
  position: relative; z-index: 2;   /* above the card-wide overlay */
  display: grid; gap: .3rem;
  margin: 0; padding: .625rem 0 0; list-style: none;
  border-top: 1px solid var(--border);
}
.cell__links a {
  display: inline-flex; align-items: center; gap: .4rem;
  font-family: var(--font-mono); font-size: .6875rem;
  letter-spacing: .09em; color: var(--text-muted);
  transition: color var(--base);
}
.cell__links i { font-style: normal; color: var(--instr); }
.cell__links a:hover { color: var(--instr); }

@media (max-width: 720px) {
  /* the receipts are tap targets on a phone, not footnotes */
  .cell__links { gap: .5rem; }
  .cell__links a { padding-block: .25rem; font-size: .75rem; }
}

/* The flagship is 2 rows tall but its copy only fills one, which left a void
   between the tree and the receipts. Let the tree panel take the slack: rows
   stay pinned to the top, so it reads as a file browser with room in it
   rather than as a card with a hole in it. */
.cell--multi .tree { flex: 1 1 auto; align-content: start; }
@media (max-width: 1080px) {
  /* one row tall here — the tree sizes to its own content again */
  .cell--multi .tree { flex: 0 0 auto; }
}

/* ── operator iteration 2026-09-11: header simplification ──
   The header carried five controls on the right (chip, gold CTA, action
   menu, terminal, index) plus a left-aligned link row. Operator directive:
   nav centered and alone in the middle, the gold CTA pinned to the right
   border, terminal sitting with the token chip, and the redundant text
   buttons gone.

   The nav is centered against the VIEWPORT rather than the flex row, so an
   uneven brand/right-cluster weight cannot pull it off-centre. Below the
   desktop breakpoint the link row is already hidden by style.css, so the
   action menu comes back as the only way in. */
@media (min-width: 1081px) {
  /* Three columns, equal side tracks: the nav sits in the middle track and
     is therefore centred on the header itself, while both side tracks still
     RESERVE their space. Absolute centring was tried first and collided the
     link row into the right cluster, because an absolutely positioned
     element reserves nothing. */
  .nav__inner {
    /* The header is no longer boxed to the content column. Centring the nav
       on the viewport while a ~450px control cluster sits on the right needs
       more room than the 1240px wrap allows: with equal side tracks the
       cluster overflowed into the links. A full-width bar with its own
       gutter gives the middle track room AND puts the CTA on the actual
       right border, which is what was asked for. */
    position: relative;
    max-width: none;
    padding-inline: clamp(1.25rem, 2.5vw, 2.25rem);
    display: grid;
    /* minmax(0,1fr) — a bare 1fr has an AUTO minimum, so the wider side
       track (the control cluster) refuses to shrink and shoves the middle
       track off centre. Allowing both side tracks to go to zero forces them
       equal, which is what actually centres the nav on the header. */
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    align-items: center;
    column-gap: clamp(1rem, 2.5vw, 2rem);
  }

  /* EXPLICIT track placement (operator 2026-09-11: terminal + START moved to
     the left cluster beside the logo; the token pill takes the centre track).
     Naming the columns matters — with the middle track previously empty, grid
     auto-placement dropped .nav__right into it and the cluster drifted to
     centre. */
  .nav__left {
    grid-column: 1; justify-self: start;
    display: flex; align-items: center; gap: .5rem;
    min-width: 0;
  }
  .nav__left .brand { margin-right: .35rem; }

  /* the token pill is the centre of the header now */
  .nav__inner > .chip { grid-column: 2; justify-self: center; }

  /* style.css sets `.nav__links + .nav__right { margin-left: clamp(...) }`,
     which would push the cluster off its own track. */
  .nav__links + .nav__right,
  .nav__right { grid-column: 3; justify-self: end; margin-left: 0; gap: .5rem; flex-wrap: nowrap; }

  /* START is injected into .nav__left and sits in the flow there, so the old
     absolute centring is gone. */
  .nav__left #act-open { position: static; transform: none; }

  /* the index overlay was a second menu. one menu. */
  .menu-btn { display: none; }

  /* a wrapping CTA ("connect wallet" over two lines) grows the right track,
     squeezes the middle one and drags the pill off true centre. Keep every
     control on one line so the three tracks stay honest. */
  .nav__inner .btn,
  .nav__inner #btn-connect,
  .nav__inner .term-btn,
  .nav__inner .chip { white-space: nowrap; }
}

/* Narrow-desktop ladder. True centring only works while the control cluster
   fits its side track; below ~1360px it does not, and the cluster starts
   overlapping the links. Rather than abandon the centred nav, the cluster
   sheds detail in two steps. The chip keeps its label and copy action at
   every step, so the token embed never stops working. */
/* The address ladder. The FULL 42-char address is shown while the centre
   track can carry it; below that the pill falls back to the truncated form
   rather than dropping the address altogether, and only at the narrowest
   desktop does it shed the address entirely. The copy action works at every
   step, so the token embed never stops functioning. */
.chip__addr--short { display: none; }
@media (min-width: 1081px) and (max-width: 1439px) {
  .chip__addr--full  { display: none; }
  .chip__addr--short { display: inline; }
}
@media (min-width: 1081px) and (max-width: 1179px) {
  .nav__left .term-btn__text { display: none; }
  .nav__left .term-btn { padding-inline: .6rem; }
  .nav__inner { column-gap: 1rem; }
}
/* Below the desktop grid the header is a simple flex row; the long address
   has no room there. */
@media (max-width: 1080px) {
  .chip__addr--full  { display: none; }
  .chip__addr--short { display: inline; }
}

/* ── operator iteration 2026-09-11: the writing column carries two
   publications ──
   It was built around a single one (the substack), using a meta table plus a
   CTA row. Adding the blog to that shape would have stacked a second table
   and pushed the column well past the lab column beside it. Two compact
   cards instead: each names itself, says what it carries, and links out. The
   column now balances the link list opposite it instead of towering over it. */
.pubs { list-style: none; margin: 1.25rem 0 0; padding: 0; display: grid; gap: .75rem; }

.pub {
  border: 1px solid var(--rule, rgba(244,239,226,.10));
  border-radius: 3px;
  padding: 1rem 1.1rem;
  background: rgba(255,255,255,.012);
  transition: border-color var(--base, 240ms) var(--ease, ease), background var(--base, 240ms) var(--ease, ease);
}
.pub:hover { border-color: var(--instr-line, rgba(46,169,161,.30)); background: rgba(46,169,161,.03); }

.pub__top {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: .75rem; flex-wrap: wrap; margin-bottom: .4rem;
}
.pub__name {
  font-family: var(--font-display, inherit);
  font-size: 1.02rem; font-weight: 500; letter-spacing: -.01em;
  color: var(--text-primary, #f4efe2); margin: 0;
}
.pub__where {
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: .6875rem; letter-spacing: .08em; text-transform: lowercase;
  color: var(--instr, #2ea9a1);
}
.pub__desc {
  font-size: .8125rem; line-height: 1.65;
  color: var(--text-muted, #b3a98d); margin: 0 0 .85rem;
}
.pub__cta { display: flex; gap: .5rem; flex-wrap: wrap; }

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

/* ── operator 2026-09-11: featured lab card in the footer ──
   "labs" was one word in a row of eleven, which said nothing about the
   relationship between the agent and the company. The card states it
   outright and carries the mark. It sits above the footer row so it reads
   as a statement rather than another link. */
.labcard {
  display: flex; align-items: center; gap: clamp(1rem, 3vw, 2rem);
  flex-wrap: wrap;
  border: 1px solid var(--rule, rgba(244,239,226,.10));
  border-radius: 4px;
  padding: 1.15rem clamp(1.1rem, 3vw, 1.75rem);
  margin-bottom: 2.25rem;
  background: rgba(255,255,255,.015);
}
.labcard__logo { height: 46px; width: auto; flex: none; opacity: .95; }
.labcard__body { flex: 1 1 320px; min-width: 0; }
.labcard__k {
  font-family: var(--font-mono); font-size: .625rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--instr, #2ea9a1); margin: 0 0 .3rem;
}
.labcard__t {
  margin: 0; font-size: .875rem; line-height: 1.6;
  color: var(--text-secondary, #b3a98d); max-width: 62ch;
}
.labcard__t strong { color: var(--text-primary, #f4efe2); font-weight: 500; }
.labcard__cta { display: flex; align-items: center; gap: .6rem; flex: none; }
.labcard__x {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: 4px;
  border: 1px solid var(--rule, rgba(244,239,226,.10));
  color: var(--text-secondary, #b3a98d);
  transition: color var(--fast, 160ms) var(--ease, ease), border-color var(--fast, 160ms) var(--ease, ease);
}
.labcard__x svg { width: 15px; height: 15px; }
.labcard__x:hover { color: var(--text-primary, #f4efe2); border-color: var(--rule-strong, rgba(244,239,226,.22)); }

@media (max-width: 640px) {
  .labcard { gap: .9rem; }
  .labcard__logo { height: 38px; }
  .labcard__cta { width: 100%; }
}
@media (prefers-reduced-motion: reduce) { .labcard__x { transition: none; } }

/* ── operator 2026-09-11: footer links removed, card takes their column ──
   The ten plain links are gone (every destination is in the menu, and the
   footer's mini hamburger opens the same panel). The lab card moves out of
   its own full-width row into the middle column they vacated, so it shrinks
   rather than shouting. */
.foot__inner > .labcard {
  margin-bottom: 0;
  align-self: start;
  gap: .9rem;
  padding: .95rem 1.1rem;
}
.foot__inner > .labcard .labcard__logo { height: 34px; }
.foot__inner > .labcard .labcard__body { flex: 1 1 200px; }
.foot__inner > .labcard .labcard__t { font-size: .8125rem; line-height: 1.55; }
.foot__inner > .labcard .labcard__cta { gap: .45rem; }
.foot__inner > .labcard .labcard__x { width: 30px; height: 30px; }

/* mini hamburger: same footprint as the social icons so the row reads as
   one set of controls, with its own jade tint to say it is not a link out */
.foot__menu {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0; cursor: pointer;
  border: 1px solid var(--instr-a40, rgba(46,169,161,.30));
  border-radius: 6px; background: rgba(46,169,161,.06);
  color: var(--instr, #2ea9a1);
  transition: color var(--fast, 160ms) var(--ease, ease),
              border-color var(--fast, 160ms) var(--ease, ease),
              background var(--fast, 160ms) var(--ease, ease);
}
.foot__menu:hover { color: #cfeeea; border-color: var(--instr, #2ea9a1); background: rgba(46,169,161,.14); }
.foot__menu span { display: grid; gap: 3px; width: 13px; }
.foot__menu i { display: block; height: 1px; background: currentColor; }
.foot__menu i:nth-child(2) { width: 9px; }

@media (max-width: 860px) {
  .foot__inner > .labcard { grid-column: 1 / -1; }
}
@media (prefers-reduced-motion: reduce) { .foot__menu { transition: none; } }

/* ── operator 2026-09-11: footer spacing pass ────────────────────────────
   The three columns carried 71px, 153px and 40px of content, all pinned to
   the top of a 153px row by `align-items: start`. That left an 82px void
   under the brand and a 113px void under the socials, which is what made the
   footer read as unfinished rather than airy.

   The socials move UNDER the brand and pin to the bottom of that column, so
   the left side fills its own height and the card holds the right. Two
   columns instead of three, and both now reach the same baseline. */
@media (min-width: 861px) {
  .foot__inner {
    grid-template-columns: minmax(0, 1fr) minmax(21rem, 30rem);
    grid-template-rows: auto 1fr;
    column-gap: clamp(2rem, 5vw, 4rem);
    row-gap: clamp(1.75rem, 3vw, 2.5rem);
    align-items: start;
  }
  .foot__brand        { grid-column: 1; grid-row: 1; }
  .foot__social       { grid-column: 1; grid-row: 2; align-self: end; }
  .foot__inner > .labcard { grid-column: 2; grid-row: 1 / span 2; align-self: start; }
}

/* The footer wordmark. v3.css hides `.brand__word` on mobile to buy room in
   the HEADER, where the mark competes with the chip and STAKE. That selector
   was never scoped to the nav, so it also emptied the footer brand — leaving
   a lone diamond glyph beside dead space. The footer has the room. */
.foot__brand .brand__word { display: block; }

/* The legal rule sat on the same element as the wrap's own padding, so the
   border drew at the padding-box edge: a divider 64px wider than the content
   it divides, at both ends. Inset it to the content column. */
.foot__legal { border-top: 0; position: relative; }
.foot__legal::before {
  content: "";
  position: absolute; top: 0;
  left: var(--gutter); right: var(--gutter);
  border-top: 1px solid var(--border);
}

/* the menu control sits in the icon row and should measure like one */
.foot__social .foot__menu { width: 40px; height: 40px; border-radius: var(--radius-sm); }

/* ─────────── hero plate: keep the motion, drop the per-frame re-raster ───────────
   The plate rode --vel on three properties at once: opacity, filter and
   transform. opacity and transform are compositor properties and are nearly
   free; `filter` is not — animating saturate() on a playing video forces the
   whole layer to re-rasterise every scroll frame. Measured on the desktop
   homepage: 43.3ms per frame with it, 27ms with the filter frozen, 16ms with
   the video gone entirely. So the filter animation alone cost ~16ms a frame
   for a saturation shift nobody can consciously see while scrolling.

   The filter is now STATIC at its resting value. The plate still wakes with
   the scroll through opacity and scale, which is the part you actually
   perceive, and those stay on the compositor. */
.hero__plate {
  filter: saturate(.78) contrast(1.06);
  will-change: opacity, transform;
}


/* ═══════════════════════════════════════════════════════════════
   mind map — #map
   A radial web: one core, nine branches, thirty-five leaves.
   Rendered LIT. Motion is a one-shot entrance plus hover/focus.
   Nothing in here loops; every declared motion runs once and stops.
   ═══════════════════════════════════════════════════════════════ */

.mindmap-fit { margin-block-start: 2.5rem; }
.mindmap {
  width: 100%;
  max-width: 960px;
  height: 940px;
  margin: 0 auto;
  position: relative;
}

/* faint dot field, masked to a soft disc so the web sits on something */
.mindmap__grid {
  position: absolute; inset: 0;
  background-image: radial-gradient(circle, rgba(196,168,98,.13) 1px, transparent 1px);
  background-size: 28px 28px;
  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 0%, rgba(0,0,0,.5) 52%, transparent 74%);
          mask-image: radial-gradient(circle at 50% 50%, #000 0%, rgba(0,0,0,.5) 52%, transparent 74%);
  pointer-events: none;
}

.mindmap__paths {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  overflow: visible;
  pointer-events: none;
}
.mindmap__paths path {
  fill: none;
  stroke: var(--gold);
  stroke-width: 1.1;
  opacity: .32;
  transition: opacity .3s var(--ease), stroke-width .3s var(--ease), stroke .3s var(--ease);
}
.mindmap__paths path[data-kind="leaf"] { opacity: .22; }
.mindmap__paths path.is-hi {
  opacity: .85;
  stroke-width: 1.6;
  stroke: var(--gold-bright);
  filter: drop-shadow(0 0 4px var(--gold-a30));   /* filter ONLY in highlight state */
}

/* on desktop the grouping wrappers are structural only — the nodes
   position themselves against the canvas, not against their group */
.mm-branch, .mm-leaves { display: contents; }

/* ── nodes ─────────────────────────────────────────────────── */
.mindmap .node {
  position: absolute;
  top: 50%; left: 50%;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .15rem;
  padding: .4rem .55rem;
  text-align: center;
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  transform:
    translate(-50%, -50%)
    rotate(var(--a, 0deg))
    translateY(calc(-1 * var(--r, 0px)))
    rotate(calc(-1 * var(--a, 0deg)));
  transition:
    transform .25s var(--ease),
    border-color .25s var(--ease),
    background .25s var(--ease),
    opacity .6s var(--ease);
}

/* blurred gold bloom behind a node, lit only on hover/focus */
.mindmap .node::before {
  content: '';
  position: absolute;
  inset: -14px;
  border-radius: inherit;
  background: radial-gradient(circle, var(--gold-a30), transparent 70%);
  filter: blur(10px);
  opacity: 0;
  transition: opacity .25s var(--ease);
  pointer-events: none;
  z-index: -1;
}
.mindmap .node:hover::before,
.mindmap .node:focus-visible::before,
.mindmap .node.is-active::before { opacity: .5; }

.mindmap .node:focus-visible {
  outline: 2px solid var(--btn-teal);
  outline-offset: 2px;
}

/* the description is the readout's source of truth; on desktop it is
   present for assistive tech and invisible to sighted users */
.node__desc {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ── core ──────────────────────────────────────────────────── */
.mindmap .node--core {
  width: 150px; height: 150px;
  border-radius: 50%;
  background: radial-gradient(circle, var(--elevated), var(--bg));
  border: 1.5px solid var(--gold-a30);
  gap: 0;
}
.node__brain { width: 112px; height: 112px; display: block; overflow: visible; }
.node__hemi { transition: stroke-opacity .45s var(--ease); }
.mindmap .node--core:hover .node__hemi,
.mindmap .node--core:focus-visible .node__hemi { stroke-opacity: .8; }

.node__corelabel {
  position: absolute;
  inset-block-end: 14px; left: 50%;
  transform: translateX(-50%);
  font-family: var(--font-display);
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .26em;
  text-transform: uppercase;
  color: var(--gold);
}

/* ── branch ────────────────────────────────────────────────── */
.mindmap .node--branch {
  width: 112px;
  min-height: 60px;
  border-radius: 10px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
}
.node__idx {
  font-family: var(--font-mono);
  font-size: .5625rem;
  letter-spacing: .14em;
  color: var(--text-faint);
}
.mindmap .node--branch .node__name {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: .6875rem;
  line-height: 1.25;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--text-secondary);
}

/* ── leaf ──────────────────────────────────────────────────── */
.mindmap .node--leaf {
  min-width: 88px;
  min-height: 34px;
  max-width: 150px;
  border-radius: 8px;
  background: rgba(13, 17, 24, .92);
  border: 1px solid var(--border);
}
.mindmap .node--leaf .node__name {
  font-family: var(--font-mono);
  font-size: .5625rem;
  line-height: 1.3;
  letter-spacing: .06em;
  color: var(--text-muted);
}

/* ── hover / focus / active ────────────────────────────────── */
.mindmap .node--branch:hover,
.mindmap .node--branch:focus-visible,
.mindmap .node--branch.is-active { border-color: var(--gold-a30); background: var(--elevated); }
.mindmap .node--branch:hover .node__name,
.mindmap .node--branch:focus-visible .node__name,
.mindmap .node--branch.is-active .node__name { color: var(--text-primary); }

.mindmap .node--leaf:hover,
.mindmap .node--leaf:focus-visible,
.mindmap .node--leaf.is-active { border-color: var(--gold-a30); background: var(--surface-2); }
.mindmap .node--leaf:hover .node__name,
.mindmap .node--leaf:focus-visible .node__name,
.mindmap .node--leaf.is-active .node__name { color: var(--text-primary); }

.mindmap .node--core:hover,
.mindmap .node--core:focus-visible,
.mindmap .node--core.is-active { border-color: var(--gold); }

.mindmap .node:hover,
.mindmap .node:focus-visible,
.mindmap .node.is-active {
  transform:
    translate(-50%, -50%)
    rotate(var(--a, 0deg))
    translateY(calc(-1 * var(--r, 0px)))
    rotate(calc(-1 * var(--a, 0deg)))
    scale(1.07);
  z-index: 4;
}

/* ── one-shot entrance (JS adds .is-live once, then unobserves) ── */
.js .mindmap .node { opacity: 0; }
.js .mindmap .node {
  transform:
    translate(-50%, -50%)
    rotate(var(--a, 0deg))
    translateY(calc(-1 * var(--r, 0px)))
    rotate(calc(-1 * var(--a, 0deg)))
    scale(.92);
}
.js .mindmap.is-live .node {
  opacity: 1;
  transform:
    translate(-50%, -50%)
    rotate(var(--a, 0deg))
    translateY(calc(-1 * var(--r, 0px)))
    rotate(calc(-1 * var(--a, 0deg)))
    scale(1);
}
.js .mindmap.is-live .node--core   { transition-delay: 0s; }
.js .mindmap.is-live .node--branch { transition-delay: .25s; }
.js .mindmap.is-live .node--leaf   { transition-delay: .5s; }
/* once a pointer or keyboard lands on a node the stagger must not
   hold its scale back */
.js .mindmap.is-live .node:hover,
.js .mindmap.is-live .node:focus-visible,
.js .mindmap.is-live .node.is-active { transition-delay: 0s; }

/* ── readout ───────────────────────────────────────────────── */
.mindmap__readout {
  max-width: 960px;
  margin: 1.25rem auto 0;
  padding: .85rem .25rem 0;
  min-height: 3.25rem;            /* fixed: text swaps cause zero layout shift */
  border-top: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: .6875rem;
  line-height: 1.55;
  letter-spacing: .04em;
  color: var(--text-secondary);
}
.mindmap__readout-k {
  color: var(--gold);
  text-transform: uppercase;
  letter-spacing: .14em;
  margin-right: .6rem;
}
.mindmap__readout-v { color: var(--text-secondary); }

/* ── the squeeze: keep the full web on screen on short-wide laptops ── */
@media (max-width: 1100px) and (min-width: 960px) {
  .mindmap {
    transform: scale(.84);
    transform-origin: center top;
    margin-block-end: -140px;
  }
}

/* ── under 960px the radial form is abandoned entirely ─────── */
@media (max-width: 959.98px) {
  .mindmap__grid,
  .mindmap__paths,
  .mindmap__readout { display: none; }

  .mindmap {
    height: auto;
    max-width: 640px;
    display: flex;
    flex-direction: column;
    gap: .85rem;
    margin-top: 2rem;
  }

  /* every node drops out of polar space and back into flow */
  .js .mindmap .node,
  .mindmap .node {
    position: static;
    transform: none;
    opacity: 1;
    width: auto;
    max-width: none;
    min-height: 0;
    text-align: left;
    align-items: stretch;
  }
  .js .mindmap.is-live .node { transform: none; }
  .mindmap .node:hover,
  .mindmap .node:focus-visible,
  .mindmap .node.is-active { transform: none; }
  .mindmap .node::before { display: none; }

  .mm-branch {
    display: block;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    overflow: hidden;
  }
  .mm-leaves {
    display: flex;
    flex-direction: column;
    border-top: 1px solid var(--border);
  }

  .mindmap .node--core {
    width: 100%; height: auto;
    border-radius: var(--radius);
    border: 1px solid var(--gold-a30);
    background: var(--surface);
    flex-direction: row;
    align-items: center;
    gap: .75rem;
    padding: .85rem 1rem;
  }
  .node__brain { width: 56px; height: 56px; flex: none; }
  .node__corelabel {
    position: static;
    transform: none;
    inset-block-end: auto; left: auto;
    font-size: .8125rem;
  }

  .mindmap .node--branch {
    width: 100%;
    border: 0;
    border-radius: 0;
    background: transparent;
    flex-direction: row;
    align-items: baseline;
    gap: .6rem;
    padding: .8rem .9rem;
  }
  .mindmap .node--leaf {
    border: 0;
    border-radius: 0;
    background: transparent;
    padding: .6rem .9rem;
    border-top: 1px solid rgba(24, 28, 38, .7);
  }
  .mindmap .node--leaf:first-child { border-top: 0; }

  /* on mobile the description IS the content — un-hide it */
  .mindmap .node--branch .node__desc,
  .mindmap .node--leaf .node__desc {
    position: static;
    width: auto; height: auto;
    margin: .2rem 0 0;
    overflow: visible;
    clip: auto;
    clip-path: none;
    white-space: normal;
    font-family: var(--font-mono);
    font-size: .625rem;
    line-height: 1.6;
    letter-spacing: .02em;
    color: var(--text-muted);
    display: block;
  }
  .mindmap .node--branch .node__name,
  .mindmap .node--leaf .node__name { display: block; }
  .mindmap .node--branch .node__idx { flex: none; }
  .mindmap .node--leaf .node__name {
    font-size: .6875rem;
    color: var(--text-secondary);
  }
  /* the branch row needs its name + desc stacked beside the index */
  .mindmap .node--branch > .node__name,
  .mindmap .node--branch > .node__desc { flex: 1 1 auto; }
  .mindmap .node--branch { flex-wrap: wrap; }
  .mindmap .node--branch .node__desc { flex-basis: 100%; }
}

@media (max-width: 380px) {
  .mindmap .node--branch, .mindmap .node--leaf { padding-inline: .7rem; }
}

/* ── reduced motion: everything lands already arrived ──────── */
@media (prefers-reduced-motion: reduce) {
  .js .mindmap .node,
  .js .mindmap.is-live .node {
    opacity: 1;
    transform:
      translate(-50%, -50%)
      rotate(var(--a, 0deg))
      translateY(calc(-1 * var(--r, 0px)))
      rotate(calc(-1 * var(--a, 0deg)));
    transition-delay: 0s;
    transition-duration: .01ms;
  }
  /* colour and border still respond; the scale does not */
  .mindmap .node:hover,
  .mindmap .node:focus-visible,
  .mindmap .node.is-active {
    transform:
      translate(-50%, -50%)
      rotate(var(--a, 0deg))
      translateY(calc(-1 * var(--r, 0px)))
      rotate(calc(-1 * var(--a, 0deg)));
  }
  .mindmap__paths path { transition-duration: .01ms; }
}
@media (prefers-reduced-motion: reduce) and (max-width: 959.98px) {
  .js .mindmap .node,
  .js .mindmap.is-live .node,
  .mindmap .node:hover,
  .mindmap .node:focus-visible,
  .mindmap .node.is-active { transform: none; }
}

/* ═══════════════ intro drift — the constellation behind the hero ═══════════════
   The canvas is opaque and paints its own ground from --bg, so it replaces the
   section ground rather than sitting on it. pointer-events:none keeps the hero
   buttons clickable; v3.js listens on the SECTION instead. */
#intro { overflow: hidden; }               /* .section is already position:relative */
.intro__drift {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  z-index: 0;
  pointer-events: none;
}
#intro .wrap { position: relative; z-index: 1; }
@media print { .intro__drift { display: none !important; } }

/* ── hero drift: open the viewable band on MOBILE ONLY (operator 2026-09-12)
   "the piece of the animation that is visible on mobile is not where the
   neural graphs are forming."

   Measured at 390x844: the section was 818px tall, the copy ink ran 128->726,
   leaving 128px above and 93px below. The fixed nav (--nav-h 100px) covers
   almost the whole top band, so the actual visible strip above the copy was
   ~28px. The constellation lives in those bands, so nearly all of it was
   forming out of sight.

   Expanding the bands also raises node count for free: N derives from the FREE
   field area, so a taller open band seeds more nodes into exactly the space
   that is now visible. Desktop is untouched — it has open field beside the
   copy and needs none of this. */
@media (max-width: 720px) {
  #intro {
    padding-block-start: calc(var(--nav-h, 100px) + 9.5rem);
    padding-block-end: 11rem;
  }
}
@media (max-width: 420px) {
  #intro {
    padding-block-start: calc(var(--nav-h, 100px) + 8.5rem);
    padding-block-end: 10rem;
  }
}

/* ── "Looking for the Lab?" hint ──────────────────────────────────
   A cold visitor arrives at the agent and never learns the lab exists.
   This is a quiet, dismissible aside, not a modal: it never traps focus,
   never covers the hero CTAs, and once dismissed it stays dismissed. */
.labhint {
  /* Lives as a direct child of <body>, NOT inside .hero (overflow:hidden) or any
     section, so no ancestor can trap or clip it. z sits above the nav (50), the
     status bar (48) and the ticker, and below the menu panel (58), overlay (60)
     and terminal (65) so a real modal still covers it. (operator 2026-09-13) */
  position: fixed;
  right: clamp(.75rem, 2vw, 1.5rem);
  bottom: clamp(3rem, 6vh, 4.5rem);
  z-index: 56;                      /* above nav + status bar, below the modals */
  width: min(19rem, calc(100vw - 2rem));
  padding: .95rem 1.05rem 1rem;
  background: var(--surface, #0d1118);
  border: 1px solid var(--border-strong, rgba(255,255,255,.14));
  border-radius: 10px;
  box-shadow: 0 18px 44px -16px rgba(0,0,0,.75);
  opacity: 0; transform: translateY(10px);
  transition: opacity .42s var(--ease, ease), transform .42s var(--ease, ease);
}
.labhint.is-in { opacity: 1; transform: none; }
.labhint__k {
  margin: 0 0 .35rem; padding-right: 1.2rem;
  font-family: var(--font-display); font-weight: 600; font-size: .875rem;
  color: var(--gold);
}
.labhint__b {
  margin: 0 0 .7rem;
  font-family: var(--font-display); font-size: .8125rem; line-height: 1.5;
  color: var(--text-secondary);
}
.labhint__b strong { color: var(--text); font-weight: 600; }
.labhint__go {
  display: inline-flex; align-items: center; gap: .4rem; min-height: 32px;
  font-family: var(--font-mono); font-size: .6875rem;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--btn-teal, #36d8ca); text-decoration: none;
}
.labhint__go:hover, .labhint__go:focus-visible { color: var(--gold-bright, #d4b872); }
.labhint__x {
  position: absolute; top: .3rem; right: .35rem;
  width: 32px; height: 32px; display: grid; place-items: center;
  background: none; border: 0; cursor: pointer;
  color: var(--text-muted); font-size: 1.1rem; line-height: 1;
}
.labhint__x:hover, .labhint__x:focus-visible { color: var(--text); }
@media (prefers-reduced-motion: reduce) {
  .labhint { transition: none; }
}
@media (max-width: 560px) {
  .labhint { bottom: 3.2rem; right: .6rem; left: .6rem; width: auto; }
}
