/* =============================================================================
   Systems Research — styles.css

   Six colours, three type roles, one column, one moving thing.

   The palette comes from telephone equipment rather than software: bakelite,
   brass, verdigris, oxblood. Brass carries the structure and is the colour with
   the largest share of the page; verdigris means "carrying signal" and appears
   only where that is literally true.

   Section map
     1  tokens
     2  reset and base
     3  type roles
     4  layout: the sheet
     5  masthead and stamps
     6  status readout
     7  pipeline and trace          <- the signature element
     8  prose, departments, buttons
     9  form
    10  close and footer
    11  responsive
    12  reduced motion, forced colours, print
   ========================================================================== */

/* --------------------------------------------------------------- 1  tokens */

:root {
  /* Six values. Everything else is one of these at an opacity. */
  --bakelite: #14120F; /* ground */
  --label:    #D8CFBD; /* text, aged label paper */
  --brass:    #B08D46; /* structure: rules, node rings, addresses */
  --live:     #5E8F7D; /* verdigris: stages carrying signal */
  --dark:     #3A362F; /* material of the stages not yet live */
  --oxblood:  #7A2A22; /* one accent. Twice on the page, no more. */

  /* Derived. The trace filament is the one near-white on the page and it only
     ever appears inside the canvas, so it is not a seventh palette colour. */
  --filament: #D6ECDE;

  /* Type */
  --sans: "Archivo", ui-sans-serif, system-ui, "Segoe UI", Helvetica, Arial, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;

  /* A real scale: 1.25, anchored at 17px body. */
  --t-2xs: 0.6875rem; /*  11 */
  --t-xs:  0.75rem;   /*  12 */
  --t-sm:  0.8125rem; /*  13 */
  --t-base: 1.0625rem;/*  17 */
  --t-md:  1.3125rem; /*  21 */
  --t-lg:  1.625rem;  /*  26 */
  --t-xl:  clamp(1.9rem, 1.1rem + 2.9vw, 2.75rem);

  /* Rhythm */
  --gutter: clamp(1.25rem, 1rem + 2.2vw, 3.5rem);
  --measure: 64ch;
  --block-gap: clamp(3.25rem, 2.5rem + 3vw, 5rem);
  --indent: 3.75rem; /* aligns body content with the heading, past the address */

  /* Motion. One curve, one range. */
  --ease: cubic-bezier(.2, .7, .2, 1);
  --fast: 200ms;
  --mid:  300ms;
  --slow: 380ms;
}

