/* Front Panel — Altair 8800
   The panel scales as one object: every dimension is in em, and the chassis
   font-size is the single fluid knob. Change that one clamp and the whole
   machine resizes without anything drifting out of alignment. */

:root {
  --page: #0b0e11;
  --page-raised: #141a20;
  --ink: #e7edf3;
  --ink-dim: #9aa7b4;
  --ink-faint: #8794a1;
  --rule: #232c35;
  --accent: #ff4b3e;
  --accent-cool: #6fb3d2;
  /* Button fill. The bright accent only reaches 3.3:1 against white. */
  --accent-solid: #d92e20;
  --accent-solid-hover: #c62a1d;

  /* Sampled off a head-on photograph of an original: the case frame is a
     medium slate blue and the panel it surrounds is neutral dark charcoal.
     The render used to have these the other way round. */
  --chassis-hi: #4d8cc0;
  --chassis-lo: #2d6698;
  --panel-hi: #3e413f;
  --panel-lo: #303331;
  --silkscreen: #eceee4;

  --lamp-off: #401518;
  --lamp-rim: #1b0a0c;
  --lamp-on: 255, 62, 48;

  --chrome-hi: #eef1f4;
  --chrome-mid: #b9c0c8;
  --chrome-lo: #79828c;

  --font-ui: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font-family: var(--font-ui);
  line-height: 1.55;
  overflow-x: hidden;
}

a { color: var(--accent-cool); }
a:hover { color: #9fd0e6; }

.skip {
  position: absolute;
  left: -9999px;
}
.skip:focus {
  left: 1rem;
  top: 1rem;
  z-index: 10;
  background: var(--accent-solid);
  color: #fff;
  padding: 0.5rem 0.9rem;
  border-radius: 4px;
}

/* ---------- masthead ---------- */

.masthead {
  max-width: 62rem;
  margin: 0 auto;
  padding: clamp(1.75rem, 5vw, 3.5rem) 1.25rem 1rem;
  text-align: center;
}

.masthead h1 {
  font-size: clamp(2rem, 7vw, 3.4rem);
  line-height: 1.03;
  margin: 0 0 0.5em;
  letter-spacing: -0.02em;
  font-weight: 650;
}

.standfirst {
  margin: 0 auto;
  max-width: 34em;
  color: var(--ink-dim);
  font-size: clamp(0.98rem, 2.1vw, 1.12rem);
  text-wrap: balance;
}

/* ---------- the machine ---------- */

.stage {
  padding: clamp(0.75rem, 2.5vw, 2rem) clamp(0.5rem, 2vw, 1.5rem) 0.5rem;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.chassis {
  /* The one knob. Everything below is in em. */
  font-size: clamp(7.2px, 1.16vw, 13.5px);
  --col: 2.2em;
  --col-wide: 4em;
  /* Extra air at each octal group boundary, the way the panel is laid out. */
  --octal-gap: 0.55em;
  width: 100%;
  max-width: 84em;
  padding: 1.5em 1.5em 0;
  border-radius: 0.7em;
  background:
    linear-gradient(178deg, var(--chassis-hi), var(--chassis-lo) 55%, #27587f);
  box-shadow:
    0 0.1em 0 rgba(255,255,255,0.28) inset,
    0 -0.25em 0.6em rgba(0,0,0,0.35) inset,
    0 1.6em 3.4em rgba(0,0,0,0.62);
  position: relative;
}

/* Brushed metal, cheap and convincing at this scale. */
.chassis::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0.09;
  background: repeating-linear-gradient(
    92deg,
    rgba(255,255,255,0.5) 0 1px,
    rgba(0,0,0,0.28) 1px 2px
  );
  mix-blend-mode: overlay;
}

/* The badge: a brushed aluminium strip across the bottom of the case, black
   lettering, MITS logo at the left and the model name beside it. */
.chassis-badge {
  display: flex;
  align-items: center;
  gap: 0.9em;
  margin: 1.5em -1.5em 0;
  padding: 0.45em 1.8em 0.5em;
  border-radius: 0 0 0.55em 0.55em;
  background:
    repeating-linear-gradient(90deg,
      rgba(255,255,255,0.16) 0 1px, rgba(0,0,0,0.09) 1px 3px),
    linear-gradient(180deg, #c2c5c0, #949893 48%, #aeb1ac);
  box-shadow:
    0 0.09em 0 rgba(255,255,255,0.55) inset,
    0 -0.12em 0.3em rgba(0,0,0,0.28) inset;
}

.brand-mits {
  display: inline-flex;
  align-items: center;
  font-weight: 700;
  font-size: 0.85em;
  letter-spacing: 0.02em;
  color: #16191c;
}

/* The slash that trails the MITS wordmark on the original badge. */
.brand-slash {
  display: inline-block;
  width: 0.75em;
  height: 0.85em;
  margin-left: 0.12em;
  background: #16191c;
  clip-path: polygon(62% 0, 100% 0, 38% 100%, 0 100%);
}

.brand-model {
  font-size: 1.18em;
  font-weight: 700;
  letter-spacing: 0.05em;
  color: #191c1a;
  text-transform: uppercase;
}

.panel {
  background: linear-gradient(176deg, var(--panel-hi), var(--panel-lo));
  border-radius: 0.35em;
  padding: 1.5em 1.5em 1.35em;
  box-shadow:
    0 0 0 0.09em rgba(0,0,0,0.45),
    0 0.5em 1.4em rgba(0,0,0,0.4) inset;
}

/* ---------- lamps ---------- */

.lamp-deck { display: flex; flex-direction: column; gap: 1.5em; }

.lamp-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 2em;
}

.lamp-left { display: flex; gap: 1.6em; }

.lamp-right {
  display: flex;
  justify-content: flex-end;
}

/* The DATA lamps are eight wide and share the right edge with ADDRESS, so
   D7..D0 land directly above A7..A0 — and above the data switches that
   write them. That vertical relationship is the whole point of the layout,
   and it falls out of both rows being right-aligned with the same columns. */

.lamp-group { position: relative; }

.lamp-group-label {
  text-align: center;
  font-size: 0.82em;
  letter-spacing: 0.28em;
  color: var(--silkscreen);
  opacity: 0.85;
  text-transform: uppercase;
}

/* STATUS sits under its lamps, hung from a rule that spans the group. Only the
   eight bus status lamps are inside the bracket; INTE and PROT are their own
   group and stay outside it, as on the panel. */
.lamp-group-label.is-under {
  margin-top: 0.55em;
  padding-top: 0.4em;
  border-top: 0.085em solid var(--silkscreen);
  opacity: 0.7;
  letter-spacing: 0.22em;
  font-size: 0.72em;
}

/* No DATA or ADDRESS heading is printed over the lamps on the real panel.
   These exist only for the wrapped narrow layout. */
.lamp-group-label.is-wrap-only { display: none; }

.lamp-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.42em;
  width: var(--col);
  flex: 0 0 auto;
  position: relative;
}

/* Dashed leader lines run down the panel from each data lamp to the address
   lamp below it, and on down to the switch that writes it. They are the most
   recognisable thing printed on an Altair and were missing entirely. */
.lamp-cell.has-leader::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  width: 0.085em;
  margin-left: -0.04em;
  background: repeating-linear-gradient(
    180deg,
    var(--silkscreen) 0 0.28em,
    transparent 0.28em 0.62em
  );
  opacity: 0.5;
  pointer-events: none;
}

