/* ===========================================================================
   STEPPER · instrument console, in two lights.

   The whole page is one machine: a quiet ground, hairline rules, and a single
   green that means exactly one thing: a signal that is live. Amber means the
   machine stopped. Nothing else gets colour.

   THEMES.

     :root                    light, the default, no condition attached
     :root[data-theme=dark]   dark, reached only through the switch

   Light carries near-black 1.5px rules; dark goes back to a hairline, because
   a heavy line on a near-black ground reads as a wall rather than an edge.
   ======================================================================== */

/* --------------------------------------------------------------- light ---
   LIGHT IS THE DEFAULT. The bare :root carries it, so a reader with no
   stored choice gets light and nothing has to load before that is true.
   Dark is a deliberate second palette reached only through the switch: the
   operating system preference is not consulted, because a default that
   changes depending on whose machine it is on is not a default.

   Two accent tokens, and the distinction matters:
     --signal      an accent used as a FILL or a BORDER  (LEDs, bars, rules)
     --signal-ink  an accent used as TEXT on the ground  (must stay readable)
   In dark they are nearly the same. In light they are not, and using the
   wrong one is how a green-on-white site becomes unreadable.
   ======================================================================== */

:root {
  color-scheme: light;

  color-scheme: light;

  --void:      #ebedec;
  --panel:     #ffffff;
  --panel-2:   #f3f4f3;
  --panel-3:   #e3e6e4;
  --well:      #f8f9f8;
  --substrate: #d1d5d2;
  --line:      #181b1a;
  --line-2:    #4b504d;
  --line-soft: rgba(24, 27, 26, .26);

  --text:      #101413;
  --text-2:    #525855;
  --dim:       #70766f;
  --on-signal: #031a08;   /* dark ink: white on this green is 2.3:1 */

  --signal:    #00c805;
  --signal-2:  #00a00a;
  --signal-ink:#007a05;
  --tint:      rgba(0, 168, 12, 0.11);
  --tint-soft: rgba(0, 168, 12, 0.045);
  --tint-line: rgba(0, 168, 12, 0.26);
  --grid:      rgba(0, 150, 20, 0.042);
  --glow:      rgba(0, 200, 5, 0.26);
  --amber:     #b26a00;
  --amber-glow:rgba(178, 106, 0, .28);

  --g1: #00dc12;
  --g2: #00c805;
  --g3: #00a30c;
  --halo:   radial-gradient(ellipse 880px 520px at 76% 4%,
              rgba(0, 200, 5, .065), transparent 60%);
  --halo-2: radial-gradient(ellipse 620px 380px at 12% 96%,
              rgba(0, 200, 5, .035), transparent 62%);

  --scrim:  rgba(235, 237, 236, 0.88);
  --lift:   0 1px 2px rgba(20, 26, 23, .06), 0 8px 24px -14px rgba(20, 26, 23, .18);
  --lift-2: 0 1px 3px rgba(20, 26, 23, .08), 0 18px 40px -22px rgba(20, 26, 23, .30);

  /* Rules are 1.5px and close to black. A pale hairline disappears on a
     bright ground and makes a panel look unfinished; this is an instrument
     face, and the lines on one are drawn to be seen. */
  /* Derived from whichever ramp is live, so they belong here rather than in
     both palettes. Custom properties resolve on the element they are read
     from, and both palettes live on :root, so these follow the theme without
     being written twice. */
  --grad:      linear-gradient(135deg, var(--g1) 0%, var(--g2) 50%, var(--g3) 100%);
  --grad-flat: linear-gradient(90deg,  var(--g2) 0%, var(--g3) 100%);

  --display: "Chakra Petch", "Arial Narrow", sans-serif;
  --sans:    "Spline Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono:    "Spline Sans Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* MOTION: latch, do not ease.
     A flip-flop has no in-between value. It holds, then on the edge it is
     something else. So nothing on this page glides: transitions advance in
     discrete stages, which is both what the machine does and the clearest way
     to not look like every other site easing things in on a cubic curve.
     --latch  arriving content, five stages
     --snap   pointer feedback, two stages, close enough to instant
     --draw   a rule drawing itself, one segment at a time */
  --latch: steps(5, end);
  --snap:  steps(2, end);
  --draw:  steps(14, end);

  --gutter: clamp(16px, 4vw, 52px);
  --maxw: 1360px;
  --rule: 1.5px solid var(--line);
}

/* ---------------------------------------------------------------- dark ---
   A token swap and nothing else. No rule below this block knows which theme
   is running, which is the only way a two-theme site stays maintainable.
   In the dark the rules go back to a hairline: on a near-black ground a
   heavy line reads as a wall rather than an edge. */

:root[data-theme="dark"] {
  color-scheme: dark;

  color-scheme: dark;

  /* ground */
  --void:      #05070a;
  --panel:     #0a0e13;
  --panel-2:   #10161d;
  --panel-3:   #161f28;
  --well:      #04070a;   /* inside an instrument: canvas, <pre> */
  --substrate: #0e161c;   /* a gate that is not switching */
  --line:      #1c2630;
  --line-2:    #2a3844;
  --line-soft: rgba(28, 38, 48, .65);

  /* ink */
  --text:      #cfdae2;
  --text-2:    #93a3af;
  --dim:       #5e6d79;
  --on-signal: #04120a;   /* text printed on top of a signal fill */

  /* signal, rationed */
  --signal:    #00e015;
  --signal-2:  #00b80f;
  --signal-ink:#2ee23c;
  --tint:      rgba(0, 224, 21, 0.11);
  --tint-soft: rgba(0, 224, 21, 0.038);
  --tint-line: rgba(0, 224, 21, 0.18);
  --grid:      rgba(0, 224, 21, 0.032);
  --glow:      rgba(0, 224, 21, 0.30);
  --amber:     #ffb020;
  --amber-glow:rgba(255, 176, 32, .35);

  /* THE RAMP. Three stops, always used as one object, never sampled apart.
     It is the brand's only gradient and it means one thing: a signal getting
     stronger. Deep green is a settled gate, bright green is one that just
     switched. The trace on the analyser, the rail over the stats and a lit
     LED are all showing that same idea, which is why it is defined once here
     and referenced everywhere else. */
  --g1: #4dfa5c;
  --g2: #00e015;
  --g3: #00a80c;
  --halo:      radial-gradient(ellipse 880px 520px at 76% 4%,
                 rgba(0, 224, 21, .13), transparent 64%);
  --halo-2:    radial-gradient(ellipse 620px 380px at 12% 96%,
                 rgba(0, 224, 21, .07), transparent 66%);

  --scrim:     rgba(5, 7, 10, 0.88);   /* the sticky topbar */
  --lift:      none;                   /* panels need no shadow in the dark */
  --lift-2:    none;

  --rule: 1px solid var(--line);
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background-color: var(--void);
  /* faint engineering grid: the surface the instrument sits on */
  background-image:
    linear-gradient(to right,  var(--grid) 1px, transparent 1px),
    linear-gradient(to bottom, var(--grid) 1px, transparent 1px);
  background-size: 56px 56px;
  color: var(--text);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  transition: background-color .28s ease, color .28s ease;
}

h1, h2, h3, h4 { margin: 0; line-height: 1.04; font-family: var(--display); }
p, dl, dd, dt, ol, ul { margin: 0; padding: 0; }
li { list-style: none; }
a { color: inherit; text-decoration: none; }
img, svg { max-width: 100%; }
:focus-visible { outline: 2px solid var(--signal); outline-offset: 3px; }

code {
  font-family: var(--mono);
  font-size: 0.88em;
  color: var(--signal-ink);
  background: var(--tint-soft);
  border: 1px solid var(--tint-line);
  padding: 1px 5px;
}

/* Off-screen until it is focused, then it lands in the corner like any other
   control. Never display:none, because a hidden link cannot be tabbed to, which
   defeats the entire point of having one. */
.skip {
  position: absolute; z-index: 100;
  top: 8px; left: 8px;
  transform: translateY(-160%);
  padding: 11px 18px;
  font-family: var(--mono); font-size: 12px; font-weight: 600;
  letter-spacing: .13em; text-transform: uppercase;
  background: var(--grad); color: var(--on-signal);
  transition: transform .14s var(--snap);
}
.skip:focus { transform: none; }

/* visible to a screen reader, to nobody else */
.sr {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%);
  white-space: nowrap;
}

.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 var(--gutter); }
.stack { display: flex; flex-direction: column; }
.g6 { gap: 6px; } .g10 { gap: 10px; } .g14 { gap: 14px; }
.g20 { gap: 20px; } .g28 { gap: 28px; } .g40 { gap: 40px; }

/* text that carries the gradient instead of a flat accent, used only on the
   words a heading turns on, never on running copy */
.grad {
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
@supports not (background-clip: text) { .grad { color: var(--signal-ink); } }

/* ---------------------------------------------------------------- labels */

.ck {
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--dim);
}
.ck-sig { color: var(--signal-ink); }
.ck-amber { color: var(--amber); }

/* the recurring 6px signal square that prefixes labels */
.sq {
  display: inline-block;
  width: 6px; height: 6px;
  background: var(--grad);
  margin-right: 8px;
  vertical-align: 1px;
  box-shadow: 0 0 7px var(--glow);
}
.sq-off { background: var(--line-2); box-shadow: none; }

/* ---------------------------------------------------------------- buttons */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  padding: 13px 22px;
  border: 1px solid var(--signal);
  background: transparent;
  color: var(--signal-ink);
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: color .12s var(--snap), background-color .12s var(--snap),
              border-color .12s var(--snap), transform .12s var(--snap),
              box-shadow .12s var(--snap);
}
/* a scan line sweeps the button on hover: the probe passing over a pad */
.btn::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 40%, var(--glow) 50%, transparent 60%);
  transform: translateX(-120%);
  transition: transform .5s steps(7, end);
  pointer-events: none;
}
.btn:hover::after, .btn:focus-visible::after { transform: translateX(120%); }
.btn:hover { background: var(--tint); }

.btn-solid {
  background: var(--grad);
  background-size: 180% 180%;
  border-color: transparent;
  color: var(--on-signal);
  box-shadow: 0 0 0 rgba(0,0,0,0);
}
.btn-solid:hover {
  background-position: 100% 0;
  transform: translateY(-1px);
  box-shadow: 0 8px 22px -10px var(--glow);
}
.btn-solid:active { transform: translateY(0); }
.btn-ghost { border-color: var(--line-2); color: var(--text-2); background: transparent; }
.btn-ghost:hover { border-color: var(--text-2); color: var(--text); background: var(--panel-2); }
.btn-sm { padding: 9px 15px; font-size: 11px; }
.btn[disabled] {
  border-color: var(--line-2);
  color: var(--dim);
  cursor: not-allowed;
  background: transparent;
  transform: none;
  box-shadow: none;
}
.btn[disabled]::after { display: none; }

/* ---------------------------------------------------------------- topbar */

