/*
 * One page, one stylesheet.
 *
 * Sizes, dims and gaps are three scales: pick a step, never write a fresh
 * number. The one rule that carries meaning is the face -- a machine-produced
 * fact is set in the mono one, and nearly everything on this page is a
 * machine-produced fact.
 *
 * This file is downloaded by whoever opens the page, comments included.
 */

:root {
  --ink: #17191c;
  --ink-quiet: #4a4f57;
  --ink-faint: #767c86;
  --page: #f7f8fa;
  --card: #ffffff;
  --edge: #dfe3e8;
  --accent: #1a5fb4;
  --bad: #b3261e;

  /* One colour per kind of byte, and the legend is generated from this list
     rather than written twice. Chosen to stay apart for the commonest colour
     blindness: blue/amber/red rather than red/green. */
  --link: #cfe0f5;
  --link-ink: #10396e;
  --header: #ded3f2;
  --header-ink: #4a2f86;
  --record: #cceadd;
  --record-ink: #14563d;
  --record-alt: #e6f4ee;
  --filler: #e6e8ec;
  --filler-ink: #5c626b;
  --encrypted: #fbe3c0;
  --encrypted-ink: #7a4c05;
  --trailer: #dde3ea;
  --trailer-ink: #3c4855;
  --unclaimed: #f7cfcc;
  --unclaimed-ink: #8c1d17;

  --text-caption: 0.75rem;
  --text-small: 0.85rem;
  --text-body: 0.95rem;
  --text-lead: 1.1rem;
  --text-title: 1.5rem;

  /* One height for everything on the form's bottom row. The fields reach it
     through their padding anyway; saying it once is what stops the button
     drifting from them again, as it had at 38px against their 34. */
  --control: 34px;

  --gap-xs: 4px;
  --gap-s: 8px;
  --gap-m: 12px;
  --gap-l: 20px;
  --gap-xl: 32px;

  --font-mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  --font: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
}

@media screen and (prefers-color-scheme: dark) {
  :root {
    --ink: #e6e8ec;
    --ink-quiet: #b0b6bf;
    --ink-faint: #858c96;
    --page: #14161a;
    --card: #1c1f24;
    --edge: #2e333a;
    --accent: #78aeff;
    --bad: #ff8a80;

    --link: #1d3a5f;
    --link-ink: #bcd8ff;
    --header: #3a2f5c;
    --header-ink: #d8c9ff;
    --record: #1c4a38;
    --record-ink: #b6e8d2;
    --record-alt: #16332a;
    --filler: #2b2f36;
    --filler-ink: #a0a6b0;
    --encrypted: #5a3f10;
    --encrypted-ink: #ffd79a;
    --trailer: #2a323b;
    --trailer-ink: #c2cbd6;
    --unclaimed: #5c1f1b;
    --unclaimed-ink: #ffc4bf;
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  padding: var(--gap-l);
  background: var(--page);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--text-body);
  line-height: 1.5;
}

/* Wide, because a telegram is two tables and a map of its bytes side by side,
   and 1100px gave each of them a third of a screen. The map alone wants 16
   bytes to a row without scrolling. */
main, .masthead { max-width: 1600px; margin: 0 auto; }

/* **Indented to where the cards' text starts, not to where their edges do.**
   Everything below the title sits inside a box with a border and a gap of its
   own, so a title flush with the page's own padding began one gap and one
   border to the left of every other word on the page, and read as misaligned
   -- which it was. `border-box` keeps the width the same as `main`'s. */
.masthead { padding: 0 calc(var(--gap-l) + 1px); }

.masthead h1 { font-size: var(--text-title); margin: 0 0 var(--gap-xs); font-weight: 600; }
.lede { margin: 0 0 var(--gap-l); color: var(--ink-quiet); max-width: 70ch; }

/* ---------------------------------------------------------------- the form */

.panel {
  background: var(--card);
  border: 1px solid var(--edge);
  border-radius: 8px;
  padding: var(--gap-l);
  margin-bottom: var(--gap-l);
}

/**
 * `hidden` beats a `display` rule, everywhere.
 *
 * The attribute is only a `display: none` in the browser's own sheet, so any
 * rule here that gives an element a `display` quietly cancels it -- which is
 * what `.field` did to the link box: it was marked hidden and drawn anyway.
 * Said once, with the one `!important` on the page, because the alternative is
 * remembering it at every rule that sets a `display`.
 */