@font-face {
  font-family: "Archivo";
  src: url("/assets/fonts/archivo-subset.woff2") format("woff2-variations");
  font-weight: 400 800;
  font-stretch: 100% 125%;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("/assets/fonts/plexmono-400-subset.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("/assets/fonts/plexmono-500-subset.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

/* -------------------------------------------------------- 2  reset + base */

*, *::before, *::after { box-sizing: border-box; }

/* Buttons and links carry display values that would otherwise beat [hidden].
   This is load-bearing: it is what keeps the Send button off the page when no
   endpoint is configured. */
[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  color-scheme: dark;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--bakelite);
  color: var(--label);
  font: 400 var(--t-base)/1.62 var(--sans);
  font-stretch: 100%;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

/* A single soft raking light from the upper left — the lighting condition of
   the equipment this page is about. Two per cent, fixed, no movement. It is
   here so the ground is not a flat black field. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(120ch 90ch at 8% -10%, rgb(216 207 189 / 5.5%), transparent 62%),
    radial-gradient(70ch 60ch at 96% 108%, rgb(176 141 70 / 3.5%), transparent 60%);
}

img, svg, canvas { display: block; max-width: 100%; }

a { color: inherit; }

:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 3px;
  border-radius: 2px;
}

::selection { background: rgb(176 141 70 / 32%); color: var(--label); }

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip {
  position: absolute;
  left: var(--gutter);
  top: 0;
  z-index: 20;
  transform: translateY(-120%);
  padding: .65rem 1rem;
  background: var(--brass);
  color: var(--bakelite);
  font: 500 var(--t-sm)/1 var(--mono);
  text-decoration: none;
  border-radius: 0 0 2px 2px;
  transition: transform var(--fast) var(--ease);
}
.skip:focus-visible { transform: translateY(0); }

/* ----------------------------------------------------------- 3  type roles */

/* Mono is not decoration. It marks anything machine-readable: a label, a
   measurement, a status, a stage name, an address. Sans is for anything a
   person wrote in sentences. */
.mono {
  font-family: var(--mono);
  font-size: var(--t-sm);
  letter-spacing: .045em;
  font-variant-ligatures: none;
}

h1, h2, h3 { margin: 0; font-weight: 700; line-height: 1.14; }

h2 {
  font-size: var(--t-lg);
  font-stretch: 112%;
  letter-spacing: -.005em;
  text-wrap: balance;
}

p { margin: 0 0 1.05em; }
p:last-child { margin-bottom: 0; }

/* --------------------------------------------------------- 4  the sheet */

/* 64rem, not 78. The measure is 64ch; a wider sheet only buys empty right-hand
   column, which reads as an unfinished grid rather than a deliberate one. */
.sheet {
  --edge: min(var(--gutter), 6vw);
  max-width: 64rem;
  margin: 0 auto;
  padding: clamp(1.75rem, 1rem + 3vw, 3.25rem) var(--edge) clamp(2.5rem, 2rem + 3vw, 4rem);
}

.rule {
  height: 1px;
  margin: 0;
  border: 0;
  background: linear-gradient(90deg, var(--brass) 0%, rgb(176 141 70 / 38%) 82%, rgb(176 141 70 / 12%) 100%);
}

.block { margin-block: var(--block-gap); }

/*
 * The diagram is the only thing on the page that breaks the sheet.
 *
 * The bleed sits on the diagram itself, not on the section around it, so the
 * pipeline runs the full width of the screen while its heading stays in the
 * same address column as every other heading on the page.
 *
 * body has overflow-x: hidden, so the few pixels of scrollbar that 100vw
 * overshoots by are clipped off a transparent margin and never reach content.
 */
.trace--bleed {
  margin-inline: calc(50% - 50vw);
  padding-inline: var(--edge);
}

/* Past a certain width the stages would stretch into a thin band, so the
   pipeline stops widening and re-centres. */
@media (min-width: 100rem) {
  .trace--bleed { padding-inline: calc(50vw - 46rem); }
}

/* Every block is addressed. The numbers are row addresses on a panel, not
   editorial numerals — mono, small, quiet, and they continue the numbering the
   status readout starts. */
.block__head {
  display: grid;
  grid-template-columns: 3.25rem 1fr;
  align-items: baseline;
  gap: 0 .5rem;
  margin-bottom: clamp(1.5rem, 1rem + 1.6vw, 2.5rem);
}

.addr {
  color: var(--brass);
  opacity: .9;
  font-size: var(--t-xs);
  letter-spacing: .14em;
}

/* -------------------------------------------------- 5  masthead and stamps */

/*
 * The stamp lives inside the lockup rather than opposite it.
 *
 * Sitting it at the far right meant it wrapped onto its own line at anything
 * under about 1000px and then aligned to the mark instead of the wordmark,
 * which looked like a mistake. Inside the lockup it aligns by construction at
 * every width, and it reads the way it should: a nameplate with an instrument
 * stamp under it.
 */
.masthead { margin-bottom: clamp(1.5rem, 1rem + 1.4vw, 2.25rem); }

.masthead__lockup { display: flex; align-items: flex-start; gap: 1rem; }
.mark { flex: none; margin-top: .3rem; }
.masthead__text { min-width: 0; }

.wordmark {
  font-size: var(--t-xl);
  font-weight: 800;
  font-stretch: 125%;
  letter-spacing: -.012em;
  line-height: .92;
  text-transform: uppercase;
}

.tagline-small {
  margin: .5rem 0 0;
  font-family: var(--mono);
  font-size: var(--t-xs);
  letter-spacing: .1em;
  color: var(--brass);
}

/* The instrument stamp. Reserved width so filling in the seed shifts nothing. */
.stamp { margin: .7rem 0 0; display: flex; flex-wrap: wrap; gap: .55rem; align-items: baseline; }
.stamp__key { color: var(--brass); opacity: .9; letter-spacing: .14em; }
.stamp__seed { min-width: 6ch; font-weight: 500; color: var(--label); }
.stamp__note { color: var(--label); opacity: .62; }
.stamp__note::before { content: "· "; }

/* ---------------------------------------------------- 6  status readout */

.readout { margin-top: clamp(2rem, 1.5rem + 2vw, 3.25rem); }

.readout__list { margin: 0; display: grid; gap: .1rem; max-width: 46rem; }

.row {
  display: grid;
  grid-template-columns: 3.25rem 9.5rem 1fr;
  align-items: baseline;
  gap: 0 .5rem;
  padding-block: .5rem;
  border-bottom: 1px solid rgb(176 141 70 / 13%);
}
.row:last-child { border-bottom: 0; }

.row__n { color: var(--brass); opacity: .85; font-size: var(--t-xs); letter-spacing: .14em; }

/* The keys are brass, the values are label. Brass is the structural colour on
   this page and it needs enough area to hold the page against the ground —
   without it this becomes near-black with one accent, which is exactly the
   look this design is trying not to be. */
.row dt {
  color: var(--brass);
  text-transform: uppercase;
  letter-spacing: .16em;
  font-size: var(--t-xs);
}

.row dd { margin: 0; color: var(--label); font-weight: 500; }

/* Resolve, once, on load. Not typed out character by character — the values
   settle into place the way a needle settles. */
.js .row { opacity: 0; }
.js.ready .row {
  animation: settle var(--slow) var(--ease) forwards;
  animation-delay: calc(var(--i) * 45ms);
}
@keyframes settle {
  from { opacity: 0; transform: translateY(.4rem); filter: blur(1px); }
  60%  { opacity: 1; }
  to   { opacity: 1; transform: none; filter: none; }
}

/* --------------------------------------------- 7  the pipeline and trace */

.trace {
  position: relative;

  /* Declared here, not on .stages, so the static fallback can measure itself
     against exactly the same track and offsets the diagram uses. */
  --stage-gap: clamp(.5rem, .2rem + 1vw, 1.5rem);
  --node-size: 1.25rem;
  --node-half: .625rem;
  --headroom: clamp(4.5rem, 3rem + 6vw, 8.5rem); /* space above the node line */
  --headroom-m: clamp(3rem, 2rem + 6vw, 5rem);   /* same, once stacked */
  --rail: 2rem;                                  /* left rail, stacked layout */
}

/* The trace layer sits above the node row and is purely decorative — the
   diagram underneath is the accessible artefact. */
.trace__layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
}

.trace__canvas { width: 100%; height: 100%; opacity: 0; }
.trace--live .trace__canvas { opacity: 1; transition: opacity var(--slow) var(--ease); }

/*
 * Static composed frame for the no-JavaScript case.
 *
 * Both SVGs stretch (preserveAspectRatio: none) so their internal axis line
 * lands exactly on the node line. The horizontal one puts its axis at 80% of
 * its own height, so its height is the node line divided by 0.8; the vertical
 * one puts its axis at 25% of its width. Everything is derived from the same
 * tokens the grid uses, so the frame cannot drift out of register.
 */
.trace__static { position: absolute; opacity: .85; }

.trace__static--h {
  top: 0;
  left: 0;
  height: calc((var(--headroom) + var(--node-half)) * 1.25);
  /* Node 1 centre, plus one column and one gap to reach node 2, plus a third
     of the next column -- which is where the live trace stops. */
  width: calc(
    var(--node-half)
    + 1.34 * ((100% - 5 * var(--stage-gap)) / 6)
    + 1.34 * var(--stage-gap)
  );
}

.trace__static--v { display: none; }
.trace--live .trace__static, .js .trace__static { display: none; }

.stages {
  position: relative;
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: var(--stage-gap);
}

.stage {
  position: relative;
  /* Room above the node for the signal to run through. This is the signature
     element of the page; it gets height accordingly. */
  padding-top: var(--headroom);
}

/* The connector between one stage and the next. Solid brass where the system
   is energised, dashed where it is not — the difference reads as "not wired up
   yet", never as "failed to load". */
.stage:not(:last-child)::after {
  content: "";
  position: absolute;
  top: calc(var(--headroom) + var(--node-half));
  /* The node is the first child of the stage, so its centre is half a node in
     from the stage's left edge -- not at 50%. Getting this wrong left every
     connector floating a half-column to the right of the nodes it joins. */
  left: var(--node-half);
  width: calc(100% + var(--stage-gap));
  height: 0;
  border-top: 1px dashed color-mix(in srgb, var(--dark) 70%, var(--label));
}
/* A link is only energised when the stage at each end of it is. LEAD is live
   and MQL is not, so LEAD's connector stays dashed — the signal has somewhere
   to arrive from and nowhere to go. */
.stage.is-live:has(+ .stage.is-live)::after { border-top: 1px solid var(--brass); opacity: .7; }

.stage__node {
  --pulse: 0;
  position: relative;
  z-index: 2;
  display: block;
  width: var(--node-size);
  height: var(--node-size);
  border-radius: 50%;
  border: 2px dashed color-mix(in srgb, var(--dark) 58%, var(--label));
  background: var(--bakelite);
  margin-bottom: 1.05rem;
}

/* A live node is a lamp behind a jack: solid ring, verdigris fill, and it
   answers when the beam reaches it. No bounce, no scale-up — a change in
   weight and brightness, which is what a filament actually does. */
.stage.is-live .stage__node {
  border: 2px solid var(--brass);
  background: color-mix(in srgb, var(--live) calc(58% + var(--pulse) * 42%), var(--bakelite));
  box-shadow: 0 0 0 calc(var(--pulse) * 5px) rgb(94 143 125 / 18%);
  transition: background var(--fast) var(--ease), box-shadow var(--fast) var(--ease);
}

.stage__name {
  font-family: var(--mono);
  font-size: var(--t-sm);
  font-weight: 500;
  letter-spacing: .15em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--label) 66%, var(--bakelite));
  margin-bottom: .55rem;
}
.stage.is-live .stage__name { color: var(--label); }

