/* Steropes, the instrument panel.
 *
 * The medium is `crt.css`, carried verbatim from Keraunos by way of Oikos and
 * loaded before this file: palette, type, glass and decay, identical in every
 * project on the domain so the whole set reads as one instrument. Nothing that
 * belongs to the medium belongs in here.
 *
 * What this file adds is Steropes's own. The series reserves white for the one
 * thing each instrument exists to show — a lightning strike on Keraunos, the
 * tuned station on Oikos. Here it is `--c-strike` again, in its third sense: a
 * bit that just arrived from the sky, the walk's head, and nothing else.
 *
 * Every colour the canvas draws with lives here rather than in the drawing code,
 * so the stylesheet and the canvas cannot disagree. ui.js reads them back with
 * getComputedStyle.
 */

/* ── Instrument tokens ────────────────────────────────────────────────────────
   Added to the shared palette rather than replacing any of it. Both media, in
   the order crt.css declares them. */
:root,
:root[data-theme="dark"] {
  /* The walk's settled history. Deliberately NOT --c-line: furniture may sit at
     the threshold of visibility, but the walk is the subject of the page and the
     first draft drew it at about 1.2:1 against the void, which made the one
     thing worth looking at the hardest thing to see. */
  --walk-tail: #3d3d46;

  /* The only colour on the page, and it means one thing: this test is failing.
     Derived from the series' own P3 amber phosphor ratio [1, .72, .34] applied
     to the text grey, rather than picked by eye. */
  --c-fail: #c8905b;

  --grid: rgba(255, 255, 255, 0.022);

  --gap: 1.5rem;
}

:root[data-theme="light"] {
  --walk-tail: #b0b0b8;
  --c-fail: #8a5417;
  --grid: rgba(0, 0, 0, 0.02);
}

/* ── Coatings ─────────────────────────────────────────────────────────────────
   The phosphor the tube is coated with, carried over from Tyche so the two
   instruments wear the same three. They belong to the medium and would sit in
   `crt.css` if that file were not byte-identical across the set; forking the
   shared medium to give one project a setting is the more expensive of the two
   mistakes, so they are additions here instead.

   Dark only, and `[data-theme="dark"]` is part of every selector: a coating is
   a property of a tube, and paper has one ink. `white` is the absence of a
   coating, which is the grey palette above, so choosing it writes an attribute
   no rule matches.

   `--c-strike`, the bloom and the grid are not repeated. White is reserved for
   the strike itself in every coating, which is exactly where the grey tube
   already stood. `--c-fail` is not repeated either: it is the one colour on the
   page that means something, and a failing badge that changed hue with the
   coating would be decoration rather than a signal. */

/* Oil 6, by GrafxKid. https://lospec.com/palette-list/oil-6 */
:root[data-theme="dark"][data-palette="oil"] {
  --c-void: #272744;
  --c-panel: #292948;
  --c-line: #373960;
  --c-land: #6e5982;
  --c-dim: #ab8b95;
  --c-text: #ffe6b7;

  --ink-rest: #ffe6b7;
  /* Sixty per cent of the way from the line to the land, which is where the
     grey tail sits between its own two. The walk is the subject of the page and
     it has to keep that footing on every tube. */
  --walk-tail: #584c74;
}

/* Crimson, by WildLeoKnight. https://lospec.com/palette-list/crimson */
:root[data-theme="dark"][data-palette="crimson"] {
  --c-void: #1b0326;
  --c-panel: #220529;
  --c-line: #4b1039;
  --c-land: #83353a;
  --c-dim: #cd5947;
  --c-text: #c9cdb7;

  --ink-rest: #c9cdb7;
  --walk-tail: #6d263a;
}

/* Blood Demon RX, by Chicknhawk. https://lospec.com/palette-list/blood-demon-rx */
:root[data-theme="dark"][data-palette="demon"] {
  --c-void: #171f37;
  --c-panel: #1f2138;
  --c-line: #4b2941;
  --c-land: #98374e;
  --c-dim: #f7495f;
  --c-text: #ffcfe2;

  --ink-rest: #ffcfe2;
  --walk-tail: #793149;
}