[hidden] { display: none !important; }

.field { display: block; margin-bottom: var(--gap-m); }
.label { display: block; font-size: var(--text-small); color: var(--ink-quiet); margin-bottom: var(--gap-xs); }
.hint { color: var(--ink-faint); font-weight: 400; }

textarea, input[type=text], select {
  width: 100%;
  padding: var(--gap-s);
  border: 1px solid var(--edge);
  border-radius: 6px;
  background: var(--page);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: var(--text-small);
}
/**
 * `display: block`, and it is not decoration.
 *
 * A textarea is inline by default, so the block that holds it reserves a line
 * box under it for descenders -- **5.8px measured here** -- and the box then
 * ends above the bottom of its own label. Nothing showed that while the only
 * textarea on the page sat alone; the key box sits on a row beside a select and
 * two buttons aligned to the bottom, and it was the one control 5.8px high.
 * Reported from a screenshot on 2026-09-03.
 *
 * A `select` in the same place has no such gap, which is why the row was right
 * before this box arrived.
 */
textarea { resize: vertical; display: block; }

/**
 * The notation picker, made to look like the field beside it.
 *
 * The rule above already gives it the border, the background and the padding --
 * and a `select` throws all three away unless `appearance` is turned off,
 * rendering the operating system's own control instead. So it sat beside the
 * key field looking like something from a different page.
 *
 * The chevron is drawn here because turning `appearance` off takes the native
 * one with it. Two copies of it, one per theme: it is a background image, and a
 * background image cannot pick up `currentColor`.
 */
select {
  font-family: var(--font);
  appearance: none;
  -webkit-appearance: none;
  /* Room for the chevron, so a long option cannot run underneath it. */
  padding-right: var(--gap-xl);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%234a4f57' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--gap-s) center;
  background-size: 10px 6px;
}
@media screen and (prefers-color-scheme: dark) {
  select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23b0b6bf' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  }
}

/* Turning `appearance` off can take the focus ring with it, and a control you
   cannot see the focus on is one a keyboard cannot be driven round. Said once
   for all three, so they cannot come to differ. */
textarea:focus-visible, input[type=text]:focus-visible, select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}

/* **Links, quietly.** The only ones here are the index's, and the browser's own
   blue-then-purple was the loudest thing on a page of grey and hex. Accent, no
   rule under it until it is pointed at, and no `visited` colour at all: which
   telegram somebody read first is not a fact about the paste, and half an index
   in purple looked like it meant something. */
a {
  color: var(--accent);
  text-decoration: none;
  border-radius: 3px;
}
a:hover { text-decoration: underline; text-underline-offset: 3px; }
a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

input[type=text], select, button { height: var(--control); }

/* The key box is a textarea so that a pasted key file keeps its line breaks,
   and it sits on the form's bottom row, so it has to be one control high and
   not wrap. It can still be dragged taller to read a long list. */
textarea.one-line {
  height: var(--control);
  min-height: var(--control);
  white-space: pre;
  overflow-x: auto;
}

.row { display: flex; gap: var(--gap-m); align-items: flex-end; flex-wrap: wrap; }
.row .field { margin-bottom: 0; }
.narrow { width: 160px; }
.grow { flex: 1 1 260px; }

button {
  padding: var(--gap-s) var(--gap-l);
  border: 0;
  border-radius: 6px;
  background: var(--accent);
  color: #fff;
  font-size: var(--text-body);
  font-weight: 500;
  cursor: pointer;
}
button:hover { filter: brightness(1.08); }
button[disabled] { opacity: 0.6; cursor: default; }

/**
 * The second button, made quieter than the first.
 *
 * Two solid accent buttons side by side ask the reader which one the form is
 * for, every time. This one keeps the shape and the height and gives up the
 * fill, so Decode stays the obvious thing to press.
 *
 * `filter: brightness()` on a transparent background does nothing, so the
 * hover has to be a real colour rather than the rule above.
 */
button.quiet {
  background: transparent;
  color: var(--accent);
  border: 1px solid var(--edge);
}
button.quiet:hover { filter: none; border-color: var(--accent); background: var(--card); }
button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.privacy { margin: var(--gap-m) 0 0; font-size: var(--text-caption); color: var(--ink-faint); }
.shared { margin: var(--gap-m) 0 0; font-size: var(--text-caption); color: var(--ink-quiet); }