/* Data lamps lead to the address row: the gap between the two lamp rows. */
.lamp-row:first-child .lamp-cell.has-leader::after { height: 1.5em; }

/* Address lamps lead on down to the numbers above the switches, stopping just
   short of the rule those numbers sit on. */
.lamp-row:last-child .lamp-cell.has-leader::after { height: 4.55em; }

.lamp {
  width: 1.15em;
  height: 1.15em;
  border-radius: 50%;
  background:
    radial-gradient(circle at 34% 30%, rgba(255,255,255,0.22), transparent 55%),
    var(--lamp-off);
  box-shadow:
    0 0 0 0.16em var(--lamp-rim),
    0 0.06em 0.12em rgba(0,0,0,0.6) inset;
  position: relative;
}

/* --glow is written by the renderer, 0..1, from the bit's duty cycle. */
.lamp::after {
  content: "";
  position: absolute;
  inset: -0.06em;
  border-radius: 50%;
  opacity: var(--glow, 0);
  background: radial-gradient(circle at 40% 34%,
    rgba(255,235,232,0.98), rgba(var(--lamp-on), 0.98) 42%, rgba(var(--lamp-on), 0.5) 72%, transparent 88%);
  box-shadow:
    0 0 0.55em rgba(var(--lamp-on), 0.85),
    0 0 1.5em rgba(var(--lamp-on), 0.42);
}

.lamp-label {
  font-size: 0.76em;
  letter-spacing: 0.06em;
  color: var(--silkscreen);
  opacity: 0.82;
  white-space: nowrap;
}

/* ---------- switches ---------- */

/* No rule divides the lamps from the switches on the real panel: the leader
   lines cross that space instead. */
.switch-deck {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 2em;
  margin-top: 3.2em;
}

.switch-left { display: flex; gap: 0.35em; }

.switch-right-wrap { display: flex; flex-direction: column; }

/* Two banks of eight sit shoulder to shoulder and read as the original's
   single row of sixteen. On a phone they separate into the two halves people
   already name: sense switches and data switches. */
.switch-banks { display: flex; justify-content: flex-end; position: relative; }

/* The two words sit in the strip of panel reserved to the right of the
   switches, each one level with the rule it names. */
/* The pair straddles the rules: Data above the upper one, Address below the
   lower one, which is the only way two words fit either side of a 0.29em gap.
   Offsets are in the legend's own em, tuned against the measured rule heights. */
.rule-legend {
  position: absolute;
  left: 100%;
  bottom: 1.37em;
  padding-left: 0.6em;
  display: flex;
  flex-direction: column;
  font-size: 0.68em;
  line-height: 1;
  color: var(--silkscreen);
  opacity: 0.72;
  white-space: nowrap;
}
.rule-legend-data { margin-bottom: 0.43em; }

.switch-right {
  display: flex;
  justify-content: flex-end;
}

.switch-cell { position: relative; }

/* ---- the silkscreen around the sense switches ----
   A rule runs the width of the bank, broken around each switch number, and a
   second rule below the switches is broken into one segment per octal digit.
   Under bits 7-0 that lower rule doubles: the upper of the pair is Data, the
   lower is Address. All of it was missing; the grouping was being carried by
   invented vertical dividers instead. */

/* The captions are flex items in a centred column, so they shrink to their
   text. The rules are drawn on them and have to span the whole switch column,
   which means stretching them first — an empty caption is 0px wide otherwise. */
.switch-cell[data-bit] .switch-caption-up,
.switch-cell[data-bit] .switch-caption-down { align-self: stretch; }

.switch-cell[data-bit] .switch-caption-up { position: relative; }

.switch-cell[data-bit] .switch-caption-up::before,
.switch-cell[data-bit] .switch-caption-up::after {
  content: "";
  position: absolute;
  bottom: 0.58em;
  height: 0.085em;
  background: var(--silkscreen);
  opacity: 0.55;
}
.switch-cell[data-bit] .switch-caption-up::before { left: 0; right: calc(50% + 0.95em); }
.switch-cell[data-bit] .switch-caption-up::after { left: calc(50% + 0.95em); right: 0; }

