/* 300 Baud — the machine's own layer.
 *
 * Surfaces, ink, type scale and the studio footer come from the shared
 * logical-art.css. This file only adds what a modem panel needs.
 *
 * On the accent: the shared shell's rule is that a machine's accent has to be
 * earned by sampling the hardware, and one that cannot cite its accent uses
 * the neutral. That has now been done, and the answer is still the neutral.
 *
 * The roster carried #f0a500 marked provisional, and it was wrong twice over.
 * A Smartmodem's indicators are red, not amber. And the only colour that can
 * honestly be evidenced from a photograph is the unlit lens, because a lit
 * indicator blows out to near white in any exposure that also shows the case.
 * Sampled across the lamp row of a 1982 unit, the brightest five per cent of
 * lens pixels average #9f0007.
 *
 * Which cannot be the accent, and the measurement is what says so rather than
 * a matter of taste: #9f0007 is 2.13:1 against this page's background, where an
 * accent used for strokes and focus rings has to carry 4.5:1. The neutral is
 * 9.59:1. So --la-instrument stays neutral, and it is now an evidenced decision
 * instead of a pending one.
 *
 * Source for the sample: Wikimedia Commons, "Hayes Smartmodem 1982 (2).jpg" by
 * the user Aeroid, CC BY-SA 4.0. A measured colour is a fact rather than a
 * derivative work, and the file is cited here rather than republished.
 */

body {
  margin: 0;
  background: var(--la-page);
  color: var(--la-ink);
  font-family: var(--la-font-ui);
  font-size: var(--la-text-base);
  line-height: var(--la-leading-body);
  -webkit-text-size-adjust: 100%;
}

/* box-sizing and the horizontal padding both matter, and both were wrong.
   This sheet has no universal border-box reset, so max-width applied to the
   CONTENT box: 640px of column plus 24px either side, an outer 688 and a
   column 40px wider than every other site in the family. The studio and the
   four other byte domains all land on a 600px column, so this one sat wider
   and 20px further left than anything it sits beside. */
.modem {
  box-sizing: border-box;
  max-width: var(--la-measure);
  margin: 0 auto;
  /* 3rem, not --la-space-5 (2.25rem). The studio and every other byte domain
     open with 48px above the trail; this one opened with 36 and started its
     title twelve pixels higher than anything it sits beside. */
  padding: 3rem 1.25rem var(--la-space-4);
}

.hero {
  margin-bottom: var(--la-space-5);
}

.eyebrow {
  margin: 0 0 var(--la-space-2);
  font-family: var(--la-font-mono);
  font-size: var(--la-text-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--la-ink-faint);
}

h1 {
  margin: 0 0 var(--la-space-3);
  font-family: var(--la-font-display);
  font-size: var(--la-text-3xl, 3.4rem);
  line-height: var(--la-leading-tight);
  letter-spacing: -0.01em;
}

h2 {
  margin: 0 0 var(--la-space-2);
  font-size: var(--la-text-lg);
  line-height: var(--la-leading-tight);
}

.lede {
  margin: 0 0 var(--la-space-3);
  color: var(--la-ink-dim);
}

.panel {
  margin-bottom: var(--la-space-4);
  padding: var(--la-space-4);
  background: var(--la-card);
  border: 1px solid var(--la-hairline);
  border-radius: var(--la-radius);
}

.prose p {
  margin: 0 0 var(--la-space-3);
  color: var(--la-ink-dim);
}

.prose p:last-child {
  margin-bottom: 0;
}

.note {
  margin: 0 0 var(--la-space-3);
  font-size: var(--la-text-sm);
  color: var(--la-ink-faint);
}

/* ---- Controls ------------------------------------------------------------ */

.controls {
  display: flex;
  flex-wrap: wrap;
  gap: var(--la-space-3);
  margin-bottom: var(--la-space-3);
}

.field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  flex: 1 1 20rem;
  min-width: 0;
}

.field-narrow {
  flex: 1 1 14rem;
}