.topbar {
  position: sticky; top: 0; z-index: 60;
  background: var(--scrim);
  backdrop-filter: blur(9px);
  border-bottom: var(--rule);
}
.topbar-in {
  max-width: var(--maxw); margin: 0 auto;
  padding: 0 var(--gutter);
  height: 58px;
  display: flex; align-items: center; gap: 26px;
}
.brandmark { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
/* The mark is wider than it is tall, so the box has to be too. A square box
   letterboxes it and leaves the wordmark sitting against dead space. */
.brandmark img { width: 26px; height: 26px; display: block; }

/* ---------------------------------------------------------- the die frame -
   The mark carries a lot of internal detail, which on a bare page reads as a
   sticker somebody dropped there. A square well gives it an edge to sit
   inside, and two corner brackets in the signal green echo the alignment
   marks on a reticle — the same vocabulary as the section markers. */

.markframe {
  position: relative;
  display: grid;
  place-items: center;
  width: 38px; height: 38px;
  flex: none;
  background: var(--well);
  border: 1px solid var(--line-2);
  transition: border-color .14s var(--snap), background .14s var(--snap);
}
.markframe::before,
.markframe::after {
  content: "";
  position: absolute;
  width: 7px; height: 7px;
  border: 1px solid var(--signal);
  pointer-events: none;
}
.markframe::before { top: -1px; left: -1px; border-right: 0; border-bottom: 0; }
.markframe::after { bottom: -1px; right: -1px; border-left: 0; border-top: 0; }
.brandmark:hover .markframe {
  border-color: color-mix(in srgb, var(--signal) 45%, var(--line-2));
  background: color-mix(in srgb, var(--signal) 7%, var(--well));
}

/* The footer takes the same frame at a size where the die inside it reads. */
.foot-mark { display: flex; align-items: center; gap: 14px; }
.foot-mark img {
  width: 56px; height: 56px; display: block;
  padding: 7px;
  background: var(--well);
  border: 1px solid var(--line-2);
  box-sizing: content-box;
}
.foot-mark b {
  font-family: var(--display);
  font-size: 27px;
  font-weight: 700;
  letter-spacing: .17em;
  color: var(--text);
}
.brandmark b {
  font-family: var(--display);
  font-size: 16px; font-weight: 700;
  letter-spacing: 0.16em;
  color: var(--text);
}
/* The inline mark is cells, not a line, so it takes fills rather than strokes
   and follows the palette like everything else. The edge is the wavefront; the
   settled cells are gates the edge has already passed. */
.brandmark .m-edge { fill: var(--g1); }
.brandmark .m-set  { fill: var(--g3); }
.brandmark img { image-rendering: auto; }

.topnav { display: flex; gap: 22px; margin-left: auto; }
.topnav a {
  position: relative;
  font-family: var(--mono); font-size: 11px; font-weight: 500;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-2);
  padding: 4px 0;
  transition: color .15s;
}
/* the underline grows out of the gradient rather than switching on */
.topnav a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: var(--grad-flat);
  transform: scaleX(0); transform-origin: left;
  transition: transform .2s var(--snap);
}
.topnav a:hover { color: var(--signal-ink); }
.topnav a:hover::after { transform: scaleX(1); }
.top-links { display: flex; gap: 12px; align-items: center; }
@media (max-width: 940px) {
  /* The nav carried margin-left:auto, so hiding it left the theme switch
     stranded beside the wordmark. The empty link row takes over the job. */
  .topnav { display: none; }
  .top-links { margin-left: auto; }
  .topbar-in { gap: 14px; }
}

/* ------------------------------------------------------------ theme switch
   Two states, both always visible, with the active one carrying the gradient.
   A switch that shows only the state you are not in is a riddle. */

.themer {
  /* The button size and the pill size are the same size, so they are one
     number. They were two, and they drifted the moment one of them changed. */
  --sw: 30px;
  --sh: 24px;
  display: inline-flex; align-items: center;
  border: 1px solid var(--line-2);
  background: var(--panel-2);
  padding: 2px;
  flex: none;
  position: relative;
}
.themer button {
  position: relative; z-index: 1;
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--sw); height: var(--sh);
  border: 0; background: transparent; cursor: pointer;
  color: var(--dim);
  transition: color .2s ease;
}
.themer button svg { width: 14px; height: 14px; display: block; }
.themer button[aria-pressed="true"] { color: var(--on-signal); }
.themer button:hover { color: var(--text); }
.themer button[aria-pressed="true"]:hover { color: var(--on-signal); }
/* the sliding gradient pill sits behind whichever half is active */
.themer::before {
  content: ""; position: absolute; z-index: 0;
  top: 2px; left: 2px; width: var(--sw); height: var(--sh);
  background: var(--grad);
  transition: transform .18s var(--snap);
}
.themer[data-at="light"]::before { transform: translateX(var(--sw)); }

/* ---------------------------------------------------------------- hero */

.hero { border-bottom: var(--rule); position: relative; isolation: isolate; }
/* THE HALO: one soft light thrown from behind the console, and a second,
   quieter one low on the left so the band does not feel top-heavy. It is the
   ramp again, spread out: the glow a live instrument puts on its own bezel. */
.hero::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: var(--halo), var(--halo-2);
}
.hero-in {
  max-width: var(--maxw); margin: 0 auto;
  padding: clamp(30px, 6vw, 88px) var(--gutter) clamp(28px, 4vw, 56px);
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  gap: clamp(28px, 5vw, 72px);
  align-items: center;
}
@media (max-width: 940px) { .hero-in { grid-template-columns: 1fr; } }

.hero h1 {
  font-size: clamp(38px, 5.6vw, 78px);
  font-weight: 700;
  letter-spacing: -0.02em;
  text-wrap: balance;
  margin: 16px 0 0;
}
.hero h1 em {
  font-style: normal;
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
@supports not (background-clip: text) { .hero h1 em { color: var(--signal-ink); } }

.hero-sub {
  max-width: 54ch;
  margin-top: 20px;
  color: var(--text-2);
  font-size: 16.5px;
}
.hero-sub b { color: var(--text); font-weight: 600; }
.hero-cta { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 28px; }
/* Side by side they would each be about 160px wide, which is not a button.
   Stacked and full width, they are two clear targets. */
@media (max-width: 560px) {
  .hero-cta { flex-direction: column; align-items: stretch; }
  .hero-cta .btn { width: 100%; }
}

.hero-stats {
  display: grid; grid-template-columns: repeat(4, 1fr);
  border: var(--rule); margin-top: 34px;
  background: var(--panel);
  position: relative;
  box-shadow: var(--lift);
}
/* a 2px gradient rail along the top: the one place the full ramp is shown */
.hero-stats::before {
  content: ""; position: absolute; left: 0; right: 0; top: -1px; height: 2px;
  background: var(--grad-flat);
}
@media (max-width: 640px) { .hero-stats { grid-template-columns: repeat(2, 1fr); } }
.hero-stats div { padding: 14px 16px; border-right: var(--rule); }
.hero-stats div:last-child { border-right: 0; }
@media (max-width: 640px) {
  .hero-stats div:nth-child(2) { border-right: 0; }
  .hero-stats div:nth-child(-n+2) { border-bottom: var(--rule); }
}
.hero-stats b {
  display: block; font-family: var(--display); font-size: 26px;
  font-weight: 700; color: var(--signal-ink); letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums; margin-top: 2px;
}
/* How much of the die did work on the last edge. The bar is the same figure
   as the number beside it, read as a proportion of the whole array. */
.hero-stats .spark {
  display: block; height: 2px; margin-top: 8px;
  background: var(--line); position: relative; overflow: hidden;
}
.hero-stats .spark::after {
  content: ""; position: absolute; top: 0; bottom: 0; left: 0;
  width: var(--w, 0%); background: var(--grad);
  transition: width .12s linear;
}

/* ---------------------------------------------------------------- console */

#machine .console { margin-bottom: clamp(20px, 3vw, 32px); }
.console {
  border: var(--rule);
  background: var(--panel);
  position: relative;
  box-shadow: var(--lift-2);
}
/* corner ticks: an instrument bezel, not a card */
.console::before, .console::after {
  content: ""; position: absolute; width: 9px; height: 9px;
  border: 1px solid var(--signal); pointer-events: none;
}
.console::before { top: -1px; left: -1px; border-right: 0; border-bottom: 0; }
.console::after { bottom: -1px; right: -1px; border-left: 0; border-top: 0; }

.console-bar {
  display: flex; align-items: center; gap: 12px;
  padding: 11px 16px; border-bottom: var(--rule);
  background: var(--panel-2);
}
.console-bar .grow { margin-left: auto; }

.livedot {
  width: 7px; height: 7px; background: var(--signal);
  box-shadow: 0 0 8px var(--glow);
  animation: pulse 1.9s ease-in-out infinite;
}
.livedot.halt { background: var(--amber); box-shadow: 0 0 8px var(--amber-glow); animation: none; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }

/* the die: 2,368 gates */
.die-wrap { padding: 16px; }
@media (max-width: 560px) {
  .die-wrap, .panel-body { padding: 12px; }
  .readout div { padding: 10px 12px; }
  .console-bar { padding: 10px 12px; }
}
/* Both die maps: the console one and the launchpad one. The canvas carries
   an intrinsic size for the no-CSS case, and the width is taken back here so
   it can never push the page wider than the viewport. */
.die-wrap canvas {
  display: block; width: 100%; max-width: 100%; height: auto;
  background: var(--well); border: var(--rule);
}
.die-note { padding: 0 16px 14px; }

/* ---------------------------------------------------------- the readout ---
   Four figures read off the machine on every frame. It is the last band of
   the instrument and the first thing anybody actually looks at, so the cells
   are separated by a hairline rather than boxed, and the numbers are set in
   the display face at a size you can read across a desk. */

.readout {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  background: var(--line-soft);
  border-top: var(--rule);
}
.readout div {
  background: var(--panel);
  padding: 14px 16px 16px;
  display: flex; flex-direction: column; gap: 5px;
  min-width: 0;
}
.readout b {
  font-family: var(--display);
  font-size: clamp(20px, 2vw, 27px); font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--signal-ink);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
/* The output port is the one figure that changes for a reason, so it is the
   one allowed to announce that it changed. */
.readout b.hot { color: var(--g1); }

@media (max-width: 620px) {
  .readout { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---------------------------------------------------------------- sections */

.sec { border-bottom: var(--rule); padding: clamp(52px, 7vw, 96px) 0; }
/* A section opens the way a datasheet page does: a running head across the
   full measure with the section number on it, a rule under that, and only
   then the title. The note sits beside the title rather than above it, so the
   eye lands on what the section is called before it lands on the caveat. */
.sec-head {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  grid-template-areas:
    "index index"
    "title note";
  column-gap: clamp(20px, 4vw, 56px);
  row-gap: clamp(16px, 2.4vw, 30px);
  align-items: start;
  margin-bottom: clamp(28px, 4vw, 48px);
}
.sec-index {
  grid-area: index;
  display: flex; align-items: center;
  padding-bottom: 12px;
  border-bottom: var(--rule);
  font-family: var(--mono); font-size: 10.5px; font-weight: 500;
  letter-spacing: 0.24em; text-transform: uppercase; color: var(--dim);
}
.sec-head h2 { grid-area: title; margin-top: 0; }
.sec-note { grid-area: note; padding-top: 6px; }
@media (max-width: 860px) {
  .sec-head {
    grid-template-columns: 1fr;
    grid-template-areas: "index" "title" "note";
  }
  .sec-note { padding-top: 0; }
}
.sec-num { color: var(--signal-ink); }
.sec-head h2 {
  font-size: clamp(30px, 4.2vw, 54px);
  font-weight: 700; letter-spacing: -0.015em; text-wrap: balance;
  margin-top: 12px;
}
/* The word a heading turns on gets a FIELD behind it, not a highlight.
   The first version was a flat tint hugging the glyphs, and it read exactly
   like a find-in-page match: a rectangle somebody had swiped a marker over.
   This is meant to read as a lit region of the die instead, so it borrows the
   things a die has and a highlighter does not:

     · it squares up to the line box rather than to the letters, with the
       ascender and descender room included, so it is a plate the type sits ON
     · a hairline in the accent, at the alpha the panels use, so it has an edge
     · a dot field, the way a piece of perf board is drilled. A line grid was
       tried first and it fought the letterforms; dots read as a surface
       without competing with the type standing on it
     · a heavier floor rule that runs the full width of the plate

   It still wipes in a segment at a time, because a machine does not fade. */
.sec-head h2 em {
  font-style: normal;
  position: relative;
  display: inline-block;
  padding: .04em .24em .12em;
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  isolation: isolate;
}
@supports not (background-clip: text) { .sec-head h2 em { color: var(--signal-ink); } }

/* the field, and the floor it stands on */
.sec-head h2 em::before,
.sec-head h2 em::after {
  content: ""; position: absolute; z-index: -1;
  transform: scaleX(0); transform-origin: left;
  transition: transform .55s var(--draw);
}
.sec-head h2 em::before {
  /* Squared to the line box and deep enough that a descender sits INSIDE the
     plate rather than hanging through its floor. */
  left: 0; right: 0; top: -.02em; bottom: 0;
  background:
    radial-gradient(var(--tint-line) 1.1px, transparent 1.2px) 0 0 / 9px 9px,
    var(--tint);
  border: 1px solid var(--tint-line);
  border-bottom: 0;
}
/* The floor the plate stands on, arriving a beat after it. It replaces the
   bottom border rather than sitting under one, so the edge is a single line
   of one weight. */
.sec-head h2 em::after {
  left: 0; right: 0; bottom: 0; height: 3px;
  background: var(--grad-flat);
  transition-delay: .12s;
}
.sec-head.in h2 em::before,
.sec-head.in h2 em::after { transform: scaleX(1); }

/* Nothing to wipe if motion is unwelcome: the band is simply there. */
/* ------------------------------------------------------------ the gateway ---
   The door to /launchpad.html. It is one anchor, not a card with a button
   inside it, so the whole plate is the target and the keyboard gets a single
   stop rather than two. */

.nav-go { color: var(--signal-ink) !important; }
.nav-go::after { background: var(--grad) !important; }

.gateway {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: 0;
  border: var(--rule);
  background: var(--panel);
  box-shadow: var(--lift);
  text-decoration: none; color: inherit;
  position: relative; overflow: hidden;
  transition: box-shadow .18s var(--snap), border-color .18s var(--snap);
}
@media (max-width: 860px) { .gateway { grid-template-columns: 1fr; } }

/* The same 2px gradient rail the cards use, so a hover means one thing across
   the whole page. */
.gateway::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--grad); transform: scaleY(0); transform-origin: top;
  transition: transform .28s var(--latch);
  z-index: 2;
}
.gateway:hover::before, .gateway:focus-visible::before { transform: scaleY(1); }
.gateway:hover { border-color: var(--signal-2); }
.gateway:focus-visible { outline: 2px solid var(--signal); outline-offset: 3px; }

.gateway-face { padding: clamp(22px, 3.4vw, 38px); }
.gateway-face h3 {
  font-family: var(--display);
  font-size: clamp(21px, 2.4vw, 30px); font-weight: 700;
  letter-spacing: -0.01em; line-height: 1.2;
  margin: 12px 0 12px; color: var(--text); text-wrap: balance;
}
.gateway-face h3 em {
  font-style: normal;
  background: var(--grad); -webkit-background-clip: text;
  background-clip: text; color: transparent;
}
@supports not (background-clip: text) { .gateway-face h3 em { color: var(--signal-ink); } }
.gateway-face p {
  font-size: 14.5px; line-height: 1.6; color: var(--text-2);
  max-width: 46ch; margin-bottom: 20px;
}
.gateway-go {
  display: inline-flex; align-items: center; gap: 9px;
  font-family: var(--mono); font-size: 11.5px; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--signal-ink);
  padding-bottom: 4px; border-bottom: 1px solid var(--signal);
}
/* The arrow steps rather than glides: the same motion language as the rest. */
.gateway-go i { font-style: normal; transition: transform .2s var(--snap); }
.gateway:hover .gateway-go i { transform: translateX(5px); }

