:root {
  /* Every control is this tall, whatever is written on it. A CJK glyph is taller
     than a Latin one, so a row sized by its contents grows when the interface
     switches to Chinese or Japanese and shoves the whole layout down a few
     pixels. Fixed height *and* line-height: 1 — one without the other still
     moves. */
  --control-height: 2.1rem;
  /* The case. A period machine was moulded in something between bone and sand;
     this is a muted version of it, dark enough not to shout on a dark page. */
  --case: #b6ab93;
  --ink: #d7dbe0;
  --ink-dim: #7d8794;
  --ground: #14171c;
  --panel: #1b1f26;
  --edge: #2b313a;
  --accent: #5fc9a0;
  --error: #e2726e;
  color-scheme: dark;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--ground);
  color: var(--ink);
  font: 14px/1.5 ui-sans-serif, system-ui, sans-serif;
}

.bar {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  padding: 0.6rem 1rem;
  background: var(--panel);
  border-bottom: 1px solid var(--edge);
}

/* Row one: the name and what this is, centred over everything below. */
.bar__name {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.bar__title {
  margin: 0;
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  height: var(--control-height);
  line-height: var(--control-height);
}

/* Fixed height plus line-height 1, because a CJK glyph is taller than a Latin
   one and an auto-height line box shifts the whole bar on every language change. */
.bar__tagline {
  margin: 0;
  height: var(--control-height);
  line-height: 1;
  display: flex;
  align-items: center;
  color: var(--ink-dim);
}

/* Row two: what to run on the left, how it looks and what to press on the
   right. space-between rather than a margin, so the two groups stay apart at
   any width and wrap as units rather than interleaving. */
.bar__controls {
  display: flex;
  justify-content: space-between;
  align-items: center;
  /* Tight enough that the longest language fits on one line at a laptop width.
     A bar that wraps in one language and not another moves the whole page when
     the language changes, which is the same complaint as a row that grows.
     English is the long one — its words are wider than the same words in Chinese
     or Japanese — so every gap here is set by what English needs. */
  gap: 0.75rem 0.75rem;
  flex-wrap: wrap;
}

.bar__group {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
}

.control {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  color: var(--ink-dim);
  /* Same reason as the buttons: the label beside the control is the other half
     of the row, and it is the half that is actually translated. */
  height: var(--control-height);
  line-height: 1;
}

.control--locale { gap: 0.25rem; }

/* Equal to each other in either layout. The names are two, two and three
   characters long, and left to themselves they came out three sizes. Wide enough
   for the longest of them and no wider: in the horizontal bar these three are
   the widest thing on the row, and the row has to hold the English wording of
   everything else on one line. In the panel they stretch instead, so this floor
   does not decide anything there. */
.control--locale button { min-width: 3.3rem; }

/* Which machine you are on. Not one setting among many: it decides the
   language, the screen and the look of the whole page, so it sits with the
   name of the project and shows its state without being opened. Two buttons
   joined into one control, the way a switch with two positions reads. */
.dialects {
  display: flex;
  margin-left: 1.8rem;
}

.dialect {
  border-radius: 0;
  /* Both the same width, and wider than their words need: this is the switch
     that changes everything on the page, and two keys of two different sizes
     read as two different kinds of thing. */
  width: 8.5rem;
  padding: 0.2rem 0;
  background: transparent;
  color: var(--ink-dim);
  text-align: center;
}

.dialect:first-child { border-radius: 5px 0 0 5px; }
.dialect:last-child { border-radius: 0 5px 5px 0; border-left-width: 0; }

.dialect[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: #0d1512;
  font-weight: 600;
}

/* The Apple one is lit in its own machine's colour: the phosphor its screen
   writes in, which is the same green as the prompt you type at in that mode. */
.dialect[data-dialect-choice="apple"][aria-pressed="true"] {
  background: #66ff66;
  border-color: #66ff66;
  color: #0b1a0f;
}

/* The pressed one owns the seam between them, so the join does not go missing.
   Berkeley is first now — it is the dialect the page opens in — and this holds
   whichever of them is pressed, because it is written about the seam and not
   about a side. */
.dialect[aria-pressed="true"] + .dialect { border-left-color: var(--accent); }

/* The monitor and CRT controls belong to the Apple screen and mean nothing in
   the other mode, so they are hidden rather than left sitting there inert. */
.control--apple { display: none; }
body[data-mode="apple"] .control--apple { display: flex; }

.control--berkeley { display: none; }
body[data-mode="berkeley"] .control--berkeley { display: flex; }

/* And a gap under the colours. A grid of squares butted against the row of keys
   below it reads as one control with a lid on, rather than as two things. */
body[data-mode="berkeley"] .control--berkeley { margin-bottom: 0.45rem; }

/* Sixteen colours in the width of one key: two rows of eight squares, which is
   the only control on either panel that is not a row of words. It is a colour
   you are choosing, and a colour is the one thing that shows itself. */
/* Both classes, so this beats the column .control__choices sets further down:
   sixteen swatches in a flex column is sixteen stripes down the panel. */
.control__choices.control__swatches {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  gap: 3px;
}

.control__swatches button {
  height: 1.1rem;
  min-width: 0;
  padding: 0;
  border-radius: 3px;
  border: 1px solid #0006;
  box-shadow: none;
}

.control__swatches button[aria-pressed="true"] {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  background-color: currentColor;
}

/* In Apple mode the controls stand beside the machine rather than above it.
 *
 * `display: contents` on the bar is what makes that possible without a second
 * copy of the markup: the bar stops generating a box and its two rows become
 * items of the body's grid directly, so the name row can span the top while the
 * controls take a column of their own next to the screen. The bar's own
 * background and border go with it, which is why they are restated below on the
 * two rows that now carry them.
 */
body[data-mode="apple"] {
  display: grid;
  /* The machine's own width, measured in main.js, so the two of them are a pair
     standing together in the middle of the window rather than one object
     centred in its half and another pinned to the edge. Before the first
     measurement it is 1fr, which is where this started. */
  /* The panel does not shrink. Its width is not a taste: a row is a name and a
     key side by side, the key is 8rem, and the name has to fit beside it on one
     line in three languages. 15.85rem is that sum — the key, the gap between
     them, the panel's padding and its margin, and five full-width characters of
     name at one em each. A range here would let the name's share be whatever
     was left over, which in Japanese is the difference between one line and
     two, so there is one number and test/wiring.test.js reads it. */
  grid-template-columns: minmax(0, var(--machine-width, 1fr)) 15.85rem;
  grid-template-rows: auto 1fr;
  justify-content: center;
  column-gap: 0.5rem;
  /* The same grey as the bar across the top, so the machine and the panel of
     controls beside it are two objects standing on one surface. */
  background: var(--panel);

  /* How thick the machine is, in one place, used by the case and by the room
     reserved for it — they were two numbers for one afternoon and the case was
     silently clipped on three sides. The foot is deeper than the rest, as the
     bottom of a monitor's moulding always was: it is where the works went. */
  --case-width: 2.2rem;
  --case-foot: 3.3rem;
  /* The right cheek is wider than the other three, because that is where the
     controls went. At an even 2.2rem the moulding there is about ten pixels
     wide once the tube face and the recess have taken their share, and a slot
     cut into it landed on the recess wall instead — measured, not guessed. */
  --case-cheek: 3.4rem;
  --recess: 0.75rem;
  --tube-face: 0.85rem;
  /* The gap between the machine and the edge of its half of the page. The panel
     of controls uses the same one, which is what lines the two up. */
  --case-gap: 0.4rem;

  /* The moulding itself, defined once. The panel beside the machine is made of
     the same plastic, and two definitions of one material drift apart. */
  --case-face:
    repeating-linear-gradient(0deg, #0000000a 0 1px, #0000 1px 3px),
    linear-gradient(#d8ccb2 0%, #cfc3a8 42%, #c4b89c 100%);
  --case-edges:
    inset 0 2px 0 #fffaf0b3,
    inset 0 -3px 5px #00000059,
    inset 4px 0 6px -4px #00000045,
    inset -4px 0 6px -4px #00000045;
  --case-drop: 0 1.6rem 3rem -0.8rem #000000ad, 0 0.3rem 0.7rem #00000059;
  /* Writing on the moulding: the dark of the recess for labels, a shade deeper
     for the words on the keys, and keys a little darker than the face. */
  --case-ink: #40382a;
  --case-ink-dim: #6b6151;
  --case-key: #c6ba9e;
  --case-key-edge: #8e836b;
}

/* Both dialects lay out the same way: the name across the top, the work in the
   middle, the controls in a column down the right. The bar stops generating a
   box so that its two rows become items of the body's grid directly — no second
   copy of the markup, and the controls do not move when the dialect changes,
   which is one less thing for the eye to re-find. What differs between the two
   is the material and what is in the middle, not the skeleton. */
body[data-mode] .bar { display: contents; }

body[data-mode] .bar__name {
  grid-column: 1 / -1;
  padding: 0.6rem 1rem;
  background: var(--panel);
  /* No rule beneath it. The page below is the same grey, so the line was
     dividing one surface from itself. */
  border-bottom: 0;
}

body[data-mode] .bar__controls {
  grid-column: 2;
  grid-row: 2;
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  /* The wrap on this in the horizontal bar is right there and wrong here. Turned
     on its side in a panel of fixed height, a short window makes it break into a
     second column: every row loses a quarter of its width, the Japanese labels
     go to two lines, and half the controls end up outside the panel reachable
     only by scrolling sideways. It was doing this at 1024×768. The panel scrolls
     down instead — which is what the overflow-y below is for. */
  flex-wrap: nowrap;
  gap: 1rem;
  padding: 0.9rem;
  /* A short window scrolls this panel rather than the page. */
  min-height: 0;
  overflow-y: auto;
}

/* The Apple panel is part of the machine: the same plastic, standing on the same
   desk, the same gap from the edge of the page — which is what lines its top and
   bottom up with the case beside it. */
body[data-mode="apple"] .bar__controls {
  /* And being part of the machine means being no taller than it. This panel
     carries five controls the other one has no use for — the manual, the
     monitor, the shape of the pixel, the CRT — and once the edge modes joined
     them the column was taller than the screen it stands beside at anything
     under 1400 by 900, so it scrolled: a machine with a scrollbar down its face.
     Everything on it is a little shorter here, and the row height is a variable
     precisely so that this is one number rather than fifteen. */
  --control-height: 1.85rem;
  gap: 0.7rem;
  padding: 0.75rem;
  margin: var(--case-gap) var(--case-gap) var(--case-gap) 0;
  /* As tall as the machine and centred against it. --glass-height is measured in
     main.js because CSS cannot work it out from here: the screen is 4:3, its
     half of the page is not, and whichever of the two runs out first decides. */
  align-self: center;
  height: calc(var(--glass-height, 100%) + var(--case-width) + var(--case-foot));
  background: var(--case-face);
  border-radius: calc(var(--case-width) * 0.32);
  box-shadow: var(--case-edges), var(--case-drop);
  border-left: 0;
  color: var(--case-ink-dim);
}

/* Berkeley's panel is not part of anything. This dialect is a program on a
   workstation, not a machine, so its controls are the page's own chrome: flat,
   ruled off from the work with one line, nothing pretending to be moulded. */
body[data-mode="berkeley"] .bar__controls {
  border-left: 1px solid var(--edge);
}

/* Writing and keys on the moulding. Dark brown on beige, because this panel is
   part of the machine now and not part of the page's chrome. */
body[data-mode="apple"] .bar__controls .control,
body[data-mode="apple"] .bar__controls .control span {
  color: var(--case-ink-dim);
}

/* The lettering on the moulding. Small caps rather than capitals: lower case
   letters are drawn as small capitals and the real capitals stay full height, so
   it reads as a machine's label without shouting. Chrome synthesises it for
   fonts that have no small-cap glyphs, and CJK is unaffected by it — which is
   why the size of every row is fixed elsewhere and not left to the text. */
body[data-mode="apple"] .bar__controls {
  font-variant-caps: small-caps;
  letter-spacing: 0.04em;
}

body[data-mode="apple"] .bar__controls button,
body[data-mode="apple"] .bar__controls select {
  background: var(--case-key);
  border-color: var(--case-key-edge);
  color: var(--case-ink);
  /* Keys sit slightly proud of the face they are set into. */
  box-shadow: inset 0 1px 0 #fffaf07a, 0 1px 1px #0000002e;
}

body[data-mode="apple"] .bar__controls button:hover,
body[data-mode="apple"] .bar__controls select:hover {
  border-color: var(--case-ink-dim);
}

/* Struck, in the machine's own colours: the key loses its lit top edge and
   gains the shadow of the recess it is sitting in. */
body[data-mode="apple"] .bar__controls button:active,
body[data-mode="apple"] .bar__controls button.key--hit {
  background: #b3a88d;
  box-shadow: inset 0 2px 3px #0000005e;
}

body[data-mode="apple"] .bar__controls [data-locale][aria-pressed="true"] {
  background: var(--case-ink-dim);
  border-color: var(--case-ink);
  color: #efe6d2;
}

body[data-mode="apple"] .bar__controls input[type="range"] { accent-color: #7a6f57; }
body[data-mode="apple"] .bar__controls input[type="checkbox"] { accent-color: #7a6f57; }

body[data-mode] .bar__group {
  flex-direction: column;
  align-items: stretch;
  gap: 0.5rem;
  flex-wrap: nowrap;
}

body[data-mode="apple"] .bar__group { gap: 0.4rem; }

body[data-mode="apple"] .control__choices { gap: 0.25rem; }

/* Label on the left, control on the right, in a column narrow enough that they
   would otherwise sit on top of each other. */
body[data-mode] .bar__controls .control {
  justify-content: space-between;
  gap: 0.5rem;
}

body[data-mode] .bar__controls select,
body[data-mode] .bar__controls [data-examples],
body[data-mode] .bar__controls [data-manual],
body[data-mode] .bar__controls [data-work],
body[data-mode] .bar__controls .control__choices { width: 8rem; max-width: none; }

/* A key on the panel is as wide as its column, so side padding buys nothing —
   the label is centred either way — and it is the only thing between an
   eight-character Japanese label and a second line it has no room for. A
   full-width glyph is exactly one em, so eight of them are 112px; 8rem less the
   default 0.6rem padding is 107, and less this is 118. Checked in
   test/wiring.test.js, which reads both numbers out of this file. */
body[data-mode] .bar__controls .control__choices button,
body[data-mode] .bar__controls [data-examples],
body[data-mode] .bar__controls [data-manual],
body[data-mode] .bar__controls [data-work] {
  padding-inline: 0.25rem;
}

/* Three positions stacked, the same width and the same place as a menu — only
   open, because there are three of them and a menu hides which one you are in.
   The row is taller than the others and fixed at that: three keys and two gaps,
   none of it decided by the words on them. */
.control--stack {
  align-items: flex-start;
  height: auto;
}

.control__choices {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.control__choices button { width: 100%; }

[data-monitor-choice][aria-pressed="true"],
[data-geometry-choice][aria-pressed="true"],
[data-edge-choice][aria-pressed="true"],
[data-screen-choice][aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: #0d1512;
  font-weight: 600;
}

body[data-mode="apple"] .bar__controls [data-monitor-choice][aria-pressed="true"],
body[data-mode="apple"] .bar__controls [data-geometry-choice][aria-pressed="true"],
body[data-mode="apple"] .bar__controls [data-edge-choice][aria-pressed="true"],
body[data-mode="apple"] .bar__controls [data-screen-choice][aria-pressed="true"] {
  background: var(--case-ink-dim);
  border-color: var(--case-ink);
  color: #efe6d2;
}

body[data-mode] .bar__controls input[type="range"] { width: 6.6rem; }
/* A key going down: darker, sunk into its own shadow, and moved a pixel with it.
   The same look for a press held down and for the moment after a click, because
   they are the same event as far as a reader is concerned. */
button:active,
button.key--hit {
  transform: translateY(1px);
  filter: brightness(0.93);
  box-shadow: inset 0 1px 3px #00000059;
}

@keyframes key-hit {
  from { transform: translateY(1px); filter: brightness(0.9); }
  to { transform: translateY(0); filter: brightness(1); }
}

button.key--hit { animation: key-hit 220ms ease-out; }

/* Which language is on. Same idea as the dialect switch: a control with more
   than one position has to show which one it is in. */
[data-locale][aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: #0d1512;
  font-weight: 600;
}

/* The three languages share the row equally and fill it: they are the same
   kind of thing, and a row of buttons of three different widths reads as three
   different kinds of thing. */
body[data-mode] .bar__controls .control--locale {
  justify-content: stretch;
  gap: 0.3rem;
}

body[data-mode] .bar__controls .control--locale button {
  flex: 1 1 0;
  padding-inline: 0;
  text-align: center;
}

/* A checkbox comes before its label in the markup, which is right for a row of
   controls and wrong for a column of them: every other line here reads name on
   the left, control on the right, and this one alone read the other way. */
body[data-mode="apple"] .bar__controls .control:has(input[type="checkbox"]) {
  flex-direction: row-reverse;
  justify-content: space-between;
}

body[data-mode="apple"] .stage {
  grid-column: 1;
  grid-row: 2;
}

/* Berkeley, in three columns: what you type on the left, the field in the
 * middle, the controls on the right.
 *
 * The manual's SPLITSCREEN is graphics above and text below, and this is not
 * that. It is the one deliberate departure in how the two are laid out, and the
 * reason is the shape of the screens people read this on: wide and short, where
 * a field with a transcript under it gets neither enough height nor enough
 * width. What is not changed is the behaviour — FULLSCREEN, SPLITSCREEN and
 * TEXTSCREEN still decide how much room the drawing gets, exactly as the manual
 * says. Only the axis differs, and docs/architecture.md says so.
 */
body[data-mode="berkeley"] {
  display: grid;
  grid-template-columns: 1fr 15.85rem;
  grid-template-rows: auto 1fr;
  background: var(--panel);
}

body[data-mode="berkeley"] .stage {
  grid-column: 1;
  grid-row: 2;
  grid-template-columns: minmax(18rem, 26%) 1fr;
}

/* Both rows stated, not just both columns. The canvas comes first in the markup
   and asks for column 2; auto-placement then refuses to go back for column 1 and
   puts the transcript on a second row underneath — which is the layout this
   change was undoing. */
body[data-mode="berkeley"] .stage__terminal {
  grid-column: 1;
  grid-row: 1;
  border-left: 0;
  border-right: 1px solid var(--edge);
  /* A column of its own can afford a size you can read. 13px was the size of a
     strip beside a drawing; this is where the work is done. */
  font-size: 14px;
}

body[data-mode="berkeley"] .stage__canvas {
  grid-column: 2;
  grid-row: 1;
}

button, select {
  font: inherit;
  color: var(--ink);
  background: #262c35;
  border: 1px solid var(--edge);
  border-radius: 4px;
  /* Height from the variable, never from the text inside it. */
  height: var(--control-height);
  line-height: 1;
  padding: 0 0.6rem;
  cursor: pointer;
}

button:hover, select:hover { border-color: var(--accent); }
select:disabled, option:disabled { color: var(--ink-dim); cursor: not-allowed; }

input[type="range"] { width: 6.5rem; accent-color: var(--accent); }

.stage {
  flex: 1;
  min-height: 0;
  display: grid;
  /* Explicit, not implicit. An auto row is sized from its content, and its
     content is a canvas whose intrinsic size is its pixel buffer — see below. */
  grid-template-rows: 1fr;
  grid-template-columns: 1fr minmax(20rem, 32%);
}

/* Every canvas here is sized in device pixels from its own CSS box, which makes
   the CSS box the one thing that must never depend on the buffer. A canvas with
   no CSS size reports its buffer as its layout size, so `canvas.width =
   clientWidth * dpr` doubles it every frame: 800 pixels becomes 250 gigabytes in
   eight frames and the tab is gone before anything renders.
   Absolute positioning settles it for good — an out-of-flow element cannot
   contribute to its ancestors' size at all, so the loop has nowhere to close.
   test/surface.test.js holds this. */
.stage__canvas {
  position: relative;
  min-width: 0;
  overflow: hidden;
}

/* Berkeley: the glass is the whole area, so this wrapper is a pass-through. The
   Apple rules below give it a shape and a case; here it must add nothing. */
.glass {
  position: absolute;
  inset: 0;
}

.glass canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* Too narrow for a machine with a panel beside it. This is not a phone layout —
 * that is not designed yet — it is the difference between degrading and
 * collapsing: at 420 pixels the column took most of the width, the padding took
 * the rest, and the screen came out with no size at all. Stacked, everything is
 * legible and nothing is broken.
 */
@media (max-width: 46rem) {
  /* Three columns need width there is not any of. Both dialects stack: the work
     above, the controls below it in a row that wraps. This is degrading, not a
     phone layout — which is not designed and does not pretend to be. */
  body[data-mode="berkeley"] {
    grid-template-columns: 1fr;
    grid-template-rows: auto minmax(12rem, 1fr) auto;
  }

  body[data-mode="berkeley"] .stage {
    grid-column: 1;
    grid-row: 2;
    grid-template-columns: 1fr;
    grid-template-rows: 1fr auto;
  }

  body[data-mode="berkeley"] .stage__canvas {
    grid-column: 1;
    grid-row: 1;
  }

  body[data-mode="berkeley"] .stage__terminal {
    grid-column: 1;
    grid-row: 2;
    border-right: 0;
    border-top: 1px solid var(--edge);
  }

  body[data-mode="berkeley"] .bar__controls {
    grid-column: 1;
    grid-row: 3;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    border-left: 0;
    border-top: 1px solid var(--edge);
  }

  body[data-mode="apple"] {
    grid-template-columns: 1fr;
    /* The screen keeps a floor. Stacked, the controls are tall enough on a short
       window to leave the row above them nothing at all, and a screen of no
       height is a screen that reports 4:3 as NaN. */
    grid-template-rows: auto minmax(12rem, 1fr) auto;
  }

  body[data-mode="apple"] .stage {
    grid-column: 1;
    grid-row: 2;
  }

  body[data-mode="apple"] .stage__canvas {
    padding-left: calc(var(--case-width) + var(--case-gap));
  }

  body[data-mode="apple"] .bar__controls {
    grid-column: 1;
    grid-row: 3;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    align-self: stretch;
    height: auto;
  }
}

/* The three screens.
 *
 * FULLSCREEN, SPLITSCREEN and TEXTSCREEN are commands in both manuals, so this
 * is the language deciding the layout rather than a stylesheet deciding it for
 * everyone. `data-screen` on the body is set by shell/screen.js.
 *
 * FULLSCREEN hides the transcript, NOT the field you type into: an input in a
 * display:none pane cannot be focused or typed into at all, and the screen went
 * full with the keyboard inside it.
 */
body[data-screen="full"] .stage {
  grid-template-columns: 1fr;
  grid-template-rows: 1fr auto;
}

body[data-screen="text"] .stage {
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
}
/* Berkeley only: its drawing and its transcript are two boxes, so a text screen
   means hiding one of them. The Apple mode has one screen and the text is the
   bottom of it — hiding the canvas there would hide the text with it. */
body[data-mode="berkeley"][data-screen="text"] .stage__canvas { display: none; }

/* The Apple screen never changes shape. One glass, 280 by 192, and the three
   commands change what is on those lines rather than how much room anything
   gets: a split crops the drawing, it does not shrink it. So there is one layout
   for all three modes — the screen, and the row you type on, which is on the
   screen itself and leaves that row empty. */
body[data-mode="apple"] .stage {
  grid-template-columns: 1fr;
  grid-template-rows: 1fr auto;
}

/* The machine.
 *
 * A television of the period drawn in CSS: no photograph, no logo, no badge, and
 * no reproduction of anyone's industrial design — trade dress is a separate
 * right from copyright and a case that copied a recognisable product would
 * engage it however the pixels were made. See docs/licensing.md.
 *
 * What makes it read as a moulded object is depth and light, in that order. A
 * set of this period did not have a picture with a frame painted round it: the
 * tube sat *back* inside a stepped opening, and the picture did not reach the
 * edge of the tube's own face. So there are three surfaces between the picture
 * and the room, and each one is lit differently:
 *
 *   the tube face   black, around the picture — the raster never filled it
 *   the recess wall the step back to the front, so it is lit from BELOW: dark
 *                   at the top where the moulding overhangs it
 *   the case front  the moulding itself, lit from above like everything else
 *
 * The one that does the most work is the recess, because its lighting is
 * inverted. Without it this was a flat face with a line round the picture, and
 * it read as a rounded rectangle rather than a machine.
 *
 * None of it takes part in layout: the canvas is sized in device pixels from its
 * own box, so everything here is either a shadow or an out-of-flow element, for
 * the reason set out beside .stage__canvas above.
 */
body[data-mode="apple"] .stage__canvas {
  display: grid;
  place-items: center;
  padding: calc(var(--case-width) + var(--case-gap));
  padding-right: calc(var(--case-cheek) + var(--case-gap));
  padding-bottom: calc(var(--case-foot) + var(--case-gap));
  /* The desk shows through: the page's own colour, so the machine and the panel
     of controls beside it stand on one surface. Only the glass is black, because
     only the glass is a screen. */
  background: none;
  /* So the glass can be sized against this box in both directions. */
  container-type: size;
}

body[data-mode="apple"] .glass {
  position: relative;
  inset: auto;
  aspect-ratio: 4 / 3;
  width: auto;
  max-width: 100%;
  /* Two declarations on purpose. The first is right whenever there is more room
     across than down, which is the usual way round, and is understood by
     everything. The second is right in both directions and wins where container
     query units are understood.

     `max-height: 100%` is what this wanted to be and it does not work: the
     percentage did not resolve here, the aspect ratio won, and the screen came
     out 1360 by 1020 in a 1360 by 702 box — off the bottom of the page, case and
     all. Measured, not guessed. */
  /* Whichever of the two runs out first, so 4:3 holds either way.
     
     The width term is not a loop, though it looks like one: what main.js turns
     into this column's width is computed from the *height* of the space, which
     the column cannot change. When the window is too narrow for that width the
     grid clamps the column, and then this term is what keeps the picture square
     inside it instead of leaving the machine sticking out under the panel.
     
     Sizing it from the height alone is what did that: the case hung 50 pixels
     past the pane, the pane clips, and the brightness wheel ended up underneath
     the controls. */
  height: min(100cqh, 75cqw);
  background: #000;
  /* A cathode ray tube is a bulging bottle, so its corners are generous. */
  border-radius: 4% / 5.4%;
  /* The tube's own face: black, around the picture, because the raster stopped
     short of it. Then the shadow the recess throws onto it. */
  box-shadow:
    0 0 0 var(--tube-face) #0e0e13,
    0 0 0 calc(var(--tube-face) + 0.07rem) #000000a6;
  isolation: isolate;
  container-type: size;
}

/* The wall of the recess: the step from the front of the case back to the tube.
 *
 * Four walls, four tones, and each one flat along its own edge — which is the
 * whole difference between a hole and a wash of colour. A light above and a
 * little to the left leaves the top wall facing away from it and the bottom wall
 * facing into it, so those two are the darkest and the lightest; the side walls
 * face sideways and sit between them, the left a shade brighter than the right.
 *
 * It was one linear-gradient across the whole ring first. That put the correct
 * tones on the top and bottom and then washed the *side* walls smoothly from
 * dark to light down their length, which nothing in the world does — and it read
 * as paint rather than depth. Per-side border colours are the fix: a border
 * changes tone at the corners, where a real moulding changes direction.
 */
body[data-mode="apple"] .glass::before {
  content: "";
  position: absolute;
  inset: calc((var(--tube-face) + var(--recess)) * -1);
  z-index: -1;
  border: var(--recess) solid;
  border-color: #6b6151 #978c73 #cdc2a6 #a89d83;
  border-radius: calc(var(--recess) * 2.2);
  background: #0e0e13;
  box-shadow: inset 0 1px 2px #00000073;
}

/* The front of the machine. */
body[data-mode="apple"] .glass__case {
  position: absolute;
  inset: calc(var(--case-width) * -1);
  right: calc(var(--case-cheek) * -1);
  bottom: calc(var(--case-foot) * -1);
  z-index: -2;
  border-radius: calc(var(--case-width) * 0.32);
  /* Nearly flat, and lit at its edges: a slab this size does not change colour
     down its whole height, and a wash across the face reads as paint. */
  background: var(--case-face);
  box-shadow: var(--case-edges), var(--case-drop);
}

/* The brightness, in the same slot the decoration used to draw.
 *
 * A real <input type="range">, so dragging it, tabbing to it and reading it out
 * are the browser's job and not ours. It is styled to look like what it looked
 * like: a slot cut into the moulding, lit as a hole is — dark along the top edge
 * where the case overhangs it — with a pale wheel in it.
 *
 * Vertical by writing-mode, which is how a range is turned on its side now;
 * `direction: rtl` puts the bright end at the top, where a wheel you push up is
 * expected to go.
 */
.glass__brightness { display: none; }

body[data-mode="apple"] .glass__brightness {
  display: block;
  position: absolute;
  z-index: 2;
  right: calc(var(--case-cheek) * -0.78);
  top: 27%;
  width: calc(var(--case-cheek) * 0.24);
  height: 16%;
  margin: 0;
  padding: 0;
  appearance: none;
  writing-mode: vertical-lr;
  direction: rtl;
  background: linear-gradient(#6f6552, #7d735e);
  border-radius: calc(var(--case-cheek) * 0.08);
  box-shadow: inset 0 2px 3px #00000073, inset 0 -1px 0 #fff8e880, 0 1px 0 #fff8e857;
  cursor: ns-resize;
}

body[data-mode="apple"] .glass__brightness::-webkit-slider-runnable-track {
  background: none;
  width: 100%;
}

/* The wheel: lit on its upper face, shaded under it, as it was drawn before. */
body[data-mode="apple"] .glass__brightness::-webkit-slider-thumb {
  appearance: none;
  width: 100%;
  height: 0.7rem;
  border: 0;
  border-radius: 1px;
  background: linear-gradient(#e6dcc4 0 40%, #cfc3a8 40% 78%, #9d927a 78% 100%);
  box-shadow: 0 1px 1px #00000055;
}

body[data-mode="apple"] .glass__brightness::-moz-range-thumb {
  width: 100%;
  height: 0.7rem;
  border: 0;
  border-radius: 1px;
  background: linear-gradient(#e6dcc4 0 40%, #cfc3a8 40% 78%, #9d927a 78% 100%);
}

/* What the wheel does. On the picture only: the moulding around it is a lit
   room, not part of the screen. */
body[data-mode="apple"] .glass canvas { filter: brightness(var(--brightness, 1)); }

/* The vent strip across the foot, with the power lamp at the end of it. A screen
   showing nothing and a machine that is off look the same. */
body[data-mode="apple"] .glass__case::after {
  content: "";
  position: absolute;
  left: 10%;
  right: 10%;
  bottom: calc(var(--case-foot) * 0.3);
  height: 0.32rem;
  border-radius: 999px;
  background:
    radial-gradient(circle 0.15rem at calc(100% - 0.7rem) 50%, #c9ffdb 0 38%, #3fd472 40% 82%, #0000 84%),
    repeating-linear-gradient(90deg, #0000002e 0 2px, #0000 2px 6px);
  box-shadow: inset 0 1px 1px #00000038, 0 1px 0 #fff6e04d;
}

/* The light on the glass: one soft streak from the same source, and the dark the
   moulding throws around the edge of the picture. Above the canvas, below the
   row you type on, and never in the way of a pointer. */
.glass__sheen, .glass__case { display: none; }

body[data-mode="apple"] .glass__sheen {
  display: block;
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  border-radius: inherit;
  background: linear-gradient(146deg, #ffffff12 0 13%, #ffffff07 20%, #0000 40%);
  /* Tight to the edge, so it reads as the moulding's shadow on the glass and
     not as a vignette. The first version was dark enough to swallow the prompt
     at column 0. */
  box-shadow: inset 0 0 0.7rem #00000073;
}

body[data-mode="apple"] .glass__case { display: block; }

/* The picture takes the tube's corners rather than sitting square inside them.
   A border-radius clips a canvas's own bitmap, which is what is wanted here —
   clipping the parent instead would have clipped the case off the machine.

   Tighter than the moulding around it, deliberately. At the tube's own radius
   the curve reaches into the last row of text and takes the first character of
   it with it: the prompt at column 0 of row 24 came out as half a question
   mark. A real tube did hide its corners; a reader who cannot see the prompt is
   a worse outcome than a corner that is slightly too square. */
body[data-mode="apple"] .glass canvas {
  z-index: 0;
  border-radius: 0.9% / 1.2%;
}


/* The examples, with what each one draws.
 *
 * A native <dialog>, so Escape, the backdrop and the focus trap are the
 * browser's. The picture on the left is the program run by the same interpreter
 * the page runs — not a screenshot in a folder, which is a claim about a program
 * that nothing checks and that goes stale the day the program changes.
 */
.picker {
  width: 85vw;
  /* Definite, not a ceiling. With `max-height` the box was as tall as its list
     happened to be, which left the picture and the program under it sharing
     whatever was left; the two panes are the point, so they get the room and
     the list scrolls inside it. */
  height: 88vh;
  /* A column with one scrolling part in it, and the dialog itself is not that
     part. Left to itself the box grew past 88vh — a title, a note that wraps to
     two lines at some widths, a list with a viewport-height cap of its own and a
     row of buttons under it — and the dialog scrolled as well as the list, so
     there were two scrollbars down the right hand side and neither of them
     moved the whole thing. The list takes what is left instead.

     The column is on [open] and not here: a closed dialog is hidden by
     `display: none` in the browser's own stylesheet, and a display of any kind
     in a page stylesheet beats it — which puts the whole dialog on top of the
     page, permanently, over everything you were trying to click. */
  overflow: hidden;
  padding: 1rem 1.1rem;
  border: 1px solid var(--edge);
  border-radius: 10px;
  background: var(--panel);
  color: var(--ink);
  font: inherit;
}

.picker::backdrop { background: rgb(0 0 0 / 0.55); }

.picker[open] {
  display: flex;
  flex-direction: column;
}

.picker__title {
  margin: 0 0 0.75rem;
  font-size: 1rem;
  font-weight: 600;
  height: var(--control-height);
  line-height: var(--control-height);
}

.picker__panes {
  display: grid;
  flex: 1;
  /* The picture is one size and the list is everything else: a name and its
     description on one line each, which is what the extra width buys. */
  /* The picture is 4:3 and sized by the width of its column, so on a short wide
     window a column at 30% of the width is a picture taller than the dialog it
     is in. The height it may take is capped in the one unit that knows about
     that: 46vh of column is a picture about 34vh tall. */
  grid-template-columns: minmax(16rem, min(30%, 46vh)) 1fr;
  gap: 1.2rem;
  min-height: 0;
}

/* The picture, on the same black the machine draws on, and whatever the program
   printed underneath it — some of them print and never draw at all. */
.picker__preview {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  min-width: 0;
  /* A grid item is at least as tall as its contents unless it says otherwise, so
     a column holding a 4:3 picture and a fifteen-line program grew past the row
     it was in and was drawn over the buttons. Without this line it does that at
     every window from 1000x560 to 1314x719; with it, the column takes the height
     it is given and the program scrolls inside it. It and the cap on the column
     width above are both needed and neither is enough — the cap holds the wide
     short windows, this holds the rest. `minmax(0, 1fr)` on the row was tried in
     the same breath and turned out to change nothing at any window measured; it
     is not in the file, because a rule doing no work under a comment saying it
     does is worse than no rule at all. */
  min-height: 0;
}

.picker__preview canvas {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  background: #000;
  border: 1px solid var(--edge);
  border-radius: 6px;
}

/* The program, under the picture it draws. It takes what is left of the column
   and scrolls inside that, so a long one cannot push the picture off the top. */
.picker__source {
  /* Fills what the picture leaves and scrolls inside it, and gives that room up
     first when there is not enough: three lines of a program is little use, but
     it is better than a dialog that does not fit on the screen. */
  flex: 1;
  min-height: 3rem;
  margin: 0;
  overflow: auto;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--edge);
  border-radius: 6px;
  background: #1b1f26;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  line-height: 1.45;
  white-space: pre;
  color: var(--ink);
}

.picker__printed {
  margin: 0;
  max-height: 8rem;
  overflow: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  line-height: 1.35;
  color: var(--ink-dim);
  white-space: pre-wrap;
}

/* One program per row, and as many rows as there are: the list scrolls, the
   dialog does not grow. */
.picker__list {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  /* Its share of the dialog, not a share of the window: the window does not
     know about the title above it or the buttons below. */
  min-height: 0;
  overflow-y: auto;
  padding-right: 0.25rem;
}

/* One program per row: the row is what you press, and the key at the end of it
   takes the program away. */
.picker__row {
  display: flex;
  gap: 0.35rem;
}

.picker__row .picker__item { flex: 1; min-width: 0; }

.picker__save {
  flex: none;
  height: auto;
  padding: 0 0.7rem;
  font-size: 12px;
  color: var(--ink-dim);
  background: #222831;
}

.picker__save:hover { color: var(--ink); }

/* One row per program: the name in a column of its own, the description beside
   it. Names are sixteen characters at most, so the column is sixteen wide and
   the descriptions all start in the same place. */
.picker__item {
  display: flex;
  align-items: baseline;
  gap: 0.9rem;
  height: auto;
  padding: 0.5rem 0.8rem;
  text-align: left;
  background: #222831;
  line-height: 1.4;
}

.picker__item b {
  flex: none;
  width: 16ch;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  letter-spacing: 0.02em;
}

.picker__item span {
  color: var(--ink-dim);
  font-size: 13px;
  min-width: 0;
}

/* The badge on a program that only one machine can run. Last in the row and
   allowed to shrink to nothing: the description is what matters, and on a narrow
   dialog the name of a machine is not worth a wrapped line. */
.picker__only {
  flex: none;
  margin-left: auto;
  padding: 0.05rem 0.4rem;
  border: 1px solid var(--line);
  border-radius: 3px;
  color: var(--ink-dim);
  font-size: 11px;
  font-style: normal;
  letter-spacing: 0.04em;
  white-space: nowrap;
}

.picker__item[aria-pressed="true"] .picker__only {
  border-color: var(--accent);
  color: var(--accent);
}

.picker__item[aria-pressed="true"] {
  border-color: var(--accent);
  background: #26332e;
}

.picker__item[aria-pressed="true"] b { color: var(--accent); }

.picker__note {
  margin: 0.7rem 0 0;
  min-height: 1.2rem;
  color: var(--ink-dim);
  font-size: 12px;
}

/* What the import just did, under the list rather than in it: the list is the
   disk and this is a remark about it. It keeps its height whether or not there
   is anything to say, so adding a file does not move the buttons under the
   pointer that is about to click one. */
.work__said {
  margin: 0.5rem 0 0;
  min-height: 1.2rem;
  color: var(--ink-dim);
  font-size: 13px;
}

.work__import { margin-right: auto; }

.picker__foot {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  margin-top: 0.7rem;
}

.picker__run {
  background: var(--accent);
  border-color: var(--accent);
  color: #0d1512;
  font-weight: 600;
}

/* On the Apple side the dialog is part of the machine, like the panel is. */
body[data-mode="apple"] .picker {
  background: var(--case-face);
  border-color: var(--case-key-edge);
  color: var(--case-ink);
}

body[data-mode="apple"] .picker__title,
body[data-mode="apple"] .picker__printed,
body[data-mode="apple"] .picker__note { color: var(--case-ink-dim); }

body[data-mode="apple"] .picker__item {
  background: var(--case-key);
  border-color: var(--case-key-edge);
  color: var(--case-ink);
}

body[data-mode="apple"] .picker__item span { color: #6b6151; }

body[data-mode="apple"] .picker__save {
  background: var(--case-key);
  border-color: var(--case-key-edge);
  color: var(--case-ink-dim);
}

body[data-mode="apple"] .picker__save:hover { color: var(--case-ink); }

body[data-mode="apple"] .picker__source {
  background: #efe9da;
  border-color: var(--case-key-edge);
  color: var(--case-ink);
}

body[data-mode="apple"] .picker__item[aria-pressed="true"] {
  background: #b3a88d;
  border-color: var(--case-ink);
}

body[data-mode="apple"] .picker__item[aria-pressed="true"] b { color: var(--case-ink); }

/* Every key in the dialog is one of the machine's, including the one that
   closes it — a single dark button on a beige panel reads as an oversight. */
body[data-mode="apple"] .picker button {
  background: var(--case-key);
  border-color: var(--case-key-edge);
  color: var(--case-ink);
}

body[data-mode="apple"] .picker button:active,
body[data-mode="apple"] .picker button.key--hit { background: #b3a88d; }

body[data-mode="apple"] .picker__run {
  background: var(--case-ink-dim);
  border-color: var(--case-ink);
  color: #efe6d2;
}

@media (max-width: 46rem) {
  .picker__panes { grid-template-columns: 1fr; }
}

/* The editor. The same dialog again, with a textarea in it: what EDIT opens is
   the workspace as text, and text is what a textarea is for. No colouring, no
   completion, nothing this project would have to carry a dependency for. */
.editor {
  width: min(52rem, 85vw);
  height: min(70vh, 38rem);
}

.editor__text {
  flex: 1;
  min-height: 0;
  resize: none;
  margin: 0.6rem 0 0;
  padding: 0.7rem 0.8rem;
  border: 1px solid var(--edge);
  border-radius: 6px;
  background: #1b1f26;
  color: var(--ink);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 13px;
  line-height: 1.5;
  white-space: pre;
  overflow: auto;
}

.editor__text:focus {
  outline: none;
  border-color: var(--accent);
}

body[data-mode="apple"] .editor__text {
  background: #efe9da;
  border-color: var(--case-key-edge);
  color: var(--case-ink);
}

/* The disk. Same dialog again, and the same shape of row as the reference: a
   name in a column of its own, what is in it, and the two things you can do with
   it. Shorter than the other two, because a list of saved work is usually a list
   of one. */
.work {
  width: min(46rem, 85vw);
  /* One size, whether the disk holds one file or twenty. A box that is the
     height of its contents is a box that jumps every time you save something,
     and this is a list that grows while you work. */
  height: min(66vh, 34rem);
}

/* Room between what the dialog says and the first thing it lists. Without it the
   note reads as a heading for the first row rather than for all of them. */
.work__note { margin-bottom: 1.1rem; }

.work__body {
  min-height: 0;
  overflow-y: auto;
  padding-right: 0.4rem;
  border-top: 1px solid var(--edge);
}

/* A row of a table rather than a line of text: ruled off from its neighbours,
   and lit from the top the way a physical shelf is, so the eye can follow one
   across to the keys at its end. */
.work__row {
  display: grid;
  grid-template-columns: 12rem 1fr auto auto;
  align-items: center;
  gap: 0.6rem;
  padding: 0.55rem 0.5rem;
  border-bottom: 1px solid var(--edge);
  background: linear-gradient(180deg, #ffffff0a, #00000014);
}

.work__row:hover { background: linear-gradient(180deg, #ffffff14, #00000014); }

.work__row b {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  letter-spacing: 0.02em;
}

.work__row > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-dim);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
}

.work__load, .work__row .picker__save {
  height: auto;
  padding: 0.2rem 0.7rem;
  font-size: 12px;
}

/* A file with nothing in it says so, in words rather than by being blank. */
.work__nothing { font-style: italic; opacity: 0.75; }

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

/* An empty disk has something to say, and what it says is how to fill it. */
.work__empty {
  margin: 0.5rem 0;
  color: var(--ink-dim);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 13px;
}

body[data-mode="apple"] .work__row > span,
body[data-mode="apple"] .work__empty { color: #6b6151; }

body[data-mode="apple"] .work__load {
  background: var(--case-key);
  border-color: var(--case-key-edge);
  color: var(--case-ink-dim);
}

/* The same shelf, in the machine's materials. */
body[data-mode="apple"] .work__body { border-top-color: var(--case-key-edge); }

body[data-mode="apple"] .work__row {
  border-bottom-color: var(--case-key-edge);
  background: linear-gradient(180deg, #ffffff40, #0000000f);
}

body[data-mode="apple"] .work__row:hover {
  background: linear-gradient(180deg, #ffffff66, #0000000f);
}

/* The reference. Same dialog as the examples, laid out as a list of rows: the
   name in a column of its own, what it does, and one example you can press. */
.manual__body {
  /* What is left of the dialog after the title, the note and the buttons — not a
     share of the window, which is a different height and does not know about
     them. */
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding-right: 0.4rem;
}

.manual__group { margin-bottom: 1.1rem; }

.manual__group h3 {
  margin: 0 0 0.4rem;
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-dim);
  border-bottom: 1px solid var(--edge);
  padding-bottom: 0.25rem;
}

.manual__row {
  display: grid;
  grid-template-columns: 15rem 1fr 15rem;
  align-items: baseline;
  gap: 0.8rem;
  padding: 0.15rem 0;
  font-size: 13px;
}

.manual__row b {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  letter-spacing: 0.02em;
}

.manual__row > span { color: var(--ink-dim); }

/* The example is a key: pressing it types the line and runs it, which is the
   shortest way from reading about a word to watching it work. */
.manual__example {
  height: auto;
  padding: 0.1rem 0.5rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.manual__note { margin-bottom: 0.8rem; }

body[data-mode="apple"] .manual__group h3 {
  color: var(--case-ink);
  border-bottom-color: var(--case-key-edge);
}

body[data-mode="apple"] .manual__row > span { color: #6b6151; }

@media (max-width: 60rem) {
  .manual__row {
    grid-template-columns: 1fr;
    gap: 0.1rem;
    padding: 0.35rem 0;
  }
}