/*
 * The definitions are always present and always legible, at every width.
 *
 * An earlier pass hid the four unlit ones behind hover. That was wrong: it made
 * the diagram look half-rendered instead of half-lit, which is the opposite of
 * the point. A stage that is not yet carrying signal still has a name and a
 * definition -- what it does not have is a lamp. Hover and focus raise a stage
 * from dim to full rather than from nothing to something.
 */
.stage__def {
  margin: 0;
  font-size: var(--t-xs);
  line-height: 1.5;
  color: color-mix(in srgb, var(--label) 66%, var(--bakelite));
  max-width: 24ch;
  transition: color var(--fast) var(--ease);
}

@media (hover: hover) {
  .stage:hover .stage__def,
  .stage:focus-visible .stage__def { color: var(--label); }
  .stage:hover .stage__name,
  .stage:focus-visible .stage__name { color: var(--label); }
}

.stage { outline-offset: 8px; border-radius: 2px; }

/* ------------------------------------- 8  prose, departments and buttons */

/*
 * Optional texture slot -- see README, "Optional photography".
 *
 * Save a punch-down block photograph at /assets/texture-punchdown.jpg, then set
 * --texture on :root to switch it on:
 *
 *     :root { --texture: url("/assets/texture-punchdown.jpg"); }
 *
 * It appears behind this one section, desaturated and multiplied at 6%. Until
 * that line exists no request is made at all -- pointing the rule straight at
 * the file instead would 404 on every page load forever.
 *
 * If you can see it as an image rather than as a material, it is too strong.
 * Lower the opacity; do not change the blend mode.
 */
