/* 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: #4a83b2;
  --chassis-lo: #2e6088;
  --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;

  /* Switch handle colours, sampled from the reference photograph rather than
     chosen. The panel is not monochrome and this page drew it that way: every
     switch used the same silver gradient above.

     MITS colour-coded the address bank at the sense/data boundary. Switches 15
     down to 8, the sense switches a program can read at port 255, are magenta;
     7 down to 0, the data switches, are cream. That split is exactly the
     distinction this page spends a chapter teaching, and it was printed on the
     hardware in colour the whole time.

     The operator asked whether the switches were metal or silver. Both answers
     are right about different parts: the CAPS are moulded plastic in these
     colours, and the bushing, collar and shaft are bare nickel-plated metal,
     visible as a silver ring at the base of every switch. That ring is drawn by
     .switch-well and stays. */
  --handle-sense: #b23453;      /* address switches 15-8 */
  --handle-data: #d5cfbb;       /* address switches 7-0, and the power switch */
  --handle-control: #4397ac;    /* the six real control switches */
  --handle-aux: #303228;        /* the two AUX switches, which were never wired */

  --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 of the sixteen sense switches including their five octal gaps. The
     control switches take the same width so their left edge lands under switch
     15, which is where the real panel puts them. */
  --bank-width: calc(16 * var(--col) + 5 * var(--octal-gap));
  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%, #275475);
  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.05) 0 1px, rgba(0,0,0,0.028) 1px 4px),
    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: 800;
  font-size: 0.8em;
  letter-spacing: 0.04em;
  color: #f2f4f5;
  background: #16191c;
  padding: 0.12em 0.34em 0.14em 0.5em;
  transform: skewX(-12deg);
  border-radius: 0.06em;
}
.brand-mits > * { transform: skewX(12deg); }

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

.brand-model {
  /* Squared, wide and geometric on the original badge. Eurostile and
     Microgramma are the family; where neither is installed a wide grotesque
     stretched slightly is closer than a default sans. */
  font-family: Eurostile, "Microgramma", "Bank Gothic", "Square721 BT",
    "Arial Narrow", var(--font-ui);
  font-size: 1.18em;
  font-weight: 700;
  transform: scaleX(1.08);
  transform-origin: left center;
  letter-spacing: 0.05em;
  color: #191c1a;
  text-transform: uppercase;
}

/* The panel's contents are built by panel.js, so before that runs the box is
   empty and everything below it sits 500 to 700px too high. These reserve the
   height the built panel takes, measured per layout and asserted in
   tests/skeleton.test.mjs so they cannot drift away from the real thing. */
#panel-root { min-height: 22.9em; }

@media (max-width: 900px) and (orientation: portrait) {
  #panel-root { min-height: 65.9em; }
}
@media (max-width: 480px) and (orientation: portrait) {
  #panel-root { min-height: 73.9em; }
}
@media (min-width: 1000px) and (min-height: 720px) {
  body:not([data-panel-fit="folded"]) #panel-root { min-height: 32.04em; }
}

.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; }

/* Aligned at the TOP, not the bottom. The status group carries the "STATUS"
   legend under its lamps and the data group carries nothing, so the status
   group is taller. Aligning the row at the bottom made that legend push the
   whole data group down: the data lamps sat 0.043 of the panel's height below
   the status lamps, on a panel where the two are one row on one line.
   docs/PANEL-GEOMETRY.md lists them together at 0.1613 for exactly that reason,
   and the render only ever measured the status half, so it recorded a 0.003
   delta while the other half of the same row was out by thirteen times that.
   Top alignment puts every lamp label on one line and every lamp under it. */
.lamp-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  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; }

/* Compact layout: the two boxes vanish and their cells sit in one row, exactly
   as they did when they were siblings. */
.switch-power,
.switch-controls { display: contents; }

/* Printed at the left-hand end of the number rule in the faithful layout. */
.sense-legend {
  display: none;
  position: absolute;
  right: 100%;
  bottom: 9.3em;
  padding-right: 0;
  font-size: 0.68em;
  letter-spacing: 0.16em;
  color: var(--silkscreen);
  opacity: 0.7;
  white-space: nowrap;
}

.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; }

/* Which bank a handle belongs to. data-bit is set by panel.js for the sixteen
   address switches; bit 8 and up are the sense half. */
.switch-cell[data-bit="8"] .switch-paddle,
.switch-cell[data-bit="9"] .switch-paddle,
.switch-cell[data-bit="10"] .switch-paddle,
.switch-cell[data-bit="11"] .switch-paddle,
.switch-cell[data-bit="12"] .switch-paddle,
.switch-cell[data-bit="13"] .switch-paddle,
.switch-cell[data-bit="14"] .switch-paddle,
.switch-cell[data-bit="15"] .switch-paddle { --handle: var(--handle-sense); }

