/* ──────────────────────────────────────────────────────────────────────────
   pueo.magikh0e.pl — tactical field interface

   One stylesheet, no framework, no build step. Self-hosted fonts so the page
   has no third-party requests at all: a tool for looking at what radios in
   the room are broadcasting should not itself phone anywhere.

   The palette is the device's. #ffa62b is the accent the firmware's own UI
   draws menu selections in (uiUniversalColor, preset 0); the greens are the
   "ok" states from its status bar. The ground is darker than the panel so
   panels read as lit surfaces without a gradient anywhere.
   ────────────────────────────────────────────────────────────────────────── */

@font-face {
  font-family: 'JetBrains Mono';
  src: url('../fonts/JetBrainsMono-latin.woff2') format('woff2');
  font-weight: 100 800;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F,
                 U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215;
}
@font-face {
  font-family: 'VT323';
  src: url('../fonts/VT323-latin.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+2000-206F, U+2122, U+2212;
}

:root {
  --ground:   #05080a;
  --rail:     #070d11;
  --panel:    #0a1116;
  --well:     #070d11;
  --line:     #22323c;
  --line-dim: #16232b;
  --ink:      #d7e0e6;
  --ink-hi:   #ffffff;
  --ink-dim:  #9fb3bf;
  --ink-mute: #4d6472;
  --amber:    #ffa62b;
  --amber-bg: #16100a;
  --amber-ln: #3a2f14;
  --green:    #7ce577;
  --red:      #ff4d4d;   /* Conf::Strong, the TFT_RED rows */
  --green-bg: #0b1710;
  --green-ln: #1f3a2a;

  --mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  --display: 'VT323', 'JetBrains Mono', monospace;

  --gutter: 40px;
  --maxw: 1200px;
}

/* The page is dark by design rather than by preference, so there is no light
   variant to switch to -- but honour a forced light scheme by keeping the
   contrast ratios rather than inverting into something unreadable. */
@media (prefers-contrast: more) {
  :root { --ink-dim: #cfdce4; --ink-mute: #8ba3b1; --line: #3a4f5c; }
}

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

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 14px;
  line-height: 1.7;
  -webkit-text-size-adjust: 100%;
}

a { color: var(--amber); text-underline-offset: 3px; }
a:hover, a:focus-visible { color: var(--ink-hi); }

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

h1, h2, h3 { margin: 0; font-weight: 500; }
p { margin: 0; }

.skip {
  position: absolute; left: -9999px; top: 0;
  background: var(--amber); color: #000; padding: 10px 16px; z-index: 10;
}
.skip:focus { left: 0; }

.wrap {
  max-width: var(--maxw);
  margin: 0 auto;
  padding-inline: var(--gutter);
}

/* ── top rail ───────────────────────────────────────────────────────────── */
.rail {
  border-bottom: 1px solid var(--line-dim);
  background: var(--rail);
}
.rail .wrap {
  display: flex; align-items: center; gap: 18px;
  padding-block: 14px;
  flex-wrap: wrap;
}
.rail img { width: 26px; height: 26px; opacity: 0.9; }
.rail .name {
  font-family: var(--display); font-size: 26px;
  letter-spacing: 0.1em; color: var(--ink-hi);
}
.rail .sub { font-size: 12px; color: var(--ink-mute); letter-spacing: 0.14em; }
.rail .chips { margin-left: auto; display: flex; gap: 10px; flex-wrap: wrap; }

.chip {
  font-size: 11px; letter-spacing: 0.1em;
  border: 1px solid var(--line); color: var(--ink-mute);
  padding: 5px 11px; white-space: nowrap;
}
.chip.ok    { border-color: var(--green-ln); background: var(--green-bg); color: var(--green); }
.chip.ver   { border-color: var(--amber-ln); background: var(--amber-bg); color: var(--amber); }

/* ── hero ───────────────────────────────────────────────────────────────── */
.hero {
  position: relative;
  isolation: isolate;          /* keep the sweep's blend inside the hero */
  border-bottom: 1px solid var(--line-dim);
  background-color: var(--rail);
  /* Scanlines, not a gradient wash: a 1px line every 3px at 2% white. */
  background-image: repeating-linear-gradient(
    to bottom,
    rgba(255, 255, 255, 0.022) 0, rgba(255, 255, 255, 0.022) 1px,
    transparent 1px, transparent 3px);
}

/* A real 300 MHz to 2.4 GHz sweep behind the top of every page: spectrum
   trace along the top, waterfall below it.

   Screen blend rather than a low-opacity overlay. The capture is near black
   by nature, so screening it over the surface beneath makes its background
   disappear and leaves only what the radio actually heard, which is the
   same reason the section banners on magikh0e.pl blend rather than sit in a
   box. Measured on the file: fifth-percentile luminance 1.5 of 255, peak
   62.6. That peak is why the opacity is this high. The layer is barely
   visible below about 0.7, and it cannot wash anything out, because the
   brightest pixel in it can only lift a surface a quarter of the way to
   white.

   Masked, not cropped. A hard edge would read as a photograph in a box. It
   fades downward into the content and inward from both sides, which matters
   at wide viewports where cover scales the capture up and its own edges
   would otherwise end in mid-air. */
.hero::before,
.doc::before {
  content: "";
  position: absolute;
  z-index: -1;                 /* above the surface, below the content */
  pointer-events: none;
  background-image: url("/assets/sweep.webp");
  background-size: cover;
  background-position: center top;
  background-repeat: no-repeat;
  mix-blend-mode: screen;
  opacity: 0.85;
  -webkit-mask-image:
    linear-gradient(to bottom, #000 0%, #000 38%, transparent 96%),
    linear-gradient(to right, transparent 0%, #000 14%,
                              #000 86%, transparent 100%);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to bottom, #000 0%, #000 38%, transparent 96%),
    linear-gradient(to right, transparent 0%, #000 14%,
                              #000 86%, transparent 100%);
  mask-composite: intersect;
}

/* The hero fills its own band, and isolate keeps the blend inside it: the
   hero has an opaque panel colour of its own to melt into. */
.hero::before { inset: 0; }

/* The subpages have no band. The sweep is a strip across the top instead,
   behind the page title and the jump list, gone by the time the prose
   starts. 300px rather than a percentage because these pages run from short
   to very long and a proportional strip would be a different thing on each
   one.

   Deliberately not isolated, unlike the hero. main.doc has no background,
   so an isolated blend would composite against transparency and paint the
   capture's black as a box a shade darker than the page. Left un-isolated,
   it reaches the body's --ground and melts into it. */
.doc::before {
  inset: 0 0 auto 0;
  height: 300px;
}

/* The hero is tall and narrow on a phone, and cover on a 16:9 capture in a
   tall box zooms until one waterfall column fills the screen: the spectrum
   trace, which is the part that says what the picture is, crops away
   entirely. Anchoring the width instead keeps the trace as a strip near the
   top and lets the waterfall run off the bottom, where the mask is already
   fading it out. The subpage strip is short and wide at every width, so it
   only needs the simpler mask. */
@media (max-width: 760px) {
  .hero::before {
    background-size: 150% auto;
    background-position: center top;
  }
  .hero::before,
  .doc::before {
    -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 30%,
                                        transparent 88%);
    mask-image: linear-gradient(to bottom, #000 0%, #000 30%,
                                transparent 88%);
  }
  .doc::before { height: 220px; }
}

/* The lede and the page titles sit over the brightest columns of the sweep.
   Somebody who has asked for more contrast has asked for the text, not the
   texture. */
@media (prefers-contrast: more) {
  .hero::before,
  .doc::before { opacity: 0.3; }
}

/* Nothing here animates, but a capture this busy behind a headline is the
   kind of texture that reduced-motion users often also want quieter, and
   the page costs nothing by honouring it. */
@media (prefers-reduced-motion: reduce) {
  .hero::before,
  .doc::before { opacity: 0.55; }
}
.hero .wrap {
  display: flex; gap: 48px; align-items: center;
  padding-block: 56px;
}
.hero .mark {
  width: 280px; height: 280px; flex-shrink: 0;
  /* A small cool halo, not the 28px amber bloom the flat white owl had.
     That glow existed to give a line drawing some presence; the badge is
     already lit from inside and is green, so amber around it read as a
     mistake rather than as a glow. */
  filter: drop-shadow(0 0 14px rgba(124, 229, 119, 0.22));
}
.hero .body { display: flex; flex-direction: column; gap: 18px; flex-grow: 1; min-width: 0; }
.hero h1 {
  font-family: var(--display); font-size: 96px; line-height: 0.86;
  letter-spacing: 0.05em; color: var(--ink-hi);
}
.hero .lede { max-width: 620px; font-size: 15px; color: var(--ink-dim); }
/* The owl line is the hook and the answer to "why is it called that", so it
   carries more weight than the spec sentence under it. */
.hero .lede:first-of-type { font-size: 16.5px; color: var(--ink); }

.actions { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 4px; }
.btn {
  display: inline-block; text-decoration: none;
  border: 1px solid var(--line); color: var(--ink-dim);
  padding: 13px 24px; font-size: 13px; letter-spacing: 0.12em;
  min-height: 44px;
}
.btn.primary {
  border-color: var(--amber); background: #1a1206; color: var(--amber);
  box-shadow: inset 0 0 22px rgba(255, 166, 43, 0.18);
}
.btn:hover, .btn:focus-visible { color: var(--ink-hi); border-color: var(--amber); }

/* ── sections ───────────────────────────────────────────────────────────── */
section { padding-top: 44px; }
.eyebrow {
  font-size: 13px; letter-spacing: 0.24em; color: var(--amber);
  margin-bottom: 6px;
}
.note { font-size: 12px; color: var(--ink-mute); margin-bottom: 20px; }

/* Below the breakpoint the sample output is always on screen, so the
   instruction to hover would be describing something that cannot happen. */
.on-narrow { display: none; }

.grid2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; }
.grid3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 26px; }
.span2 { grid-column: span 2; }

.panel {
  border: 1px solid var(--line);
  background: var(--panel);
  box-shadow: inset 0 0 0 1px rgba(255, 166, 43, 0.04);
}
.panel.pad, .panel .pad { padding: 24px 26px; }

/* ── radio sectors ──────────────────────────────────────────────────────── */
/* At rest a sector lists what the module does; pointing at it swaps in the
   kind of output that module produces. The log is absolutely positioned over
   the spec list rather than replacing it, so the panel never changes height
   and the grid never reflows under the pointer. */
.sector { position: relative; overflow: hidden; min-height: 236px; transition: border-color 120ms steps(2); }
.sector.wide { min-height: 206px; }
.sector .head {
  display: flex; justify-content: space-between; align-items: center;
  gap: 12px; padding: 18px 22px 0;
}
.sector .head .who { font-size: 16px; color: var(--ink-hi); letter-spacing: 0.06em; }
.sector .head .band { font-size: 11px; letter-spacing: 0.1em; color: var(--green); white-space: nowrap; }
.sector .idle { padding: 12px 22px 20px; transition: opacity 120ms steps(2); }
.sector .idle .range { font-size: 12px; color: var(--ink-mute); margin-bottom: 14px; }
.sector .idle ul { margin: 0; padding: 0; list-style: none; font-size: 13px; line-height: 1.95; color: var(--ink-dim); }

.sector .log {
  position: absolute; inset: 64px 0 0;
  margin: 0;
  padding: 0 22px 20px;
  display: flex; flex-direction: column; justify-content: flex-end; gap: 6px;
  opacity: 0; pointer-events: none;
  transition: opacity 80ms steps(2);
}
.sector .log .lines { margin: 0; padding: 0; list-style: none; display: contents; }
.sector .log li {
  font-size: 12px; line-height: 1.5; color: var(--green);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  opacity: 0;
}
.sector .log li.hit { color: var(--amber); }

/* Visible on narrow screens, where the output is always shown; announced to
   assistive tech everywhere, which a CSS ::before would not be. */
.logcap {
  font-size: 11px; letter-spacing: 0.14em; color: var(--ink-mute);
  text-transform: uppercase;
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%);
  white-space: nowrap;
}