/* ── Frame ────────────────────────────────────────────────────────────────────
   Bezel, instrument, bezel. The two bars are fixed height and the panel takes
   what is left, which is what stops a long readouts column from pushing the
   status line off the bottom of the tube. */
body {
  display: flex;
  flex-direction: column;
}

.panel {
  flex: 1;
  /* Without this a grid child refuses to shrink below its content and the
     column scrolls the page instead of itself. */
  min-height: 0;
  display: grid;
  grid-template-columns: 1fr 22rem;
  /* The row is the panel's height, not its content's. An `auto` row cannot go
     below the min-content of what is in it, and what is in it is a canvas whose
     intrinsic height is whatever the last resize wrote to its attributes: on a
     window made shorter the row kept the old height, the stage overflowed, and
     the status line ended up drawn across the middle of the instrument. The
     zero minimum lets the row shrink; the canvas is 100% of it either way. */
  grid-template-rows: minmax(0, 1fr);
}

/* ── The bezel ────────────────────────────────────────────────────────────────
   The row every instrument in the set wears: what this is on the left, what it
   is doing and the controls that drive it on the right. */
.head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  height: 44px;
  flex-shrink: 0;
  padding: 0 var(--gap);
  border-bottom: 1px solid var(--c-line);
}

/* The wordmark is the page's heading, so it is one. Both children set their own
   size and weight; these two only stop the `h1` defaults reaching the slug. */
.mark {
  display: flex;
  align-items: baseline;
  gap: 10px;
  min-width: 0;
  font-size: 13px;
  font-weight: 400;
}

.wordmark {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.22em;
  color: var(--c-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Every caption and control on the bezel reads as the same kind of thing. */
.slug,
.node,
.state,
.ctl {
  font-size: 10px;
  line-height: 14px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--c-dim);
}

/* Never squeezed: the controls are the reason the bezel is here, and a narrow
   screen takes the mark's room rather than theirs. */
.controls {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-shrink: 0;
}

.link-state {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* The indicator is itself a strike counter: it flashes on arrival. Idling, it
   breathes, so a quiet sky reads as running rather than as dead. */
.pip {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--c-land);
  flex-shrink: 0;
}

.pip[data-phase="live"] {
  background: var(--c-text);
  animation: breathe 2.8s ease-in-out infinite;
}

/* Not linked and looking for it, which is the medium's own seek. */
.pip:not([data-phase="live"]) {
  animation: seek 1.6s ease-in-out infinite;
}

/* A state worth naming is a state that is not live, so this one does not get to
   be subtle. */
.state {
  color: var(--c-text);
}

.state[hidden] {
  display: none;
}

button.ctl,
a.ctl {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: color 120ms linear;
}

button.ctl:hover,
a.ctl:hover {
  color: var(--c-text);
}

/* Splits the controls into what they do: the link, the display, elsewhere. */
.rule {
  width: 1px;
  height: 10px;
  background: var(--c-line);
  flex-shrink: 0;
}

/* ── The status line ──────────────────────────────────────────────────────────
   The running commentary. Everything the instrument does says so here, which is
   what makes the waiting legible: a page that is doing nothing and a page that
   is waiting for the sky look identical without it. */
.status {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  height: 28px;
  flex-shrink: 0;
  padding: 0 var(--gap);
  border-top: 1px solid var(--c-line);
  /* Not the bezel's uppercase: this line carries values, not labels, and a
     drawn result or a hostname read in small caps is a result read twice. */
  font-size: 11px;
  color: var(--c-dim);
  /* It is furniture, not selection: dragging across the page should not pick
     up a clock that has already changed by the time it is pasted. */
  user-select: none;
}

.stamp,
.source {
  flex-shrink: 0;
  color: var(--c-land);
  text-decoration: none;
}

.source {
  transition: color 120ms linear;
}

.source:hover {
  color: var(--c-text);
}

/* One line, and it truncates rather than wrapping: the bezel is a fixed height
   and a two-line message would push the instrument off the tube. */
.message {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--c-text);
}

/* Everything the sky hands over arrives at full white and settles into the grey
   of the interface, which is the series' decay rule and the whole reason this
   line is worth watching. */
.message.settle {
  animation: settle 1.4s ease-out;
}

