/* Cyanotype patent plate. Blue is the ground, not a background: every line, letter and
   rule is reversed out of it in paper white, and the single warm accent is light itself. */

:root {
  /* Blueprint ground, three depths: outside the sheet, the sheet, and a recessed frame. */
  --ink-0: #04121f;
  --ink-1: #0a2540;
  --ink-2: #0d3057;
  --ink-3: #113a68;

  /* Paper-white linework, at the weights a plate actually uses. */
  --line-hair: rgba(233, 241, 248, 0.26);
  --line-thin: rgba(233, 241, 248, 0.44);
  --line-bold: rgba(233, 241, 248, 0.78);
  /* Raised from 0.62/0.40: measured against the sheet, 0.40 gave about 3.1:1, under the
     4.5:1 these small sizes need. */
  --paper: #eaf1f8;
  --paper-dim: rgba(234, 241, 248, 0.82);
  --paper-faint: rgba(234, 241, 248, 0.68);

  /* The accent is the projected light. Nothing else may use it. */
  --light: #ffd08a;
  --light-hot: #fff2d6;

  --flag-good: #8fe3b0;
  --flag-warn: #ffcf87;
  --flag-bad: #ff9d8a;

  --gothic: 'Archivo', ui-sans-serif, system-ui, 'Helvetica Neue', Arial, sans-serif;
  --script: 'Courier Prime', 'Courier New', ui-monospace, monospace;

  --gap: clamp(1.1rem, 2.4vw, 2.2rem);
  --hair: 1px;
}

*, *::before, *::after { box-sizing: border-box; }

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

body {
  margin: 0;
  padding: clamp(0.5rem, 2vw, 2.25rem);
  background: var(--ink-0);
  /* Faint drafting grid, the ground the plate is drawn on. */
  background-image:
    linear-gradient(var(--line-hair) var(--hair), transparent var(--hair)),
    linear-gradient(90deg, var(--line-hair) var(--hair), transparent var(--hair));
  background-size: 48px 48px, 48px 48px;
  color: var(--paper);
  font-family: var(--gothic);
  font-size: clamp(0.95rem, 0.35vw + 0.88rem, 1.05rem);
  line-height: 1.55;
}

.vh {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

.skip {
  position: absolute; left: -9999px; top: 0;
  background: var(--paper); color: var(--ink-1);
  padding: 0.6rem 1rem; z-index: 20; font-weight: 600;
}
.skip:focus { left: 0.5rem; top: 0.5rem; }

:focus-visible {
  outline: 2px solid var(--light);
  outline-offset: 2px;
}

/* ---------- the sheet ---------- */

.sheet {
  position: relative;
  max-width: 1500px;
  margin: 0 auto;
  background: var(--ink-1);
  border: var(--hair) solid var(--line-bold);
  outline: var(--hair) solid var(--line-hair);
  outline-offset: 5px;
  padding: clamp(1rem, 2.6vw, 2.6rem);
  display: flex;
  flex-direction: column;
  gap: clamp(1.6rem, 3.2vw, 3rem);
}

/* Corner registration marks, as on a real plate. */
.reg { position: absolute; width: 22px; height: 22px; }
.reg::before, .reg::after {
  content: ''; position: absolute; background: var(--line-thin);
}
.reg::before { inset-inline: 0; height: var(--hair); top: 50%; }
.reg::after { inset-block: 0; width: var(--hair); left: 50%; }
.reg--tl { top: 9px; left: 9px; }
.reg--tr { top: 9px; right: 9px; }
.reg--bl { bottom: 9px; left: 9px; }
.reg--br { bottom: 9px; right: 9px; }

/* ---------- title strip ---------- */

.strip {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--gap);
  padding-bottom: 0.9rem;
  border-bottom: var(--hair) solid var(--line-bold);
}

.strip__id h1 {
  margin: 0;
  font-size: clamp(1.75rem, 4.6vw, 3.4rem);
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 0.98;
  text-transform: uppercase;
}