.refusal {
  background: var(--card);
  border: 1px solid var(--bad);
  border-left-width: 4px;
  border-radius: 6px;
  padding: var(--gap-m) var(--gap-l);
  margin-bottom: var(--gap-l);
  color: var(--bad);
}

/* ------------------------------------------------------------- the results */

.summary {
  display: flex;
  gap: var(--gap-l);
  flex-wrap: wrap;
  margin-bottom: var(--gap-l);
  padding: var(--gap-m) var(--gap-l);
  background: var(--card);
  border: 1px solid var(--edge);
  border-radius: 8px;
}
/* The two buttons that write a file, pushed to the right of the row the facts
   are on. `center` against the row's own stretch: a button stretched to the
   height of a two-line fact is a button the size of the box it sits in. */
.exports {
  margin-left: auto;
  display: flex;
  gap: var(--gap-s);
  align-items: center;
}

.fact { display: flex; flex-direction: column; }
.fact .name { font-size: var(--text-caption); color: var(--ink-faint); text-transform: uppercase; letter-spacing: 0.04em; }
.fact .value { font-family: var(--font-mono); font-size: var(--text-lead); }

/* The survey's numbers: the summary row's shape, without its box -- it is
   inside a card already, and a box inside a box reads as two things. */
.counts {
  display: flex;
  gap: var(--gap-l);
  flex-wrap: wrap;
  margin-bottom: var(--gap-m);
}

/* A telegram drawn closed, which is what a capture of hundreds is drawn as.
   The line carries what the index line carries, so the two agree. */
details.folded { margin-bottom: var(--gap-m); }
details.folded > summary {
  cursor: pointer;
  padding: var(--gap-s) var(--gap-l);
  background: var(--card);
  border: 1px solid var(--edge);
  border-radius: 8px;
  font-size: var(--text-small);
  color: var(--ink-quiet);
}
details.folded > summary::marker { color: var(--ink-faint); }
details.folded[open] > summary { margin-bottom: var(--gap-m); }

.card {
  background: var(--card);
  border: 1px solid var(--edge);
  border-radius: 8px;
  padding: var(--gap-l);
  margin-bottom: var(--gap-l);
}
/* Descendant, not child. Two of these headings are built inside a wrapper div
   -- the addresses and the status byte -- so `.card > h3` missed them and the
   browser's own h3 showed through: 17.8px and near-black beside 15.2px and
   grey, which read as a deliberate hierarchy and was not one. Measured. */
.card h2, .telegram-head h2 { font-size: var(--text-lead); margin: 0 0 var(--gap-xs); font-weight: 600; }


.card h3 { font-size: var(--text-body); margin: var(--gap-l) 0 var(--gap-s); font-weight: 600; color: var(--ink-quiet); }
.subtitle { margin: 0 0 var(--gap-m); color: var(--ink-quiet); font-size: var(--text-small); }
.subtitle code { font-family: var(--font-mono); }

.problem { color: var(--bad); font-size: var(--text-small); margin: 0 0 var(--gap-m); }

/* A telegram waiting for a key, which is not a fault: it is intact and nothing
   was given to open it with. Drawn in the accent rather than in `--bad`, so the
   red on this page keeps meaning "something is wrong with these bytes". */
.asking { color: var(--accent); font-size: var(--text-small); margin: 0 0 var(--gap-m); }

/* What the decoder itself said, under the sentence written for a reader. Kept
   small and grey: it is evidence rather than a message. */
.aside {
  color: var(--ink-faint);
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  margin: calc(0px - var(--gap-s)) 0 var(--gap-m);
  white-space: pre-wrap;
}

/* ------------------------------------------------------------ the byte grid
 *
 * The picture. One cell per byte, sixteen to a row, each carrying the colour of
 * the field that claimed it -- and every byte is claimed by exactly one, which
 * is a property of the report rather than of this stylesheet.
 */

.region { margin-bottom: var(--gap-l); }
.region-name { font-size: var(--text-small); color: var(--ink-quiet); margin-bottom: var(--gap-s); }
.region-name .decrypted { color: var(--encrypted-ink); }