.stage {
  position: relative;
  /* A faint rule grid behind the walk: this is a plotter's paper, and it gives
     the eye a scale to read the walk's rescaling against. */
  background-image: linear-gradient(var(--grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid) 1px, transparent 1px);
  background-size: 32px 32px;
}

#walk {
  display: block;
  width: 100%;
  height: 100%;
}

.readouts {
  border-left: 1px solid var(--c-line);
  background: var(--c-panel);
  padding: var(--gap);
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  overflow-y: auto;
}

/* ── Lede ─────────────────────────────────────────────────────────────────── */
.mast {
  color: var(--c-dim);
  max-width: 30ch;
}

/* ── Readout blocks ───────────────────────────────────────────────────────── */
.block {
  border-top: 1px solid var(--c-line);
  padding-top: 0.75rem;
}

/* The same caption the bezel uses, because they are the same kind of thing: a
   panel that names its blocks in one idiom and its controls in another reads as
   two instruments bolted together. Left names the block, right qualifies it —
   a unit, a window, a size. Never a value: values belong in the rows, where
   they are aligned against each other and can be compared. */
.label {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  font-size: 10px;
  line-height: 14px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--c-dim);
}

/* The one large figure on the page. Bits, because bits are what this makes. */
.figure {
  font-size: 2.75rem;
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--c-text);
  margin-top: 0.25rem;
}

/* ── Captions ─────────────────────────────────────────────────────────────────
   Everything on the page that names a value rather than being one. One rule, so
   a label cannot drift into its own size depending on which block it landed in,
   and the panel's captions match the bezel's exactly.

   The 10/12 split is the whole hierarchy: a caption is smaller than the number
   it introduces, so a column of pairs reads as pairs without a rule between
   them. */
.figure small,
.row > span,
.test .name,
.bar-range label,
.set > span,
.card-label {
  font-size: 10px;
  line-height: 14px;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--c-dim);
}

.figure small {
  margin-left: 0.5rem;
}

/* Pool gauge: a bar of the pool's own bytes, not a percentage of an abstraction. */
.gauge {
  height: 3px;
  background: var(--c-line);
  margin-top: 0.75rem;
}

.gauge span {
  display: block;
  height: 100%;
  background: var(--c-text);
  width: 0;
}

.rows {
  margin-top: 0.5rem;
  display: grid;
  gap: 0.25rem;
}

.row {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
}

.row b {
  font-weight: 400;
  color: var(--c-text);
}

/* ── Draws ────────────────────────────────────────────────────────────────────
   The one part of the page that takes input, so the one part with a pressable
   edge. It sits on the stage rather than in the readouts column: a deck of
   fifty-two cards and a printed plate need the room, and asking the sky a
   question is the thing this page is for, not a sidebar accessory. */
.bar {
  position: absolute;
  inset: auto 0 0 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1.25rem;
  padding: var(--gap);
  /* Reads over the walk without hiding it. */
  background: linear-gradient(transparent, var(--c-void) 55%);
}

.bar-draws {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
}

/* On a wide screen the three settings rows are simply part of the bar's own
   wrap, so their wrapper is not a box at all, and the chips that would open
   them are not needed. */
.bar-forms {
  display: contents;
}

.opener {
  display: none;
}

.bar button,
.card button {
  font: inherit;
  color: var(--c-text);
  background: var(--c-panel);
  border: 1px solid var(--c-line);
  padding: 0.4rem 0.75rem;
  cursor: pointer;
}

.bar button:hover:not(:disabled),
.card button:hover:not(:disabled) {
  border-color: var(--c-dim);
}

.bar button:disabled {
  color: var(--c-line);
  cursor: default;
}

.bar-range {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.bar-range input {
  font: inherit;
  font-variant-numeric: tabular-nums;
  width: 5rem;
  color: var(--c-text);
  background: var(--c-panel);
  border: 1px solid var(--c-line);
  padding: 0.4rem 0.5rem;
}

.bar-range select {
  font: inherit;
  color: var(--c-text);
  background: var(--c-panel);
  border: 1px solid var(--c-line);
  padding: 0.4rem 0.5rem;
  cursor: pointer;
}

.bar-range input:focus-visible {
  border-color: var(--c-dim);
}

/* ── The answer ───────────────────────────────────────────────────────────── */
.card {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(46rem, calc(100% - 3rem));
  max-height: calc(100% - 9rem);
  overflow-y: auto;
  background: var(--c-panel);
  border: 1px solid var(--c-line);
  padding: 1.25rem;
}

.card[hidden] {
  display: none;
}

.card-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
}