.field > span {
  font-size: var(--la-text-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--la-ink-faint);
}

input[type="text"],
select {
  width: 100%;
  padding: 0.6rem 0.7rem;
  background: var(--la-sunken);
  color: var(--la-ink);
  border: 1px solid var(--la-hairline-strong);
  border-radius: var(--la-radius-sm);
  font-family: var(--la-font-mono);
  font-size: var(--la-text-sm);
}

.buttons {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--la-space-3);
  margin-bottom: var(--la-space-3);
}

.btn {
  padding: 0.6rem 1.1rem;
  border-radius: var(--la-radius-sm);
  border: 1px solid var(--la-hairline-strong);
  background: var(--la-raised);
  color: var(--la-ink);
  font: inherit;
  font-size: var(--la-text-sm);
  cursor: pointer;
  transition: background var(--la-transition), border-color var(--la-transition);
}

.btn-primary {
  background: var(--la-instrument-solid);
  border-color: transparent;
  color: #fff;
}

.btn:hover {
  border-color: var(--la-instrument);
}

.toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--la-text-sm);
  color: var(--la-ink-dim);
}

/* ---- Scope --------------------------------------------------------------- */

#scope {
  display: block;
  width: 100%;
  height: auto;
  background: var(--la-sunken);
  border: 1px solid var(--la-hairline);
  border-radius: var(--la-radius-sm);
}

/* ---- Received ------------------------------------------------------------ */

.received {
  min-height: 4.5rem;
  margin: 0 0 var(--la-space-3);
  padding: var(--la-space-3);
  background: var(--la-sunken);
  border: 1px solid var(--la-hairline);
  border-radius: var(--la-radius-sm);
  font-family: var(--la-font-mono);
  font-size: var(--la-text-sm);
  white-space: pre-wrap;
  word-break: break-word;
  overflow-x: auto;
}

.readout {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--la-space-3);
  margin: 0;
}

.readout dt {
  font-size: var(--la-text-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--la-ink-faint);
}

.readout dd {
  margin: 0.2rem 0 0;
  font-family: var(--la-font-mono);
  font-size: var(--la-text-sm);
  color: var(--la-ink);
}

/* ---- Footer -------------------------------------------------------------- */

/* Same box as .modem above, so the footer column and the content column share
   one left edge. Reported 2026-09-17: this was content-box with a 24px gutter
   while .modem is border-box with 20px, so the footer, the prev/next strip and
   the studio footer all sat 20px left of everything above them. */
.foot {
  box-sizing: border-box;
  max-width: var(--la-measure);
  margin: 0 auto;
  padding: 0 1.25rem var(--la-space-5);
}

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

/* ---- The terminal --------------------------------------------------------
   80 columns, always fitting the width available, never scrolling sideways.
   The font size is set from JS because the exact advance width of a monospace
   glyph varies between the faces in the stack, so guessing a ch-to-em ratio
   in CSS gets it wrong for somebody. The clamp here is only a sane starting
   point before that runs. */

.crt {
  position: relative;
  margin-bottom: var(--la-space-3);
  padding: var(--la-space-3);
  background: #000;
  border: 1px solid var(--la-hairline-strong);
  border-radius: var(--la-radius-sm);
  overflow: hidden;
  cursor: text;
}

.crt:focus,
.crt:focus-within {
  outline: 2px solid var(--la-instrument);
  outline-offset: 2px;
}

/* The terminal's own face.
 *
 * Everything else on the page can take whatever monospace the device offers,
 * but the screen cannot. A BBS draws its frames out of CP437 box-drawing
 * characters, and those only join into continuous rules if the glyph is as
 * tall as the line box. Fonts differ on that: several common fallbacks draw a
 * vertical bar noticeably shorter than a full line, and the frame comes out as
 * a dashed line with a nick at every row boundary. Setting line-height to 1 is
 * necessary but not sufficient, which is why that fix alone did not hold.
 *
 * So the face is not left to chance. This is DejaVu Sans Mono subset to
 * exactly the 222 glyphs the terminal can draw (ASCII plus the CP437 high
 * range) and nothing else, which is why it is under nine kilobytes. Its
 * vertical bar measures 1.188 em against a 1 em row, so it laps into the row
 * below rather than falling short, and the rules join.
 *
 * font-display is block rather than swap on purpose: swapping mid-render would
 * show a screenful of broken frames first and then repair it, which is worse
 * than a moment of nothing.
 */