.block--textured { position: relative; isolation: isolate; }
.block--textured::before {
  content: "";
  position: absolute;
  inset: -2rem calc(var(--edge) * -1);
  z-index: -1;
  pointer-events: none;
  background: var(--texture, none) center / cover no-repeat;
  filter: grayscale(1) contrast(.9);
  mix-blend-mode: multiply;
  opacity: .06;
}

.prose { max-width: var(--measure); margin-left: var(--indent); }
.prose p + p { margin-top: 1.15em; }

.depts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr));
  gap: clamp(2rem, 1.5rem + 3vw, 4rem);
  margin-left: var(--indent);
}

.dept { max-width: 40ch; }

.dept__name {
  font-size: var(--t-md);
  font-stretch: 118%;
  font-weight: 700;
  margin-bottom: .35rem;
}

.dept__mono {
  color: var(--brass);
  font-family: var(--mono);
  font-size: var(--t-xs);
  letter-spacing: .09em;
  margin-bottom: 1rem;
}

.dept__routes {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .75rem 1.25rem;
  margin-top: 1.35rem;
}

.btn {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  padding: .5rem 1.15rem;
  border: 1px solid var(--brass);
  border-radius: 2px; /* a milled edge, not a card corner */
  background: transparent;
  color: var(--label);
  font: 500 var(--t-sm)/1 var(--mono);
  letter-spacing: .1em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease),
              color var(--fast) var(--ease);
}
.btn:hover { background: rgb(176 141 70 / 14%); }

.btn--solid { background: var(--brass); border-color: var(--brass); color: var(--bakelite); }
.btn--solid:hover { background: color-mix(in srgb, var(--brass) 88%, var(--label)); }
.btn--solid:disabled { opacity: .6; cursor: progress; }