.strip__sub {
  margin: 0.45rem 0 0;
  font-family: var(--script);
  font-size: 0.78rem;
  color: var(--paper-dim);
  max-width: 46ch;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.strip__intake { display: flex; flex-direction: column; align-items: flex-start; gap: 0.5rem; }

/* Drafting-note weight, not badges. The first viewport is the three figures; this is a way to
   find the rest, not a plea placed in front of the thing people came for. */
.strip__links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 0.9rem;
  margin: 0;
  padding: 0;
  list-style: none;
  font-family: var(--script);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.strip__links a { color: var(--paper-faint); text-decoration-color: var(--line-thin); }
.strip__links a:hover { color: var(--light); text-decoration-color: currentColor; }

.strip__note {
  margin: 0;
  font-family: var(--script);
  font-size: 0.72rem;
  color: var(--paper-faint);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* ---------- controls, drawn as plate hardware ---------- */

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.7rem 1.15rem;
  border: var(--hair) solid var(--line-bold);
  background: transparent;
  color: var(--paper);
  font: 600 0.86rem/1 var(--gothic);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 140ms ease, color 140ms ease, border-color 140ms ease;
}
.btn:hover:not(:disabled) { background: var(--paper); color: var(--ink-1); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }

.btn__mark { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.6; }

.btn--intake input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }

.btn--export { border-color: var(--light); color: var(--light); overflow: hidden; }
.btn--export:hover:not(:disabled) { background: var(--light); color: var(--ink-0); }

/* The export button is the one control that must not offer a half-finished answer.
   Measured before this existed: it unlocked 2.1 s after load, when the coarse 33-grid pass
   finished, and stayed unlocked for the eight seconds the real 65-grid solve took. The label
   read "Download STL" throughout and the only disabled styling was a 40% fade, so an amber
   button at 40% on a dark ground still read as live. Hence a stated state, a label that says
   which, and the accent withheld until the answer is final. */
.btn--export[data-state='busy'],
.btn--export[data-state='blocked'] {
  border-color: var(--line-thin);
  color: var(--paper-faint);
  /* Not the generic disabled fade: it would wash out the progress line below. */
  opacity: 1;
}
.btn--export[data-state='busy'] { cursor: progress; }
.btn--export[data-state='blocked'] { cursor: not-allowed; }
.btn--export[data-state='blocked'] { border-color: var(--flag-bad); color: var(--flag-bad); }

/* Determinate, not a spinner. The fraction is the solver's own iteration count against its
   budget, the same number Fig. 3's bar carries, so it is a measurement rather than a comforting
   animation. Drawn as a dimension line filling along the foot, matching that bar. */
.btn--export::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2px;
  width: 100%;
  background: var(--light);
  transform: scaleX(var(--fill, 0));
  transform-origin: left center;
  transition: transform 200ms linear, opacity 200ms ease;
  opacity: 0.8;
}
.btn--export[data-state='ready']::after,
.btn--export[data-state='written']::after,
.btn--export[data-state='blocked']::after { opacity: 0; }

/* ---------- the margin tab that points at the file ---------- */

/* Rides the right drafting margin, which is otherwise empty. Set vertically because a
   horizontal button there would either overlap the sheet or be too narrow to read, and a
   vertical tab is the one shape that fits a margin without taking width from the drawing.
   Presentation only: it holds no state of its own and writes no file, it forwards to the
   real button, so the two can never disagree about whether a solve has finished. */
.rail {
  position: fixed;
  right: 0;
  top: 50%;
  transform: translateY(-50%) translateX(100%);
  z-index: 40;

  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.55rem;
  padding: 0.9rem 0.45rem;

  border: var(--hair) solid var(--light);
  border-right: 0;
  border-radius: 3px 0 0 3px;
  background: var(--ink-0);
  color: var(--light);

  font-family: var(--script);
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  cursor: pointer;

  opacity: 0;
  pointer-events: none;
  transition: transform 260ms cubic-bezier(0.22, 0.61, 0.36, 1), opacity 200ms ease,
              background 140ms ease, color 140ms ease;
}
.rail__label { writing-mode: vertical-rl; }
.rail__mark { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 2; }

/* Shown only when there is a file to fetch and the real button is off screen. Both
   conditions are set from main.js; neither is inferred from styling. */
.rail[data-visible='1'] {
  transform: translateY(-50%) translateX(0);
  opacity: 1;
  pointer-events: auto;
}
.rail:hover { background: var(--light); color: var(--ink-0); }
.rail:focus-visible { outline: var(--hair) solid var(--light); outline-offset: 3px; }

/* Never offer the file before the requested grid has landed, same gate as the real button. */
.rail[data-state='busy'],
.rail[data-state='blocked'] { display: none; }