/* The right half is a specimen of what the launchpad does: four lines of real
   assembly and an output byte. Static, because a moving picture of a program
   on a page nobody asked to run one on is noise. */
.gateway-strip {
  border-left: var(--rule);
  background: var(--well);
  display: flex; flex-direction: column;
  min-width: 0;
}
@media (max-width: 860px) {
  .gateway-strip { border-left: 0; border-top: var(--rule); }
}
.gs-head {
  display: flex; align-items: center; gap: 9px;
  padding: 11px 16px; border-bottom: 1px solid var(--line-soft);
  background: var(--panel-2);
  font-family: var(--mono); font-size: 10px; font-weight: 600;
  letter-spacing: .16em; color: var(--dim);
}
.gs-code {
  flex: 1 1 auto;
  margin: 0; padding: 18px 16px;
  font-family: var(--mono); font-size: 13px; line-height: 1.85;
  color: var(--text-2);
  overflow-x: auto;
}
.gs-code b { color: var(--signal-ink); font-weight: 600; }
.gs-foot {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 16px; border-top: 1px solid var(--line-soft);
  background: var(--panel-2);
}
.gs-bits { display: flex; gap: 3px; }
.gs-bits i {
  width: 12px; height: 12px;
  background: var(--panel-3); border: 1px solid var(--line-soft);
}
.gs-bits i.on { background: var(--grad); border-color: transparent; }
.gs-bits i:nth-child(4) { margin-right: 5px; }

/* ======================================================= THE LAUNCHPAD ===
   A second page, built out of the same parts as the first. Nothing below
   invents a new visual language: it is the panel, the ledger and the plate
   already defined above, arranged around an editor. */

/* The hero here carries a status table rather than a console, so the columns
   sit closer to even than on the front page. */
.lp-hero { grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); align-items: center; }
@media (max-width: 940px) { .lp-hero { grid-template-columns: 1fr; } }

/* ------------------------------------------------------- the chipset ---
   One package, lifted off the page, with the network it answers to drawn
   around it. It replaced a status table in the launchpad hero, and the status
   moved to a strip underneath where it reads faster anyway.

   The float is a still one: the package sits above its own shadow and stays
   there. A part bobbing on a sine wave would be the only eased thing on this
   site, and it would read as a logo animation rather than as an object. */

.chipset {
  perspective: 1200px;
  display: flex; align-items: center; justify-content: center;
  /* A floating package projects upward: at fifty-six degrees a lift of L
     shows as 0.83L above where the object sits. The top pad is that room. */
  padding: clamp(30px, 6vw, 56px) 0 clamp(16px, 3vw, 30px);
  overflow: hidden;
}
.chip-scene {
  position: relative;
  width: min(430px, 62vw); aspect-ratio: 1;
  /* Two corrections, both because a tilted plane does not fill its own box.
     translateY comes first in the list, so it moves the ALREADY-rotated
     result down: the drawing is thrown upward by the float, and this puts it
     back in the middle of the space it occupies. The negative bottom margin
     then gives back the dead half that the square was still reserving. */
  transform: translateY(15%) rotateX(56deg) rotateZ(-38deg);
  transform-style: preserve-3d;
  margin-bottom: -4%;
}

/* --- the network ------------------------------------------------------- */

.chip-trace {
  position: absolute; left: 50%; top: 50%;
  height: 1px; margin-top: -0.5px;
  transform-origin: left center;
  transform-style: preserve-3d;
  background: var(--line-2);
  opacity: .45;
}
/* The pulse: the trace fills from the package outward as --run climbs. */
.chip-trace::after {
  content: ""; position: absolute; inset: 0;
  background: var(--grad-flat);
  transform: scaleX(var(--run, 0)); transform-origin: left;
}
/* Opacity is set by chipset.js: it carries how far round the orbit this
   trace sits as well as whether a pulse is on it. */

.chip-node {
  position: absolute;
  width: 16px; height: 16px; margin: -8px 0 0 -8px;
  background: var(--panel-3);
  border: 1px solid var(--line-2);
  /* The transform is set by chipset.js: it carries the node's place on the
     orbit as well as whatever the animation is doing to it. */
  transition: box-shadow .14s var(--snap), background-color .1s var(--snap);
}
.chip-node.on {
  background: var(--g2); border-color: transparent;
  box-shadow: 0 10px 18px -10px var(--glow);
}
.chip-node.agreed {
  background: var(--grad); border-color: transparent;
  box-shadow: 0 12px 22px -10px var(--glow);
}

/* --- the package ------------------------------------------------------- */

/* Cast on the ground plane, not on the package, so the gap between them is
   what reads as height. */
.chip-shadow {
  position: absolute; left: 50%; top: 50%;
  width: 42%; height: 42%; margin: -21% 0 0 -21%;
  background: radial-gradient(ellipse at center,
              rgba(16, 22, 19, .22), transparent 70%);
  filter: blur(11px);
  transform: translateZ(1px);
}

.chip-pkg {
  position: absolute; left: 50%; top: 50%;
  width: 48%; height: 48%; margin: -24% 0 0 -24%;
  /* The lift is set by chipset.js, as a fraction of the scene rather than a
     count of pixels: the same drawing has to hold at 430px and at 240. */
  transform-style: preserve-3d;
}

/* The side wall of the package: five outlines stacked down in Z. Cheaper
   than four real faces and, at this angle, indistinguishable from them. */
.chip-slab {
  position: absolute; inset: 0;
  background: var(--panel-3);
  border: 1px solid var(--line-2);
}

.chip-body {
  position: absolute; inset: 0;
  background: linear-gradient(150deg, var(--panel-2), var(--panel-3));
  border: var(--rule);
  box-shadow: var(--lift-2), 0 0 0 1px var(--line-soft) inset;
  display: flex; flex-direction: column;
  padding: 9%;
  gap: 6%;
}

/* The leads. Four banks, seven to a side, sunk a little below the body so
   the package has a thickness rather than being a card. */
.chip-pins { position: absolute; display: flex; transform: translateZ(-9px); }
.chip-pin { background: var(--line-2); border-radius: 1px; }

.chip-pins-n, .chip-pins-s {
  left: 12%; right: 12%; justify-content: space-between;
}
.chip-pins-n { top: -7%; }
.chip-pins-s { bottom: -7%; }
.chip-pins-n .chip-pin, .chip-pins-s .chip-pin { width: 5%; height: 9px; }

.chip-pins-e, .chip-pins-w {
  top: 12%; bottom: 12%; flex-direction: column; justify-content: space-between;
}
.chip-pins-w { left: -7%; }
.chip-pins-e { right: -7%; }
.chip-pins-e .chip-pin, .chip-pins-w .chip-pin { height: 5%; width: 9px; }

/* The die on top of the package: the same diagonal wavefront as the mark. */
.chip-die {
  flex: 1 1 auto;
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  gap: 6%;
  transform-style: preserve-3d;
}
.chip-cell {
  background: var(--panel-3);
  border: 1px solid var(--line-soft);
  transition: box-shadow .14s var(--snap);
}
.chip-cell.set { background: var(--g3); border-color: transparent; opacity: .62; }
.chip-cell.edge {
  background: var(--g1); border-color: transparent; opacity: 1;
  transform: translateZ(9px);
  box-shadow: 0 8px 14px -6px var(--glow);
}
.chip-cell.agreed { background: var(--grad); border-color: transparent; opacity: 1; }

.chip-mark {
  flex: none;
  font-family: var(--mono); font-size: 9px; font-weight: 600;
  letter-spacing: .22em; color: var(--dim);
  text-align: center;
}