/* focus-within as well as hover, so a keyboard walk through the page shows
   the same thing a pointer does. */
.sector:hover, .sector:focus-within { border-color: var(--amber); }
.sector:hover .idle, .sector:focus-within .idle { opacity: 0.10; }
.sector:hover .log, .sector:focus-within .log { opacity: 1; }
.sector:hover .log li, .sector:focus-within .log li { animation: ln 260ms steps(1) forwards; }
.sector .log li:nth-child(1) { animation-delay:  40ms; }
.sector .log li:nth-child(2) { animation-delay: 160ms; }
.sector .log li:nth-child(3) { animation-delay: 280ms; }
.sector .log li:nth-child(4) { animation-delay: 400ms; }
.sector .log li:nth-child(5) { animation-delay: 520ms; }
@keyframes ln { to { opacity: 1; } }

.caret { animation: blink 1s steps(1) infinite; color: var(--green); }
@keyframes blink { 50% { opacity: 0; } }

/* ── readouts ───────────────────────────────────────────────────────────── */
.well {
  border: 1px solid var(--line-dim); background: var(--well);
  padding: 16px 20px;
  font-size: 12.5px; line-height: 2;
  overflow-x: auto;
}
.well pre { margin: 0; font: inherit; }
.dim { color: var(--ink-mute); }
.hi { color: var(--ink-hi); }
.am { color: var(--amber); }
.gr { color: var(--green); }
.rd { color: var(--red); }