@font-face {
  font-family: "CP437";
  src: url("/fonts/cp437.woff2") format("woff2");
  font-display: block;
  font-weight: 400;
  font-style: normal;
}

.terminal {
  font-family: "CP437", var(--la-font-mono);
  font-size: clamp(0.42rem, 2.1vw, 0.82rem);
  /* Exactly one, because box-drawing characters only join up when the line
     box is the character cell. Any leading at all and every vertical rule in
     a BBS screen comes out as a dashed line. A DOS text screen had none. */
  line-height: 1;
  white-space: pre;
  width: 80ch;
  max-width: 80ch;
  min-height: 24em;
  color: #aaa;
  background: #000;
}

.terminal .trow {
  height: 1em;
}

/* The cursor is a cell, not a caret, because that is what a terminal had. */
.terminal .cur {
  background: #aaa;
  color: #000;
}

.crt:focus .terminal .cur,
.crt:focus-within .terminal .cur {
  animation: blink 1.06s steps(1, end) infinite;
}

@keyframes blink {
  0%, 50% { background: #aaa; color: #000; }
  50.01%, 100% { background: transparent; color: inherit; }
}

@media (prefers-reduced-motion: reduce) {
  .crt:focus .terminal .cur,
  .crt:focus-within .terminal .cur {
    animation: none;
  }
}

/* The real input, invisible but present, so a phone opens its keyboard.
   box-sizing matters here: without it the input measured wider than the box
   it sits in and made the terminal report horizontal overflow even though
   the overflow was clipped and nothing looked wrong. */
.keyboard {
  position: absolute;
  inset: 0;
  box-sizing: border-box;
  width: 100%;
  height: 100%;
  max-width: 100%;
  padding: 0;
  border: 0;
  background: transparent;
  color: transparent;
  caret-color: transparent;
  font-size: 16px; /* under 16px, iOS zooms the page on focus */
  opacity: 0;
  cursor: text;
}

.hint kbd {
  padding: 0.1em 0.35em;
  background: var(--la-sunken);
  border: 1px solid var(--la-hairline);
  border-radius: 0.25rem;
  font-family: var(--la-font-mono);
  font-size: 0.9em;
}

/* ---- The rack ------------------------------------------------------------ */

.rack {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--la-space-3);
  margin-bottom: var(--la-space-3);
}

.lamp {
  padding: 0.3rem 0.6rem;
  border: 1px solid var(--la-hairline-strong);
  border-radius: var(--la-radius-sm);
  font-family: var(--la-font-mono);
  font-size: var(--la-text-xs);
  letter-spacing: 0.06em;
  color: var(--la-ink-faint);
}

.lamp[data-on="true"] {
  color: var(--la-page);
  background: var(--la-instrument);
  border-color: transparent;
}

.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* ---- The directory ------------------------------------------------------- */

.directory {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: var(--la-space-3);
  margin-bottom: var(--la-space-3);
}

.directory .btn {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  align-items: flex-start;
  text-align: left;
}

.directory .btn span {
  font-family: var(--la-font-mono);
  font-size: var(--la-text-xs);
  color: var(--la-ink-faint);
}

/* ---- Transfer ------------------------------------------------------------ */

.transfer {
  display: flex;
  align-items: center;
  gap: var(--la-space-3);
  font-size: var(--la-text-sm);
  color: var(--la-ink-dim);
}