.switch-cell[data-bit] .switch-caption-down { position: relative; }

/* The address rule, and above it the data rule on the low eight only. */
.switch-cell[data-bit] .switch-caption-down::before,
.is-data-bank .switch-cell[data-bit] .switch-caption-down::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 0.085em;
  background: var(--silkscreen);
  opacity: 0.55;
}
.switch-cell[data-bit] .switch-caption-down::before { top: 0.72em; }
.is-data-bank .switch-cell[data-bit] .switch-caption-down::after { top: 0.30em; }

/* The bus reads in octal, so lamps and switches alike break into groups of
   three counting up from bit 0, with bit 15 alone at the top. Lamps and
   switches take the same break, which is what keeps each lamp in column with
   the switch that writes it. */
.lamp-cell.octal-break,
.switch-cell.octal-break { margin-left: var(--octal-gap); }

/* The octal digits span the same groups, so they take the same offsets. */
.octal-digit:nth-child(n+2) { margin-left: var(--octal-gap); }

/* Break the lower rules into one segment per octal group. */
.switch-cell.octal-break .switch-caption-down::before,
.switch-cell.octal-break .switch-caption-down::after { left: 0.5em; }

/* The number rule runs left towards the sense-switch legend but stops at bit 0,
   rather than trailing off the end of the bank. */
.switch-cell[data-bit="0"] .switch-caption-up::after { display: none; }

.switch-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35em;
  width: var(--col);
  flex: 0 0 auto;
  /* Touch target is deliberately larger than the visible switch. */
  padding: 0.5em 0;
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

.switch-cell.wide { width: var(--col-wide); }

.switch-caption-up,
.switch-caption-down {
  font-size: 0.7em;
  letter-spacing: 0.04em;
  color: var(--silkscreen);
  opacity: 0.8;
  text-align: center;
  line-height: 1.2;
  white-space: pre-line;
  display: flex;
  justify-content: center;
}
.switch-caption-up { min-height: 2.3em; align-items: flex-end; }
.switch-caption-down { min-height: 2.3em; align-items: flex-start; }

.switch-well {
  width: 1.5em;
  height: 2.5em;
  border-radius: 0.35em;
  background: linear-gradient(180deg, #1a222a, #2c3742);
  box-shadow: 0 0.08em 0.2em rgba(0,0,0,0.65) inset;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}

.switch-paddle {
  width: 0.82em;
  height: 1.85em;
  border-radius: 0.42em;
  background: linear-gradient(96deg, var(--chrome-hi), var(--chrome-mid) 45%, var(--chrome-lo));
  box-shadow:
    0 0.1em 0.25em rgba(0,0,0,0.55),
    0 0 0 0.05em rgba(0,0,0,0.35);
  transform-origin: 50% 50%;
  transition: transform 90ms cubic-bezier(0.2, 0.9, 0.3, 1.4);
  transform: translateY(0.42em) scaleY(0.88);
}

.switch-cell[data-position="up"] .switch-paddle {
  transform: translateY(-0.42em) scaleY(0.88);
}
.switch-cell[data-position="center"] .switch-paddle {
  transform: translateY(0) scaleY(1);
}

.switch-cell:focus-visible { outline: 0.14em solid var(--accent-cool); outline-offset: 0.15em; border-radius: 0.3em; }

/* Guided mode points at the switch you need next. */
.switch-cell.is-called-out {
  border-radius: 0.3em;
  box-shadow: 0 0 0 0.12em var(--accent), 0 0 1.2em rgba(255,75,62,0.55);
  animation: callout 1.6s ease-in-out infinite;
}

@keyframes callout {
  0%, 100% { box-shadow: 0 0 0 0.12em var(--accent), 0 0 0.7em rgba(255,75,62,0.35); }
  50% { box-shadow: 0 0 0 0.12em var(--accent), 0 0 1.6em rgba(255,75,62,0.75); }
}

@media (prefers-reduced-motion: reduce) {
  .switch-cell.is-called-out { animation: none; }
}

.switch-cell.is-momentary .switch-well::after {
  content: "";
  position: absolute;
  inset: auto 0 -0.55em 0;
  height: 0.14em;
  background: rgba(255,255,255,0.25);
  border-radius: 1em;
}

/* Octal digit readout under each group of three data switches. */
.octal-readout {
  display: flex;
  justify-content: flex-end;
  margin-top: 0.5em;
}

.octal-digit {
  width: calc(3 * var(--col));
  flex: 0 0 auto;
  align-self: center;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 0.95em;
  color: var(--accent);
  opacity: 0.9;
}

.panel-hint {
  margin: 1.1rem 0 0;
  color: var(--ink-dim);
  font-size: 0.95rem;
  text-align: center;
  min-height: 1.5em;
}
.panel-hint b { color: var(--ink); }

/* ---------- narrow screens: split the address bank into its two real halves ---------- */

@media (max-width: 900px) and (orientation: portrait) {
  .chassis { font-size: clamp(9px, 2.6vw, 13px); }

  /* The wrapped layout breaks every vertical relationship the leader lines
     describe — the data lamps no longer sit over the address lamps they pair
     with — so the leaders and the rule legend come off rather than point at
     the wrong thing. The group headings come back in their place. */
  .lamp-cell.has-leader::after { display: none; }
  .rule-legend { display: none; }
  .lamp-group-label.is-wrap-only { display: block; margin-bottom: 0.5em; }

  .lamp-row { flex-direction: column; align-items: stretch; gap: 1.6em; }
  .lamp-left { justify-content: center; }
  /* With the label above its lamp, a wrapped address bank puts A7's label
     immediately under A15's lamp. The row gap is what keeps each label
     attached to the lamp it names. */
  .lamp-right, .lamp-right.is-data {
    display: grid;
    grid-template-columns: repeat(8, 1fr);
    justify-content: stretch;
    row-gap: 1.15em;
  }

  /* Plain column, not column-reverse. Reversing put the data banks above the
     control switches, so ON/OFF, the first thing anyone needs, was the last
     thing they reached, and DOM order stopped matching visual order for
     keyboard and screen-reader users. */
  .switch-deck { flex-direction: column; gap: 1.5em; }
  .switch-banks { flex-direction: column; gap: 1.4em; }
  .switch-bank { width: 100%; }
  .switch-right { display: grid; grid-template-columns: repeat(8, 1fr); justify-content: stretch; }
  /* Ten status lamps will not fit in a row on a phone, so the eight true
     status lamps take an eight-wide grid and the two flag lamps centre. */
  .lamp-left.is-status { display: grid; grid-template-columns: repeat(8, 1fr); width: 100%; }
  .lamp-group.is-status { width: 100%; }
  .lamp-left { flex-wrap: wrap; justify-content: center; }
  .lamp-cell { width: auto; min-width: 2.4em; }
  /* The octal grouping does not line up with eight-bit banks, so read it as
     a plain octal number instead of trying to sit it under the switches. */
  .octal-readout { display: flex; justify-content: center; gap: 0.6em; }
  .octal-digit { width: auto; }
  .switch-left { flex-wrap: wrap; justify-content: center; gap: 0.5em 0.2em; }
  .switch-cell { width: auto; min-width: 2.9em; padding: 0.55em 0; }
  .switch-cell.wide { width: auto; min-width: 4.6em; }
  /* The wrapped banks are an even eight-column grid, so the octal offsets have
     nothing to line up with and only push the last switch off the edge. */
  .switch-cell.octal-break,
  .lamp-cell.octal-break,
  .octal-digit:nth-child(n+2) { margin-left: 0; }
  .bank-label {
    font-size: 0.8em;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--silkscreen);
    opacity: 0.75;
    margin: 0 0 0.4em;
  }
}