.grid {
  display: grid;
  grid-template-columns: auto repeat(16, 1fr);
  gap: 2px;
  font-family: var(--font-mono);
  font-size: var(--text-small);
  max-width: 720px;
}
.offset {
  color: var(--ink-faint);
  padding-right: var(--gap-s);
  text-align: right;
  font-size: var(--text-caption);
  align-self: center;
}
.byte {
  padding: 3px 0;
  text-align: center;
  border-radius: 3px;
  cursor: default;
  border: 1px solid transparent;
}
.byte.link      { background: var(--link);      color: var(--link-ink); }
.byte.header    { background: var(--header);    color: var(--header-ink); }
.byte.record    { background: var(--record);    color: var(--record-ink); }
.byte.data      { background: var(--record-alt); color: var(--record-ink); }
.byte.filler    { background: var(--filler);    color: var(--filler-ink); }
.byte.encrypted { background: var(--encrypted); color: var(--encrypted-ink); }
.byte.trailer   { background: var(--trailer);   color: var(--trailer-ink); }
.byte.unclaimed { background: var(--unclaimed); color: var(--unclaimed-ink); }
.byte.lit { border-color: currentColor; font-weight: 700; }

.legend { display: flex; flex-wrap: wrap; gap: var(--gap-m); margin: var(--gap-m) 0; font-size: var(--text-caption); }
.legend span { display: inline-flex; align-items: center; gap: var(--gap-xs); color: var(--ink-quiet); }
.swatch { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }

/* ---------------------------------------------------------------- the lists */

table { width: 100%; border-collapse: collapse; font-size: var(--text-small); }
th {
  text-align: left;
  font-weight: 600;
  color: var(--ink-quiet);
  font-size: var(--text-caption);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border-bottom: 1px solid var(--edge);
  padding: var(--gap-xs) var(--gap-s) var(--gap-xs) 0;
}
td { padding: var(--gap-xs) var(--gap-s) var(--gap-xs) 0; border-bottom: 1px solid var(--edge); vertical-align: top; }
tr.selectable { cursor: default; }
tr.selectable:hover { background: color-mix(in srgb, var(--accent) 8%, transparent); }
.mono { font-family: var(--font-mono); }
/* A column holding a fact is as wide as the fact, so the sentence beside it
   gets the rest. Only the short ones are pinned: an address is a whole locator
   on one line and has to be free to wrap, or every table on the page grows
   wider than the page. */
td.mono { white-space: nowrap; }
td.mono.faint { white-space: normal; }
/* For a run of text inside a cell rather than the cell itself: a date, which
   reads as two things when it is broken across two lines. */
.nowrap { white-space: nowrap; }

/* A cell of hex that lights its own bytes in the picture above when pointed at.
   Underlined rather than coloured: the colour in this table already means which
   kind of byte it is, and a second meaning on the same channel would be one
   meaning too many. */
/* The page's own tooltip, shown the moment something is pointed at rather than
   after whatever delay a browser gives a `title`. Fixed to the viewport and
   hung off body, since every table here sits in a scroll box that would clip
   it. `pointer-events: none` so it can never sit between the pointer and the
   thing being pointed at. */
.tip {
  position: fixed;
  z-index: 10;
  max-width: 46ch;
  padding: var(--gap-xs) var(--gap-s);
  border-radius: 6px;
  background: var(--ink);
  color: var(--page);
  font-size: var(--text-caption);
  line-height: 1.45;
  pointer-events: none;
  box-shadow: 0 2px 10px rgb(0 0 0 / 0.25);
}
.tip[hidden] { display: none; }

/* Anything that has one says so, quietly. */
[data-tip] { cursor: help; }

td.points { text-decoration: underline dotted var(--ink-faint); text-underline-offset: 3px; }
td.points:hover { text-decoration-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, transparent); }
.quiet { color: var(--ink-quiet); }
.faint { color: var(--ink-faint); font-size: var(--text-caption); }
.scroll { overflow-x: auto; }

/* The kind of a record used to be a coloured chip in the value column --
   `reading`, `time`, `register`, `other`. The quantity column says the same
   thing better now that it carries the name a locator is written in: `Date` is
   a date and `ErrorFlags` is the alarms register, so the chip repeated the
   neighbouring cell in a second colour. */