.cmd {
  border: 1px solid var(--line-dim); background: var(--well);
  padding: 14px 16px; font-size: 12.5px; color: var(--amber);
  overflow-x: auto;
}
.cmd code { font: inherit; white-space: pre; }

.digest { font-size: 12px; line-height: 1.9; color: var(--ink-mute); word-break: break-all; }

.tags { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 2px; }

/* ── footer ─────────────────────────────────────────────────────────────── */
footer {
  margin-top: 48px;
  border-top: 1px solid var(--line-dim); background: var(--rail);
}
footer .wrap {
  display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  padding-block: 18px; font-size: 12px; color: var(--ink-mute);
}

/* ── narrow ─────────────────────────────────────────────────────────────── */
/* Hover is not available on a phone, so below the two-column breakpoint the
   log stops being a hover state and becomes part of the panel. Nothing is
   hidden behind an interaction that the device cannot perform. */
@media (max-width: 900px) {
  :root { --gutter: 20px; }

  .on-wide { display: none; }
  .on-narrow { display: inline; }

  .hero .wrap { flex-direction: column; align-items: flex-start; gap: 28px; padding-block: 40px; }
  .hero h1 { font-size: 76px; }
  /* Centred once the hero stacks. The wrap goes to a column and aligns
     everything left, which is right for the headline and the prose and
     leaves a square badge hanging off one side of a narrow screen. This
     centres the badge alone, so the text keeps its left edge. */
  .hero .mark { width: 200px; height: 200px; align-self: center; }

  .grid2, .grid3 { grid-template-columns: minmax(0, 1fr); }
  .span2 { grid-column: auto; }

  .sector, .sector.wide { min-height: 0; }
  .sector .idle { opacity: 1 !important; }
  .sector .log {
    position: static; opacity: 1; padding: 0 22px 20px;
    border-top: 1px solid var(--line-dim); margin: 0 0 0;
    padding-top: 14px;
  }
  .sector .log li { opacity: 1; animation: none !important; white-space: normal; }
  .sector .logcap {
    position: static; width: auto; height: auto; margin: 0 0 6px;
    clip-path: none; overflow: visible;
  }
}