/* No margin to ride on a narrow screen, and a vertical tab would sit over the drawing. */
@media (max-width: 900px) {
  .rail { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .rail { transition: opacity 200ms ease; }
}

/* ---------- the chain: picture, object, projection ---------- */

.chain {
  display: grid;
  grid-template-columns: 1fr auto 1fr auto 1.35fr;
  align-items: center;
  gap: clamp(0.5rem, 1.4vw, 1.4rem);
}

.fig { margin: 0; display: flex; flex-direction: column; gap: 0.7rem; min-width: 0; }

.fig__frame {
  position: relative;
  aspect-ratio: 1;
  border: var(--hair) solid var(--line-thin);
  background: var(--ink-2);
  overflow: hidden;
}
.fig__frame--wide { aspect-ratio: 1024 / 380; }

.fig__frame canvas { display: block; width: 100%; height: 100%; }

.fig__tag {
  position: absolute;
  top: 0; left: 0;
  padding: 0.28rem 0.5rem;
  font: 700 0.66rem/1 var(--script);
  letter-spacing: 0.1em;
  background: var(--ink-1);
  border-right: var(--hair) solid var(--line-thin);
  border-bottom: var(--hair) solid var(--line-thin);
  text-transform: uppercase;
}

.fig figcaption {
  display: flex; flex-direction: column; gap: 0.15rem;
  font-family: var(--script);
  font-size: 0.74rem;
  color: var(--paper-dim);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.fig figcaption b {
  font-family: var(--gothic);
  font-size: 0.88rem;
  letter-spacing: 0.06em;
  color: var(--paper);
}

/* The projection is the artefact; it gets the light and the largest frame. */
.fig--cast .fig__frame--cast { background: #061625; }

.arrow {
  display: flex; flex-direction: column; align-items: center; gap: 0.3rem;
  align-self: center;
  padding-bottom: 2.6rem;
  min-width: 54px;
}
.arrow svg { width: 100%; height: 14px; overflow: visible; }
.arrow line { stroke: var(--line-bold); stroke-width: 1; }
.arrow path { fill: var(--line-bold); }
.arrow span {
  font: 400 0.64rem/1 var(--script);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--paper-faint);
}

/* ---------- stamp ---------- */

.stamp {
  position: absolute;
  right: 0.7rem; bottom: 0.7rem;
  padding: 0.34rem 0.7rem;
  font: 700 0.68rem/1 var(--script);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  border: 2px solid var(--flag-warn);
  color: var(--flag-warn);
  background: rgba(4, 18, 31, 0.72);
  transform: rotate(-3deg);
}
.stamp[data-state='good'] { border-color: var(--flag-good); color: var(--flag-good); }
.stamp[data-state='warn'] { border-color: var(--flag-warn); color: var(--flag-warn); }
.stamp[data-state='bad'] { border-color: var(--flag-bad); color: var(--flag-bad); }
.stamp[data-state='busy'] { border-style: dashed; }

/* Solve progress, drawn as a dimension line filling along the foot of the figure.
   The solve is genuinely slow at fine grids; it gets an honest indicator rather than a
   cheapened result. */
.prog {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 3px;
  background: var(--line-hair);
  opacity: 0;
  transition: opacity 200ms ease;
}
.prog[data-busy='1'] { opacity: 1; }
/* Scaled rather than resized. This bar fills while the solver is the busiest thing on the
   page, which is the worst possible moment to ask for a relayout every frame; a transform is
   composited instead. Visually identical, because the bar is a solid block of one colour. */
.prog span {
  display: block;
  height: 100%;
  width: 100%;
  background: var(--light);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 160ms linear;
}

/* ---------- section headings ---------- */

.ruled {
  margin: 0 0 1rem;
  padding-bottom: 0.4rem;
  border-bottom: var(--hair) solid var(--line-bold);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--paper-dim);
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  flex-wrap: wrap;
}

/* What a control reaches. The sheet has two figures that both show a wall, and a block of
   controls sitting between them, so which controls own which figure was a guess. This says
   it on the heading rather than three paragraphs down. */
.scope {
  margin-left: auto;
  font-family: var(--script);
  font-size: 0.62rem;
  font-weight: 400;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 0.14rem 0.45rem;
  border: var(--hair) solid currentColor;
  color: var(--paper-faint);
  white-space: nowrap;
}
.scope--fast { color: var(--flag-good); }
.scope--slow { color: var(--flag-warn); }
.scope--local { color: var(--light); }

.detail__grid {
  display: grid;
  grid-template-columns: minmax(0, 0.78fr) minmax(0, 1.22fr);
  gap: var(--gap);
  align-items: start;
}
/* The section is genuinely thin - six millimetres of relief across a hundred - and it is
   drawn in true proportion, so its frame is short rather than padded out with void. The
   ray diagram fills the rest of the column. */
.fig__frame--wide { aspect-ratio: 1024 / 300; }
.fig__frame--rays { aspect-ratio: 1024 / 440; }

.detail__stack { display: flex; flex-direction: column; gap: var(--gap); min-width: 0; }

/* ---------- parameters ---------- */

.axes__lead {
  margin: -0.4rem 0 1.3rem;
  max-width: 78ch;
  font-family: var(--script);
  font-size: 0.8rem;
  color: var(--paper-dim);
}

/* The two groups differ in kind, not in degree: one rescales an existing solve and one
   throws it away and solves again. Splitting them is what makes the wait explicable. The
   gap between groups is deliberately larger than the gap inside one. */
.axes__group + .axes__group { margin-top: calc(var(--gap) * 1.5); }

.axes__gh {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin: 0;
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--paper);
}
.axes__gnote {
  margin: 0.3rem 0 0.9rem;
  max-width: 72ch;
  font-family: var(--script);
  font-size: 0.72rem;
  line-height: 1.5;
  color: var(--paper-faint);
}