.bits { display: flex; flex-wrap: wrap; gap: var(--gap-s); margin: 0 0 var(--gap-m); padding: 0; list-style: none; }
.bit {
  border: 1px solid var(--edge);
  border-radius: 6px;
  padding: var(--gap-xs) var(--gap-s);
  font-size: var(--text-caption);
  color: var(--ink-faint);
}
.bit.set { border-color: var(--bad); color: var(--bad); font-weight: 600; }
.bit .mask { font-family: var(--font-mono); }

.notes { margin: 0; padding-left: var(--gap-l); color: var(--ink-quiet); font-size: var(--text-small); }

/* The model that fits one meter. The serial and the model name are an `h3`
   and the count below it a subtitle, which is the shape Readings already has
   -- so the two sections read as the same kind of thing rather than as two
   inventions. Nothing here draws a border of its own: the headings separate
   the meters, as they do up there. */
.model > .faint.mono { margin-bottom: var(--gap-xs); }

/* The address a model marks as the value its counter froze, rather than what
   it reads now. A badge and not a column: one row in a table of four carries
   it, and a column would be empty on the rest. The class is named for the
   concept and the badge reads `reset`, which is the word every other screen
   uses for the event. */
.reset-value {
  margin-left: var(--gap-s);
  font-family: var(--font-sans);
  font-size: var(--text-caption);
  padding: 1px 6px;
  border-radius: 10px;
  background: var(--filler);
  color: var(--filler-ink);
}
/* Beside the value it refuses, not under it: `.problem` is written for a
   paragraph and carries a bottom margin that would push the row apart. */
td .problem { margin: 0 0 0 var(--gap-s); }

/* A timestamp is one word. Left to wrap it breaks mid-value -- `2026-08-` over
   `31T07:29` -- which reads as two things. The table already scrolls sideways
   when it has to, so there is somewhere for the width to go.

   **Three classes because it has to beat `td.mono.faint`**, which sets
   `white-space: normal` so that the address beside it can wrap. That rule is
   (0,2,1) and a plain `td.when` is (0,1,1), so the shorter selector loaded and
   did nothing -- found by reading the computed style, not the file. */
td.mono.faint.when { white-space: nowrap; }

/* Sixteen bytes to a row at every width, because the offsets down the left are
   only useful if a row is always the same length. On a narrow screen the grid
   scrolls inside its own box rather than reflowing to eight, which would make
   the picture a different shape on a phone from the one in the manual. */
/* A way into a long paste: one line per telegram, no bullet, the model or what
   the frame says it is beside the link. */
/* Two columns, so eleven descriptions start at one place rather than stepping
   right when the ordinals reach two digits. `display: contents` on the row is
   what puts the link and its description into the grid: the columns are shared
   by every line only if they are the *list's*, not each item's. */
.index {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: max-content 1fr;
  column-gap: var(--gap-m);
  row-gap: 4px;
}
.index li { display: contents; }
/* The sections at the foot of the page, set apart from the telegrams above
   them: they are about the whole paste rather than about one meter. */
.index.whole { margin-top: var(--gap-m); }
.index a { font-weight: 600; }

/* Room above anything the index jumped to, so it does not sit flush against
   the top of the window. */
.telegram, .card { scroll-margin-top: var(--gap-m); }

/* **One telegram is one card here, and two cards where there is room.**
   Narrow, the card is the telegram itself and its three parts -- heading,
   picture, tables -- are plain boxes inside it. So the heading is inside a card
   rather than floating above one, and the picture is under the line saying how
   many bytes there are instead of several screens below it.

   The three are separate elements only so that the wide layout can put them in
   two columns; here they are simply stacked, in the order they are written. */
.telegram {
  background: var(--card);
  border: 1px solid var(--edge);
  border-radius: 8px;
  padding: var(--gap-l);
  margin-bottom: var(--gap-l);
}
.telegram > .card {
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0;
  margin-bottom: 0;
}
/* The gap between the picture and the tables under it, which the card each of
   them used to be gave them for free. */
.telegram > .telegram-figure { margin-bottom: var(--gap-l); }