@media (max-width: 520px) {
  .hero h1 { font-size: 60px; }
  .rail .sub { display: none; }
  .btn { width: 100%; text-align: center; }
}

@media (prefers-reduced-motion: reduce) {
  .sector:hover .log li, .sector:focus-within .log li { animation: none; opacity: 1; }
  .caret { animation: none; }
  .sector, .sector .idle, .sector .log { transition: none; }
}

@media print {
  body { background: #fff; color: #000; }
  .sector .log { position: static; opacity: 1; }
  .sector .log li { opacity: 1; }
}


/* ──────────────────────────────────────────────────────────────────────────
   Ported pages: firmware.html and carrier-board.html

   Their prose, tables and diagrams came across verbatim from the pages on
   magikh0e.pl, so the markup they arrive in is that site's: <pre class=
   "content"> blocks with <strong class="term"> inside, figures captioned
   with .t-caption, and three inline SVGs that paint themselves from CSS
   variables. Restyling them here rather than rewriting the markup keeps the
   measured facts in those blocks exactly as they were written.
   ────────────────────────────────────────────────────────────────────────── */

/* The diagrams name these; they are the other site's palette, remapped. The
   names stay as they are so the SVGs need no edits at all. */
:root {
  --text:       #d7e0e6;
  --text-dim:   #9fb3bf;
  --bg-card:    #0a1116;
  --border-hi:  #33464f;
  --orange:     #ffa62b;
  --green:      #7ce577;
  --blue:       #6ab6e8;
}

/* position, because the sweep strip at the top of every subpage is a
   ::before on this element. Not isolation: see the sweep block. */
.doc { position: relative; padding-bottom: 8px; }

/* A one-paragraph orientation under the jump list: what this page is for and
   which page comes after it. Four pages only work if each says where it sits. */
/* Each subpage's own name. The nav marks which page you are on and the
   <title> says it again, but nothing in the document did -- and the h1
   is the first thing a screen reader and a search result read. Sized to
   sit under the rail without competing with the section eyebrows, which
   are what the eye should land on. */
.pagetitle {
  margin: 22px 0 0;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ink-hi);
}