@media (max-width: 560px) {
  .chip-node { width: 12px; height: 12px; margin: -6px 0 0 -6px; }
  .chip-pins-n .chip-pin, .chip-pins-s .chip-pin { height: 6px; }
  .chip-pins-e .chip-pin, .chip-pins-w .chip-pin { width: 6px; }
  .chip-mark { font-size: 8px; letter-spacing: .16em; }
}
@media (prefers-reduced-motion: reduce) {
  .chip-cell, .chip-node { transition: none; }
  .chip-cell.edge { transform: none; }
}

/* ------------------------------------------------------ the launch state ---
   Six facts, each either true or not yet. A strip rather than a table: the
   question a reader has here is "what is live", and a row of cells answers it
   in one pass where a two-column table makes them read down. */

.hero .statestrip { margin-top: clamp(6px, 2vw, 18px); }
.statestrip {
  border: var(--rule); background: var(--panel); box-shadow: var(--lift);
  margin-bottom: clamp(18px, 3vw, 30px);
}
.statestrip-head {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 16px; border-bottom: var(--rule); background: var(--panel-2);
}
.statestrip-head .grow { margin-left: auto; }
.statestrip-cells {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
  gap: 1px; background: var(--line-soft);
}
.statecell {
  background: var(--panel);
  padding: 13px 16px 15px;
  display: flex; flex-direction: column; gap: 6px;
  min-width: 0;
}
.statecell b {
  font-family: var(--mono); font-size: 12.5px; font-weight: 600;
  letter-spacing: .08em; color: var(--dim);
  overflow-wrap: anywhere;
}
.statecell.on b { color: var(--signal-ink); }
/* A live row carries the rail; a pending one does not, so the strip can be
   read by colour alone from across a table. */
.statecell.on { position: relative; }
.statecell.on::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--grad);
}

/* ------------------------------------------------------- the workbench ---
   Editor on the left, the machine it feeds on the right. Both columns are
   min-width: 0 so a long line of assembly scrolls inside the editor instead
   of widening the page. */

.wb {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: 20px; align-items: start;
}
@media (max-width: 1000px) { .wb { grid-template-columns: 1fr; } }

.wb-editor { display: flex; flex-direction: column; }

/* The gutter and the textarea share one box and one scroll position, so the
   numbers stay beside the lines they number. */
.wb-code {
  display: flex; align-items: stretch;
  border: 1px solid var(--line-2);
  background: var(--well);
  min-height: 300px;
}
.wb-gutter {
  flex: none; width: 42px;
  padding: 12px 0;
  background: var(--panel-2);
  border-right: 1px solid var(--line-soft);
  overflow: hidden;
  font-family: var(--mono); font-size: 12.5px; line-height: 20px;
  font-variant-numeric: tabular-nums;
  user-select: none;
}
.wb-gutter .gl {
  display: block; font-style: normal;
  padding-right: 10px; text-align: right;
  color: var(--dim);
}
/* The line the assembler stopped on. One colour, one line, no red wash over
   the whole editor: the error message already says what happened. */
.wb-gutter .gl.bad {
  color: var(--on-signal); background: var(--amber); font-weight: 600;
}

#wb-src {
  flex: 1 1 auto; min-width: 0;
  padding: 12px 14px;
  background: transparent; color: var(--text);
  border: 0; outline: none; resize: vertical;
  font-family: var(--mono); font-size: 12.5px; line-height: 20px;
  tab-size: 2;
  min-height: 300px;
  white-space: pre; overflow-wrap: normal; overflow-x: auto;
}
.wb-code:focus-within { border-color: var(--signal); }
#wb-src::selection { background: var(--tint); }
#wb-src::-webkit-scrollbar,
.listing::-webkit-scrollbar { width: 8px; height: 8px; }
#wb-src::-webkit-scrollbar-track { background: var(--panel-2); }
#wb-src::-webkit-scrollbar-thumb { background: var(--line-2); }

/* The assembler's one line of speech. It is the only thing on the page that
   changes colour to mean something, so it earns the three states. */
.wb-msg {
  font-family: var(--mono); font-size: 11.5px; line-height: 1.5;
  letter-spacing: .03em;
  padding: 10px 12px;
  border-left: 2px solid var(--line-2);
  background: var(--panel-2);
  color: var(--text-2);
  min-height: 40px;
}
.wb-msg.ok { border-left-color: var(--signal); color: var(--text); }
.wb-msg.bad { border-left-color: var(--amber); color: var(--text); }
.wb-msg.warn { border-left-color: var(--amber); color: var(--text-2); }

/* A legend strip along the foot of a panel: the field widths the listing
   above it is drawn to. */
.panel-foot {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 10px 12px; border-top: var(--rule); background: var(--panel-2);
}
.panel-foot .grow { margin-left: auto; }

/* ------------------------------------------------- the live opcode table ---
   The table is wired to the processor above it: an instruction lights its own
   row when it executes and the light decays from there, so what you read is
   the instruction mix of whatever is running rather than a list. The tint is
   set per row by app.js, because the value is continuous and CSS has no
   business interpolating a colour it cannot measure. */

.isa-live {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 11px 14px;
  border: var(--rule); border-bottom: 0;
  background: var(--panel-2);
}
.isa-live .grow {
  margin-left: auto;
  font-weight: 600; letter-spacing: .16em; color: var(--signal-ink);
  font-variant-numeric: tabular-nums;
}
/* The one executing right now, rather than merely warm. */
#isa-body tr { transition: none; }
/* On the executing row the wash IS the accent, so the type stops being one:
   accent green on a green field is the one combination that reads worse the
   brighter it gets. */
#isa-body tr.live td { color: var(--text); }
#isa-body tr.live td.op { color: var(--text); font-weight: 700; }
#isa-body tr.live td.num { color: var(--text); font-weight: 600; }
/* A rail on the left edge, drawn on the first cell because a table row cannot
   carry a pseudo-element reliably. Its colour is set by app.js: it tracks the
   heat rather than the .live flag, so a row that was touched a moment ago is
   still marked after the wash behind it has faded. */
#isa-body tr td:first-child {
  border-left: 2px solid transparent;
  transition: border-left-color .1s var(--snap);
}
/* Brighter still on the row executing right now. */
#isa-body tr.live td:first-child { border-left-width: 3px; }

/* ---------------------------------------------------- instruction table ---
   Four groups, ordered the way somebody writing a program thinks rather than
   by opcode number. Every row is a button: clicking it drops the form into
   the editor, so the table is a keyboard rather than a poster. */

.isa-groups {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: 18px; align-items: start;
}
.isa-group {
  border: var(--rule); background: var(--panel); box-shadow: var(--lift);
  display: flex; flex-direction: column;
}
.isa-group-head {
  display: flex; align-items: baseline; gap: 10px;
  padding: 12px 14px; border-bottom: var(--rule); background: var(--panel-2);
}
.isa-count {
  margin-left: auto;
  font-family: var(--mono); font-size: 10px; letter-spacing: .12em;
  color: var(--dim);
}
.isa-op {
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr) auto;
  align-items: baseline; gap: 4px 12px;
  width: 100%; text-align: left;
  padding: 9px 14px;
  background: transparent; border: 0;
  border-bottom: 1px solid var(--line-soft);
  border-left: 2px solid transparent;
  cursor: pointer; font: inherit;
  transition: background-color .12s var(--snap), border-left-color .12s var(--snap);
}
.isa-op:last-of-type { border-bottom: 0; }
.isa-op:hover, .isa-op:focus-visible {
  background: var(--tint); border-left-color: var(--signal); outline: none;
}
.isa-op code {
  font-family: var(--mono); font-size: 12.5px; font-weight: 600;
  color: var(--text); background: none; padding: 0; border: 0;
  white-space: nowrap;
}
.isa-op:hover code { color: var(--signal-ink); }
.isa-eff {
  font-size: 12px; color: var(--text-2); line-height: 1.4;
  min-width: 0; overflow-wrap: anywhere;
}
/* The flag column is the same width on every row so C Z reads as a column
   rather than as trailing text. */
.isa-fl {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .14em;
  color: var(--signal-ink); min-width: 22px; text-align: right;
}
.isa-group-note {
  padding: 10px 14px; border-top: var(--rule); background: var(--panel-2);
  font-size: 12px; color: var(--dim); line-height: 1.45;
}

/* The launchpad pairs the ROM with the register file, and the file is always
   sixteen rows tall. A floor under the listing keeps the two panels the same
   object rather than a tall one beside a stub. */
#lp-listing { min-height: 322px; max-height: 322px; }