@media (min-width: 901px), (orientation: landscape) {
  .bank-label { display: none; }
  /* Room at the right edge for the Data/Address legend. Both the lamp rows and
     the switch banks are right-aligned to this same content edge, so reserving
     it here moves them together and the lamp-over-switch columns stay true. */
  .panel { padding-right: 5.4em; }
}

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

.console {
  max-width: 58rem;
  margin: clamp(1.5rem, 4vw, 3rem) auto 0;
  padding: 0 1.25rem 4rem;
}

.console-tabs {
  display: flex;
  gap: 0.35rem;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 1.4rem;
  overflow-x: auto;
}

.tab {
  appearance: none;
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  color: var(--ink-faint);
  font: inherit;
  font-size: 0.95rem;
  padding: 0.6rem 0.9rem;
  cursor: pointer;
  white-space: nowrap;
}
.tab:hover { color: var(--ink-dim); }
.tab.is-active { color: var(--ink); border-bottom-color: var(--accent); }

.tabpanel { display: none; }
.tabpanel.is-active { display: block; }

h2 { font-size: 1.35rem; margin: 0 0 0.6rem; letter-spacing: -0.01em; }
h3 { font-size: 1.02rem; margin: 1.8rem 0 0.5rem; letter-spacing: 0.01em; }
h3 small { color: var(--ink-faint); font-weight: 400; }
p { margin: 0 0 1rem; }