.pagelede {
  margin: 26px 0 0;
  font-size: 15px;
  line-height: 1.75;
  color: var(--ink);
  max-width: 760px;
}

/* The four things inside "what this fork adds" were one unbroken block six
   screens tall. These are what make them findable.

   Matched on the class rather than the tag, because the right level depends
   on what is above it. added.html is one section whose h1 is the only thing
   over these, so there they are h2; on enclosure.html they sit under a
   section eyebrow and stay h3. Scoped to .doc so it cannot reach the rail's
   own .sub, which is a different thing wearing the same name. */
.doc .sub {
  margin: 40px 0 14px;
  font-size: 17px;
  color: var(--ink-hi);
  letter-spacing: 0.02em;
  scroll-margin-top: 20px;
}
.doc .sub::before { content: "4 "; color: var(--amber); }


/* A subhead inside a long section.
   Surveillance is 44% of added.html by itself and reads as one wall of
   prose. The divisions were already there -- a bolded lead-in starting each
   new topic -- they just were not anything a reader could see at a glance
   or a nav could link to. These are those, promoted.
   Deliberately quieter than .sub and with no marker, so the section's own
   heading still reads as the section and these read as its parts. */
.doc .sub2 {
  margin: 32px 0 12px;
  font-size: 14px;
  color: var(--ink-hi);
  letter-spacing: 0.02em;
  scroll-margin-top: 20px;
}

/* Second level of the on-page nav, for those subheads. */
/* A top-level entry that has children takes its own row.
   Without this the parent stays on the line above with whatever else fits,
   and its children appear underneath both of them: on the hardware page
   "Building one" shared a row with "The 2.4 GHz band" while owning the
   sub-list below, so the one child read as belonging to either. One parent
   per line makes the outline unambiguous, and the sub-row beneath it can
   only be about the thing directly above. */
.jump > a:has(+ .deep) { flex-basis: 100%; }

/* Its own row, and a flex row at that.
   It was display:block holding inline links, while .jump a gives every link
   7px of vertical padding and a border. Inline boxes do not reserve room for
   that, so a wrapped second line sat on top of the first, and a line break
   could fall between a link and its own text. flex-basis:100% takes a full
   row in the outer flex; wrapping then happens between items instead of
   through them. */
.jump .deep {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  flex-basis: 100%;
  align-items: center;
  margin: 2px 0 6px 14px;
  padding-left: 12px;
  border-left: 1px solid var(--line);
}
.jump .deep a { font-size: 12px; color: var(--ink-mute); }

.doc section > .sub:first-of-type { margin-top: 26px; }
.doc section { padding-top: 40px; scroll-margin-top: 20px; }
.doc section > .eyebrow {
  margin-bottom: 18px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--line);
}