/* The UA rule [hidden] { display: none } is author-level and loses to the
   display above, so the XMODEM progress row was on screen from load and
   ui.transfer.hidden = true never took it away. Any selector carrying a
   display value has to restate the hidden case itself. */
.transfer[hidden] { display: none; }

.transfer progress {
  flex: 1 1 auto;
  min-width: 8rem;
}

code {
  padding: 0.1em 0.35em;
  background: var(--la-sunken);
  border: 1px solid var(--la-hairline);
  border-radius: 0.25rem;
  font-family: var(--la-font-mono);
  font-size: 0.9em;
}

/* Questions, glossary and sources are styled by the shared shell now, as
   .la-faq / .la-glossary / .la-sources. Only the link colour is local. */

a {
  color: var(--la-ink);
  text-decoration: underline;
  text-underline-offset: 0.18em;
}

a:hover {
  color: var(--la-instrument);
}

/* ---- Tables in the notebook ---------------------------------------------- */

.table-scroll {
  overflow-x: auto;
  margin-bottom: var(--la-space-3);
}

.table-scroll table {
  border-collapse: collapse;
  font-family: var(--la-font-mono);
  font-size: var(--la-text-sm);
}

.table-scroll th,
.table-scroll td {
  padding: 0.35rem 0.9rem 0.35rem 0;
  text-align: left;
  border-bottom: 1px solid var(--la-hairline);
  color: var(--la-ink-dim);
}