/* The dismiss reads as the key that does it, the way the series' panels close. */
.card-head button {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 10px;
  line-height: 14px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--c-dim);
  transition: color 120ms linear;
}

.card-head button:hover:not(:disabled) {
  border: 0;
  color: var(--c-text);
}

/* One pane at a time: the sheet carries its own, and it covers the same
   viewport from inside the top layer. */
body:has(.sheet[open]) > .crt {
  visibility: hidden;
}

/* ── Configuration ────────────────────────────────────────────────────────────
   The same box as the card, on the glass rather than on the stage. Everything
   modal about it — the ground behind, Escape, keeping Tab inside — is the
   browser's, which is most of why it is a <dialog> and not another div. */
.sheet {
  /* What centres a modal dialog: the UA sets `inset: 0` and this, and the
     medium's reset zeroes every margin on the page, this one included. Without
     it the panel sits in the top-left corner. */
  margin: auto;
  width: min(30rem, calc(100% - 3rem));
  max-height: calc(100% - 6rem);
  overflow-y: auto;
  color: var(--c-text);
  background: var(--c-panel);
  border: 1px solid var(--c-line);
  /* Nothing on the panel itself: it is a stack of ruled sections, and each one
     carries its own. */
  padding: 0;
}

.sheet::backdrop {
  /* The void, not black: on a coated tube the ground behind the panel is that
     tube's ground. */
  background: color-mix(in srgb, var(--c-void) 72%, transparent);
}

/* The panel's own bezel row, ruled off from what it holds and staying put if
   the panel ever holds more than one screen of settings. */
.sheet .card-head {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: 0.75rem 1.25rem;
  background: var(--c-panel);
  border-bottom: 1px solid var(--c-line);
}

/* The title names the panel rather than labelling a value, so it takes the
   wordmark's spacing and the text colour, not a caption's. */
.sheet .card-label {
  letter-spacing: 0.22em;
  color: var(--c-text);
}

.group {
  padding: 0.75rem 1.25rem;
}

.group + .group {
  border-top: 1px solid var(--c-line);
}

/* A caption trailed by a rule, the series' section break. */
.group-head {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding-bottom: 0.25rem;
}

.group-head span {
  font-size: 10px;
  line-height: 14px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--c-dim);
  flex-shrink: 0;
}

.group-head::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--c-line);
}

/* The name of a setting, and its control: caps label on the left, the choices
   right-aligned to the edge every other row in the panel ends at. The shape
   every instrument in the set presents a setting in. */
.set {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.375rem 0;
}

.set > span {
  flex-shrink: 0;
}

/* Wrapping rather than overflowing: four choices beside their own name do not
   always fit, and a row running off the edge of the panel is worse than a
   second line. */
.set .choices {
  justify-content: flex-end;
  margin-top: 0;
}

/* Bare labels, not boxes. Every control in the set is a small caps word that
   lights when you point at it, and the panel is not the place the set starts
   drawing buttons with edges. */
.choices {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin-top: 0.25rem;
}

/* The coatings are a second question about the same setting, so they are a
   second row inside the group rather than a group of their own. */
.choices + .choices {
  margin-top: 0.75rem;
}

/* The one that is on is simply lit, which is how a held control is marked
   everywhere else on the page. The bloom is the medium's own `.glow`, put on
   by the script that sets the state. */
.choices .ctl[aria-pressed="true"] {
  color: var(--c-text);
}

.note {
  max-width: 46ch;
  font-size: 11px;
  line-height: 20px;
  color: var(--c-dim);
  margin-top: 0.5rem;
}

.note b {
  font-weight: 400;
  color: var(--c-text);
}

/* The answer itself. Large, because it is what was asked for. */
.result {
  font-size: clamp(2.5rem, 7vw, 4rem);
  font-weight: 600;
  line-height: 1.1;
  color: var(--c-strike);
  margin: 0.75rem 0 0;
  word-break: break-word;
}