/* A run of preformatted text is the unit these pages are written in: tables
   that line up in a monospace column, and paragraphs hard-wrapped to fit
   beside them. It has to wrap on a narrow screen rather than scroll the
   page sideways, but the wrap must not re-flow the aligned tables, so the
   break is on existing whitespace only. */
.content {
  margin: 0 0 22px;
  font: inherit;
  font-size: 13.5px;
  line-height: 1.85;
  color: var(--ink-dim);
  white-space: pre-wrap;
  overflow-wrap: break-word;
  tab-size: 4;
}
.content .term, .term { color: var(--ink-hi); font-weight: 500; }
.content a { overflow-wrap: anywhere; }
.content em { color: var(--amber); font-style: normal; }
.t-orange { color: var(--amber); }
.t-green  { color: var(--green); }

/* Figures: diagrams, screenshots, photographs. */
figure { margin: 0 0 26px; }
.t-mb { margin-bottom: 26px; }
.t-mt { margin-top: 12px; }
figure.diagram {
  border: 1px solid var(--line);
  background: var(--panel);
  padding: 20px 22px 16px;
}
figure.diagram svg { display: block; width: 100%; height: auto; }
.t-caption {
  /* The same measure the body prose sets.
     The <pre> blocks wrap their source at 78 characters, which renders at
     632 px in the 13.5 px monospace they use -- so that, and not the panel,
     is the column this page reads in. A caption left to fill its panel came
     out 1066 px and 167 characters a line: wider than the images it
     captions, and half again past the widest measure anyone reads
     comfortably.
     A pixel value rather than ch, because ch in a proportional font is the
     width of a zero and lands nowhere near the monospace column it is
     matching. If the body font changes, this changes with it. */
  max-width: 632px;
  margin-top: 12px;
  font-size: 12.5px;
  line-height: 1.75;
  color: var(--ink-mute);
}
.t-caption .term { color: var(--ink-dim); }
figure img, .photo img { max-width: 100%; height: auto; }

.screen-strip {
  display: flex; gap: 14px; flex-wrap: wrap;
  justify-content: center; align-items: flex-start;
}
.screen-strip img {
  width: 100%; max-width: 320px; height: auto;
  border: 1px solid var(--line);
}
#panel .screen-strip { gap: 20px; }
#panel .note { max-width: 860px; }

/* The renders are the only thing on the site you might want closer than the
   layout can give you, so each links to its own 720x960 file. */
.screen-zoom {
  display: inline-flex; text-decoration: none; cursor: zoom-in; flex-shrink: 0;
}
.screen-zoom:focus-visible { outline: 2px solid var(--amber); outline-offset: 3px; }
.screen-strip .screen-zoom { max-width: 322px; }
#panel .screen-strip .screen-zoom { flex: 0 0 auto; }

/* A screen render beside the prose that reads it. Doubled, because at 1:1 the
   three-line rows are smaller than the paragraph explaining them. */
/* Width only, height from the image's own aspect. Both were pinned at
   480x640, which is 240x320 doubled -- and when the renders moved to the
   3.5" panel's 320x480 that stopped being the aspect and started being a
   squash. A render should be the shape the panel is. */
.screen-fig {
  width: 480px; height: auto;
  border: 1px solid var(--line);
  flex-shrink: 0;
}
@media (max-width: 1040px) {
  .screen-fig { width: 320px; }
}

/* A row of screen renders inside a panel, with its caption below them.

   These were four copies of the same inline style on added.html and a fifth
   that had already drifted, which is what an inline style does when it is
   used more than once.

   320px is the render at 1:1 with the panel it is a picture of. .screen-fig
   doubles to 480 so a render beside a paragraph competes with it, but that
   is one render; a row of four at 480 wrapped to two lines, made the panel
   1612px tall, and put six and a half thousand pixels of thumbnail into a
   page that is already long. At 320 the three-shot rows fit on one line.

   Centred, because the panel is full width and the row is not: three shots
   fill it, one leaves two thirds of a bordered box empty to the right,
   which reads as a layout fault rather than a panel with one picture in it.
   The panel cannot shrink to fit instead -- the caption under it is a
   full-width paragraph, so width:fit-content resolves back to the whole
   column. */