/* The mnemonic beside a listing row. */
.listing .src { color: var(--dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.listing .line.at .src { color: var(--signal-ink); }

@media (max-width: 560px) {
  .wb-gutter { width: 34px; }
  #wb-src, .wb-gutter { font-size: 11.5px; line-height: 19px; }
}

@media (prefers-reduced-motion: reduce) {
  .sec-head h2 em::before, .sec-head h2 em::after { transform: scaleX(1); }
}
.sec-copy { color: var(--text-2); max-width: 62ch; font-size: 16px; }
.sec-copy + .sec-copy { margin-top: 14px; }
.sec-copy b { color: var(--text); font-weight: 600; }

/* a hairline that draws itself in when the section arrives */
.drawline { height: 1px; background: var(--line); position: relative; overflow: hidden; }
.drawline::after {
  content: ""; position: absolute; inset: 0;
  background: var(--grad-flat);
  transform: scaleX(0); transform-origin: left;
  transition: transform .9s var(--draw);
}
.drawline.in::after { transform: scaleX(1); }

/* blocks that arrive rather than appear. 14px is enough to read as motion
   without the page ever looking like it is assembling itself */
.rise { opacity: 0; transform: translateY(14px); }
.rise.in {
  opacity: 1; transform: none;
  transition: opacity .42s var(--latch), transform .42s var(--latch);
}
/* Reached by scrolling back up, or jumped clean over. The reader has already
   been here; making them watch it arrive again is a stutter, not a flourish. */
.rise.in.instant { transition: none; }
.drawline.in.instant::after { transition: none; }

/* ---------------------------------------------------------------- grids */

.cards {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(232px, 1fr));
  border: var(--rule); border-right: 0; border-bottom: 0;
  background: var(--panel);
  position: relative; box-shadow: var(--lift);
}
/* one rail for the whole set, because the cards are peers rather than steps */
.cards::before {
  content: ""; position: absolute; left: 0; right: 0; top: -1px; height: 2px;
  background: var(--grad-flat); z-index: 1;
}
.cards > div {
  padding: 22px 22px 26px;
  border-right: var(--rule); border-bottom: var(--rule);
  position: relative;
  transition: background-color .12s var(--snap);
}
.cards > div::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--grad); transform: scaleY(0); transform-origin: top;
  transition: transform .24s var(--latch);
}
.cards > div:hover { background: var(--panel-2); }
.cards > div:hover::before { transform: scaleY(1); }
.cards h4 {
  font-family: var(--display); font-size: 19px; font-weight: 600;
  margin: 9px 0 7px; color: var(--text);
}
.cards p { font-size: 14px; color: var(--text-2); line-height: 1.55; }
.cards .big {
  font-family: var(--display); font-size: 34px; font-weight: 700;
  color: var(--signal-ink); letter-spacing: -0.01em; margin: 8px 0 4px;
  font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------- panels */

.panel { border: var(--rule); background: var(--panel); box-shadow: var(--lift); }
.panel-head {
  padding: 12px 16px; border-bottom: var(--rule); background: var(--panel-2);
  display: flex; align-items: center; gap: 10px;
}
/* a trailing label in a panel head belongs at the far end, the same way it
   does in the console bar */
.panel-head .grow { margin-left: auto; }
.panel-body { padding: 16px; }

.split {
  display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: 20px; align-items: start;
}
@media (max-width: 900px) { .split { grid-template-columns: 1fr; } }

/* ------------------------------------------------------- register file ---
   Sixteen rows of eight flip-flops. The bit cells are the same square the
   mark is made of and the same square the die map draws, because they are all
   the same object: one bit of state, lit or not. */

.regs { border: var(--rule); background: var(--panel); }
.reg {
  display: grid;
  grid-template-columns: 30px auto minmax(0, 1fr);
  align-items: center; gap: 12px;
  padding: 5px 10px;
  border-bottom: 1px solid var(--line-soft);
}
.reg:last-child { border-bottom: 0; }
.reg .k {
  font-family: var(--mono); font-size: 10px; letter-spacing: .1em;
  color: var(--dim);
}
/* Fixed cells, not a bar that stretches: a bit is a bit, and eight of them
   should read as eight objects rather than as one meter. */
.reg .bits { display: flex; gap: 3px; flex: none; }
.reg .bit {
  width: 12px; height: 12px; flex: none;
  background: var(--panel-3);
  border: 1px solid var(--line-soft);
  /* No transition: a flip-flop changes on the edge or it does not change. */
}
.reg .bit.on { background: var(--grad); border-color: transparent; }
/* every fourth cell carries a heavier edge, so eight bits stay countable */
.reg .bit:nth-child(4) { margin-right: 5px; }
.reg .v {
  font-family: var(--mono); font-size: 12.5px; font-weight: 600;
  color: var(--text); font-variant-numeric: tabular-nums; text-align: right;
  justify-self: end;
}
.reg.hot .v { color: var(--signal-ink); }

/* ---------------------------------------------------------- ROM listing ---
   One row per word, and the word shown as the four fields the decoder splits
   it into, to scale. A jump spends its width on an address; a mov barely uses
   any of it. The mnemonic follows, for anyone who would rather read English. */

.listing { font-family: var(--mono); font-size: 12px; max-height: 300px; overflow-y: auto; }
.listing .line {
  display: grid;
  grid-template-columns: 30px minmax(90px, 1fr) minmax(0, 1.15fr);
  align-items: center; gap: 10px;
  padding: 4px 12px;
  color: var(--text-2);
  border-left: 2px solid transparent;
}
.listing .line .pc { color: var(--dim); font-variant-numeric: tabular-nums; }
.listing .word { display: flex; gap: 2px; }
.listing .fld {
  flex-grow: 1; flex-basis: 0;
  font-style: normal; font-size: 9px; line-height: 14px; text-align: center;
  color: var(--on-signal); background: var(--g3);
  overflow: hidden;
}
/* the four fields are told apart by weight, not by four different hues:
   colour still means one thing on this page */
.listing .fld-op { background: var(--g2); }
.listing .fld-rd { background: var(--g3); }
.listing .fld-rs { background: var(--g3); opacity: .78; }
.listing .fld-im { background: var(--g3); opacity: .58; }
.listing .fld.zero { background: var(--panel-3); color: var(--dim); }
.listing .line.at {
  background: var(--tint);
  border-left-color: var(--signal);
  color: var(--signal-ink);
}
.listing .line.at .fld-op { background: var(--g1); }
.listing::-webkit-scrollbar { width: 8px; }
.listing::-webkit-scrollbar-track { background: var(--panel-2); }
.listing::-webkit-scrollbar-thumb { background: var(--line-2); }
.listing::-webkit-scrollbar-thumb:hover { background: var(--signal-2); }

/* controls */
.controls { display: flex; flex-wrap: wrap; gap: 8px; }
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  font-family: var(--mono); font-size: 11px; font-weight: 500; letter-spacing: .1em;
  padding: 7px 12px; border: 1px solid var(--line-2); background: transparent;
  color: var(--text-2); cursor: pointer; text-transform: uppercase;
  transition: color .12s var(--snap), border-color .12s var(--snap),
              background-color .12s var(--snap);
}
.chip:hover { border-color: var(--text-2); color: var(--text); }
.chip[aria-pressed="true"] {
  border-color: var(--signal); color: var(--signal-ink);
  background: var(--tint);
}

.field { display: flex; flex-direction: column; gap: 7px; }
.field input, .field select {
  font-family: var(--mono); font-size: 14px;
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--line-2); padding: 11px 13px; width: 100%;
}
.field input:focus { border-color: var(--signal); outline: none; }
.hint { font-family: var(--mono); font-size: 10.5px; color: var(--dim); letter-spacing: .06em; }

/* ------------------------------------------------------ chip economics ---
   A ledger, not a card grid: label left, figure right, and the reason it is
   that figure underneath in the quiet weight. Everything is computed, so the
   numbers move as the chips above are picked. */

.econ { border: var(--rule); background: var(--panel-2); }
.econ-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 4px 14px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--line-soft);
}
.econ-row:last-child { border-bottom: 0; }
.econ-row b {
  font-family: var(--mono); font-size: 16px; font-weight: 600;
  color: var(--signal-ink); font-variant-numeric: tabular-nums;
  text-align: right;
}
/* the note runs the full width beneath both, so long reasons never squeeze
   the figure into two lines */
.econ-note {
  grid-column: 1 / -1;
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .06em;
  color: var(--dim);
}
/* the last row is the one that matters: it is what a sponsor is paid */
.econ-row:last-child { background: var(--tint-soft); }
.econ-row:last-child b { font-size: 19px; }

/* ----------------------------------------------------- the component shelf
   Volumes on a shelf. Each spine is turned away until you reach for it, then
   it squares up and opens. The rotation is the only place on this page that
   uses perspective, and it earns it: these are objects, and the shelf is the
   one metaphor that makes seven blocks feel countable. */

.shelf-wrap { margin-top: clamp(30px, 4vw, 48px); }
.shelf {
  display: flex; gap: 10px; align-items: stretch;
  margin-top: 20px;
  /* The lean throws each spine's far edge past its own layout box, so the
     last one was being sliced off by the scroll container. The right pad is
     that overhang. */
  /* Vertical room as well as horizontal. A leaning spine's corners travel
     outside its own box, and a scroll container was slicing the top-right
     one clean off, which is what made the lean read as a crop rather than a
     depth. */
  /* The left pad is the other overhang. With the vanishing point at 88% an
     opening spine is pushed AWAY from it, so the leftmost one travels left as
     it comes forward: measured at 40px past the box, which the scroll
     container was slicing off "Register read" with. */
  padding: 30px 54px 30px 52px;
  perspective: 1500px;
  perspective-origin: 88% 50%;
  overflow-x: auto;
}
.spine {
  /* Closed spines share the full width rather than sitting at a fixed 84px,
     which left half the row empty on a wide screen. The open one takes a set
     share and the rest divide what is left, so the shelf always reaches the
     right edge. */
  flex: 1 1 0;
  min-width: 76px;
  height: 404px;
  position: relative;
  padding: 0; margin: 0;
  border: var(--rule); background: var(--panel);
  /* A darker right edge stands in for the thickness of the block. Without it
     the spines read as leaning cards rather than as things with a spine. */
  border-right-width: 4px;
  box-shadow: var(--lift);
  cursor: pointer;
  transform-origin: left center;
  /* --i counts from the left, set by shelf.js. Volumes further from the
     viewer sit back and shrink very slightly, which is what makes the row
     read as a shelf receding rather than seven identical cards on a slant. */
  transform: rotateY(-19deg)
             translateZ(calc(var(--i, 0) * 7px - 42px))
             scale(calc(0.955 + var(--i, 0) * 0.0075));
  transition: flex .3s var(--latch), transform .3s var(--latch),
              box-shadow .3s var(--latch), filter .3s var(--latch);
  text-align: left;
  /* The far end of the row falls into shade, the near end does not. */
  filter: brightness(calc(0.94 + var(--i, 0) * 0.01));
}
/* Opening is driven by a class, never by :hover. Hovering a flex item that
   then grows moves it out from under the pointer, which drops the hover,
   which shrinks it back under the pointer: the spine flickered open and shut
   several times a second. A class set on mouseenter cannot do that. */
.spine.open {
  flex: 0 0 340px;
  transform: rotateY(0deg) translateZ(60px) scale(1);
  border-right-width: 1.5px;
  box-shadow: var(--lift-2), 0 40px 70px -40px rgba(16, 40, 28, .5);
  filter: none;
  z-index: 3;
}
.spine:focus-visible { outline-offset: 4px; }
/* the closed edge: the name read the long way, and the number at the foot */
.spine-edge {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: space-between;
  padding: 16px 0;
  transition: opacity .16s var(--snap);
}
.spine-name {
  writing-mode: vertical-rl; text-orientation: mixed;
  font-family: var(--display); font-size: 17px; font-weight: 600;
  letter-spacing: .04em; color: var(--text);
  flex: none;
}
/* How much of the die this block takes, drawn against the largest one. The
   cells are the same square as the mark, the die map and the register file,
   because they are all showing the same object: one unit of the processor. */
.spine-gauge {
  flex: 1 1 auto;
  display: flex; flex-direction: column; justify-content: flex-end;
  gap: 3px;
  width: 14px;
  margin: 16px 0;
}
.spine-gauge i {
  display: block; flex: 1 1 0; min-height: 2px;
  background: var(--panel-3);
  border: 1px solid var(--line-soft);
}
.spine-gauge i.on { background: var(--g3); border-color: transparent; }
/* The top of a filled gauge is the live end, so it carries the bright stop. */
.spine-gauge i.on:first-of-type { background: var(--g1); }
.spine:hover .spine-gauge i.on,
.spine:focus-visible .spine-gauge i.on { background: var(--g2); }
.spine-n {
  font-family: var(--mono); font-size: 12px; font-weight: 600;
  color: var(--signal-ink); font-variant-numeric: tabular-nums;
}
.spine.open .spine-edge { opacity: 0; pointer-events: none; }

.spine-face {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  padding: 14px;
  overflow: hidden;
  opacity: 0; pointer-events: none;
  transition: opacity .2s var(--snap);
}
.spine.open .spine-face { opacity: 1; }

.spine-head { display: flex; flex-direction: column; gap: 3px; }
/* The locator: the whole die with this block lit, generated per card. */
.loc-wrap { display: flex; flex-direction: column; gap: 5px; margin-top: 12px; }
.loc { display: block; width: 100%; height: auto; border: 1px solid var(--line-soft); }
.spine-head b {
  font-family: var(--display); font-size: 17px; font-weight: 600; color: var(--text);
}

/* ------------------------------------------------------------- blueprint ---
   The sheet the framework is drawn on. A faint grid, a border, registration
   marks in the corners and a title block along the foot. Without them a
   schematic reads as an illustration; with them it reads as a drawing, which
   is what it is. */