/* A uuid is read across, not admired. */
.result.mono {
  font-size: clamp(0.95rem, 2.4vw, 1.5rem);
  font-weight: 400;
  letter-spacing: 0.02em;
}

/* The monkey's paper. The gibberish recedes so the words it stumbled into are
   the reading, and the sheet scrolls rather than growing without end: a run
   left going for an hour is several thousand keys long. */
.result.typing {
  color: var(--c-dim);
  max-height: 40vh;
  overflow-y: auto;
  word-break: break-all;
  line-height: 1.5;
}

.result.typing b {
  color: var(--c-strike);
  font-weight: 600;
}

/* Fifty-two cards want to be countable at a glance. */
.deck {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(3.25rem, 1fr));
  gap: 0.25rem;
  margin: 0.75rem 0 0;
  padding: 0;
  list-style: none;
}

.deck li {
  border: 1px solid var(--c-line);
  padding: 0.4rem 0;
  text-align: center;
  color: var(--c-text);
  font-size: 14px;
}

/* The first card off the top is the one people care about. */
.deck li:first-child {
  color: var(--c-strike);
  border-color: var(--c-dim);
}

/* Bounded by height, not width. The plate is portrait, and at full width it
   pushed "save the plate" below the fold, which hid the one action the draw
   exists to offer. */
.plate {
  display: block;
  max-height: 54vh;
  max-width: 100%;
  width: auto;
  height: auto;
  margin: 0.75rem auto 0;
  border: 1px solid var(--c-line);
}

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
  margin-top: 0.875rem;
}

.prov {
  color: var(--c-dim);
  margin: 0.875rem 0 0;
}

/* While the shutter is open the reading leads the card rather than footnoting
   it, so it takes the top margin off and the emphasis of a live value. */
.prov.open {
  margin: 0.75rem 0 0;
  color: var(--c-text);
}

/* ── Waiting ──────────────────────────────────────────────────────────────────
   The wait is not dead time, it is the instrument doing the one thing that
   makes its output worth anything, so it is reported rather than hidden behind
   a spinner. */
.waiting {
  color: var(--c-text);
  margin: 0.75rem 0 0;
  font-size: 1.25rem;
}

.waiting small {
  display: block;
  font-size: 12px;
  color: var(--c-dim);
  margin-top: 0.375rem;
  letter-spacing: 0.08em;
}

.track {
  height: 2px;
  background: var(--c-line);
  margin-top: 0.875rem;
  overflow: hidden;
}

.track span {
  display: block;
  height: 100%;
  width: 0;
  background: var(--c-text);
  transition: width 0.3s linear;
}

/* Without a known target there is nothing honest to fill, so it paces instead
   of pretending to measure. */
.track[data-indeterminate] span {
  width: 30%;
  animation: pace 1.6s ease-in-out infinite;
}

@keyframes pace {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(333%); }
}

@media (prefers-reduced-motion: reduce) {
  .track[data-indeterminate] span {
    animation: none;
    width: 100%;
    opacity: 0.35;
  }
}

/* ── Test badges ──────────────────────────────────────────────────────────────
   Four rows, and each is a small chart rather than a label with a colour on it.
   Name, the trace and the threshold it is running against, the verdict in a
   word, the p-value. */
.test {
  display: grid;
  grid-template-columns: 1fr auto auto auto;
  align-items: center;
  gap: 0 0.625rem;
  padding: 0.3rem 0;
}

.test .name {
  color: var(--c-text);
}

/* The verdict in a word as well as in a colour. Colour alone puts the one
   reading this panel exists to deliver out of reach of anyone who cannot see
   the amber, and it is the reading, not decoration. */
.verdict {
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--c-dim);
  /* Wide enough for the longest of them. Sized to "ok" it was the failing rows,
     the only ones anyone urgently needs to read, that collided with the figure
     beside them. */
  width: 4ch;
  text-align: right;
}

.test .p {
  color: var(--c-land);
  font-size: 11px;
  width: 5ch;
  text-align: right;
}

.spark {
  display: block;
  width: 96px;
  height: 20px;
}

/* The surrogate keeps the column and draws nothing in it, so its rows stay
   aligned with the live ones they are there to be compared against. */
span.spark {
  width: 96px;
  height: 20px;
}