.shots {
  display: flex;
  gap: 18px;
  align-items: flex-start;
  flex-wrap: wrap;
  justify-content: center;
  margin-bottom: 22px;
}
.shots .screen-fig { width: 320px; }

/* A panel with one shot in it has no row to fit, so 320 just makes the box
   emptier. It gets the 480 .screen-fig is for. */
.shots .screen-zoom:only-of-type .screen-fig { width: 480px; }
.shots-cap { flex-basis: 100%; margin-top: 2px; }

/* Four shots where the last two are a before/after pair. Left to wrap they
   fall 3 + 1 and the pair is split across rows, which is the one thing the
   caption is pointing at. Two fixed columns put them under each other. */
.shots.pairs {
  display: grid;
  grid-template-columns: repeat(2, 320px);
  justify-content: center;
}
.shots.pairs .shots-cap { grid-column: 1 / -1; }

@media (max-width: 760px) {
  .shots { gap: 14px; }
  .shots .screen-fig { width: 100%; max-width: 320px; }
  .shots.pairs { grid-template-columns: 1fr; }
}

/* Stacked, three screens is nearly 900px of scrolling to get past a set of
   thumbnails. Swiping one row is the better trade: they stay legible and
   the section stays the height of one screen. */
@media (max-width: 900px) {
  #panel .screen-strip {
    flex-wrap: nowrap;
    overflow-x: auto;
    justify-content: flex-start;
    padding-bottom: 10px;
    scroll-snap-type: x proximity;
    overscroll-behavior-x: contain;
  }
  #panel .screen-strip img { flex: 0 0 auto; scroll-snap-align: start; }
}

/* See Also, and the other lists of links with a trailing comment. */
.link-list { display: flex; flex-direction: column; gap: 2px; }
.link-list a {
  display: block;
  padding: 9px 12px;
  text-decoration: none;
  border: 1px solid transparent;
  font-size: 13.5px;
}
.link-list a:hover, .link-list a:focus-visible {
  border-color: var(--line);
  background: var(--panel);
}
.tree-comment { color: var(--ink-mute); font-size: 12.5px; }

/* The build gotchas: a term and what it costs you. */
dl dt { font-weight: 500; }
dl dd { line-height: 1.7; }

/* A pointer to where something lives now, rather than a second copy of it. */
.seealso {
  margin: 0 0 22px;
  padding: 12px 16px;
  border-left: 2px solid var(--amber-ln);
  font-size: 13px;
  color: var(--ink-mute);
  max-width: 760px;
}

/* ── nav ────────────────────────────────────────────────────────────────── */
.brand { display: flex; align-items: center; gap: 12px; text-decoration: none; }
.brand:hover .name, .brand:focus-visible .name { color: var(--amber); }

.rail .nav { display: flex; gap: 4px; flex-wrap: wrap; }
.rail .nav a {
  text-decoration: none;
  color: var(--ink-dim);
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 9px 12px;
  border: 1px solid transparent;
}
.rail .nav a:hover, .rail .nav a:focus-visible {
  color: var(--ink-hi); border-color: var(--line);
}
.rail .nav a[aria-current="page"] {
  color: var(--amber); border-color: var(--amber-ln); background: var(--amber-bg);
}

/* ── nav groups ─────────────────────────────────────────────────────────── */
/* Hardware has two subpages and What's added has one. As eight flat items the
   rail did not say which of them was a section and which was a detail of one,
   and it wrapped to two rows on a laptop. The parent stays a plain link, so
   Hardware is still one click, and its subpages hang below it. */
.rail .navgroup { position: relative; display: flex; align-items: stretch; }

.rail .navcaret {
  appearance: none; -webkit-appearance: none; background: none;
  cursor: pointer; font: inherit; font-size: 10px; line-height: 1;
  color: var(--ink-dim); border: 1px solid transparent;
  padding: 9px 8px 9px 2px;
}

.rail .navcaret:hover, .rail .navcaret:focus-visible {
  color: var(--ink-hi); border-color: var(--line);
}