.table-scroll th {
  color: var(--la-ink-faint);
  font-weight: 600;
  font-size: var(--la-text-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* SD and RD, the lamps that flickered as data moved. */
.lamp-tiny {
  padding: 0.3rem 0.5rem;
  min-width: 2.6rem;
  text-align: center;
}

/* The form exists only to give a soft keyboard a reason to offer a return
   key. It must not take up any space of its own. */
.keyform {
  position: absolute;
  inset: 0;
  margin: 0;
  overflow: hidden;
}

/* Line 25, which belonged to the terminal program rather than to the board.
   Inverse video, the way every DOS comms program drew it. */
.statusline {
  font-family: var(--la-font-mono);
  font-size: inherit;
  line-height: 1;
  white-space: pre;
  width: 80ch;
  max-width: 80ch;
  margin-top: 0.4em;
  color: #000;
  background: #aaa;
}

/* Available to a screen reader, absent from the page. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---- The modem itself -----------------------------------------------------
   A Hayes Smartmodem was a flat dark box with a row of lamps along the front:
   HS, AA, CD, OH, RD, SD, TR, MR. The lamps were red, because through the
   early eighties a red gallium-arsenide-phosphide LED was what an indicator
   lamp WAS; green and yellow were newer and more expensive, and blue did not
   arrive in a usable form until the nineties.

   That red is used here for the lamps only, not as the machine's accent. The
   shell's rule is that an accent has to be earned by sampling the hardware,
   and reasoning from what LEDs of the period were is not the same as taking a
   value off a photograph of a real unit. The page chrome stays neutral until
   somebody does that. */

.modem-panel {
  margin-bottom: var(--la-space-3);
}

.panel-face {
  display: flex;
  align-items: center;
  gap: var(--la-space-4);
  flex-wrap: wrap;
  padding: var(--la-space-3) var(--la-space-4);
  background: linear-gradient(180deg, #17191b 0%, #0e1011 100%);
  border: 1px solid var(--la-hairline-strong);
  border-radius: var(--la-radius-sm);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
}

.panel-brand {
  margin: 0;
  font-family: var(--la-font-mono);
  font-size: var(--la-text-xs);
  letter-spacing: 0.22em;
  color: var(--la-ink-dim);
  line-height: 1.5;
}

.panel-brand span {
  display: block;
  font-size: 0.82em;
  letter-spacing: 0.16em;
  color: var(--la-ink-faint);
}

.lamps {
  display: flex;
  gap: clamp(0.7rem, 3.2vw, 1.5rem);
  list-style: none;
  margin: 0;
  padding: 0;
}

.lamps li {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
}

.led {
  width: 0.62rem;
  height: 0.62rem;
  border-radius: 50%;
  background: #3a1512;
  box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.9);
  transition: background 60ms linear, box-shadow 60ms linear;
}

.led[data-on="true"] {
  background: #ff4436;
  box-shadow: 0 0 6px rgba(255, 68, 54, 0.75), inset 0 0 2px rgba(255, 200, 190, 0.6);
}

.led-label {
  font-family: var(--la-font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.06em;
  color: var(--la-ink-faint);
}

.panel-key {
  margin-top: var(--la-space-2);
  font-size: var(--la-text-sm);
}

.panel-key summary {
  color: var(--la-ink-faint);
  cursor: pointer;
}

.panel-key summary:hover {
  color: var(--la-instrument);
}

.panel-key dl {
  margin-top: var(--la-space-2);
}

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

/* ---- Diagrams -------------------------------------------------------------
   Inline SVG so there is nothing to load and nothing for a strict CSP to
   refuse. Everything is drawn in currentColor or the shell's own tokens, so
   the figures follow the page rather than carrying their own palette. */
/* ---------------------------------------------------------------------------
   The speed ladder.

   The table is arithmetic and the player is partly a reconstruction, so the
   two are visually separated: the honesty note sits directly under the picker
   rather than in a footnote somebody can miss.
   --------------------------------------------------------------------------- */

.ladder-scroll {
  overflow-x: auto;
  margin-bottom: var(--la-space-4);
}

.ladder-scroll:focus-visible {
  outline: 2px solid var(--la-instrument);
  outline-offset: 2px;
}

.ladder-table {
  width: 100%;
  min-width: 26rem;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}

.ladder-table th,
.ladder-table td {
  padding: var(--la-space-2) var(--la-space-2);
  border-bottom: 1px solid var(--la-hairline);
  text-align: right;
}

.ladder-table thead th {
  color: var(--la-ink-faint);
  font-weight: 400;
  white-space: nowrap;
}

.ladder-table tbody th,
.ladder-table thead th:first-child { text-align: left; }
.ladder-table tbody th { font-weight: 600; color: var(--la-ink); }

.ladder-picker {
  display: flex;
  flex-wrap: wrap;
  gap: var(--la-space-2);
  margin-bottom: var(--la-space-3);
}

.ladder-picker button,
.ladder-controls button {
  font: inherit;
  font-family: var(--la-font-mono);
  color: var(--la-ink);
  background: var(--la-raised);
  border: 1px solid var(--la-hairline-strong);
  border-radius: var(--la-radius-sm);
  padding: 0.55rem 0.9rem;
  cursor: pointer;
  /* WCAG 2.5.8 wants a comfortable target on a coarse pointer. */
  min-height: 44px;
}

.ladder-picker button[aria-pressed="true"] {
  background: var(--la-sunken);
  border-color: var(--la-instrument);
  color: var(--la-ink);
}

.ladder-picker button:focus-visible,
.ladder-controls button:focus-visible {
  outline: 2px solid var(--la-instrument);
  outline-offset: 2px;
}

.ladder-detail { margin-top: var(--la-space-3); }

.ladder-note { margin: 0 0 var(--la-space-2); color: var(--la-ink-dim); }

.ladder-honesty {
  margin: 0 0 var(--la-space-3);
  padding: var(--la-space-2) var(--la-space-3);
  background: var(--la-sunken);
  border-left: 2px solid var(--la-hairline-strong);
  color: var(--la-ink-faint);
  font-size: 0.92em;
}

.ladder-controls {
  display: flex;
  flex-wrap: wrap;
  gap: var(--la-space-2);
  margin-bottom: var(--la-space-3);
}

.ladder-phases {
  margin: 0 0 var(--la-space-3);
  padding-left: 1.4rem;
  color: var(--la-ink-faint);
  font-family: var(--la-font-mono);
  font-size: 0.86em;
  line-height: 1.7;
}

.ladder-phases li[data-active="true"] {
  color: var(--la-ink);
}

.ladder-demo {
  /* Deliberately NOT the .terminal class. That class pins the width to 80ch,
     which is right for an eighty-column screen and wrong for a preview: it
     forced this box to 514px inside a 292px column and made the whole page
     scroll sideways on a phone. It only wants the face, not the geometry. */
  font-family: "CP437", var(--la-font-mono);
  max-width: 100%;
  margin: 0;
  padding: var(--la-space-3);
  min-height: 13em;
  background: #000;
  color: #aaaaaa;
  border: 1px solid var(--la-hairline-strong);
  border-radius: var(--la-radius-sm);
  font-size: clamp(0.6rem, 2.4vw, 0.8rem);
  line-height: 1.25;
  white-space: pre;
  overflow-x: auto;
}

/* Build-time ANSI art: the real renderer's output, frozen into the page.
   Same face as the terminal and the same leading rule, because box drawing
   only joins under both. */
.ansi-art {
  margin: 0 0 var(--la-space-4);
  padding: var(--la-space-3);
  background: #000;
  border: 1px solid var(--la-hairline-strong);
  border-radius: var(--la-radius-sm);
  font-family: "CP437", var(--la-font-mono);
  font-size: clamp(0.5rem, 1.9vw, 0.78rem);
  line-height: 1;
  white-space: pre;
  overflow-x: auto;
  max-width: 100%;
}

/* The picture converter. */
.converter { margin-top: var(--la-space-3); }
.converter[data-over="true"] { outline: 2px dashed var(--la-instrument); outline-offset: 6px; }

.conv-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--la-space-3);
  margin-bottom: var(--la-space-3);
}

/* The file input itself is unstylable across browsers, so the label is the
   button and the input is hidden inside it. Keeping the input in the label
   rather than removing it from the flow is what preserves keyboard access. */
.conv-file { position: relative; overflow: hidden; cursor: pointer; }
.conv-file input {
  position: absolute;
  inset: 0;
  opacity: 0;
  cursor: pointer;
}
.conv-file:focus-within { outline: 2px solid var(--la-instrument); outline-offset: 2px; }

.conv-screen { min-height: 12em; }

/* Hero illustrations. Drawings rather than photographs, and captioned as such,
   because a page that keeps promising its claims are checkable should not put
   a generated picture next to them without saying what it is. */
.hero-figure {
  margin: var(--la-space-3) 0 var(--la-space-4);
}

.hero-figure img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--la-hairline);
  border-radius: var(--la-radius-sm);
}