.bp {
  display: flex; flex-direction: column; margin-top: 12px; min-height: 0;
  flex: 1 1 auto;
  border: 1px solid var(--line-2);
  background-color: var(--well);
  background-image:
    linear-gradient(to right, var(--grid) 1px, transparent 1px),
    linear-gradient(to bottom, var(--grid) 1px, transparent 1px);
  background-size: 13px 13px;
}
.bp-reg { stroke: var(--line-2); stroke-width: 1.2; }
.bp-title {
  display: grid; grid-template-columns: 1fr auto auto;
  gap: 0; border-top: 1px solid var(--line-2);
  margin-top: auto;   /* pinned to the foot of the sheet, always visible */
}
.bp-cell {
  display: flex; flex-direction: column; gap: 1px;
  padding: 6px 9px; border-left: 1px solid var(--line-2); min-width: 0;
}
.bp-cell:first-child { border-left: 0; }
.bp-cell b {
  font-family: var(--mono); font-size: 11px; font-weight: 600; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bp-cell .ck { font-size: 8.5px; letter-spacing: .16em; }

/* the frameworks themselves: four kinds of mark and nothing else */
.fw { display: block; width: 100%; height: auto; margin: 0; flex: 0 1 auto; min-height: 0; }
.fw-w { stroke: var(--line-2); stroke-width: 1; }
.fw-hot { stroke: var(--signal); stroke-width: 1.5; }
.fw-dead { stroke: var(--line-soft); stroke-width: 1; stroke-dasharray: 2 3; }
.fw-b { fill: none; stroke: var(--text-2); stroke-width: 1.2; }
.fw-dim { stroke: var(--line-2); }
.fw-cell { fill: var(--panel-3); stroke: var(--line-soft); stroke-width: .8; }
.fw-cell.fw-on { fill: var(--signal); stroke: none; }
.fw-t {
  font-family: var(--mono); font-size: 8.5px; letter-spacing: .08em;
  fill: var(--dim); stroke: none;
}

@media (max-width: 700px) {
  /* No room to fan them out on a phone, so they stand square and scroll. */
  .shelf { perspective: none; }
  /* No room to share on a phone: fixed widths and a horizontal scroll. */
  .shelf { padding: 10px 12px 10px 4px; perspective: none; }
  .spine { transform: none; filter: none; flex: 0 0 132px; height: 400px; }
  /* The framework is 260 units wide; anything narrower than 306 clips it. */
  .spine.open { flex: 0 0 306px; transform: none; }
}
@media (prefers-reduced-motion: reduce) { .spine { transform: none; } }

/* --------------------------------------------------------- the symbols ---
   Three objects seen at an angle, one per section that earns one. The tilt is
   the only perspective on this page besides the shelf, and it is here for the
   same reason: these are objects, not charts.

   Cells lift off their plate in Z. That is what makes a wavefront read as
   something passing over a die rather than a colour changing. */

.symbol {
  perspective: 1000px;
  display: flex; justify-content: center; align-items: center;
  /* A guard: a rotated square's diagonal is 1.41 times its side, so the plate
     is sized against that below. This catches whatever the maths misses. */
  overflow: hidden;
  padding: clamp(30px, 5vw, 62px) 0 clamp(18px, 2vw, 26px);
}

.sym-plate {
  display: grid;
  gap: 5px;
  width: min(360px, 52vw); aspect-ratio: 1;
  transform: rotateX(56deg) rotateZ(-43deg);
  transform-style: preserve-3d;
}
.sym-cell {
  background: var(--panel-3);
  border: 1px solid var(--line-soft);
  /* No transition on the fill: a flip-flop has no in-between value. The lift
     is allowed to move, because a physical thing rising does take time. */
  transition: transform .18s var(--snap), box-shadow .18s var(--snap);
}
/* Printed, and recessive. A wafer fills up behind the lens, but the lens is
   what you are meant to be watching, so the finished fields sit back. */
.sym-cell.set { background: var(--g3); border-color: transparent; opacity: .32; }
/* Whatever is under the lens right now. */
.sym-cell.edge, .sym-cell.head {
  background: var(--g1); border-color: transparent; opacity: 1;
  transform: translateZ(22px);
  box-shadow: 0 14px 26px -8px var(--glow);
}

/* Not everything belongs in the middle of the page. A symbol placed off to
   one side, with its caption held against the opposite margin, reads as a
   thing on a bench rather than as a centred illustration. */
.symbol.aside {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(20px, 4vw, 60px);
  align-items: center;
  justify-content: start;
  padding: clamp(24px, 3.5vw, 48px) 0 clamp(16px, 2vw, 24px);
}
/* object right, words left.
   Perspective applies to a direct child only, so it has to live on the box
   that actually holds the transformed thing, not on .symbol above it. */
.symbol.aside > :first-child {
  grid-column: 2; justify-self: center;
  perspective: 1100px;
  display: flex; align-items: center; justify-content: center;
}
.symbol.aside .sym-cap { grid-column: 1; grid-row: 1; }
/* object left, words right */
.symbol.aside.flip > :first-child { grid-column: 1; }
.symbol.aside.flip .sym-cap { grid-column: 2; }

.sym-cap {
  max-width: 46ch;
  font-size: 14.5px; line-height: 1.65; color: var(--text-2);
}
.sym-cap b { color: var(--text); font-weight: 600; }
.sym-cap .ck { display: block; margin-bottom: 10px; }
.sym-cap .sym-legend {
  display: flex; flex-wrap: wrap; gap: 6px 16px; margin-top: 14px;
}
.sym-cap .sym-legend span {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .08em;
  color: var(--dim); display: inline-flex; align-items: center; gap: 7px;
}
.sym-cap .sym-legend i {
  width: 9px; height: 9px; flex: none;
  border: 1px solid var(--line-2); background: var(--panel-3);
}
.sym-cap .sym-legend i.k-set { background: var(--g3); border-color: transparent; }
.sym-cap .sym-legend i.k-head { background: var(--g1); border-color: transparent; }
.sym-cap .sym-legend i.k-gone { background: transparent; opacity: .35; }

@media (max-width: 820px) {
  .symbol.aside, .symbol.aside.flip {
    grid-template-columns: 1fr;
    justify-items: center;
  }
  .symbol.aside > :first-child,
  .symbol.aside.flip > :first-child { grid-column: 1; grid-row: 1; }
  .symbol.aside .sym-cap,
  .symbol.aside.flip .sym-cap { grid-column: 1; grid-row: 2; text-align: left; }
}

/* § 02 · the skyline ------------------------------------------------------
   Seven towers on a tilted floor, one per block of the processor, each as
   tall as the gates that block really carries. The bricks stack in Z out of
   the floor plane rather than in Y across it, which is the difference between
   a bar chart and a building. */

.sym-floor {
  display: flex; align-items: flex-end; gap: clamp(9px, 1.4vw, 15px);
  width: min(400px, 60vw); height: min(230px, 34vw);
  transform: rotateX(62deg) rotateZ(-38deg);
  transform-style: preserve-3d;
}
.sym-tower {
  position: relative;
  flex: 1 1 0; min-width: 0; aspect-ratio: 1;
  transform-style: preserve-3d;
  /* The footprint: where the block sits on the die, drawn whether or not
     anything is standing on it yet. */
  border: 1px solid var(--line-soft);
  background: var(--panel-2);
}
.sym-brick {
  position: absolute; inset: 0;
  border: 1px solid var(--line-soft);
  background: transparent;
  opacity: 0;
  transition: opacity .12s var(--snap);
}
.sym-brick.set { background: var(--g3); border-color: transparent; opacity: .5; }
.sym-brick.head {
  background: var(--g1); border-color: transparent; opacity: 1;
  box-shadow: 0 12px 22px -10px var(--glow);
}
/* An optimised gate is not dimmed, it is not there. The outline stays for a
   beat so you can see what was taken. */
.sym-brick.gone {
  background: transparent; border-color: var(--line-2);
  opacity: .16;
}

/* § 06 · the consensus ring ----------------------------------------------
   Twelve nodes on a closed circuit, seen at an angle. Nothing counts until
   the result has come all the way round, which is the shape of the argument
   as much as the shape of the object. */

.sym-ring {
  position: relative;
  width: min(320px, 52vw); aspect-ratio: 1;
  transform: rotateX(60deg);
  transform-style: preserve-3d;
}
.sym-node {
  position: absolute; left: 50%; top: 50%;
  width: 30px; height: 30px; margin: -15px 0 0 -15px;
  background: var(--panel-3);
  border: 1px solid var(--line-2);
  transition: box-shadow .16s var(--snap), background-color .1s var(--snap);
}
.sym-node.set { background: var(--g3); border-color: transparent; opacity: .55; }
.sym-node.edge {
  background: var(--g1); border-color: transparent;
  box-shadow: 0 16px 26px -10px var(--glow);
}
.sym-node.agreed {
  background: var(--grad); border-color: transparent;
  box-shadow: 0 10px 20px -12px var(--glow);
}
/* Drawn from the middle outwards, and only when the circuit has closed. */
.sym-spoke {
  position: absolute; left: 50%; top: 50%;
  /* Reaches exactly the node centres: the same 46% the nodes are placed at. */
  width: 46%; height: 1px; margin-top: -0.5px;
  transform-origin: left center;
  background: var(--line-2);
  opacity: 0;
  transition: opacity .18s var(--snap);
}
.sym-spoke.on { background: var(--grad-flat); opacity: .55; }
.sym-hub {
  position: absolute; left: 50%; top: 50%;
  width: 34px; height: 34px; margin: -17px 0 0 -17px;
  border: 1px solid var(--line-2); background: var(--panel);
  transform: translateZ(4px);
  transition: background-color .12s var(--snap);
}
.sym-hub.on {
  background: var(--grad); border-color: transparent;
  transform: translateZ(40px);
  box-shadow: 0 22px 34px -14px var(--glow);
}

@media (max-width: 560px) {
  .sym-node { width: 22px; height: 22px; margin: -11px 0 0 -11px; }
  .sym-hub { width: 26px; height: 26px; margin: -13px 0 0 -13px; }
}
@media (prefers-reduced-motion: reduce) {
  .sym-brick, .sym-node, .sym-spoke, .sym-hub { transition: none; }
  .sym-hub.on { transform: translateZ(4px); }
}

.symbol-cap {
  text-align: center; max-width: 46ch; margin: 0 auto clamp(26px, 3vw, 40px);
  line-height: 1.7;
}

@media (prefers-reduced-motion: reduce) {
  .sym-cell { transition: none; }
  .sym-cell.edge, .sym-cell.head, .sym-cell.gone, .sym-cell.agreed { transform: none; }
}

/* the closing argument, set larger than the body it follows */
.name-body {
  columns: 2; column-gap: clamp(28px, 4vw, 56px);
  margin-bottom: clamp(28px, 3vw, 42px);
}
.name-body .sec-copy { max-width: none; break-inside: avoid; margin-bottom: 14px; }
@media (max-width: 820px) { .name-body { columns: 1; } }

.name-close {
  border: var(--rule); border-left: 0; background: var(--panel);
  padding: 26px 30px; position: relative; box-shadow: var(--lift);
  margin-bottom: clamp(26px, 3vw, 40px);
}
.name-close::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--grad);
}
.name-close p {
  font-family: var(--display); font-size: clamp(19px, 2.2vw, 27px);
  font-weight: 600; line-height: 1.28; color: var(--text); text-wrap: balance;
  margin-bottom: 12px;
}

/* --------------------------------------------------- specification plate ---
   A datasheet header, not a product shot. Rows of fact, then the output port
   drawn as the eight flip-flops it is. */

.plate-id {
  display: flex; align-items: baseline; gap: 12px;
  padding-bottom: 12px; border-bottom: var(--rule);
}
.plate-id b {
  font-family: var(--display); font-size: clamp(20px, 2.4vw, 28px);
  font-weight: 700; color: var(--text); letter-spacing: -.01em;
}
.plate-tick {
  font-family: var(--mono); font-size: 11px; font-weight: 600; letter-spacing: .16em;
  padding: 3px 9px; background: var(--grad); color: var(--on-signal);
  margin-left: auto; white-space: nowrap;
}
.plate-rows { display: grid; }
.plate-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 16px;
  padding: 9px 0; border-bottom: 1px solid var(--line-soft);
}
.plate-row b {
  font-family: var(--mono); font-size: 12.5px; font-weight: 600;
  color: var(--text); text-align: right;
}
.plate-out {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding-top: 14px;
}
.plate-bits { display: flex; gap: 4px; margin-left: auto; }
.plate-bits .bit {
  width: 16px; height: 16px;
  background: var(--panel-3); border: 1px solid var(--line-soft);
}
.plate-bits .bit.on { background: var(--grad); border-color: transparent; }
.plate-out b {
  font-family: var(--mono); font-size: 17px; font-weight: 600;
  color: var(--signal-ink); font-variant-numeric: tabular-nums; min-width: 30px;
  text-align: right;
}