.btn--quiet {
  border-color: transparent;
  padding-inline: 0;
  color: var(--brass);
  text-transform: none;
  letter-spacing: .04em;
  text-decoration: underline;
  text-underline-offset: .3em;
  text-decoration-thickness: 1px;
  text-decoration-color: rgb(176 141 70 / 45%);
}
.btn--quiet:hover { background: transparent; color: var(--label); text-decoration-color: currentColor; }

/* ------------------------------------------------------------- 9  the form */

.form { margin-left: var(--indent); max-width: 46rem; }

.form__panel {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.15rem 1.5rem;
  padding: clamp(1.25rem, 1rem + 1.4vw, 2rem);
  border: 1px solid rgb(176 141 70 / 34%);
  border-radius: 2px;
  background: rgb(216 207 189 / 2.5%);
}
.form__panel:focus { outline: none; }

.field { display: flex; flex-direction: column; gap: .4rem; min-width: 0; }
.field--wide { grid-column: 1 / -1; }

.field label {
  color: var(--label);
  opacity: .62;
  text-transform: uppercase;
  letter-spacing: .16em;
  font-size: var(--t-xs);
}

.field__opt { text-transform: none; letter-spacing: .04em; opacity: .7; }

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: .7rem .8rem;
  border: 1px solid rgb(216 207 189 / 20%);
  border-radius: 2px;
  background: rgb(20 18 15 / 70%);
  color: var(--label);
  font: 400 var(--t-sm)/1.5 var(--mono);
  transition: border-color var(--fast) var(--ease);
}
.field textarea { resize: vertical; min-height: 7rem; }
.field input::placeholder, .field textarea::placeholder { color: rgb(216 207 189 / 34%); }
.field input:hover, .field select:hover, .field textarea:hover { border-color: rgb(216 207 189 / 36%); }