/* The picture beside the tables rather than above them, **where there is room
   for both**.

   **1640px is the page at its full width**: 1600 of content and the body's own
   20 on each side. Below it `main` is narrower than 1600, so splitting would
   take the width out of the tables -- and the tables are what somebody reads.
   At 1280 the left column came out at 629px and "What it is" wrapped on nearly
   every row. So: two columns only where both can have their own width, and one
   column everywhere else.

   In one column the order is the DOM's -- heading, picture, tables -- so the
   picture is under the line that says how many bytes there are. The placement
   below is what moves it right, not `order`, so the reading order never
   changes.

   `sticky` inside the telegram's own block is the whole of "until the next
   telegram": a sticky element cannot leave its containing block, so it follows
   its own telegram down the page and stops where the pair ends.

   `align-self: start` and a `max-height`, because a sticky box stretched to the
   row's height has nothing to stick to, and a map taller than the screen would
   scroll its own bottom out of reach.

   **`screen and`, or a printed page comes out in two columns it has no room
   for.** Safari lays a page out for paper and still answers a width query with
   the window's width, so a telegram printed from a window wider than 1640px got
   this grid on a page 180mm across: the map took its 36rem, the column beside it
   was left about 60px, and every sentence in it came out one word to a line.
   Reported from a printed PDF on 2026-09-03 and reproduced by forcing this
   query on. A width is a fact about a window, so it is asked about a screen. */
@media screen and (min-width: 1640px) {
  /* The single card is taken apart into two, and the border it drew goes to
     the two halves below. */
  .telegram {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 36rem);
    column-gap: var(--gap-l);
    align-items: start;
    background: none;
    border: 0;
    padding: 0;
  }

  /* **The left card, drawn by the grid rather than by an element.** The heading
     and the tables are two boxes in one column, and a card has to be drawn
     around *both* -- so this is an empty grid item behind them, spanning the two
     rows. It is the first child, so everything else paints over it and no
     stacking order has to be declared.

     The alternative was a heading floating above the card, which is what this
     replaced. */
  .telegram::before {
    content: '';
    grid-column: 1;
    grid-row: 1 / 3;
    /* **`stretch`, against the row's `align-items: start`.** An empty box is
       0px tall unless something stretches it, and `start` is on the row so
       that the map card can stick. Measured at 0px, which drew the card as a
       hairline and left the tables sitting on the page. */
    align-self: stretch;
    background: var(--card);
    border: 1px solid var(--edge);
    border-radius: 8px;
  }
  /* The card's padding, split between the two boxes that sit on it -- and its
     border too, since they are placed on the grid rather than inside it, so
     `--gap-l` alone left every word 1px to the left of where the summary card
     above puts its own. Measured: 63 against 64.

     `.telegram > .telegram-main`, not `.telegram-main`: the rule stripping the
     cards above is `.telegram > .card`, which is the more specific of the two
     and wins inside a media query as much as outside one. The heading is not a
     `.card`, so it needs no such help -- and that is exactly why only the
     tables came out unpadded. */
  .telegram-head {
    grid-column: 1;
    grid-row: 1;
    padding: calc(var(--gap-l) + 1px) calc(var(--gap-l) + 1px) 0;
  }
  .telegram > .telegram-main {
    grid-column: 1;
    grid-row: 2;
    padding: 0 calc(var(--gap-l) + 1px) calc(var(--gap-l) + 1px);
    min-width: 0;
  }

  /* The right card is a card again, and starts level with the heading rather
     than below it. */
  .telegram > .telegram-figure {
    grid-column: 2;
    grid-row: 1 / 3;
    background: var(--card);
    border: 1px solid var(--edge);
    border-radius: 8px;
    padding: var(--gap-l);
    margin-bottom: 0;
    position: sticky;
    top: var(--gap-m);
    align-self: start;
    max-height: calc(100vh - 2 * var(--gap-m));
    overflow: auto;
  }
}

/* A phone, and `screen and` for the reason the block above gives: printed from
   a narrow window this held the byte map to a 460px minimum, which is wider
   than the paper leaves it. */
@media screen and (max-width: 640px) {
  body { padding: var(--gap-m); }
  .grid { font-size: var(--text-caption); min-width: 460px; }
}

/* A record whose address bytes are not on the wire: the two cells that name a
   DIF and a VIF are shown as what they mean rather than what was read. */
tr.inferred td:nth-child(2),
tr.inferred td:nth-child(3) {
  font-style: italic;
  opacity: 0.65;
}