/* Three across, so each group fills one full row instead of orphaning its last axis. */
.axes__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--gap) clamp(1.2rem, 3vw, 3rem);
}
@media (max-width: 900px) { .axes__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px) { .axes__grid { grid-template-columns: 1fr; } }

.axis {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: 0.35rem 0.8rem;
  padding-top: 0.7rem;
  border-top: var(--hair) solid var(--line-thin);
}

.axis label {
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.axis output {
  font-family: var(--script);
  font-size: 0.86rem;
  font-weight: 700;
  color: var(--light);
  font-variant-numeric: tabular-nums;
}

.axis__note {
  grid-column: 1 / -1;
  margin: 0.1rem 0 0;
  font-family: var(--script);
  font-size: 0.7rem;
  line-height: 1.45;
  color: var(--paper-faint);
}

.axis input[type='range'], .axis select { grid-column: 1 / -1; width: 100%; }

/* Slider drawn as a dimension line. */
.axis input[type='range'] {
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
  height: 22px;
  margin: 0.2rem 0 0;
  cursor: pointer;
}
.axis input[type='range']::-webkit-slider-runnable-track {
  height: var(--hair); background: var(--line-bold);
}
.axis input[type='range']::-moz-range-track {
  height: var(--hair); background: var(--line-bold);
}
.axis input[type='range']::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 3px; height: 18px;
  margin-top: -9px;
  background: var(--light);
  border: 0; border-radius: 0;
}
.axis input[type='range']::-moz-range-thumb {
  width: 3px; height: 18px;
  background: var(--light);
  border: 0; border-radius: 0;
}

.axis select {
  margin-top: 0.2rem;
  padding: 0.45rem 0.5rem;
  background: var(--ink-2);
  color: var(--paper);
  border: var(--hair) solid var(--line-thin);
  border-radius: 0;
  font: 400 0.84rem/1.2 var(--script);
}

/* ---------- findings ---------- */

.claims {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--gap);
  align-items: start;
}
.claims .ruled { grid-column: 1 / -1; }

.readout { margin: 0; font-family: var(--script); }

.readout__row {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  padding: 0.5rem 0;
  border-bottom: var(--hair) solid var(--line-hair);
}
.readout dt {
  flex: 1 1 auto;
  font-size: 0.76rem;
  color: var(--paper-dim);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;
}
.readout dd {
  margin: 0;
  font-size: 1rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--paper);
  transition: opacity 120ms ease;
}

/* While a control is being moved the pictures redraw at preview quality, and a number
   measured at preview quality is not the number this sheet promises. So the readouts hold
   their last settled value and dim to say they are behind. The alternative shipped once and
   was reported as a bug: the ray count changed the instant the lamp was grabbed, with
   nothing on the sheet to explain why. */
body[data-settling] .stage__controls .readout dd,
body[data-settling='full'] .claims .readout dd,
body[data-settling='full'] .stamp {
  opacity: 0.4;
}

.pill {
  font-size: 0.6rem;
  letter-spacing: 0.12em;
  padding: 0.12rem 0.4rem;
  border: var(--hair) solid currentColor;
  text-transform: uppercase;
}
.pill--measured { color: var(--flag-good); }
.pill--modelled { color: var(--flag-warn); }