/* ---------------------------------------------------------------- tables */

.tw { overflow-x: auto; border: var(--rule); background: var(--panel); }
table { border-collapse: collapse; width: 100%; font-family: var(--mono); font-size: 12.5px; min-width: 540px; }
th {
  text-align: left; font-size: 10px; font-weight: 600; letter-spacing: .16em;
  text-transform: uppercase; color: var(--dim);
  padding: 12px 14px; border-bottom: var(--rule); background: var(--panel-2);
  white-space: nowrap;
}
td { padding: 8px 14px; border-bottom: 1px solid var(--line-soft); color: var(--text-2); vertical-align: top; }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover td { background: var(--tint-soft); }
td.op { color: var(--signal-ink); font-weight: 600; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
td b { color: var(--text); font-weight: 600; }

/* ---------------------------------------------------------------- pipeline */

/* A pipeline is a sequence, so it is drawn as one: a single rail runs the
   width of the strip and every step hangs a numbered marker off it. The
   numbers are not decoration here, they are the order the stages happen in. */
.pipe {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr));
  gap: 1px; background: var(--line); border: var(--rule);
  position: relative; box-shadow: var(--lift);
}
.pipe::before {
  content: ""; position: absolute; left: 0; right: 0; top: -1px; height: 2px;
  background: var(--grad-flat); z-index: 1;
}
.pipe > div {
  background: var(--panel); padding: 26px 20px 24px; position: relative;
  transition: background-color .2s ease;
}
.pipe > div:hover { background: var(--panel-2); }
/* the marker sits on the rail, half in the panel and half out of it */
.pipe .n {
  position: absolute; top: -1px; left: 20px;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 26px; height: 15px; padding: 0 6px;
  font-family: var(--mono); font-size: 9.5px; font-weight: 600;
  letter-spacing: .14em;
  background: var(--grad); color: var(--on-signal);
}
.pipe h4 { font-size: 18px; font-weight: 600; margin: 12px 0 6px; }
.pipe p { font-size: 13.5px; color: var(--text-2); line-height: 1.5; }

/* ---------------------------------------------------------------- code */

pre {
  margin: 0; overflow-x: auto; background: var(--well); border: var(--rule);
  padding: 16px 18px; font-family: var(--mono); font-size: 12.5px; line-height: 1.65;
  color: var(--text-2);
}
pre .c { color: var(--dim); }
pre .k { color: var(--signal-ink); }

/* ---------------------------------------------------------------- roadmap */

/* The mark as our CLI prints it. Terminal cells are twice as tall as they
   are wide, so a square needs two characters, and the whole block sits on the
   well background because that is what a terminal is. */

.ansi {
  border: 1px solid var(--line-2); background: var(--well);
  padding: 16px 18px; margin-bottom: 14px;
  font-family: var(--mono); font-size: 13px; line-height: 1.28;
  overflow-x: auto;
}
.ansi-row { display: flex; align-items: baseline; gap: 22px; white-space: pre; }
.ansi-art { flex: none; letter-spacing: 0; }
.ansi-c { font-style: normal; color: transparent; }
/* The finished fields sit back; the edge is the thing you are watching. Same
   relationship as the moving symbols, in a font. */
.ansi-c.set { color: var(--g3); }
.ansi-c.edge { color: var(--g1); }
.ansi-txt { min-width: 0; }
.ansi-name { color: var(--signal-ink); font-weight: 600; letter-spacing: .12em; }
.ansi-edge { color: var(--signal-ink); font-weight: 600; letter-spacing: .1em; }
.ansi-dim { color: var(--dim); }

/* ------------------------------------------------- run it somewhere else ---
   A tabbed transcript. The tabs are the same chips used everywhere else, and
   the body is a real terminal transcript rather than terminal-shaped art, so
   it gets the well background and nothing more decorative than that. */

.runit {
  border: var(--rule); background: var(--panel); box-shadow: var(--lift);
  margin-bottom: 22px;
}
.runit-head {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 12px 16px; border-bottom: var(--rule); background: var(--panel-2);
}
.runit-head .chips { margin-left: auto; }
.runit-body { padding: 16px; }
.runit-code {
  border: 1px solid var(--line-2); background: var(--well);
  padding: 16px 18px; margin: 0;
  font-family: var(--mono); font-size: 12.5px; line-height: 1.75;
  color: var(--text-2); overflow-x: auto;
  white-space: pre;
}
/* A <pre> already is the code block. The inline <code> chip styling would
   otherwise paint a tinted box behind every line of the transcript. */
.runit-code code {
  background: none; border: 0; padding: 0; color: inherit;
  font-size: inherit; font-family: inherit;
}

.runit-note {
  margin-top: 12px; font-size: 13px; color: var(--dim); line-height: 1.55;
  max-width: 80ch;
}
@media (max-width: 620px) {
  .runit-head .chips { margin-left: 0; }
  .runit-code { font-size: 11.5px; padding: 12px 13px; }
}

/* ------------------------------------------------------------ roadmap ---
   The old version was four equal cards, which said the releases were four
   equal things and hid every deliverable inside a paragraph. This is a rail
   with a position on it and a checklist under each stop, because the useful
   question a reader has is not "what is planned" but "what exists today".

   The rail first: a scale with the current release marked. */

.rail { margin-bottom: clamp(26px, 3vw, 38px); position: relative; }
.rail-line {
  height: 2px; background: var(--line-2); position: relative;
  margin: 0 0 14px;
}
/* One fifth, because T-0 is the first of five. It is a position, not a
   percentage of anything, so it does not animate or creep. */
.rail-fill {
  position: absolute; left: 0; top: 0; bottom: 0; width: 10%;
  background: var(--grad-flat);
}
.rail-stops {
  display: grid; grid-template-columns: repeat(5, 1fr);
  gap: 8px;
}
.rail-stop {
  display: flex; flex-direction: column; gap: 3px;
  position: relative; padding-top: 2px;
}
.rail-stop::before {
  content: ""; position: absolute; top: -22px; left: 0;
  width: 9px; height: 9px;
  background: var(--panel); border: 2px solid var(--line-2);
}
.rail-stop.is-at::before { background: var(--signal); border-color: var(--signal); }
.rail-stop b {
  font-family: var(--mono); font-size: 11px; font-weight: 600;
  letter-spacing: .16em; color: var(--dim);
}
.rail-stop.is-at b { color: var(--signal-ink); }
.rail-stop span { font-size: 12px; color: var(--dim); line-height: 1.35; }
.rail-stop.is-at span { color: var(--text-2); }
@media (max-width: 700px) {
  .rail-stops { grid-template-columns: repeat(5, 1fr); gap: 4px; }
  .rail-stop span { display: none; }
}

/* The releases themselves: a rail down the left, one row per release. */

.rmap { display: flex; flex-direction: column; }
.rmap-row {
  display: grid; grid-template-columns: 72px minmax(0, 1fr);
  gap: clamp(14px, 2vw, 26px);
  padding: 0 0 clamp(24px, 3vw, 36px);
}
.rmap-row:last-child { padding-bottom: 0; }

/* The vertical line is drawn by the marker column, so it stops with the last
   release instead of running off into the footer. */
.rmap-rail {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  position: relative;
}
.rmap-row:not(:last-child) .rmap-rail::after {
  content: ""; position: absolute; top: 26px; bottom: -36px; width: 1px;
  background: var(--line-2);
}
.rmap-dot {
  width: 14px; height: 14px; flex: none;
  background: var(--panel); border: 2px solid var(--line-2);
  margin-top: 5px;
}
.rmap-row.is-at .rmap-dot {
  background: var(--grad); border-color: transparent;
  box-shadow: 0 0 0 4px var(--tint);
}
.rmap-tag {
  font-family: var(--mono); font-size: 11px; font-weight: 600;
  letter-spacing: .16em; color: var(--dim);
}
.rmap-row.is-at .rmap-tag { color: var(--signal-ink); }

/* A folded release is a closed drawer, not a hidden section: the summary
   stays a full-width target and the body is what opens. */
.rmap-body {
  border: var(--rule); background: var(--panel); box-shadow: var(--lift);
  position: relative; min-width: 0;
}
.rmap-row.is-at .rmap-body { border-color: var(--signal-2); }
.rmap-body::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--grad); transform: scaleY(0); transform-origin: top;
  transition: transform .28s var(--latch);
  z-index: 1;
}
.rmap-body[open]::before,
.rmap-row.is-at .rmap-body::before,
.rmap-body:hover::before { transform: scaleY(1); }

.rmap-head {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: clamp(15px, 1.8vw, 20px) clamp(18px, 2.2vw, 26px);
  cursor: pointer; list-style: none;
  transition: background-color .14s var(--snap);
}
.rmap-head::-webkit-details-marker { display: none; }
.rmap-head:hover { background: var(--panel-2); }
.rmap-body[open] .rmap-head { border-bottom: 1px solid var(--line-soft); }
.rmap-head:focus-visible { outline: 2px solid var(--signal); outline-offset: -3px; }

/* The open/shut indicator. It has two states and no in-between, which is the
   same rule every other moving thing on this page follows. */
.rmap-mark {
  width: 15px; height: 15px; flex: none; position: relative;
  border: 1px solid var(--line-2);
}
.rmap-mark::before, .rmap-mark::after {
  content: ""; position: absolute; background: var(--text-2);
  left: 50%; top: 50%;
}
.rmap-mark::before { width: 7px; height: 1px; transform: translate(-50%, -50%); }
.rmap-mark::after {
  width: 1px; height: 7px; transform: translate(-50%, -50%);
  transition: transform .16s var(--snap);
}
.rmap-body[open] .rmap-mark::after { transform: translate(-50%, -50%) scaleY(0); }
.rmap-body[open] .rmap-mark { border-color: var(--signal); }
.rmap-body[open] .rmap-mark::before { background: var(--signal-ink); }

.rmap-fold { padding: clamp(16px, 2vw, 22px) clamp(18px, 2.2vw, 26px) clamp(18px, 2.2vw, 24px); }

/* A row of controls above the list, so a reader who wants all of it at once
   does not have to click five times. */
.rmap-tools {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin-bottom: 16px;
}
.rmap-head h4 {
  font-family: var(--display); font-size: clamp(19px, 2vw, 23px);
  font-weight: 600; color: var(--text);
}
/* The state chip is the one place colour is spent in this section, and it
   means exactly one thing: how real is this. */
.rmap-state {
  margin-left: auto;
  font-family: var(--mono); font-size: 9.5px; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase;
  padding: 4px 8px;
  border: 1px solid var(--line-2); color: var(--dim);
  white-space: nowrap;
}
.rmap-state.building {
  border-color: var(--signal); background: var(--tint); color: var(--signal-ink);
}
.rmap-state.part { border-color: var(--tint-line); color: var(--signal-ink); }
.rmap-state.research { border-style: dashed; }

.rmap-fold > p {
  font-size: 14.5px; color: var(--text-2); line-height: 1.6;
  max-width: 72ch; margin-bottom: 16px;
}

/* The deliverables. A filled marker means it exists today; an open one means
   it does not, and that distinction is the entire value of the list. */