.spark polyline,
.spark circle {
  fill: none;
  stroke: var(--c-line);
  stroke-width: 1;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.spark circle {
  fill: var(--c-line);
}

/* The threshold. Everything above it passes, and it is the only reason the
   trace above it means anything, so it is drawn on every row at the same
   height whatever alpha that row is actually held to. */
.floor {
  stroke: var(--c-line);
  stroke-width: 1;
  stroke-dasharray: 2 3;
  vector-effect: non-scaling-stroke;
}

.test[data-verdict="pass"] .spark polyline {
  stroke: var(--c-dim);
}

.test[data-verdict="pass"] .spark circle {
  stroke: var(--c-dim);
  fill: var(--c-dim);
}

.test[data-verdict="pass"] .verdict {
  color: var(--c-dim);
}

/* Colour appears here and nowhere else on the page. */
.test[data-verdict="fail"] .name,
.test[data-verdict="fail"] .verdict,
.test[data-verdict="fail"] .p {
  color: var(--c-fail);
}

.test[data-verdict="fail"] .spark polyline,
.test[data-verdict="fail"] .spark circle {
  stroke: var(--c-fail);
}

.test[data-verdict="fail"] .spark circle {
  fill: var(--c-fail);
}

/* The failing side of the threshold, marked on the row that is in it: a trace
   that has crossed under should not need the reader to find the dashes. */
.test[data-verdict="fail"] .floor {
  stroke: var(--c-fail);
}

.test[data-verdict="waiting"] .name,
.test[data-verdict="waiting"] .verdict {
  color: var(--c-land);
}

/* The surrogate: a disclosure, not a control, so it reads as text rather than
   competing with the draw buttons. */
.link {
  display: block;
  margin-top: 0.5rem;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--c-dim);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.link:hover {
  color: var(--c-text);
}

#surrogate {
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--c-line);
}

#surrogate[hidden] {
  display: none;
}

.note {
  color: var(--c-dim);
  line-height: 1.6;
  margin-bottom: 0.5rem;
}

/* ── The raw stream ───────────────────────────────────────────────────────── */
/* ── Placeholders ─────────────────────────────────────────────────────────────
   Every readout that fills from the sky holds the height it will settle at, so
   the column is laid out once rather than shoved down its own length as each
   block finds its size. The figures are the settled ones: 40 bytes of raw and
   32 conditioned both wrap to three lines in this column, and the derivation
   holds seven rows. */
.stream {
  color: var(--c-dim);
  word-break: break-all;
  line-height: 1.6;
  min-height: 4.8em;
}

/* Reserved space with nothing in it reads as a broken panel, and this block
   holds its height open for the hex that has not arrived. The history needs no
   such line: its own heading already counts what it is holding. */
.stream:empty::before {
  content: "nothing yet";
  color: var(--c-land);
}

.stream b {
  color: var(--c-strike);
  font-weight: 400;
}

/* A heading inside a block, for a stage of the thing the block is about rather
   than a thing of its own. Set at the land grey the block heading sits above:
   at the same weight all three stages of the stream read as three blocks that
   had lost their rules, which is the arrangement this replaced. */
.label.sub {
  margin-top: 0.875rem;
  color: var(--c-land);
}

/* ── Derivation ───────────────────────────────────────────────────────────────
   A fix and the byte taken off it, four columns wide. Tabular figures and fixed
   columns so the decimal points line up down the list: these rows exist to be
   compared with each other, and a column that reflows on every arrival is one
   nobody can read while it is moving. */
.derive {
  margin-top: 0.375rem;
  /* Seven rows at their own line-height. */
  min-height: 112px;
  list-style: none;
  font-size: 10px;
  line-height: 16px;
  font-variant-numeric: tabular-nums;
}

.derive li {
  display: grid;
  grid-template-columns: auto 1fr 1fr auto;
  /* The fix and the byte it became are the comparison this list exists for, so
     they get more air between them than the two halves of the fix do. */
  gap: 0 0.75rem;
  color: var(--c-dim);
}

.d-t {
  color: var(--c-land);
}

.d-pos {
  text-align: right;
}

/* Where it lands. The bytes are the product, so they carry the interface's ink
   while the fix they were taken off sits back a step. */