.switch-cell[data-id="runstop"] .switch-paddle,
.switch-cell[data-id="step"] .switch-paddle,
.switch-cell[data-id="examine"] .switch-paddle,
.switch-cell[data-id="deposit"] .switch-paddle,
.switch-cell[data-id="reset"] .switch-paddle,
.switch-cell[data-id="protect"] .switch-paddle { --handle: var(--handle-control); }

.switch-cell[data-id="aux1"] .switch-paddle,
.switch-cell[data-id="aux2"] .switch-paddle { --handle: var(--handle-aux); }

.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;
  /* --handle defaults to the data cream; each bank overrides it below. The
     gradient keeps the moulded-plastic sheen: a lit top edge, a body, a shaded
     bottom, all derived from the one colour. */
  --handle: var(--handle-data);
  background:
    linear-gradient(96deg,
      color-mix(in srgb, var(--handle) 82%, #fff) 0%,
      var(--handle) 45%,
      color-mix(in srgb, var(--handle) 72%, #000) 100%);
  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;
}

/* The honesty note under the machine. Visible by default and switched off only
   in the faithful layout, so a bug in the media query fails towards telling the
   truth rather than towards claiming an accuracy the layout does not have. */
.panel-refit {
  margin: 1.1rem auto 0;
  max-width: 44em;
  text-align: left;
  font-size: 0.95rem;
  line-height: 1.55;
  color: var(--ink-dim);
  background: rgba(255, 75, 62, 0.08);
  border: 1px solid rgba(255, 75, 62, 0.45);
  border-left-width: 5px;
  border-radius: 7px;
  padding: 0.85rem 1.15rem 0.9rem;
}
.panel-refit-lead {
  display: block;
  color: var(--accent);
  font-size: 1.02rem;
  letter-spacing: 0.01em;
  margin-bottom: 0.2rem;
}
.panel-refit a { color: var(--accent-cool); text-decoration: underline; }

/* The machine's status line.
   It reports what the panel just did, so it changes on nearly every click, and
   its messages run from "Running." to a hundred and ninety characters. Sized to
   its content it grew and shrank under the machine on every keystroke: on a
   390px phone it swung between 24 and 94 pixels, shoving the whole page. So it
   reserves the room its longest message needs and never moves again. Short
   messages sit centred in that space rather than clinging to the top of it.
   --hint-lines is the reserve, set per breakpoint from the longest message. */
.panel-hint {
  --hint-lines: 3;
  display: flex;
  align-items: safe center;
  justify-content: center;
  gap: 0.75rem;
  box-sizing: border-box;
  width: 100%;
  max-width: 64ch;
  margin: 1.5rem auto 0;
  padding: 0.8rem 1.15rem;
  /* height, not min-height: the box is fixed and the rare over-long message
     scrolls inside it rather than growing it. box-sizing is border-box, so the
     padding and the two horizontal borders are inside this number.
     "safe center" centres the text but stops centring if it would push the
     first line out of the top of the scroll box, which plain centring does. */
  height: calc(var(--hint-lines) * 1.5em + 1.6rem + 2px);
  overflow-y: auto;
  scrollbar-width: thin;
  border: 1px solid var(--rule);
  border-left: 2px solid var(--accent);
  border-radius: 0.45rem;
  background: var(--page-raised);
  color: var(--ink-dim);
  font-size: 0.95rem;
  line-height: 1.5;
  text-align: center;
  text-wrap: pretty;
}

/* A lit lamp, because the thing it reports on is a wall of them. */
.panel-hint::before {
  content: "";
  flex: none;
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0.5rem var(--accent);
}

/* Narrower box, same words, so the reserve has to grow. Measured, not guessed:
   tests/hint.test.mjs drives every message the page can emit at each of these
   widths and fails if the box changes height between any two of them. */
@media (max-width: 720px) {
  .panel-hint { --hint-lines: 4; }
}
@media (max-width: 520px) {
  .panel-hint { --hint-lines: 5; }
}
@media (max-width: 360px) {
  .panel-hint { --hint-lines: 6; }
}

.panel-hint b { color: var(--ink); font-weight: 600; }

/* ---------- 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;
  }
}

/* ---------- the faithful layout ----------
   The real machine is four rows: two of lamps, then the sixteen sense
   switches, then the control switches on their own row with the power switch
   alone at the far left. Everything above this point is the compact
   rearrangement, which is what runs when the screen cannot hold the real one.
   This block is purely additive for that reason: narrower or shorter screens
   never enter it and keep the layout they already had.

   The height bound matters as much as the width. The controls moving to a
   fourth row pushes the power switch down, and it must stay reachable without
   scrolling. */
/* ---------- the faithful layout ----------
   Gated on an attribute rather than a media query, because the choice is the
   visitor's. app.js sets data-panel-fit from their preference, defaulting to
   whichever the screen can actually hold. With no JavaScript the attribute is
   never set and the folded layout stands, which is the safe way round: the
   folded layout works everywhere and admits what it is.

   Everything below is measured off a head-on photograph of an original. */


/* The bound is a media query again, not a class JavaScript adds, so the correct
   layout is already there on the first paint. Before, the folded layout was
   painted and then swapped for the real one once app.js ran, which moved the
   whole page 497px on a desktop. JavaScript now only ever opts OUT, by setting
   data-panel-fit="folded" when the visitor has asked for the readable layout.

   Consequence worth keeping: with no JavaScript at all, a desktop still gets
   the accurate machine. */

@media (min-width: 1000px) and (min-height: 720px) {
  body:not([data-panel-fit="folded"]) .switch-deck { flex-direction: column; gap: 1.88em; }
  body:not([data-panel-fit="folded"]) .switch-right-wrap { order: -1; align-self: flex-end; }

  body:not([data-panel-fit="folded"]) .switch-left { width: 100%; justify-content: space-between; gap: 0; }
  body:not([data-panel-fit="folded"]) .switch-power { display: flex; }
  /* Same width as the sense bank, pushed to the same right edge, so the
     controls begin under switch 15 as they do on the original. */
  body:not([data-panel-fit="folded"]) .switch-controls {
    display: flex;
    gap: 0;
    width: var(--bank-width);
    /* STOP sits at 0.236 of the panel's width on the original, a shade left of
       sense switch 15 rather than square under it. */
    margin-right: 1.35em;
  }

  /* Measured off the photograph: the eight control switches sit at a uniform
     pitch of 0.073 of the panel's width, starting at 0.236, which is under
     sense switch 15. They are NOT clustered by function, whatever the shape of
     the legends suggests. Here their spacing was falling out of how wide each
     label happened to be, so UNPROTECT pushed its neighbours apart and AUX
     pulled them together. */
  body:not([data-panel-fit="folded"]) .switch-controls .switch-cell,
  body:not([data-panel-fit="folded"]) .switch-controls .switch-cell.wide { width: 6.04em; }

  /* ---- the y axis, from docs/PANEL-GEOMETRY.md ----
     The control legends were setting the height of their own cells, so a
     two-line one like EXAMINE / EXAMINE NEXT made its cell taller than STOP's
     and the whole row had to be padded to keep every well on one baseline.
     That put a floor of about 6.4em under the sense-to-control band against a
     measured target of 5.37em, and pushed the control row to 0.843 of the
     panel's height where the original has it at 0.690.

     Lifting the legends out of flow removes the floor: they hang off the well
     instead of growing the cell, so a one-line and a two-line legend give the
     same cell and the wells stay aligned. */
  /* ...but lifting them out of flow moved the problem rather than removing it.
     An absolutely positioned caption resolves top/bottom against its cell, and
     the cell is a flex item in .switch-left, which stretches every item to the
     height of the tallest. The tallest is the power switch, whose OFF and ON
     legends are still in flow. So the control cells were being stretched to the
     power switch's height and "top: 100%" was putting RUN, SLOW, EXAMINE NEXT
     and the rest against the BOTTOM OF THE POWER SWITCH: 0.160 of the panel
     below their own handles, against 0.050 above. More than the gap between the
     two lamp rows, in dead space, under every control switch.

     Shrink-wrapping the cell fixes it without touching the handles, which are
     measured and matched: the well keeps its place in the cell, so the paddle
     stays at 0.697, and the caption now hangs off the well it belongs to. */
  body:not([data-panel-fit="folded"]) .switch-power .switch-cell,
  body:not([data-panel-fit="folded"]) .switch-controls .switch-cell {
    padding: 0;
    align-self: flex-start;
  }
  body:not([data-panel-fit="folded"]) .switch-power .switch-well,
  body:not([data-panel-fit="folded"]) .switch-controls .switch-well { margin: 0.5em 0; }
  body:not([data-panel-fit="folded"]) .switch-power .switch-caption-up,
  body:not([data-panel-fit="folded"]) .switch-power .switch-caption-down,
  body:not([data-panel-fit="folded"]) .switch-controls .switch-caption-up,
  body:not([data-panel-fit="folded"]) .switch-controls .switch-caption-down {
    position: absolute;
    left: 0;
    right: 0;
    min-height: 0;
  }
  body:not([data-panel-fit="folded"]) .switch-power .switch-caption-up,
  body:not([data-panel-fit="folded"]) .switch-controls .switch-caption-up { bottom: 100%; padding-bottom: 0.3em; }
  body:not([data-panel-fit="folded"]) .switch-power .switch-caption-down,
  body:not([data-panel-fit="folded"]) .switch-controls .switch-caption-down { top: 100%; padding-top: 0.3em; }

  /* The sense row's lower caption carries only the two rules, so it does not
     need a line's worth of height. */
  body:not([data-panel-fit="folded"]) .switch-right .switch-caption-down { min-height: 1em; }

  body:not([data-panel-fit="folded"]) .sense-legend { display: block; font-size: 0.58em; }

  /* The legend does not float on its own: a rule carries the eye from the words
     across to the first switch number, and the numeral rule picks it up there.
     Without it "SENSE SW." sits marooned in the empty left of the panel. */
  body:not([data-panel-fit="folded"]) .sense-legend::after {
    content: "";
    display: inline-block;
    width: 4.2em;
    height: 0.14em;
    margin-left: 0.7em;
    vertical-align: 0.28em;
    background: var(--silkscreen);
    opacity: 0.55;
  }

  /* ---- true proportions ----
     A real 8800 is about seventeen inches across. Given the room, render it
     nearer life size. That is the move that makes the rest possible: the lamps
     and lettering take their true proportion, which is roughly half what they
     had, without the type dropping below what anyone can read.

     Measured against a head-on photograph of an original: lamp diameter is
     about 0.3 of the column pitch, and it was 0.52 here. The panel face is
     2.58 wide to 1 tall, and it was 2.39. The first status lamp sits 11.9 per
     cent of the panel's width in from the left edge, and it sat at 2.

     The width bound above is set by this and not by taste. Below about 1300px
     the machine cannot be both true to those ratios and readable, because the
     type would fall under 9px. That is what the folded layout is for. */
  body:not([data-panel-fit="folded"]) .chassis { font-size: clamp(11px, 1.12vw, 17px); }

  body:not([data-panel-fit="folded"]) .lamp {
    width: 0.72em;
    height: 0.72em;
    /* The lamp is half the size it was, so the hole it sits in has to come down
       with it or the rim reads as a bezel the original does not have. */
    box-shadow:
      0 0 0 0.07em var(--lamp-rim),
      0 0.04em 0.08em rgba(0,0,0,0.6) inset;
  }
  body:not([data-panel-fit="folded"]) .lamp-cell { gap: 0.34em; }
  body:not([data-panel-fit="folded"]) .lamp-label { font-size: 0.6em; }
  body:not([data-panel-fit="folded"]) .lamp-group-label.is-under { font-size: 0.58em; margin-top: 0.45em; padding-top: 0.32em; }
  .switch-caption-up,
  body:not([data-panel-fit="folded"]) .switch-caption-down { font-size: 0.58em; min-height: 2.5em; }
  body:not([data-panel-fit="folded"]) .rule-legend { font-size: 0.58em; }

  /* The toggles are hardware, not user interface: smaller bodies, shorter
     throw, in proportion with the lamps beside them. */
  body:not([data-panel-fit="folded"]) .switch-well { width: 0.95em; height: 1.75em; border-radius: 0.22em; }
  body:not([data-panel-fit="folded"]) .switch-paddle { width: 0.52em; height: 1.25em; border-radius: 0.26em; }
  body:not([data-panel-fit="folded"]) .switch-cell[data-position="up"] .switch-paddle { transform: translateY(-0.3em) scaleY(0.88); }
  body:not([data-panel-fit="folded"]) .switch-cell[data-position="down"] .switch-paddle { transform: translateY(0.3em) scaleY(0.88); }

  /* The original does not start at its own left edge: the first status lamp is
     almost twelve per cent of the panel's width in, and the whole bottom left
     of the machine is deliberately empty. A percentage, not an em, so it stays
     right at every width. Only the left-hand group moves; the lamp rows and
     the switch banks are anchored to the right edge, so every lamp stays in
     column with the switch that writes it. */
  body:not([data-panel-fit="folded"]) .panel { padding-left: 11%; padding-top: 3.47em; padding-bottom: 3.04em; }

  body:not([data-panel-fit="folded"]) .lamp-deck { gap: 2.44em; }
  body:not([data-panel-fit="folded"]) .switch-deck { margin-top: 1.36em; }
  body:not([data-panel-fit="folded"]) .octal-readout { margin-top: 0.45em; }

  /* The layout is the real one here, so the note would be a lie. */
  body:not([data-panel-fit="folded"]) .panel-refit { display: none; }

  /* ---- the lamp field, measured against the panel rather than itself ----
     Both lamp rows on the original run at one pitch of 0.0364 of the panel's
     width, and the whole field spans 0.755 of it starting 0.123 in. This was
     at 0.0272, so every lamp and switch sat about a third too close to its
     neighbour and the machine looked cramped in the middle of its own panel
     with dead space either side. The lamp and the switch hardware scale with
     the pitch, so their ratios to it are unchanged. */
  body:not([data-panel-fit="folded"]) .chassis {
    /* Measured gap sequence off the photograph, in fractions of the panel's
       width: ten status lamps at a uniform 0.0365, then 0.1297 across to D7,
       then the data group at 0.0365 with 0.0562 at each octal boundary. So the
       octal break adds 0.0197, which is 0.54 of a pitch, not the 0.25 this had.
       That is why the groups read as evenly spread here and as distinctly
       grouped on the original. */
    --col: 3.02em;
    --octal-gap: 1.63em;
  }
  body:not([data-panel-fit="folded"]) .lamp { width: 0.98em; height: 0.98em; }

  /* The status lamps run at the same pitch as everything else on the original,
     with no extra air between INTE/PROT and the eight bus lamps: measured, all
     nine gaps are 0.0365 of the panel's width. This was adding 1.6em between
     every cell, giving 0.055, so the status group sprawled half again as wide
     as it should and shoved the data group right. */
  body:not([data-panel-fit="folded"]) .lamp-left { gap: 0; }

  /* The MITS one: a bare vertical stroke. The numeral stays in the text for
     anything reading it and is pushed out of sight, and the stroke is drawn in
     its place. */
  /* The box has to be exactly the stroke, for two reasons that both showed up
     as visible faults.

     `overflow: hidden` makes an inline-block's baseline its bottom margin edge.
     The box was as tall as the line (1.2em) with the stroke drawn from 0.16em
     to 0.88em inside it, which left the stroke floating a third of an em ABOVE
     the baseline while every other digit sat on it. It read as a superscript:
     "A15" looked like A with a raised tick.

     And a 1.2em box sitting entirely above the baseline needs more ascent than
     the text around it, so it grew the line box of any label containing a one.
     That pushed M1, and A10 through A15, a measured 0.0087 of the panel's
     height below the lamps either side of them. The row was visibly ragged. */
  body:not([data-panel-fit="folded"]) .glyph-one {
    display: inline-block;
    position: relative;
    width: 0.36em;
    height: 0.72em;
    line-height: 0.72em;
    overflow: hidden;
    text-indent: -10em;
    vertical-align: baseline;
  }
  body:not([data-panel-fit="folded"]) .glyph-one::before {
    content: "";
    position: absolute;
    text-indent: 0;
    left: 50%;
    margin-left: -0.055em;
    top: 0;
    bottom: 0;
    width: 0.11em;
    background: currentColor;
  }
  body:not([data-panel-fit="folded"]) .panel { padding-left: 12.3%; padding-right: 8.8%; }

  body:not([data-panel-fit="folded"]) .switch-well { width: 1.8em; height: 3.12em; }
  body:not([data-panel-fit="folded"]) .switch-well::before {
    width: 0.93em;
    height: 0.93em;
    margin: -0.465em 0 0 -0.465em;
  }
  body:not([data-panel-fit="folded"]) .switch-paddle { width: 0.68em; height: 1.58em; border-radius: 0.34em; }
  body:not([data-panel-fit="folded"]) .switch-cell[data-position="up"] .switch-paddle { transform: translateY(-0.88em); }
  body:not([data-panel-fit="folded"]) .switch-cell[data-position="down"] .switch-paddle { transform: translateY(0.88em); }


  /* ---- the switches as hardware ----
     A panel toggle is a threaded bushing with a bat handle sticking out of it,
     not a pill in a slot. Head-on you see a dark ring with the handle standing
     up or down from its centre, and a momentary switch at rest points straight
     at you, which reads as a dot rather than a paddle. That last one matters:
     eight of the nine control switches are centre-return, and drawing them
     thrown makes the machine look mid-operation when it is idle. */
  body:not([data-panel-fit="folded"]) .switch-well {
    width: 1.35em;
    height: 2.7em;
    background: none;
    box-shadow: none;
    border-radius: 0;
  }

  /* The bushing, always present, behind the handle. */
  body:not([data-panel-fit="folded"]) .switch-well::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    /* Measured off the photograph: the bushing is 0.315 of the column pitch and
       is nearly black, a recessed nut rather than a bright dome. It was too big
       and far too light, which read as jewellery instead of a fastener. */
    width: 0.69em;
    height: 0.69em;
    margin: -0.345em 0 0 -0.345em;
    border-radius: 50%;
    background: radial-gradient(circle at 40% 32%, #34393e, #16191c 58%, #0b0d0f);
    box-shadow:
      0 0 0 0.035em rgba(0,0,0,0.7),
      0 0.05em 0.06em rgba(255,255,255,0.16) inset;
  }

  /* The handle is 0.23 of the pitch across and 0.534 long, so it stands well
     clear of the bushing. It was stubby, which is why the switches still read as
     controls rather than as toggles somebody could catch a fingernail on. */
  body:not([data-panel-fit="folded"]) .switch-paddle {
    position: relative;
    width: 0.51em;
    height: 1.17em;
    border-radius: 0.25em;
    /* Same one-colour gradient as the base rule. This used to re-declare the
       silver chrome and so quietly undid the whole handle-colour scheme in the
       only layout that matters. */
    background:
      linear-gradient(96deg,
        color-mix(in srgb, var(--handle) 82%, #fff) 0%,
        var(--handle) 45%,
        color-mix(in srgb, var(--handle) 72%, #000) 100%);
    box-shadow: 0 0.06em 0.14em rgba(0,0,0,0.6), 0 0 0 0.035em rgba(0,0,0,0.4);
  }

  body:not([data-panel-fit="folded"]) .switch-cell[data-position="up"] .switch-paddle {
    transform: translateY(-0.83em);
  }
  body:not([data-panel-fit="folded"]) .switch-cell[data-position="down"] .switch-paddle {
    transform: translateY(0.83em);
  }
  /* At rest, seen end-on. */
  body:not([data-panel-fit="folded"]) .switch-cell[data-position="center"] .switch-paddle {
    transform: translateY(0) scaleY(0.38) scaleX(0.92);
  }
  /* The centred dot says "momentary" on its own, so the invented tick under the
     well is not needed and is not on the panel. */
  body:not([data-panel-fit="folded"]) .switch-cell.is-momentary .switch-well::after {
    display: none;
  }

  /* ---- the case ----
     The original is a square, mitred sheet-metal frame, not a rounded one. */
  /* MITS give the dress panel as 16.5 x 6.5in and the front including the case
     lip as 16.75 x 7. So the lip is 0.125in at the sides and 0.25in top and
     bottom: much thinner down the sides than across the top, where this had it
     even all round. The rails are lit as a folded box rather than filled as a
     flat border. */
  body:not([data-panel-fit="folded"]) .chassis {
    border-radius: 0.16em;
    padding: 1.2em 0.62em 0;
    background:
      linear-gradient(180deg, rgba(255,255,255,0.26), rgba(255,255,255,0) 1.1em),
      linear-gradient(0deg, rgba(255,255,255,0.16), rgba(255,255,255,0) 0.7em),
      linear-gradient(178deg, var(--chassis-hi), var(--chassis-lo) 55%, #275475);
  }
  body:not([data-panel-fit="folded"]) .panel {
    border-radius: 0;
    position: relative;
    /* The dress panel sits behind the case lip, so the frame casts a line of
       shadow onto its top and left edges. */
    box-shadow:
      0 0.1em 0.3em rgba(0,0,0,0.55) inset,
      0.08em 0 0.24em rgba(0,0,0,0.4) inset,
      0 0 0 0.05em rgba(0,0,0,0.65);
  }

  /* Painted sheet metal, not glass: a fine matte grain over the whole face. */
  body:not([data-panel-fit="folded"]) .panel::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0.5;
    background-image:
      repeating-linear-gradient(0deg, rgba(255,255,255,0.016) 0 1px, transparent 1px 3px),
      repeating-linear-gradient(90deg, rgba(0,0,0,0.032) 0 1px, transparent 1px 5px),
      repeating-linear-gradient(56deg, rgba(255,255,255,0.011) 0 1px, transparent 1px 7px);
  }
  body:not([data-panel-fit="folded"]) .chassis-badge {
    border-radius: 0 0 0.12em 0.12em;
    margin-left: -0.62em;
    margin-right: -0.62em;
  }

  /* ---- nothing on the face that is not on the machine ----
     The octal digits under the switches are a reading aid this site added; no
     Altair ever had them. They stay in the readable layout, where the point is
     to help, and come off the accurate one, where the point is the machine. */
  body:not([data-panel-fit="folded"]) .octal-readout { display: none; }
}

@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); }

/* questions people ask */

.faq { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 1.6rem; }

.faq-item {
  border: 1px solid var(--rule);
  border-radius: 0.45rem;
  background: var(--page-raised);
  overflow: hidden;
}
.faq-item summary {
  cursor: pointer;
  padding: 0.95rem 1.1rem;
  font-weight: 600;
  color: var(--ink);
  list-style: none;
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
}
.faq-item summary::-webkit-details-marker { display: none; }
/* A plain chevron, drawn rather than typed, so it needs no icon font. */
.faq-item summary::before {
  content: "";
  flex: none;
  width: 0.4rem;
  height: 0.4rem;
  border-right: 1.5px solid var(--accent);
  border-bottom: 1.5px solid var(--accent);
  transform: rotate(-45deg);
  transition: transform 0.15s ease;
}
.faq-item[open] summary::before { transform: rotate(45deg); }
.faq-item summary:hover { color: #fff; }
.faq-item p {
  margin: 0;
  padding: 0 1.1rem 1.1rem 2.85rem;
  color: var(--ink-dim);
}

@media (prefers-reduced-motion: reduce) {
  .faq-item summary::before { transition: none; }
}

/* 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; }

.guide-tracks { display: flex; gap: 0.6rem; flex-wrap: wrap; margin-bottom: 1.1rem; }
.guide-track { flex: 1 1 12rem; font-size: 0.92rem; }
.guide-track.is-active { border-color: var(--accent); color: var(--ink); }
.guide-listing { margin: 0 0 1.2rem; overflow-x: auto; }

/* the card cage */

.cage { display: grid; gap: 0.7rem; margin: 1.6rem 0 1.2rem; }
@media (min-width: 780px) { .cage { grid-template-columns: 1fr 1fr; } }

.card-slot {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.9rem;
  align-items: start;
  padding: 1rem 1.1rem;
  border: 1px solid var(--rule);
  border-left: 3px solid var(--rule);
  border-radius: 0.45rem;
  background: var(--page-raised);
  text-align: left;
  color: var(--ink-dim);
  cursor: pointer;
  font: inherit;
}
.card-slot:hover { border-color: #33404c; }
.card-slot.is-fitted { border-left-color: var(--accent); }
/* A documented board is marked by a dashed edge and its tag, NOT by dimming.
   Opacity was the obvious way to say "you cannot fit this one" and it dropped
   every line of text on those cards below the contrast threshold: axe found 32
   nodes. Legibility is not the right currency for signalling availability. */
.card-slot.is-locked { cursor: default; border-style: dashed; }
.card-slot.is-locked:hover { border-color: var(--rule); }

/* The edge connector, drawn rather than photographed: no image of one is
   cleanly licensed, and a card is mostly a rectangle with gold fingers. */
.card-edge {
  width: 2.1rem;
  height: 2.6rem;
  border-radius: 0.15rem 0.15rem 0 0;
  background:
    repeating-linear-gradient(90deg, #b9922f 0 0.16rem, #8d7126 0.16rem 0.26rem) bottom / 100% 0.5rem no-repeat,
    linear-gradient(180deg, #1f4d34, #163a27);
  box-shadow: inset 0 0 0 1px rgba(0,0,0,0.5);
  flex: none;
}
.card-slot:not(.is-fitted) .card-edge { filter: grayscale(1) brightness(0.5); }

.card-name { color: var(--ink); font-weight: 600; display: block; }
.card-meta { font-size: 0.8rem; color: var(--ink-faint); display: block; margin: 0.15rem 0 0.4rem; }
.card-what { margin: 0; font-size: 0.9rem; }
.card-consequence { margin: 0.45rem 0 0; font-size: 0.85rem; color: var(--ink-faint); }
.card-tag {
  display: inline-block;
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 0.1rem 0.4rem;
  border-radius: 0.2rem;
  border: 1px solid var(--rule);
  margin-left: 0.4rem;
  vertical-align: 0.1em;
}
.card-tag.is-doc { color: var(--ink-faint); }
.card-tag.is-in { color: var(--accent); border-color: var(--accent); }
.card-tag.is-wip { color: var(--accent-cool); border-color: var(--accent-cool); }

/* Expandable deep-dives: the LLM Lab pattern in this page's clothes. The
   pill is the affordance — an expander must LOOK expandable. */
.deeper {
  margin: 1.1rem 0 0;
  border: 1px solid var(--rule);
  border-radius: 0.45rem;
  background: var(--page-raised);
  overflow: hidden;
}
.deeper summary {
  cursor: pointer;
  padding: 0.65rem 1rem;
  font-weight: 600;
  font-size: 0.92rem;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  user-select: none;
}
.deeper summary::-webkit-details-marker { display: none; }
.deeper summary::before { content: '\25B8'; transition: transform 0.2s; }
.deeper[open] summary::before { transform: rotate(90deg); }
.deeper summary::after {
  content: 'go deeper +';
  margin-left: auto;
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 0.12rem 0.55rem;
  white-space: nowrap;
}
.deeper[open] summary::after { content: 'fold away \2212'; }
.deeper summary:hover::after { color: var(--accent-cool); border-color: var(--accent-cool); }
.deeper-body { padding: 0 1rem 0.9rem; }
.deeper-body p { font-size: 0.92rem; }
.deeper-body .source { font-size: 0.8rem; color: var(--ink-faint); }
@media (prefers-reduced-motion: reduce) {
  .deeper summary::before { transition: none; }
}

.cage-summary {
  padding: 0.9rem 1.1rem;
  border: 1px solid var(--rule);
  border-left: 2px solid var(--accent-cool);
  border-radius: 0.45rem;
  background: var(--page-raised);
  font-size: 0.95rem;
}
.cage-summary b { color: var(--ink); }
.cage-summary.is-broken { border-left-color: var(--accent); }

/* entry verification */

.verify { margin: 0 0 1.2rem; display: flex; align-items: center; gap: 0.9rem; flex-wrap: wrap; }
.verify-result { margin: 0; font-size: 0.9rem; color: var(--ink-dim); }
.verify-result.is-good { color: var(--accent-cool, #7fd1e0); }
.verify-result.is-bad { color: var(--accent); }
.verify-result b { color: var(--ink); font-variant-numeric: tabular-nums; }

/* The offending row, marked in the listing rather than only described. */
.listing tr.is-wrong { background: color-mix(in srgb, var(--accent) 16%, transparent); }
.listing tr.is-wrong td { color: var(--ink); }

/* the paper tape reader */

.tape {
  margin: 0 0 1.2rem;
  padding: 1rem;
  border: 1px solid var(--rule);
  border-radius: 0.5rem;
  background: #0d1319;
}
.cassette { margin-top: 1.2rem; }
.tape-head { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; flex-wrap: wrap; }
.tape-title { font-size: 0.85rem; color: var(--ink-dim); }
.tape-count { font-family: var(--font-mono); font-size: 0.85rem; color: var(--accent); font-variant-numeric: tabular-nums; }
.tape-meter {
  display: block;
  height: 0.4rem;
  margin: 0.7rem 0 0.9rem;
  border-radius: 0.2rem;
  background: #05090d;
  overflow: hidden;
}
.tape-fill { display: block; height: 100%; width: 0; background: var(--accent-solid); }
.tape-speeds { display: flex; gap: 0.6rem; flex-wrap: wrap; }
.tape-speed { flex: 1 1 12rem; font-size: 0.88rem; }
.tape-speed.is-active { border-color: var(--accent); color: var(--ink); }
.tape-note { margin: 0.8rem 0 1rem; color: var(--ink-faint); font-size: 0.85rem; }
.tape-nav { display: flex; gap: 0.6rem; flex-wrap: wrap; }

/* 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;
}

/* the engineering notebook, and the pointer that leads to it */
.notebook-pointer {
  padding: 0.9rem 1.1rem;
  border: 1px solid var(--rule);
  border-left: 2px solid var(--accent-cool);
  border-radius: 0.45rem;
  background: var(--page-raised);
}
.notebook-head { margin: 2.5rem 0 1rem; }
.notebook-head .crumb { margin: 0 0 1rem; font-size: 0.9rem; }
.notebook h1 { margin: 0 0 0.6rem; }
article.notebook {
  max-width: 46rem;
  margin: 0 auto;
  padding: 0 1.2rem 4rem;
}

/* 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; }

/* The state badge. A toggle that only shows its state through a dashed border
   is a toggle nobody can read at a glance, and one whose text flips between
   "on" and "off" as its NAME reads as an instruction to do the opposite of
   what it does. Name on the left, state on the right, coloured when live. */
.toggle-icon { font-style: normal; }
.toggle-name { }
.toggle-state {
  font-size: 0.78em;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.08em 0.5em 0.12em;
  border-radius: 999px;
  line-height: 1.5;
}
.sound-toggle[aria-pressed="true"] .toggle-state {
  background: var(--accent-solid);
  color: #fff;
}
.sound-toggle[aria-pressed="false"] .toggle-state {
  color: var(--ink-faint);
  box-shadow: inset 0 0 0 1px var(--rule);
}

/* The accurate/readable choice only exists where the real layout actually
   fits. A phone can never show it, so there is nothing to offer and no
   decision to put in front of anyone. Set after .sound-toggle, which the
   button also wears, so this wins on order as well as specificity. */
.panel-toggles .fit-toggle { display: none; }
@media (min-width: 1000px) and (min-height: 720px) {
  .panel-toggles .fit-toggle { display: inline-flex; }
}

/* ── 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; }

/* board photographs on the engineering notebook */
.board-photo { margin: 1.4rem 0; }
.board-photo img { max-width: 100%; height: auto; border-radius: 0.4rem; border: 1px solid var(--rule); }
.board-photo figcaption { font-size: 0.82rem; color: var(--ink-faint); margin-top: 0.4rem; }