.rmap-list {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: 1px; background: var(--line-soft);
  border-top: 1px solid var(--line-soft);
}
.rmap-list li {
  background: var(--panel);
  padding: 10px 12px 10px 30px;
  position: relative;
  font-size: 13.5px; line-height: 1.5; color: var(--dim);
}
.rmap-list li::before {
  content: ""; position: absolute; left: 12px; top: 15px;
  width: 8px; height: 8px;
  border: 1px solid var(--line-2); background: transparent;
}
.rmap-list li.done { color: var(--text-2); }
.rmap-list li.done::before { background: var(--grad); border-color: transparent; }
.rmap-list li code {
  font-size: 12px; padding: 1px 4px;
}

@media (max-width: 620px) {
  .rmap-row { grid-template-columns: 40px minmax(0, 1fr); gap: 12px; }
  .rmap-tag { font-size: 10px; letter-spacing: .1em; }
  .rmap-head { gap: 8px; }
  .rmap-state { margin-left: 0; }
}

/* ---------------------------------------------------------------- note */

.note {
  border: var(--rule); border-left: 0;
  background: var(--panel); padding: 16px 20px;
  font-size: 15px; color: var(--text-2);
  position: relative;
  box-shadow: var(--lift);
}
.note::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--grad);
}
.note b { color: var(--text); }
.note.warn::before { background: var(--amber); }

/* ---------------------------------------------------------------- footer */

/* ---------------------------------------------------------------- footer ---
   Where somebody who has read the page decides what to do next, so it carries
   destinations rather than a signature. Four columns on a desk, one on a
   phone, and a base line that states the part and the chain because those are
   the two facts a reader is most likely to want to copy. */

footer { padding: clamp(44px, 6vw, 72px) 0 clamp(40px, 5vw, 60px); }

.foot-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) repeat(3, minmax(0, 1fr));
  gap: clamp(26px, 4vw, 56px);
  align-items: start;
}
@media (max-width: 900px) {
  .foot-grid { grid-template-columns: 1fr 1fr; }
  .foot-brand { grid-column: 1 / -1; }
}
@media (max-width: 520px) {
  .foot-grid { grid-template-columns: 1fr; gap: 26px; }
}

.foot-brand { display: flex; flex-direction: column; gap: 16px; align-items: flex-start; }
.foot-logo { width: clamp(180px, 20vw, 240px); height: auto; display: block; }
.foot-line {
  font-size: 13.5px; line-height: 1.6; color: var(--text-2); max-width: 40ch;
}
.foot-social { display: flex; gap: 8px; flex-wrap: wrap; }

.foot-col { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.foot-col .ck { margin-bottom: 2px; }
.foot-col a {
  font-size: 13.5px; color: var(--text-2); text-decoration: none;
  width: fit-content;
  border-bottom: 1px solid transparent;
  transition: color .12s var(--snap), border-color .12s var(--snap);
}
.foot-col a:hover { color: var(--signal-ink); border-bottom-color: var(--signal); }

.foot-rule {
  height: 1px; background: var(--line-soft);
  margin: clamp(28px, 4vw, 44px) 0 clamp(16px, 2vw, 22px);
}
.foot-base {
  display: flex; flex-wrap: wrap; gap: 10px 26px; align-items: center;
}
.foot-base .grow { margin-left: auto; }

/* A button that is only an icon still needs to be a target. */
.btn-icon { padding: 9px 11px; }
.btn-icon svg { width: 15px; height: 15px; display: block; }
.btn-ghost svg { width: 14px; height: 14px; flex: none; }
.btn-ghost:has(svg) { display: inline-flex; align-items: center; gap: 8px; }
/* A destination that is not set up yet stays legible.
   Fading it to 45% made it invisible against a light bar, so the page looked
   as though it had no social links rather than links that are coming. It keeps
   full contrast and simply does not respond. */
.btn:disabled {
  cursor: default;
  border-style: dashed;
  color: var(--text-2);
}
.btn:disabled:hover {
  transform: none;
  border-color: var(--line-2);
  color: var(--text-2);
  background: transparent;
}
.btn-icon:disabled svg { opacity: .8; }

/* ---------------------------------------------------------------- boot */

/* One short power-on sweep. Everything is legible before it runs and after
   it finishes: it only adds a pass of light, it never hides content. */
.boot { position: relative; }
.boot::after {
  content: ""; position: absolute; inset: 0; z-index: 5; pointer-events: none;
  background: linear-gradient(180deg, transparent 0%, var(--glow) 48%, transparent 55%);
  animation: sweep 1s steps(9, end) 1 forwards;
}
@keyframes sweep {
  from { transform: translateY(-100%); opacity: .9; }
  to   { transform: translateY(100%);  opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  .drawline::after { transform: scaleX(1); }
  .rise { opacity: 1; transform: none; }
}

/* ------------------------------------------------------- touch targets ---
   Measured on a 390px viewport: the theme switch came out 30x24, the chips
   57x30 and the small buttons 34 tall. All of those are misses under a thumb,
   so on a touch pointer every control grows to at least forty. */

@media (pointer: coarse), (max-width: 760px) {
  .btn-sm { padding: 12px 16px; }
  .chip { padding: 11px 14px; }
  .themer { --sw: 46px; --sh: 40px; }
  .topnav a { padding-top: 7px; padding-bottom: 7px; }
  .isa-op { padding-top: 12px; padding-bottom: 12px; }
  .field input, .field select { padding: 13px 14px; }
  .rmap-head { padding-top: 16px; padding-bottom: 16px; }
  .brandmark { padding: 6px 0; }
  #wb-run, #wb-stop, #wb-reset { padding: 13px 18px; }
}


/* -------------------------------------------------------- the strategy desk -
   A chart the reader chose to load, the rule the chip is running, and the tape
   that came off its output port. The chart is the widest thing on the page, so
   it takes the left column and the two panels stack beside it. */

.desk {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: 20px;
  align-items: start;
}
@media (max-width: 940px) { .desk { grid-template-columns: minmax(0, 1fr); } }
/* Grid items default to min-width:auto, which is what lets a wide table shove
   the page sideways instead of scrolling inside its own wrapper. */
.desk > *, .desk .panel, .desk .stack { min-width: 0; }

.desk-chart { display: flex; flex-direction: column; min-height: 460px; }
.tv-wrap { flex: 1; display: flex; min-height: 420px; position: relative; }
.tv-box { flex: 1; min-height: 420px; }
.tv-box iframe { width: 100% !important; height: 100% !important; border: 0; }

/* Until somebody asks for it, the panel says what pressing the button costs. */
.tv-hold {
  flex: 1; display: flex; flex-direction: column; gap: 12px;
  align-items: center; justify-content: center; text-align: center;
  padding: 32px 24px;
  background:
    repeating-linear-gradient(90deg, var(--line-soft) 0 1px, transparent 1px 34px),
    repeating-linear-gradient(0deg, var(--line-soft) 0 1px, transparent 1px 34px);
}
.tv-hold .hint { max-width: 34ch; }

/* The bus: converter, die, controller. Three stages and two arrows, because
   that is genuinely the whole architecture. */
.bus {
  display: grid;
  grid-template-columns: 1fr auto 1fr auto 1fr;
  gap: 0 14px; align-items: stretch;
  margin-bottom: 18px;
}
.bus-stage {
  border: 1px solid var(--line-2);
  background: var(--panel);
  padding: 16px 18px 18px;
  display: flex; flex-direction: column; gap: 7px;
}
.bus-stage.is-core {
  border-color: var(--signal);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--signal) 22%, transparent);
}
.bus-stage b { font-size: 15px; letter-spacing: -.01em; }
.bus-stage p { font-size: 13px; line-height: 1.62; color: var(--text-2); margin: 0; }
.bus-arrow { align-self: center; width: 26px; height: 1px; background: var(--line); position: relative; }
.bus-arrow::after {
  content: ""; position: absolute; right: 0; top: -3px;
  border: 3.5px solid transparent; border-left-color: var(--line);
}
@media (max-width: 860px) {
  .bus { grid-template-columns: 1fr; gap: 10px; }
  .bus-arrow { width: 1px; height: 18px; justify-self: center; background: var(--line); }
  .bus-arrow::after { right: -3px; top: auto; bottom: 0; border: 3.5px solid transparent; border-top-color: var(--line); }
}
.bus-foot { margin: 0 0 22px; max-width: 78ch; }

/* The source the chip is actually running, shown rather than described. */
.sd-src {
  margin: 0; padding: 14px 16px;
  border: 1px solid var(--line-soft);
  background: var(--well);
  font: 12px/1.55 var(--mono);
  color: var(--text-2);
  max-height: 260px; overflow: auto;
  white-space: pre; tab-size: 8;
}
.sd-src::-webkit-scrollbar { width: 8px; height: 8px; }
.sd-src::-webkit-scrollbar-thumb { background: var(--line-2); }

/* One row per decision. The verb carries the colour; nothing else needs to. */
.sd-tape thead th { padding: 8px 10px; font-size: 9.5px; }
#sd-tape td { padding: 5px 10px; font-size: 12px; border-bottom: 1px solid var(--line-soft); }
#sd-tape td.dim { color: var(--dim); }
#sd-tape tr:last-child td { border-bottom: 0; }
.act {
  display: inline-block; min-width: 42px; text-align: center;
  padding: 2px 7px; font: 600 10.5px/1.5 var(--mono);
  letter-spacing: .07em; border: 1px solid var(--line-2); color: var(--dim);
}
.act.a1 { border-color: var(--signal); background: var(--signal); color: var(--signal-ink); }
.act.a2 { border-color: var(--amber); color: var(--amber); }

/* --------------------------------------------------------------- the wallet -
   A connect button that answers two questions before it is ever used: which
   chain this is, and whether you are on it. Connected is a quiet state, so it
   is the accent; the wrong chain is the only thing here worth an alarm. */

.top-wallet { display: flex; align-items: center; }
.btn-wallet {
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: .04em;
  white-space: nowrap;
}
.btn-wallet.is-on {
  border-color: color-mix(in srgb, var(--signal) 55%, var(--line-2));
  color: var(--signal-ink);
  background: color-mix(in srgb, var(--signal) 14%, transparent);
}
.btn-wallet.is-wrong {
  border-color: var(--amber);
  color: var(--amber);
  background: color-mix(in srgb, var(--amber) 12%, transparent);
}
@media (max-width: 900px) { .top-wallet { order: 3; } }

/* The top bar has room for the wallet button on a desk and not on a phone,
   where it was 122px wide in a row that had none to give. Below that width the
   hero carries it instead, next to the other things you might actually do. */
.hero-wallet { display: none; }
@media (max-width: 900px) {
  .top-wallet { display: none; }
  .hero-wallet { display: flex; }
}


/* ------------------------------------------------------------ the way back -
   The launchpad is a second page and needs an exit that reads as one. The
   brandmark already links home, but a logo is something people learn is
   clickable rather than something they see is. */

.btn-back {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: none;
  padding-left: 9px;
}
.btn-back svg { width: 14px; height: 14px; flex: none; }
.btn-back:hover svg { transform: translateX(-2px); }
.btn-back svg { transition: transform .12s var(--snap); }
@media (max-width: 760px) { .btn-back span { display: none; } .btn-back { padding: 9px 10px; } }

/* The top bar carries the mark, the way back, two social buttons, the wallet
   and the theme switch. On a narrow phone that row runs out of width, and the
   first thing to go is the wordmark: the framed mark still identifies the
   page, and every button stays reachable. */
@media (max-width: 560px) {
  .brandmark b { display: none; }
  .brandmark { gap: 0; }
}

/* Nav items break across lines before the bar admits it is full, which reads
   as a layout accident rather than a dense bar. Each item stays on one line
   and the row is allowed to be tight instead. */
.topnav a { white-space: nowrap; }