.hero-figure figcaption {
  margin-top: var(--la-space-2);
  font-size: var(--la-text-xs);
  color: var(--la-ink-faint);
}

.conv-exports { align-items: baseline; }
.conv-label {
  font-size: var(--la-text-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--la-ink-faint);
}
.conv-controls button:disabled { opacity: 0.45; cursor: not-allowed; }

/* The camera preview and the crop stage. */
.conv-camera-area { margin-bottom: var(--la-space-3); }
.conv-camera-area video {
  display: block;
  width: 100%;
  max-width: 40rem;
  height: auto;
  background: #000;
  border: 1px solid var(--la-hairline-strong);
  border-radius: var(--la-radius-sm);
  margin-bottom: var(--la-space-2);
}

.conv-crop { margin-bottom: var(--la-space-3); }

.crop-stage {
  position: relative;
  display: inline-block;
  /* Room for the handles, which hang outside the picture on purpose so they
     are big enough to grab on a touch screen. */
  padding: 12px;
  /* And this clips the crop box's shading to the stage. The shadow below is
     spread far enough to cover any picture, which also meant it covered the
     whole viewport: everything under the stage, the Done and Zoom and Whole
     picture controls included, sat beneath a page-sized dark layer and read as
     disabled. Clipping it here keeps the punched-out look and confines it to
     the picture. The padding is what keeps the handles inside the clip. */
  overflow: hidden;
  max-width: 100%;
  margin-bottom: var(--la-space-2);
  /* The pointer handlers call preventDefault, so the browser must not also be
     trying to pan the page under the same finger. */
  touch-action: none;
  -webkit-user-select: none;  /* Safari still needs it */
  user-select: none;
}