.d-b {
  color: var(--c-text);
  letter-spacing: 0.06em;
}

/* Thrown away by the spacing rule, and shown being thrown away. */
.derive .cut,
.derive .cut .d-b {
  color: var(--c-land);
}

.d-cut {
  grid-column: 2 / 4;
  text-align: right;
}

/* The newest row arrives at full white and settles, the same decay every other
   arrival on this page follows. */
.derive .settle .d-b {
  animation: settle 1.4s ease-out;
}

/* ── History ──────────────────────────────────────────────────────────────────
   Each row is what was asked, what came back and what it cost in strikes. The
   whole row is the control: these are the only things on the page you can go
   back to, so none of them should need aiming at. */
.log {
  margin-top: 0.5rem;
  list-style: none;
}

.log button {
  display: grid;
  /* The value takes the slack and truncates into it: a uuid must not be allowed
     to push the strike count off the column. */
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 0.75rem;
  width: 100%;
  padding: 0.25rem 0;
  border: 0;
  background: none;
  font: inherit;
  font-variant-numeric: tabular-nums;
  text-align: left;
  color: var(--c-dim);
  cursor: pointer;
  transition: color 120ms linear;
}

.log button:hover .log-value,
.log button:hover .log-what {
  color: var(--c-text);
}

.log-value {
  color: var(--c-text);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  text-align: right;
}

/* The cost is the honest part of a draw and the least interesting to read, so
   it sits at the threshold rather than in the row's voice. */
.log-cost {
  color: var(--c-land);
}

.credits {
  margin-top: auto;
  padding-top: var(--gap);
  border-top: 1px solid var(--c-line);
  color: var(--c-dim);
}

/* The medium's reset zeroes every margin, so the two notes are spaced here. */
.credits p + p {
  margin-top: 0.75rem;
}

.credits a {
  color: var(--c-dim);
}

.credits a:hover {
  color: var(--c-text);
}