.field select {
  appearance: none;
  padding-right: 2.2rem;
  background-image: linear-gradient(45deg, transparent 50%, var(--brass) 50%),
                    linear-gradient(135deg, var(--brass) 50%, transparent 50%);
  background-position: right 1rem center, right .68rem center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.field select option { background: var(--bakelite); color: var(--label); }

.field__hint { margin: 0; color: var(--label); opacity: .62; font-size: var(--t-2xs); line-height: 1.45; }

/* An error outranks a hint, so it is shown above it regardless of source
   order. The source order is what aria-describedby reads, and there the error
   comes first too. */
.field label { order: 0; }
.field input, .field select, .field textarea { order: 1; }
.field__error { order: 2; }
.field__hint { order: 3; }

.field__error {
  margin: 0;
  font-size: var(--t-xs);
  line-height: 1.45;
  color: var(--label);
  display: none;
}

/* Oxblood, use two of two: the field that is wrong. It is a bar behind light
   text rather than the text colour itself, because oxblood on bakelite does
   not carry enough contrast to be read. */
.field[data-invalid] input,
.field[data-invalid] select,
.field[data-invalid] textarea { border-color: var(--oxblood); }

.field[data-invalid] .field__error {
  display: block;
  padding: .38rem .55rem;
  border-left: 2px solid var(--oxblood);
  background: rgb(122 42 34 / 26%);
}

.form__status { grid-column: 1 / -1; margin: 0; font-size: var(--t-sm); line-height: 1.55; }
.form__status:empty { display: none; }
.form[data-state="error"] .form__status {
  padding: .7rem .85rem;
  border-left: 2px solid var(--oxblood);
  background: rgb(122 42 34 / 26%);
}

.form__foot {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .8rem 1.5rem;
  margin-top: .35rem;
}

.form__alt, .form__offline { margin: 0; color: var(--label); opacity: .62; font-size: var(--t-xs); line-height: 1.5; }
.form__offline { flex: 1 1 20rem; }
.form__alt a, .form__offline a { color: var(--brass); text-underline-offset: .25em; }

/* When the form is switched off, the offline notice already gives the address.
   Saying it a second line later, in the same style, just reads as a stutter. */
.form[data-state="offline"] .form__alt { display: none; }

.form[data-state="success"] .form__panel { display: block; }
.form__panel .lede { font-family: var(--sans); font-size: var(--t-md); font-weight: 700; font-stretch: 112%; }
.form__panel .meta { opacity: .62; }

/* ------------------------------------------------- 10  close and footer */

.close {
  margin-block: calc(var(--block-gap) * 1.15) var(--block-gap);
  margin-left: var(--indent);
}

.close__note {
  color: var(--label);
  display: flex;
  gap: .5rem;
  align-items: baseline;
  font-size: var(--t-base);
  letter-spacing: .01em;
  max-width: none;
  margin-bottom: clamp(2.5rem, 2rem + 3vw, 4.5rem);
}
.close__note .addr { margin-left: calc(var(--indent) * -1); width: 3.25rem; flex: none; }

.tagline {
  margin: 0;
  font-size: clamp(1.35rem, .9rem + 1.9vw, 2.35rem);
  font-weight: 400;
  font-stretch: 118%;
  letter-spacing: -.005em;
  color: var(--label);
}

.foot {
  display: flex;
  flex-wrap: wrap;
  gap: .6rem 2rem;
  justify-content: space-between;
  padding-top: 1.5rem;
  color: var(--label);
  opacity: .6;
  font-size: var(--t-xs);
}
.foot a { color: inherit; text-underline-offset: .3em; }
.foot__stamp { display: flex; gap: .5rem; }

/* ----------------------------------------------------------- 11  responsive */

@media (max-width: 60rem) {
  /* Collapsing the indent at the token means every rule derived from it --
     content margins, the outdented address, the texture inset -- follows
     automatically. Overriding them one at a time is how the texture plate ended
     up hanging 60px off both edges of a phone. */
  :root { --indent: 0rem; }
  .block__head { grid-template-columns: 2.75rem 1fr; }
}

@media (max-width: 46rem) {
  /* The pipeline turns to run down the page. Node in a left rail, definition
     beside it, all six visible — no hover on a phone. */
  .stages {
    grid-template-columns: 1fr;
    gap: 0;
    /* A rail for the signal to run down. Without this the nodes sit hard
       against the left edge and the waveform has nowhere to swing. */
    padding-left: var(--rail);
  }

  .stage {
    display: grid;
    grid-template-columns: var(--node-size) 1fr;
    column-gap: 1.15rem;
    padding-top: 0;
    padding-bottom: 1.75rem;
  }
  .stage:first-child { padding-top: var(--headroom-m); }
  .stage:last-child { padding-bottom: 0; }

  .stage__node { grid-row: 1 / 3; margin-bottom: 0; margin-top: .05rem; }
  .stage__name { grid-column: 2; margin-bottom: .35rem; }
  .stage__def { grid-column: 2; max-width: 40ch; }

  /* Node centre to node centre. The first stage carries the headroom as its
     padding, so its connector has to start that much lower — otherwise it
     reaches up past the node into empty space. */
  .stage:not(:last-child)::after {
    top: calc(.05rem + var(--node-half));
    left: var(--node-half);
    width: 0;
    height: 100%;
    border-top: 0;
    border-left: 1px dashed color-mix(in srgb, var(--dark) 70%, var(--label));
  }
  .stage:first-child:not(:last-child)::after {
    top: calc(var(--headroom-m) + .05rem + var(--node-half));
    height: calc(100% - var(--headroom-m));
  }
  .stage.is-live:has(+ .stage.is-live)::after { border-top: 0; border-left: 1px solid var(--brass); }

  .trace__static--h { display: none; }
  .trace__static--v {
    display: block;
    top: 0;
    /* The SVG carries its axis at 25% of its own width, so a 4rem box puts
       that axis 1rem in; the node line sits at rail + half a node. */
    width: 4rem;
    left: calc(var(--rail) + var(--node-half) - 1rem);
    height: calc(var(--headroom-m) + 30%);
  }

  .form__panel { grid-template-columns: 1fr; }
  .row { grid-template-columns: 2.4rem 1fr; row-gap: .1rem; }
  .row dd { grid-column: 2; }
}

@media (max-width: 26rem) {
  .masthead { align-items: flex-start; }
  .masthead__lockup { gap: .7rem; }
  .row { grid-template-columns: 2.1rem 1fr; }
}

/* --------------------------------- 12  reduced motion, forced colours, print */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
  }
  /* Nothing is hidden behind an animation that will not play. */
  .js .row { opacity: 1; }
  .trace--live .trace__canvas { opacity: 1; }
  @media (hover: hover) and (min-width: 60rem) {
    .stage__def { opacity: 1 !important; transform: none !important; }
  }
}

@media (forced-colors: active) {
  .stage__node { forced-color-adjust: none; border-color: CanvasText; }
  .stage.is-live .stage__node { background: Highlight; }
  .rule, .stage::after { border-color: CanvasText; }
}

@media print {
  body::before, .trace__layer, .skip { display: none; }
  body { background: #fff; color: #000; }
  .stage__def { opacity: 1 !important; transform: none !important; }
}