.btn {
  appearance: none;
  font: inherit;
  font-size: 0.95rem;
  padding: 0.6rem 1.1rem;
  border-radius: 0.4rem;
  border: 1px solid var(--rule);
  background: var(--page-raised);
  color: var(--ink);
  cursor: pointer;
  min-height: 2.75rem;
}
.btn:hover { border-color: #33404c; }
.btn:disabled { opacity: 0.4; cursor: default; }
.btn-primary { background: var(--accent-solid); border-color: var(--accent-solid); color: #fff; font-weight: 600; }
.btn-primary:hover { background: var(--accent-solid-hover); border-color: var(--accent-solid-hover); }
.btn-ghost { background: none; color: var(--ink-dim); }

/* guided mode */

.guide {
  background: var(--page-raised);
  border: 1px solid var(--rule);
  border-radius: 0.6rem;
  padding: 1.3rem;
}
.guide-head { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; }
.guide-head h2 { margin: 0; }
.guide-progress { color: var(--ink-faint); font-size: 0.85rem; margin: 0; font-variant-numeric: tabular-nums; }
.guide-body { margin: 0.9rem 0 1.2rem; font-size: 1.03rem; }
.guide-body b { color: #fff; }
.guide-body code {
  font-family: var(--font-mono);
  background: #0d1319;
  border: 1px solid var(--rule);
  border-radius: 0.25rem;
  padding: 0.08em 0.35em;
  font-size: 0.9em;
  color: var(--accent);
}
.guide-nav { display: flex; gap: 0.6rem; flex-wrap: wrap; }
.guide-foot { margin: 1.1rem 0 0; color: var(--ink-faint); font-size: 0.85rem; }

/* programs */

.program-picker { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-bottom: 1.4rem; }

.program-card {
  flex: 1 1 14rem;
  text-align: left;
  padding: 0.9rem 1rem;
  border-radius: 0.5rem;
  border: 1px solid var(--rule);
  background: var(--page-raised);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
}
.program-card:hover { border-color: #35424e; }
.program-card.is-active { border-color: var(--accent); }
.program-card b { display: block; margin-bottom: 0.2rem; }
.program-card span { color: var(--ink-faint); font-size: 0.85rem; }

.program-detail p { color: var(--ink-dim); }
.program-actions { display: flex; gap: 0.6rem; flex-wrap: wrap; margin: 1rem 0 1.4rem; }

.listing {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-mono);
  font-size: 0.83rem;
  display: block;
  overflow-x: auto;
  white-space: nowrap;
}
.listing th {
  text-align: left;
  color: var(--ink-faint);
  font-weight: 500;
  border-bottom: 1px solid var(--rule);
  padding: 0.4rem 0.8rem 0.4rem 0;
}
.listing td { padding: 0.22rem 0.8rem 0.22rem 0; border-bottom: 1px solid #171e25; }
.listing .col-octal { color: var(--accent); }
.listing .col-comment { color: var(--ink-faint); white-space: normal; }

/* machine state */

.state-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
  gap: 0.6rem;
  margin-bottom: 0.5rem;
}
.state-cell {
  background: var(--page-raised);
  border: 1px solid var(--rule);
  border-radius: 0.4rem;
  padding: 0.55rem 0.7rem;
}
.state-cell dt { color: var(--ink-faint); font-size: 0.75rem; letter-spacing: 0.06em; text-transform: uppercase; }
.state-cell dd { margin: 0.15rem 0 0; font-family: var(--font-mono); font-size: 1rem; }

.dump {
  background: #0d1319;
  border: 1px solid var(--rule);
  border-radius: 0.4rem;
  padding: 0.8rem;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  overflow-x: auto;
  color: var(--ink-dim);
  margin: 0 0 1rem;
  max-height: 18rem;
}

.sources { color: var(--ink-dim); }
.colophon { color: var(--ink-faint); font-size: 0.9rem; margin-top: 2rem; }

.foot {
  border-top: 1px solid var(--rule);
  padding: 1.5rem 1.25rem 3rem;
  text-align: center;
  color: var(--ink-faint);
  font-size: 0.88rem;
}
.foot p { margin: 0; }

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

/* ══════════════════════════════════════════════════════════
   Reading layer: the chapters, the interactives, the teletype
   ══════════════════════════════════════════════════════════ */

.prose {
  max-width: 54rem;
  margin: 0 auto;
  padding: 0 1.25rem 5rem;
}

/* sticky section stepper */

.stepper {
  position: sticky;
  top: 0;
  z-index: 20;
  background: rgba(11, 14, 17, 0.92);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--rule);
  margin-top: 2rem;
}
.stepper ol {
  display: flex;
  gap: 0.15rem;
  list-style: none;
  margin: 0 auto;
  padding: 0 1rem;
  max-width: 66rem;
  overflow-x: auto;
  scrollbar-width: none;
}
.stepper ol::-webkit-scrollbar { display: none; }
.stepper a {
  display: block;
  white-space: nowrap;
  font-size: 0.82rem;
  color: var(--ink-faint);
  text-decoration: none;
  padding: 0.7rem 0.7rem;
  border-bottom: 2px solid transparent;
}
.stepper a::before {
  content: attr(data-step);
  display: inline-grid;
  place-items: center;
  width: 1.25rem;
  height: 1.25rem;
  margin-right: 0.4rem;
  border-radius: 50%;
  border: 1px solid currentColor;
  font-size: 0.68rem;
  vertical-align: -0.2rem;
}
.stepper a:hover { color: var(--ink-dim); }
.stepper a.is-current { color: var(--ink); border-bottom-color: var(--accent); }
.stepper a.is-current::before { background: var(--accent-solid); border-color: var(--accent-solid); color: #fff; }

/* chapters */

.chapter {
  padding: clamp(2.5rem, 6vw, 4.5rem) 0 0;
  scroll-margin-top: 4rem;
}
.chapter h2 {
  font-size: clamp(1.5rem, 4vw, 2.05rem);
  letter-spacing: -0.02em;
  margin: 0 0 1rem;
  line-height: 1.15;
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
}
.chapter-num {
  flex: none;
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 50%;
  width: 1.6rem;
  height: 1.6rem;
  display: inline-grid;
  place-items: center;
  transform: translateY(-0.15em);
}
.chapter h3 { font-size: 1.06rem; margin: 2rem 0 0.6rem; }
.chapter h4 { font-size: 0.95rem; margin: 1.5rem 0 0.5rem; color: var(--ink-dim); }
.chapter ul { color: var(--ink-dim); padding-left: 1.1rem; }
.chapter li { margin-bottom: 0.5rem; }

.lede {
  font-size: clamp(1.02rem, 2.4vw, 1.18rem);
  color: var(--ink);
  margin-bottom: 1.4rem;
  text-wrap: pretty;
}
.prose p { color: var(--ink-dim); }
.prose p b, .prose li b { color: var(--ink); }
.prose code {
  font-family: var(--font-mono);
  font-size: 0.88em;
  background: #0d1319;
  border: 1px solid var(--rule);
  border-radius: 0.25rem;
  padding: 0.08em 0.35em;
  color: var(--accent);
  white-space: nowrap;
}

.defs { display: grid; gap: 0.75rem; margin: 1.5rem 0; }
.def {
  background: var(--page-raised);
  border: 1px solid var(--rule);
  border-radius: 0.55rem;
  padding: 1rem 1.15rem;
}
.def > b { display: block; margin-bottom: 0.35rem; color: var(--ink); }
.def p { margin: 0; font-size: 0.95rem; }

.callout {
  border-left: 3px solid var(--accent);
  background: linear-gradient(90deg, rgba(255,75,62,0.08), transparent 60%);
  padding: 0.9rem 1.1rem;
  border-radius: 0 0.4rem 0.4rem 0;
  margin: 1.6rem 0;
  font-size: 0.97rem;
}

/* bit readout */

.readout {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 0.6rem;
  margin: 1.4rem 0;
}
.readout-row {
  background: var(--page-raised);
  border: 1px solid var(--rule);
  border-radius: 0.5rem;
  padding: 0.7rem 0.9rem;
}
.readout-row span {
  display: block;
  font-size: 0.7rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.readout-row b {
  font-family: var(--font-mono);
  font-size: 1.25rem;
  color: var(--accent);
  letter-spacing: 0.04em;
}

/* instruction decoder */

.decoder {
  background: var(--page-raised);
  border: 1px solid var(--rule);
  border-radius: 0.6rem;
  padding: 1.1rem;
  margin: 1.4rem 0;
}
.decoder-bits { display: flex; gap: 0.6rem; flex-wrap: wrap; }
.bitgroup {
  flex: 1 1 6rem;
  text-align: center;
  background: #0d1319;
  border: 1px solid var(--rule);
  border-radius: 0.45rem;
  padding: 0.6rem 0.4rem;
}
.bitgroup b {
  display: block;
  font-family: var(--font-mono);
  font-size: 1.3rem;
  letter-spacing: 0.14em;
  color: var(--accent-cool);
}
.bitgroup span {
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.decoder-out { margin-top: 0.9rem; text-align: center; }
.decoder-mnemonic {
  font-family: var(--font-mono);
  font-size: clamp(1.3rem, 4.5vw, 1.8rem);
  color: var(--ink);
  letter-spacing: 0.03em;
}
.decoder-note { color: var(--ink-dim); font-size: 0.95rem; margin-top: 0.2rem; }
.decoder-extra { color: var(--ink-faint); font-size: 0.83rem; margin-top: 0.35rem; min-height: 1.2em; }

/* the 1976 split */

.split { display: grid; gap: 0.9rem; margin: 1.6rem 0; }
@media (min-width: 720px) { .split { grid-template-columns: 1fr 1fr; } }
.split-half {
  background: var(--page-raised);
  border: 1px solid var(--rule);
  border-radius: 0.6rem;
  padding: 1.1rem 1.2rem;
}
.split-half h3 { margin: 0 0 0.5rem; color: var(--accent); font-size: 0.85rem; letter-spacing: 0.1em; text-transform: uppercase; }
.split-half p { font-size: 0.95rem; }
.split-half blockquote {
  margin: 0.9rem 0;
  padding-left: 0.9rem;
  border-left: 2px solid var(--rule);
  color: var(--ink);
  font-style: italic;
}
.split-half blockquote.mono {
  font-family: var(--font-mono);
  font-style: normal;
  font-size: 0.84rem;
  line-height: 1.7;
  border-left-color: var(--accent);
  color: var(--ink-dim);
}
.split-half blockquote.mono b { color: var(--accent); }
.split-foot { font-size: 0.85rem; color: var(--ink-faint) !important; margin-bottom: 0 !important; }

/* teletype */

.tty-frame {
  margin: 1.5rem 0 1rem;
  border: 1px solid var(--rule);
  border-radius: 0.6rem;
  overflow: hidden;
  background: #0a0d10;
}
.tty-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  padding: 0.5rem 0.85rem;
  background: var(--page-raised);
  border-bottom: 1px solid var(--rule);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.tty-state { color: var(--ink-faint); }
.tty-frame:has(.tty.is-live) .tty-state { color: #7ddf9a; }

.tty {
  position: relative;
  background:
    radial-gradient(120% 100% at 50% 0%, rgba(255,176,64,0.05), transparent 70%),
    #07090b;
  padding: 0.9rem 1rem;
  cursor: text;
}
.tty-screen {
  margin: 0;
  font-family: var(--font-mono);
  font-size: clamp(0.72rem, 2.6vw, 0.85rem);
  line-height: 1.45;
  color: #ffb444;
  text-shadow: 0 0 6px rgba(255,150,40,0.45);
  white-space: pre-wrap;
  word-break: break-word;
  min-height: 15rem;
  max-height: 24rem;
  overflow-y: auto;
}
.tty-cursor { color: #ffb444; }
.tty.is-belled { animation: belled 120ms; }
@keyframes belled { 50% { background-color: rgba(255,180,68,0.12); } }

/* Real input element, positioned out of sight. It exists so tapping the
   teletype on a phone brings up the keyboard, which is the only way to type. */
.tty-input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  width: 1px;
  height: 1px;
  border: 0;
  padding: 0;
}

.tty-actions {
  display: flex;
  gap: 0.6rem;
  flex-wrap: wrap;
  padding: 0.85rem;
  background: var(--page-raised);
  border-top: 1px solid var(--rule);
}
.tty-hint { font-size: 0.92rem; }

/* back to top */

.totop {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  z-index: 30;
  width: 2.9rem;
  height: 2.9rem;
  border-radius: 50%;
  border: 1px solid var(--rule);
  background: var(--page-raised);
  color: var(--ink);
  font-size: 1.1rem;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity 180ms;
}
.totop.is-shown { opacity: 1; pointer-events: auto; }
.totop:hover { border-color: var(--accent); }

@media (prefers-reduced-motion: reduce) {
  .totop { transition: none; }
  .tty.is-belled { animation: none; }
}

/* ══════════════════════════════════════════════════════════
   Hero routes, glossary, keyboard help
   ══════════════════════════════════════════════════════════ */

.h1-sub {
  display: block;
  font-size: clamp(0.82rem, 2.2vw, 1.05rem);
  font-weight: 400;
  letter-spacing: 0.02em;
  color: var(--ink-dim);
  margin-top: 0.5em;
}
.h1-sub em { color: var(--accent); font-style: normal; }

.claims {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.4rem 1.4rem;
  margin: 1.4rem 0 0;
  padding: 0;
  font-size: 0.9rem;
  color: var(--ink-dim);
}
.claims li::before {
  content: "✓";
  color: var(--accent);
  margin-right: 0.4rem;
  font-weight: 700;
}

.paths { margin: 2rem auto 0; max-width: 46rem; }
.paths-q {
  margin: 0 0 0.7rem;
  font-size: 0.88rem;
  color: var(--ink-faint);
  letter-spacing: 0.03em;
}
.paths-row { display: grid; gap: 0.6rem; }
@media (min-width: 700px) { .paths-row { grid-template-columns: repeat(3, 1fr); } }

.path {
  text-align: left;
  font: inherit;
  color: var(--ink);
  background: var(--page-raised);
  border: 1px solid var(--rule);
  border-radius: 0.55rem;
  padding: 0.85rem 1rem;
  cursor: pointer;
}
.path:hover { border-color: #35424e; }
.path.is-active { border-color: var(--accent); }
.path b { display: block; font-size: 1rem; }
.path-time {
  display: block;
  font-size: 0.75rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0.1rem 0 0.4rem;
}
.path-desc { display: block; font-size: 0.87rem; color: var(--ink-dim); line-height: 1.5; }

.kbd-hint { margin: 1.6rem 0 0; font-size: 0.82rem; color: var(--ink-faint); }
kbd {
  font-family: var(--font-mono);
  font-size: 0.85em;
  border: 1px solid var(--rule);
  border-bottom-width: 2px;
  border-radius: 0.25rem;
  padding: 0.05em 0.4em;
  background: var(--page-raised);
  color: var(--ink);
}

/* glossary */

.glossary {
  display: grid;
  gap: 0.1rem 1.2rem;
  margin: 1.2rem 0 0;
}
@media (min-width: 640px) { .glossary { grid-template-columns: minmax(8rem, max-content) 1fr; } }
.glossary dt {
  font-weight: 600;
  color: var(--ink);
  padding: 0.55rem 0 0;
}
.glossary dd {
  margin: 0;
  color: var(--ink-dim);
  padding: 0.55rem 0;
  border-bottom: 1px solid #171e25;
  font-size: 0.95rem;
}
@media (max-width: 639px) {
  .glossary dt { border-top: 1px solid #171e25; }
  .glossary dd { border-bottom: 0; padding-top: 0.15rem; }
}

/* keyboard help */

.shortcuts {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  background: rgba(4, 6, 8, 0.78);
  backdrop-filter: blur(4px);
  padding: 1.25rem;
}
.shortcuts[hidden] { display: none; }
.shortcuts-card {
  background: var(--page-raised);
  border: 1px solid var(--rule);
  border-radius: 0.7rem;
  padding: 1.4rem;
  max-width: 26rem;
  width: 100%;
  max-height: 86vh;
  overflow-y: auto;
}
.shortcuts-card h2 { margin: 0 0 1rem; }
.shortcuts-card dl {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.55rem 1rem;
  margin: 0 0 1.3rem;
  align-items: baseline;
}
.shortcuts-card dt { white-space: nowrap; }
.shortcuts-card dd { margin: 0; color: var(--ink-dim); font-size: 0.92rem; }

/* ── Hero density.
   The machine is the reason anyone is here, so the hero must not push it off
   the fold. On a phone the route cards collapse to their titles and the
   claims list drops to two tight columns, which recovers roughly 450px. */

@media (max-width: 700px), (max-height: 520px) {
  .masthead { padding-top: 1.5rem; padding-bottom: 0.5rem; }
  .standfirst { font-size: 0.95rem; line-height: 1.45; }

  .claims {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.3rem 0.8rem;
    margin-top: 1rem;
    font-size: 0.78rem;
    text-align: left;
  }
  .claims li { display: flex; align-items: baseline; }

  .paths { margin-top: 1.2rem; }
  .paths-q { margin-bottom: 0.5rem; font-size: 0.8rem; }
  .paths-row { grid-template-columns: 1fr 1fr 1fr; gap: 0.4rem; }
  .path { padding: 0.6rem 0.5rem; text-align: center; }
  .path b { font-size: 0.85rem; line-height: 1.2; }
  .path-time { font-size: 0.62rem; letter-spacing: 0.03em; margin: 0.2rem 0 0; }
  .path-desc { display: none; }

  .kbd-hint { margin-top: 1rem; font-size: 0.75rem; }
  .stage { padding-top: 0.75rem; }
}

@media (min-width: 701px) {
  .masthead { padding-bottom: 0.5rem; }
}

/* At 320px the three console tabs overflowed their rail by 47px. The rail
   scrolls, so nothing was unreachable, but a third tab cut off at the screen
   edge with no affordance reads as broken. Tighten rather than rely on it. */
@media (max-width: 380px) {
  .prose, .console { padding-left: 0.85rem; padding-right: 0.85rem; }
  .tab { padding: 0.6rem 0.5rem; font-size: 0.87rem; }
  .console-tabs { gap: 0.1rem; }
}

/* 404 */
.notfound {
  max-width: 34rem;
  margin: 0 auto;
  padding: clamp(4rem, 16vh, 9rem) 1.5rem;
  text-align: center;
}
.notfound-code {
  font-family: var(--font-mono);
  font-size: clamp(1.4rem, 6vw, 2.2rem);
  letter-spacing: 0.2em;
  color: var(--lamp-off);
  margin: 0 0 1.5rem;
}
.notfound h1 { font-size: clamp(1.6rem, 5vw, 2.4rem); margin: 0 0 1rem; letter-spacing: -0.02em; }
.notfound p { color: var(--ink-dim); }
.notfound .btn { display: inline-block; text-decoration: none; margin-top: 0.8rem; }

.sound-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0.6rem auto 0;
  padding: 0.4rem 0.75rem;
  font: inherit;
  font-size: 0.8rem;
  color: var(--ink-faint);
  background: none;
  border: 1px solid var(--rule);
  border-radius: 1.2rem;
  cursor: pointer;
  min-height: 2rem;
}
.sound-toggle:hover { color: var(--ink-dim); border-color: #35424e; }
/* Dimming text with opacity is how a compliant colour becomes a failing one.
   The pressed state is already carried by the icon and the label text. */
.sound-toggle[aria-pressed="false"] { border-style: dashed; }

/* ── program library, sharing and saves ── */

.library { margin: 1.6rem 0 1rem; }
.library-head h3 { margin: 0 0 0.2rem; font-size: 1.02rem; }
.library-head p { margin: 0 0 0.9rem; font-size: 0.9rem; }

.library-grid {
  display: grid;
  gap: 0.55rem;
  grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
}

.lib-card {
  text-align: left;
  font: inherit;
  color: var(--ink);
  background: var(--page-raised);
  border: 1px solid var(--rule);
  border-radius: 0.5rem;
  padding: 0.75rem 0.9rem;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  min-height: 5.5rem;
}
.lib-card:hover { border-color: var(--accent); }
.lib-card b { font-size: 0.95rem; }
.lib-card span { font-size: 0.82rem; color: var(--ink-dim); line-height: 1.45; }
.lib-tags { display: flex; gap: 0.3rem; flex-wrap: wrap; margin-top: auto; padding-top: 0.35rem; }
.lib-tag {
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
  border: 1px solid var(--rule);
  border-radius: 1rem;
  padding: 0.08rem 0.45rem;
}

.prog-actions {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  align-items: center;
  margin: 0 0 1rem;
}
.prog-status { font-size: 0.85rem; color: var(--accent); min-height: 1.2em; }

.saved-list {
  border: 1px solid var(--rule);
  border-radius: 0.5rem;
  background: var(--page-raised);
  padding: 0.5rem;
  margin: 0 0 1.2rem;
}
.saved-list[hidden] { display: none; }
.saved-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.5rem;
  border-radius: 0.35rem;
}
.saved-row + .saved-row { border-top: 1px solid #171e25; }
.saved-row b { flex: 1; font-weight: 500; font-size: 0.92rem; }
.saved-row time { color: var(--ink-faint); font-size: 0.75rem; }
.saved-empty { color: var(--ink-faint); font-size: 0.88rem; padding: 0.5rem; }
.link-btn {
  background: none;
  border: 0;
  color: var(--accent-cool);
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
  padding: 0.3rem 0.4rem;
  min-height: 2rem;
}
.link-btn:hover { text-decoration: underline; }

.panel-toggles { display: flex; gap: 0.5rem; flex-wrap: wrap; justify-content: center; }
.radio-hz {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--accent);
  min-width: 3.6rem;
  text-align: right;
}

.spec {
  width: 100%;
  border-collapse: collapse;
  margin: 1rem 0 1.5rem;
  font-size: 0.93rem;
}
.spec th {
  text-align: left;
  vertical-align: top;
  white-space: nowrap;
  color: var(--ink);
  font-weight: 600;
  padding: 0.6rem 1rem 0.6rem 0;
  border-bottom: 1px solid #171e25;
  width: 8rem;
}
.spec td {
  color: var(--ink-dim);
  padding: 0.6rem 0;
  border-bottom: 1px solid #171e25;
  line-height: 1.55;
}
@media (max-width: 560px) {
  .spec, .spec tr, .spec th, .spec td { display: block; width: auto; }
  .spec th { border-bottom: 0; padding-bottom: 0.15rem; }
  .spec td { padding-top: 0; }
}

/* The idle teletype speaks in a dimmer voice than the machine does. */
.tty-screen.is-blank { color: #6d5228; text-shadow: none; opacity: 0.85; }

/* A keyboard-shortcuts hint is noise on a device with no keyboard. */
@media (pointer: coarse) and (hover: none) {
  .kbd-hint { display: none; }
}

/* Finished teletype lines go here for assistive tech only. */
.tty-announcer {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The machine is the reason anyone is here, so it comes before the framing.
   With the claims row and route picker above it, the chassis started 587px
   down and its switches fell below the fold on any 900px-tall laptop, which
   is most of them. */
.below-panel {
  width: 100%;
  max-width: 46rem;
  margin: 1.4rem auto 0;
}
.below-panel .claims { margin-top: 0; }
.below-panel .paths { margin-top: 1.2rem; }
.masthead { padding-bottom: 0.25rem; }
.masthead h1 { font-size: clamp(1.9rem, 5.5vw, 2.8rem); }
.standfirst { font-size: clamp(0.94rem, 1.9vw, 1.05rem); max-width: 40em; }


/* On a short viewport every pixel of panel height decides whether the switches
   are reachable without scrolling. */
@media (max-height: 700px) {
  .masthead { padding-top: 0.9rem; padding-bottom: 0.2rem; }
  .standfirst { display: none; }
  .lamp-deck { gap: 1em; }
  .switch-deck { margin-top: 1.4em; padding-top: 1em; }
}

.route-note {
  margin: 0.9rem 0 0;
  font-size: 0.85rem;
  color: var(--accent);
}
.route-note[hidden] { display: none; }