.claims__prose p {
  margin: 0 0 0.9rem;
  font-family: var(--script);
  font-size: 0.8rem;
  line-height: 1.65;
  color: var(--paper-dim);
  max-width: 62ch;
}
.claims__prose b { color: var(--paper); }

/* ---------- take part ---------- */

.give { margin-top: calc(var(--gap) * 1.2); }

/* Three columns, stated rather than auto-fit. Five items where one spans two is six column
   units, and auto-fit resolved to five columns at desktop width, which left the last item
   stranded alone on a second row. Three divides six. */
.give__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--gap) clamp(1.2rem, 3vw, 2.6rem);
}
/* The subreddit asks for the one thing nobody else can supply, a photograph of a real print, so
   it gets the width to say why. */
.give__item--wide { grid-column: span 2; }
@media (max-width: 980px) { .give__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px) {
  .give__grid { grid-template-columns: 1fr; }
  .give__item--wide { grid-column: span 1; }
}

.give__item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.35rem;
  padding-top: 0.7rem;
  border-top: var(--hair) solid var(--line-thin);
  min-width: 0;
}
.give__item b {
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.give__item span {
  font-family: var(--script);
  font-size: 0.74rem;
  line-height: 1.5;
  color: var(--paper-faint);
}

/* Drawn as plate hardware, like the other buttons, so an outbound link still reads as part of
   the drawing rather than as an advertisement dropped onto it. */
.give__go {
  margin-top: 0.3rem;
  padding: 0.4rem 0.7rem;
  border: var(--hair) solid var(--line-bold);
  color: var(--paper);
  font: 600 0.7rem/1 var(--gothic);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: none;
  transition: background 140ms ease, color 140ms ease, border-color 140ms ease;
}
.give__go:hover { background: var(--paper); color: var(--ink-1); }
.give__item--wide .give__go { border-color: var(--light); color: var(--light); }
.give__item--wide .give__go:hover { background: var(--light); color: var(--ink-0); }

/* The download flashes this section rather than opening a dialog over it. Attribute-driven and
   a plain transition, so the global reduced-motion rule shortens it like everything else. */
.give[data-flash='1'] .give__item { border-top-color: var(--light); }
.give__item { transition: border-top-color 320ms ease; }

/* ---------- title block ---------- */

.block {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr)) auto;
  border: var(--hair) solid var(--line-bold);
}
.block__cell {
  display: flex; flex-direction: column; gap: 0.2rem;
  padding: 0.7rem 0.9rem;
  border-right: var(--hair) solid var(--line-thin);
  min-width: 0;
}
.block__cell:last-child { border-right: 0; }
.block__cell--wide { grid-column: span 1; }
.block__cell--act { align-items: flex-start; justify-content: center; gap: 0.4rem; }

.block__k {
  font: 400 0.62rem/1 var(--script);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--paper-faint);
}
.block__v {
  font: 400 0.82rem/1.3 var(--gothic);
  letter-spacing: 0.02em;
}
.block__v--quiet { font-family: var(--script); font-size: 0.68rem; color: var(--paper-faint); }

/* --- Fig. 7, the movable lamp --- */

.stage__lead {
  margin: 0 0 var(--gap);
  max-width: 76ch;
  font-size: 0.92rem;
  line-height: 1.65;
  color: var(--paper-dim);
}
/* The figure gets the full width and the controls sit directly beneath what they steer.
   A right-hand column was tried first: it squeezed the scene into two thirds of the sheet
   and put the distance control furthest from the lamp it moves. */
.stage__controls {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--gap) clamp(1.2rem, 3vw, 3rem);
  align-items: start;
  margin-top: var(--gap);
  padding-top: 0.9rem;
  border-top: var(--hair) solid var(--line-thin);
}
.axis--stage { padding-top: 0; border-top: 0; }
.stage__aside { display: flex; flex-direction: column; gap: 0.7rem; min-width: 0; }
.btn--reset { align-self: flex-start; font-size: 0.68rem; padding: 0.4rem 0.7rem; }
@media (max-width: 900px) { .stage__controls { grid-template-columns: 1fr; } }

/* Taller than wide-ish on purpose: the lamp's depth travel runs diagonally down the sheet,
   so height is what the drag needs. */