.rail .navsub {
  position: absolute; top: 100%; left: 0; z-index: 40;
  display: none; flex-direction: column;
  min-width: 200px; max-width: 340px; padding: 4px;
  background: var(--rail); border: 1px solid var(--line);
}

/* Section names are written for a heading, not for a menu, so a few are long
   enough to need two lines rather than a menu wide enough to hold them. */
.rail .navsub a { white-space: normal; line-height: 1.35; }

/* A section of this page and a different page are not the same kind of thing,
   and a menu that runs them together invites a reader to click one expecting
   the other. */
.rail .navsep {
  margin: 5px 4px 2px; padding-top: 6px;
  border-top: 1px solid var(--line-dim);
  color: var(--text-dim); font-size: 10px; letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* Three ways in, because each covers a reader the others do not: a mouse, a
   keyboard tabbing through the rail, and a tap. */
.rail .navgroup:hover > .navsub,
.rail .navgroup:focus-within > .navsub,
.rail .navcaret[aria-expanded="true"] + .navsub { display: flex; }

.rail .navsub a { text-transform: none; letter-spacing: 0.04em; }

/* The trail to the page you are on, so a subpage still shows which section it
   sits in once its own entry is inside a closed menu. */
.rail .navgroup.on > a { color: var(--ink-hi); border-color: var(--line); }


/* Jump list: the old pages carried one under the title, and a page of this
   length is unusable without it. */
.jump {
  display: flex; gap: 8px; flex-wrap: wrap;
  padding: 18px 0 2px;
  border-bottom: 1px solid var(--line-dim);
  padding-bottom: 18px;
}
.jump a {
  text-decoration: none;
  font-size: 12px; letter-spacing: 0.08em;
  color: var(--ink-mute);
  border: 1px solid var(--line-dim);
  padding: 7px 11px;
}
.jump a:hover, .jump a:focus-visible {
  color: var(--amber); border-color: var(--amber-ln);
}

@media (max-width: 900px) {
  .rail .wrap { gap: 12px; }

  /* Seven items wrapped to three rows put 214px of navigation above every
     page on a phone. One row that scrolls sideways is 90, and nothing is
     unreachable -- the same trade as the screenshot strip. */
  .rail .nav {
    order: 3; width: 100%;
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    /* Fades the right edge so it reads as continuing rather than ending. */
    mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
  }
  .rail .nav::-webkit-scrollbar { display: none; }
  .rail .nav a { flex: 0 0 auto; }

  /* A submenu cannot hang out of a row that scrolls sideways, because the
     overflow that makes the row scroll is also what clips it. So on a phone
     the group flattens back into the row: every page reachable by scrolling,
     and nothing to open. */
  .rail .navgroup, .rail .navsub { display: contents; }

  /* The sections come out here. Thirty-seven items in a row that scrolls
     sideways is not a menu, and on a phone the page's own jump nav sits
     directly under its title, which is where the sections already are. What
     is left in the row is what was in it before: the pages. */
  .rail .navcaret, .rail .navsection, .rail .navsep { display: none; }
  .rail .chips { margin-left: auto; }
  .content { font-size: 13px; }
  figure.diagram { padding: 14px 12px 10px; }
}

@media print {
  .rail, .jump, footer, .skip { display: none; }
  .content, .t-caption { color: #000; }
}

/* The feature reel under Get it. Sized by its own aspect so the poster
   holds the space before the file is fetched, which preload="none" means
   is not until somebody presses play. */
.reel { margin-bottom: 20px; }
.reel video {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  max-width: 100%;
  background: #201c20;
  border: 1px solid var(--line);
  border-radius: 2px;
}

/* The mark on the signature list page. Beside the title where there is room
   for it. Below the two-column breakpoint the float costs the jump nav more
   width than the mark is worth, the chips wrapping into a narrow column with
   a tall gap beside them, so it stops floating and sits above the content. */
.pagemark { float: right; width: 108px; height: auto; margin: 4px 0 16px 24px; }
@media (max-width: 900px) {
  .pagemark { float: none; display: block; width: 84px; margin: 0 auto 14px; }
}