/* ── Narrow ───────────────────────────────────────────────────────────────── */
@media (max-width: 60rem) {
  /* The column stops being a scroller of its own and the document takes over,
     which is the only arrangement where a phone's address bar behaves. */
  html,
  body {
    height: auto;
    min-height: 100%;
  }

  .panel {
    flex: none;
    grid-template-columns: 1fr;
    grid-template-rows: auto auto;
  }

  /* The bar floats over the walk on a wide screen, where the stage has room to
     spare either side of it. On a phone it wraps to four rows and covers the
     instrument it is controlling, so it comes off the glass and sits under it:
     the walk keeps its own height and the controls get theirs, whatever the
     screen turns out to be. */
  .stage {
    display: flex;
    flex-direction: column;
    /* A grid item is min-content wide by default, and the strip below refuses
       to wrap, so without this the ten choices push the column wider than the
       phone and the whole page scrolls sideways. */
    min-width: 0;
    /* One screen, less the two bezels: the instrument and the controls that
       drive it are the first thing, and the column starts below the fold.
       dvh, so a phone retracting its address bar does not resize the walk. */
    height: calc(100dvh - 72px);
  }

  #walk {
    /* Whatever the bar leaves, rather than a share fixed in advance. The bar is
       four rows on a phone and two on a tablet, and guessing at that cost the
       walk more room than the guess was worth. */
    flex: 1;
    height: auto;
    min-height: 8rem;
  }

  /* Its own padding, not the column's: the gap is set for blocks of text a
     thumb's width apart, and spent above and below the draws it is most of a
     row of buttons. */
  .bar {
    position: static;
    background: none;
    border-top: 1px solid var(--c-line);
    padding: 0.75rem var(--gap);
    gap: 0.5rem;
    /* One column: the strip of choices, and under it the settings row for
       whichever choice wants one. Nearest the thumb, which is where the last
       thing you touch before the draw should be. */
    flex-direction: column;
    align-items: stretch;
    flex-wrap: nowrap;
  }

  /* The strip. Wrapped, ten choices are four rows of buttons standing on the
     walk; scrolled, they are one row and the walk keeps the height. The ends
     fade rather than being cut, which is the only thing that says there is
     more of it either side without a control saying so. */
  .bar-draws {
    flex-wrap: nowrap;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    /* A sideways swipe on the strip is a swipe on the strip, not the browser's
       back gesture. */
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    margin-inline: calc(var(--gap) * -1);
    padding-inline: var(--gap);
    scroll-padding-inline: var(--gap);
    mask-image: linear-gradient(90deg, transparent, #000 var(--gap), #000 calc(100% - var(--gap)), transparent);
  }

  .bar-draws::-webkit-scrollbar {
    display: none;
  }

  .bar-draws button {
    flex: none;
    scroll-snap-align: center;
  }

  .opener {
    display: block;
  }

  /* A chip whose row is open is holding it open, so it is marked the way a held
     control is marked rather than left looking like the nine beside it. */
  .opener[aria-expanded="true"] {
    color: var(--c-strike);
    border-color: var(--c-dim);
  }

  .bar-forms {
    display: block;
  }

  .bar-forms form {
    display: none;
  }

  .bar[data-open="expose"] #expose-form,
  .bar[data-open="monkey"] #monkey-form,
  .bar[data-open="range"] #range-form {
    display: flex;
  }

  /* The row is the width of the screen and the select carries the longest text
     on the page, so it takes what the label and the button leave. */
  .bar-range select {
    flex: 1;
    min-width: 0;
  }

  .bar-range input {
    flex: 1;
    width: auto;
    min-width: 0;
  }

  .readouts {
    overflow-y: visible;
    border-left: 0;
    border-top: 1px solid var(--c-line);
  }

  /* Both bezels stay on the glass while the document scrolls under them. A
     status line you have to scroll to the end of the page to read is a status
     line reporting to nobody. */
  .head,
  .status {
    position: sticky;
    z-index: 10;
    background: var(--c-void);
  }

  .head {
    top: 0;
  }

  .status {
    bottom: 0;
  }

  /* First to go: the strapline says what the lede below it says again. The
     controls never give way. */
  .slug {
    display: none;
  }

  /* The card is a child of the stage, and the stage on a phone is half a screen
     of walk, so a fifty-two card deck opened into a box that could not hold it.
     Off the stage and onto the glass: between the two bezels, which are sticky
     here and so are always exactly there. The answer gets the whole screen,
     which is the one dimension a phone is not short of. */
  .card {
    position: fixed;
    /* Hung from the top bezel rather than centred, and sized to what it holds:
       a deck fills the glass down to the status line, a card still waiting for
       the sky stays the three lines it has to say. dvh, not vh, so the address
       bar sliding away does not leave the bottom of a deck under it. */
    inset: 44px 0 auto;
    max-height: calc(100dvh - 72px);
    z-index: 9;
    transform: none;
    width: auto;
    border-width: 1px 0;
  }

  /* Bounded by the sheet now rather than by the tube, and the sheet is already
     scrolling: a plate held to half the viewport inside it left the actions
     below stranded in empty space. */
  .plate {
    max-height: none;
  }
}

@media (max-width: 30rem) {
  /* First to go, in this order: the node is diagnostic, and the credit is
     repeated in full at the foot of the column. What is left is the clock and
     the commentary, which is the line's reason for existing: at this width the
     three of them shared the row and the message truncated to "receiving …". */
  .node,
  .source {
    display: none;
  }
}

/* ── Touch ────────────────────────────────────────────────────────────────────
   Same instrument, thumbs. A 33px control is a fine mouse target and a poor
   thumb one, and the draw buttons are the whole point of the page, so they are
   sized to the 44px everyone converged on. The bezel controls and the column's
   text links take it as padding rather than as min-height, so the target grows
   without the furniture around it moving. */
@media (pointer: coarse) {
  .bar button,
  .bar-range input,
  .bar-range select {
    min-height: 44px;
  }

  /* Exactly the bezel's own height, so this fills the row without changing it.
     Typed as the two elements rather than as `.ctl`, because the rule that
     zeroes their padding is. */
  button.ctl,
  a.ctl {
    padding-top: 13px;
    padding-bottom: 13px;
  }

  /* The hit area grows, the layout does not: the dismiss is on a baseline with
     the card's label and must stay on it. */
  .card-head button {
    padding: 12px;
    margin: -12px;
  }

  .link,
  .log button {
    padding-top: 13px;
    padding-bottom: 13px;
  }
}