.fig__frame--stage { aspect-ratio: 1024 / 640; background: #071a2c; }
/* The whole figure is the control, so it says so on hover and while held. */
.fig__frame--stage canvas { cursor: grab; touch-action: none; }
.fig__frame--stage canvas[data-grabbing='1'] { cursor: grabbing; }
.fig__frame--stage canvas:focus-visible { outline: 2px solid var(--light); outline-offset: -2px; }

.readout--tight .readout__row { padding: 0.45rem 0; }
.stage__note {
  margin: 0;
  font-family: var(--script);
  font-size: 0.68rem;
  line-height: 1.6;
  color: var(--paper-faint);
}

.colophon {
  margin: 0;
  font-family: var(--script);
  font-size: 0.7rem;
  line-height: 1.6;
  color: var(--paper-faint);
  max-width: 84ch;
}

/* --- title-block credit, set as a signature panel rather than a footer --- */

.credit {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.4rem clamp(1.2rem, 3vw, 2.4rem);
  align-items: baseline;
  border-top: var(--hair) solid var(--line-bold);
  padding-top: 0.9rem;
}
.credit__h {
  margin: 0;
  font-family: var(--script);
  font-size: 0.66rem;
  font-weight: 400;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--paper-faint);
  white-space: nowrap;
}
.credit__body { display: flex; flex-direction: column; gap: 0.35rem; min-width: 0; }
.credit__name {
  margin: 0;
  font-size: 1.02rem;
  font-weight: 600;
  letter-spacing: 0.01em;
}
.credit__name a { color: var(--paper); text-decoration-thickness: var(--hair); text-underline-offset: 0.22em; }
.credit__name a:hover { color: var(--light); }
.credit__line {
  margin: 0;
  font-size: 0.82rem;
  line-height: 1.6;
  color: var(--paper-dim);
  max-width: 62ch;
}
.credit__links {
  margin: 0.15rem 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.1rem;
  font-family: var(--script);
  font-size: 0.72rem;
}
.credit__links a { color: var(--paper-dim); }
.credit__links a:hover { color: var(--light); }

/* Sits with the small print, not at the top. The interaction gets the first screen. */
.credit__visits {
  margin: 0.5rem 0 0;
  font-family: var(--script);
  font-size: 0.72rem;
  color: var(--paper-dim);
  opacity: 0.72;
}
.credit__visits-n { color: var(--paper); font-variant-numeric: tabular-nums; }

/* Two columns of small print, set like a title block's revision list. */
.credit__work {
  margin: 0.55rem 0 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.4rem clamp(1.2rem, 3vw, 2.4rem);
  border-top: var(--hair) solid var(--line-thin);
  padding-top: 0.7rem;
}
.credit__work > div { display: flex; flex-wrap: wrap; gap: 0.1rem 0.6rem; align-items: baseline; }
.credit__work dt { font-family: var(--script); font-size: 0.74rem; }
.credit__work dt a { color: var(--paper); }
.credit__work dt a:hover { color: var(--light); }
.credit__work dd {
  margin: 0;
  font-size: 0.76rem;
  line-height: 1.55;
  color: var(--paper-faint);
  flex: 1 1 14ch;
}
@media (max-width: 700px) { .credit__work { grid-template-columns: 1fr; } }

/* ---------- drag target ---------- */

.drop {
  position: fixed; inset: 0; z-index: 30;
  display: grid; place-items: center;
  background: rgba(4, 18, 31, 0.86);
}
/* `display: grid` above beats the [hidden] attribute's own display:none, which left the
   overlay permanently on top of the first viewport, scrimming it. */
.drop[hidden] { display: none; }
.drop span {
  padding: 1.2rem 2rem;
  border: 2px dashed var(--light);
  color: var(--light);
  font: 700 1rem/1 var(--script);
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

/* ---------- adaptation ---------- */

@media (max-width: 1080px) {
  .chain { grid-template-columns: 1fr; }
  /* Rotate only the arrow, not its box. Rotating the box spun a column-wide line into a
     full-height rail that ran straight through the caption underneath it. */
  .arrow { padding: 0.2rem 0 0.6rem; min-width: 0; transform: none; }
  .arrow svg { width: 46px; height: 46px; transform: rotate(90deg); }
  .detail__grid, .claims { grid-template-columns: 1fr; }
  .block { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .block__cell { border-bottom: var(--hair) solid var(--line-thin); }
  .block__cell--act { grid-column: 1 / -1; }
}

@media (max-width: 560px) {
  .strip { align-items: stretch; }
  .strip__intake { width: 100%; }
  .btn--intake { width: 100%; justify-content: center; }
  .block { grid-template-columns: 1fr; }
  /* The label would otherwise sit alone on a line and push the name off the fold. */
  .credit { grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition-duration: 1ms !important; animation-duration: 1ms !important; }
}