.crop-stage img {
  display: block;
  max-width: 100%;
  max-height: 22rem;
  width: auto;
  height: auto;
  border: 1px solid var(--la-hairline);
  border-radius: var(--la-radius-sm);
}

.crop-box {
  position: absolute;
  border: 2px solid var(--la-instrument);
  box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.55);
  /* Clipped by .crop-stage above, so the spread only has to beat the picture. */
  cursor: move;
}

.crop-box:focus-visible { outline: 2px dashed var(--la-instrument); outline-offset: 3px; }

.crop-handle {
  position: absolute;
  width: 22px;
  height: 22px;
  background: var(--la-instrument);
  border: 2px solid var(--la-page);
  border-radius: 3px;
}

.crop-handle[data-corner="nw"] { left: -11px; top: -11px; cursor: nwse-resize; }
.crop-handle[data-corner="ne"] { right: -11px; top: -11px; cursor: nesw-resize; }
.crop-handle[data-corner="sw"] { left: -11px; bottom: -11px; cursor: nesw-resize; }
.crop-handle[data-corner="se"] { right: -11px; bottom: -11px; cursor: nwse-resize; }

/* Zoomed pictures scroll inside the stage rather than pushing the page wide. */
.conv-crop .crop-scroll { overflow: auto; max-width: 100%; }
.crop-stage input[type="range"] { width: 10rem; }

/* Blink, which a PC text screen spent an entire attribute bit on. Two states a
   second is the rate the hardware used. Anyone who has asked not to be shown
   motion gets the text without the flashing rather than nothing at all, since
   a blinking cell that stops blinking is still perfectly readable. */
@keyframes ansi-blink { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }

.terminal .bl { animation: ansi-blink 1s steps(1, end) infinite; }

/* The bell. A terminal had a speaker; this flashes instead. */
.crt[data-bell="true"] { background: #333; }

@media (prefers-reduced-motion: reduce) {
  .terminal .bl { animation: none; }
  .crt[data-bell="true"] { background: #000; }
}

/* The detector laboratory. */
#lab-plot {
  display: block;
  width: 100%;
  height: auto;
  margin-top: var(--la-space-3);
  background: var(--la-sunken);
  border: 1px solid var(--la-hairline);
  border-radius: var(--la-radius-sm);
}

.lab-out {
  font-family: var(--la-font-mono);
  font-size: var(--la-text-sm);
  color: var(--la-ink-dim);
}

#lab-window { width: 12rem; }

/* Set --machine, never --accent.
 *
 * logical-art.css computes `body { --accent: var(--machine, var(--la-instrument)) }`,
 * so declaring --accent here on :root is overridden for everything inside
 * body and the whole page silently renders in the fallback grey, #bdbdbd.
 * That is the trap the canon warns about in as many words, and it cost a
 * measurement to find because the rule was present and the colour was not.
 *
 * The value itself is NOT written here. sync-logical-art.py writes it from
 * machines.json into the managed region below, because a colour typed into a
 * site is a colour that drifts from the roster -- which is what happened on
 * 2026-08-28, when the roster repainted and this file did not. */
/* la:accent */

/* Generated by scripts/sync-logical-art.py from machines.json.
 * Do not edit, and do not hand-write --machine anywhere else in
 * this sheet: the roster owns this colour so that this site and the
 * studio timeline cannot disagree about what colour 300 Baud
 * is. They did, on 2026-08-28, because this value was typed here.
 *
 * networks: the teal of a signal on a wire, and the darker of the two, because a network is the part of the estate that is still physically somewhere.
 */
body[data-machine="300baud"] { --machine: #50a8b0; }

/* /la:accent */
