/* Violet RE. One stylesheet for every page of violetre.io.
   The design system is Vio Labs (violabs.co.uk): the same tokens, the same
   three self-hosted families, the same motion, the same light/dark mechanism.
   What is local to this site is the instrument register: a rail of section
   names, hex dumps, source listings, field tables and counts, all set in mono.

   No inline styles anywhere, so a strict CSP can forbid them. Every colour is
   a token; the dark palette redefines the tokens and nothing else.

   Contents: fonts, tokens, base, masthead, theme toggle, links, rail, hero,
   artefact dump, drawn figure, jobs, bands, listings, readings, field tables,
   inventory, deliverables, formats, nots, release, footer, waitlist, privacy
   notice, motion, responsive, print. */

/* ---------- Fonts (self-hosted, SIL Open Font Licence 1.1) ---------- */
@font-face {
  font-family: "Newsreader";
  src: url("fonts/newsreader-latin-400-600-normal.woff2") format("woff2");
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Newsreader";
  src: url("fonts/newsreader-latin-400-500-italic.woff2") format("woff2");
  font-weight: 400 500;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "Public Sans";
  src: url("fonts/public-sans-latin-400-700-normal.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("fonts/ibm-plex-mono-latin-400-normal.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
/* Metric-matched local fallbacks. They hold the same line height and close to
   the same line length as the web fonts, so the swap barely moves the page. */
@font-face {
  font-family: "Newsreader Fallback";
  src: local("Georgia");
  size-adjust: 96.07%;
  ascent-override: 76.51%;
  descent-override: 27.58%;
  line-gap-override: 0%;
}
@font-face {
  font-family: "Public Sans Fallback";
  src: local("Arial");
  size-adjust: 105.6%;
  ascent-override: 89.96%;
  descent-override: 21.31%;
  line-gap-override: 0%;
}

/* ---------- Tokens ---------- */
:root {
  --paper: #ffffff;
  --paper-2: #f7f7f5;
  --ink: #14151a;
  --ink-2: #43464f;
  --ink-3: #626570;
  --line: #dcdde2;
  --line-2: #ececef;
  --line-3: #b9bbc4;
  --line-4: #8a8d98;
  --violet: #5a3ea8;
  --violet-deep: #43297f;
  --wash: #efeaf9;
  --on-violet: #ffffff;
  --shade: rgba(20, 21, 26, 0.1);
  --notice-shadow: 0 10px 28px -18px rgba(20, 21, 26, 0.35);
  /* Source-listing tokens, carried over from the reference so the two sites
     colour code the same way. AA on --paper-2 and on --wash. */
  --code-comment: #5f626d;
  --code-keyword: #5a3ea8;
  --code-string: #2a5b4c;
  --code-number: #8a4412;
  --code-title: #1d4f80;
  --code-attr: #43297f;
  /* Two marks used in the worked examples: a proposal the gate kept, and one
     it held back. Local to this site; the reference has no equivalent. */
  --keep-ink: #43297f;
  --keep-bg: #efeaf9;
  --hold-ink: #8a4412;
  --hold-bg: #fbefe2;
  color-scheme: light;

  --serif: "Newsreader", "Newsreader Fallback", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  --sans: "Public Sans", "Public Sans Fallback", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  --gutter: clamp(20px, 5vw, 64px);
  /* Narrower than the 1240px of the reference site by 60px. These pages
     carry a 150px marker rail to the left of the text column that the
     reference does not have, so the same measure of running text needs a
     narrower shell. Every other shared token keeps the reference value. */
  --wrap: 1180px;
  --rail: 150px;
  --ease: cubic-bezier(0.22, 0.7, 0.2, 1);
  --ease-draw: cubic-bezier(0.6, 0.05, 0.25, 1);
  --header-h: 68px;
  /* Height kept clear at the foot of the page for the fixed waitlist line.
     The value per breakpoint is the measured height of that line; site.js
     replaces it with the exact height once it runs, so the page never leaves
     a gap and never hides its own last paragraph. */
  --wl-h: 60px;
}

/* Dark. The reference palette, unchanged: a deep blue-grey ground, soft
   off-white ink, quiet hairlines, the violet lifted to pass AA as text. The
   same values are written twice, once for a choice made with the toggle
   (data-theme, set by theme.js before first paint) and once for the system
   setting, so dark works with scripts off. Keep the two blocks identical. */
:root[data-theme="dark"] {
  --paper: #121419;
  --paper-2: #181b21;
  --ink: #e7e8ec;
  --ink-2: #b9bcc5;
  --ink-3: #959aa6;
  --line: #2d313a;
  --line-2: #22262d;
  --line-3: #464b56;
  --line-4: #6d7280;
  --violet: #a593ee;
  --violet-deep: #c6b9f6;
  --wash: #2a2443;
  --on-violet: #121419;
  --shade: rgba(0, 0, 0, 0.35);
  --notice-shadow: 0 10px 28px -18px rgba(0, 0, 0, 0.8);
  --code-comment: #959aa6;
  --code-keyword: #b4a4f2;
  --code-string: #86c6ab;
  --code-number: #e2a877;
  --code-title: #8fbbe9;
  --code-attr: #c6b9f6;
  --keep-ink: #c6b9f6;
  --keep-bg: #2a2443;
  --hold-ink: #e2a877;
  --hold-bg: #33250f;
  color-scheme: dark;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper: #121419;
    --paper-2: #181b21;
    --ink: #e7e8ec;
    --ink-2: #b9bcc5;
    --ink-3: #959aa6;
    --line: #2d313a;
    --line-2: #22262d;
    --line-3: #464b56;
    --line-4: #6d7280;
    --violet: #a593ee;
    --violet-deep: #c6b9f6;
    --wash: #2a2443;
    --on-violet: #121419;
    --shade: rgba(0, 0, 0, 0.35);
    --notice-shadow: 0 10px 28px -18px rgba(0, 0, 0, 0.8);
    --code-comment: #959aa6;
    --code-keyword: #b4a4f2;
    --code-string: #86c6ab;
    --code-number: #e2a877;
    --code-title: #8fbbe9;
    --code-attr: #c6b9f6;
    --keep-ink: #c6b9f6;
    --keep-bg: #2a2443;
    --hold-ink: #e2a877;
    --hold-bg: #33250f;
    color-scheme: dark;
  }
}

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-padding-top: calc(var(--header-h) + 24px);
  scroll-padding-bottom: calc(var(--wl-h) + 24px);
}
/* While the privacy notice is open, keyboard focus and anchor jumps stop
   short of it, and the page can scroll far enough that nothing at its foot
   stays underneath it. site.js measures the notice into --notice-h. */
html.notice-open { scroll-padding-bottom: calc(var(--wl-h) + var(--notice-h, 0px) + 32px); }
html.notice-open .page { padding-bottom: calc(var(--wl-h) + var(--notice-h, 0px) + 16px); }
/* site.js may hand focus to <main> when the notice closes: no ring round the page. */
#main[tabindex="-1"]:focus { outline: none; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 1.0625rem;
  line-height: 1.6;
  font-feature-settings: "kern", "liga";
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-x: clip;
  overflow-wrap: break-word;
}

h1, h2, h3, h4, p, ol, ul, dl, dd, figure, blockquote, pre, table { margin: 0; }
table { border-collapse: collapse; }
img, svg { display: block; max-width: 100%; }
img { height: auto; }
a { color: inherit; }
button, input { font: inherit; color: inherit; }

/* Lists that are layout, not prose. role="list" keeps them lists in VoiceOver. */
.jobs,
.deliverables,
.nots,
.plan,
.legend { margin: 0; padding: 0; list-style: none; }

::selection { background: var(--wash); color: var(--violet-deep); }

:focus-visible {
  outline: 2px solid var(--violet);
  outline-offset: 3px;
  border-radius: 1px;
}

.shell {
  width: 100%;
  max-width: calc(var(--wrap) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.page { padding-bottom: var(--wl-h); }

.skip-link {
  position: absolute;
  left: var(--gutter);
  top: 0;
  z-index: 100;
  padding: 10px 16px;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--mono);
  font-size: 0.8125rem;
  text-decoration: none;
  transform: translateY(-160%);
}
.skip-link:focus { transform: translateY(12px); }

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------- Type ----------
   Newsreader carries the display line, Public Sans the prose, IBM Plex Mono
   every machine fact. The same division as the reference. */
h1, h2, h3 {
  font-family: var(--serif);
  font-weight: 400;
  color: var(--ink);
  letter-spacing: -0.018em;
  line-height: 1.05;
}
h1 {
  font-size: clamp(2.125rem, 5.2vw, 4.25rem);
  font-size: clamp(2rem, 9.4cqi, 4.25rem);
  line-height: 1.02;
  letter-spacing: -0.022em;
  max-width: 22ch;
}
h2 {
  font-size: clamp(1.75rem, 3.4vw, 2.75rem);
  line-height: 1.04;
  max-width: 28ch;
  text-wrap: balance;
}
h3 {
  font-size: 1.1875rem;
  font-weight: 500;
  line-height: 1.25;
  letter-spacing: -0.008em;
}

.lede {
  font-size: clamp(1.0625rem, 1.3vw, 1.1875rem);
  line-height: 1.6;
  color: var(--ink-2);
  max-width: 54ch;
}
.lede strong { color: var(--ink); }
.body { max-width: 62ch; color: var(--ink-2); }
.body + .body { margin-top: 0.95em; }
.small { font-size: 0.9375rem; line-height: 1.6; color: var(--ink-2); }
b, strong { font-weight: 600; color: var(--ink); }
code { font-family: var(--mono); font-size: 0.86em; color: var(--ink); overflow-wrap: anywhere; }

/* Mono utility for machine facts. Lower case on purpose: no shouting labels. */
.mono { font-family: var(--mono); font-size: 0.75rem; line-height: 1.5; color: var(--ink-3); }

/* Text links are always underlined, in the violet, as on the reference. */
a {
  text-decoration-line: underline;
  text-decoration-color: var(--violet);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  transition: color 0.2s ease, text-decoration-color 0.2s ease;
}
a:hover,
a:focus-visible { color: var(--violet-deep); text-decoration-thickness: 2px; }

/* Scroll cue for anything wider than its column: a hairline and a faint
   shade on whichever edge has more to show. The first two layers move with
   the content and cover the cue at each end; the last four stay with the box.
   Nothing here is animated. */
.dump,
.src,
.ftab-wrap,
.inv-wrap {
  background:
    linear-gradient(var(--scroll-bg), var(--scroll-bg)) left / 14px 100% no-repeat local,
    linear-gradient(var(--scroll-bg), var(--scroll-bg)) right / 14px 100% no-repeat local,
    linear-gradient(var(--line-4), var(--line-4)) left / 1px 100% no-repeat scroll,
    linear-gradient(var(--line-4), var(--line-4)) right / 1px 100% no-repeat scroll,
    linear-gradient(to right, var(--shade), transparent) left / 12px 100% no-repeat scroll,
    linear-gradient(to left, var(--shade), transparent) right / 12px 100% no-repeat scroll;
}
.dump:focus-visible,
.src:focus-visible { outline-offset: -2px; }

/* ---------- Masthead ---------- */
.masthead {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--paper);
  border-bottom: 1px solid var(--line);
}
.masthead::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 1px;
  background: var(--ink);
  opacity: 0;
  transition: opacity 0.3s ease;
}
.masthead.is-scrolled::after { opacity: 1; }
.masthead .shell {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px 32px;
  min-height: var(--header-h);
  padding-top: calc(10px + env(safe-area-inset-top, 0px));
  padding-bottom: 10px;
}
.brand {
  display: flex;
  align-items: center;
  gap: 13px;
  min-width: 0;
  text-decoration: none;
  color: var(--ink);
}
.brand img { display: block; width: 32px; height: 32px; flex: none; }
.wordmark {
  font-family: var(--serif);
  font-size: 1.4375rem;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.01em;
  line-height: 1.1;
}
.wordmark span {
  display: block;
  margin-top: 1px;
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 400;
  color: var(--ink-3);
  letter-spacing: 0;
}
.masthead-end { display: flex; align-items: center; gap: clamp(12px, 2vw, 24px); }
.state {
  font-family: var(--mono);
  font-size: 0.75rem;
  line-height: 1.6;
  color: var(--ink-3);
  display: grid;
  grid-template-columns: auto auto;
  gap: 0 14px;
}
.state b { color: var(--ink); font-weight: 400; }

/* Theme toggle: a moon in light (it will turn the lights down), a sun in
   dark. Shown once site.js has wired it, and not at all without scripts;
   the space is kept so nothing moves. */
.theme-toggle {
  position: relative;
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 36px;
  height: 36px;
  margin-right: -8px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--ink-2);
  cursor: pointer;
  visibility: hidden;
  transition: color 0.2s ease;
}
.theme-ready .theme-toggle { visibility: visible; }
@media (scripting: none) { .theme-toggle { display: none; } }
.theme-toggle:hover,
.theme-toggle:focus-visible { color: var(--violet-deep); }
.theme-toggle:focus-visible { outline-offset: 0; border-radius: 50%; }
.theme-icon { width: 20px; height: 20px; overflow: visible; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }
.ti-sun,
.ti-moon { transform-box: view-box; transform-origin: 12px 12px; transition: transform 0.6s var(--ease), opacity 0.35s ease; }
.ti-sun { opacity: 0; transform: rotate(-90deg) scale(0.5); }
:root[data-theme="dark"] .ti-sun { opacity: 1; transform: none; }
:root[data-theme="dark"] .ti-moon { opacity: 0; transform: rotate(90deg) scale(0.5); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .ti-sun { opacity: 1; transform: none; }
  :root:not([data-theme="light"]) .ti-moon { opacity: 0; transform: rotate(90deg) scale(0.5); }
}

/* The switch itself. site.js adds .theme-switching for the frame of the
   change, so colour transitions do not run one by one; with the View
   Transitions API the new theme is revealed in a circle from the toggle
   (the clip is animated from site.js).
   The sun and moon are left out, so they still turn into each other.
   (Under reduced motion every transition is instant anyway.) */
.theme-switching *:not(.ti-sun):not(.ti-moon),
.theme-switching *::before,
.theme-switching *::after { transition: none !important; }
.theme-switching::view-transition-old(root),
.theme-switching::view-transition-new(root) { animation: none; mix-blend-mode: normal; }
.theme-switching::view-transition-old(root) { z-index: 1; }
.theme-switching::view-transition-new(root) { z-index: 2; }

/* ---------- The listing rail ----------
   A margin column of section names, the way a disassembly carries its
   addresses beside the code. */
.rail { display: block; }
.rail-mark {
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--violet);
  padding-bottom: 10px;
  margin-bottom: 14px;
  border-bottom: 1px solid var(--line);
}
@media (min-width: 1080px) {
  .rail {
    display: grid;
    grid-template-columns: var(--rail) minmax(0, 1fr);
    column-gap: 0;
  }
  .rail > .rail-mark { padding-top: 4px; padding-right: 20px; border-bottom: 0; padding-bottom: 0; margin-bottom: 0; }
  .rail > .rail-body {
    border-left: 1px solid var(--line);
    padding-left: clamp(22px, 2.4vw, 38px);
    min-width: 0;
  }
}

/* ---------- Hero ---------- */
.hero { padding-top: clamp(34px, 5.5vw, 76px); padding-bottom: clamp(30px, 4vw, 52px); }
.hero .rail-body { container-type: inline-size; }
.hero .lede { margin-top: clamp(20px, 2.6vw, 32px); }
.hero-note {
  margin-top: 24px;
  padding-left: 14px;
  border-left: 2px solid var(--violet);
  font-family: var(--mono);
  font-size: 0.8125rem;
  line-height: 1.65;
  color: var(--ink-2);
  max-width: 52ch;
}

/* ---------- The artefact: a real archive, read off disk ---------- */
.artefact {
  --scroll-bg: var(--paper-2);
  margin: clamp(30px, 4vw, 48px) 0 0;
  max-width: 672px;
  border: 1px solid var(--line);
  background: var(--paper-2);
}
.artefact-head {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 18px;
  justify-content: space-between;
  align-items: baseline;
  padding: 10px 14px;
  background: var(--paper);
  border-bottom: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--ink-3);
}
.artefact-head b { color: var(--ink); font-weight: 400; }
.dump {
  --scroll-bg: var(--paper-2);
  margin: 0;
  padding: 16px 14px;
  overflow-x: auto;
  font-family: var(--mono);
  font-size: 0.75rem;
  line-height: 1.75;
  color: var(--ink-2);
  white-space: pre;
}
.dump .off { color: var(--ink-3); }
.dump .asc { color: var(--ink); }
.dump .mag { color: var(--violet-deep); background: var(--wash); font-weight: 400; }
.dump-narrow { display: block; }
.dump-wide { display: none; }
@media (min-width: 700px) {
  .dump-narrow { display: none; }
  .dump-wide { display: block; font-size: 0.8125rem; }
}
.artefact-foot {
  border-top: 1px solid var(--line);
  padding: 13px 14px;
  font-size: 0.875rem;
  line-height: 1.6;
  color: var(--ink-2);
  max-width: 86ch;
}
.artefact-foot code { color: var(--violet-deep); font-size: 0.9em; }

/* ---------- The drawn figure ----------
   A drawing that inks itself in when it is scrolled into view. Every animated
   path carries pathLength="1000" and one behaviour class, dr (draws), fd
   (fades) or pp (pops), plus a delay class dN, meaning N tenths of a second.
   The motion itself is in the motion section far below; these rules sit
   outside it on purpose, so they also describe the finished drawing with
   scripts off, under reduced motion and in print.

   Three stroke weights and no others: 1.2 for the subject, 1.1 for a
   secondary line, 0.75 for a hairline or a lead. Every closed shape names its
   fill, or it renders black on the dark ground. Violet appears exactly once,
   on the recovered name, which is the point of the drawing.

   Two drawings are written in the markup and one is shown, the same way the
   hero hex dump carries a wide and a narrow pane: a drawing cannot reflow, and
   scaling one viewBox across both widths would leave the type either illegible
   on a phone or shouting on a desktop. The narrow drawing is capped at its own
   design size, 328 units plus the padding, so that between a phone and the
   700px swap it stops growing instead of shouting over the body copy. */
.vr-fig { display: block; width: 100%; height: auto; padding: 16px 14px; }
.vr-narrow { display: block; max-width: 356px; margin-inline: auto; }
.vr-wide { display: none; }
@media (min-width: 700px) {
  .vr-narrow { display: none; }
  .vr-wide { display: block; }
}
.vr-hair { fill: none; stroke: var(--ink-3); stroke-width: 0.75; }
.vr-fine { fill: none; stroke: var(--ink-2); stroke-width: 1.1; stroke-linecap: round; }
.vr-box { fill: none; stroke: var(--ink); stroke-width: 1.2; stroke-linejoin: round; }
.vr-pip { fill: var(--ink); }
.vr-txt { font-family: var(--mono); font-size: 11px; fill: var(--ink); }
.vr-txt--dim { fill: var(--ink-3); }
.vr-txt--name { font-size: 12px; fill: var(--violet-deep); }

/* ---------- The two jobs ---------- */
.jobs { margin-top: clamp(30px, 4vw, 48px); border-top: 1px solid var(--ink); }
.jobs li { border-bottom: 1px solid var(--line); padding: 20px 0 22px; }
.job-name {
  display: block;
  font-family: var(--serif);
  font-size: 1.5rem;
  font-weight: 400;
  letter-spacing: -0.012em;
  line-height: 1.15;
  color: var(--ink);
  margin-bottom: 9px;
}
.job-name a { text-decoration-color: var(--line-4); text-underline-offset: 0.16em; }
.job-name a:hover,
.job-name a:focus-visible { color: var(--violet-deep); text-decoration-color: var(--violet); }
.job-text { display: block; max-width: 52ch; color: var(--ink-2); }
@media (min-width: 860px) {
  .jobs { display: grid; grid-template-columns: 1fr 1fr; column-gap: 0; }
  .jobs li { padding: 22px 0 26px; }
  .jobs li:first-child { padding-right: clamp(20px, 3vw, 48px); border-right: 1px solid var(--line); }
  .jobs li:last-child { padding-left: clamp(20px, 3vw, 48px); }
}

/* ---------- Bands ---------- */
.band {
  border-top: 1px solid var(--ink);
  padding-top: clamp(40px, 5.5vw, 80px);
  padding-bottom: clamp(40px, 5.5vw, 80px);
}
.band-tight { padding-top: clamp(34px, 4vw, 56px); padding-bottom: clamp(34px, 4vw, 56px); }
.band-plate { background: var(--paper-2); }
.band h2 + .body { margin-top: 20px; }

/* ---------- Source listings ---------- */
.pair { margin-top: clamp(26px, 3.4vw, 40px); display: grid; grid-template-columns: 1fr; gap: 16px; }
@media (min-width: 980px) {
  .pair { grid-template-columns: 1fr 1fr; gap: 0; }
  .pair > .listing + .listing { border-left: 0; }
}
.listing {
  border: 1px solid var(--line);
  background: var(--paper-2);
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.listing-head {
  display: flex;
  flex-wrap: wrap;
  gap: 3px 16px;
  justify-content: space-between;
  align-items: baseline;
  padding: 10px 14px;
  background: var(--paper);
  border-bottom: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.75rem;
}
.listing-path { color: var(--ink); word-break: break-all; }
.listing-tag { color: var(--ink-3); white-space: nowrap; }
.listing-keep .listing-tag { color: var(--violet-deep); }
.src {
  --scroll-bg: var(--paper-2);
  margin: 0;
  min-width: 0;
  padding: 16px 14px 18px;
  overflow-x: auto;
  font-family: var(--mono);
  font-size: 0.78125rem;
  line-height: 1.8;
  tab-size: 4;
  color: var(--ink-2);
}
@media (min-width: 520px) { .src { font-size: 0.8125rem; padding: 18px 16px 20px; } }
.listing-foot {
  margin-top: auto;
  border-top: 1px solid var(--line);
  padding: 9px 14px;
  font-family: var(--mono);
  font-size: 0.6875rem;
  color: var(--ink-3);
}
/* Source tokens. The same division as the reference stylesheet uses for
   highlight.js, so a listing on either site reads the same way. */
.ln { color: var(--ink-3); user-select: none; -webkit-user-select: none; }
.k { color: var(--code-keyword); }
.f { color: var(--code-title); }
.s { color: var(--code-string); }
.n { color: var(--code-number); }
.o { color: var(--ink-2); }
.fld { color: var(--ink-3); }
/* A rename the gate kept, and one it held back. */
.new { color: var(--keep-ink); background: var(--keep-bg); box-shadow: inset 0 -1px 0 var(--violet); }
.kept { color: var(--hold-ink); background: var(--hold-bg); box-shadow: inset 0 -1px 0 var(--hold-ink); }
.hit { color: var(--hold-ink); background: var(--hold-bg); box-shadow: inset 0 -1px 0 var(--hold-ink); }

/* ---------- Readings strip beneath a listing pair ---------- */
.readings {
  border: 1px solid var(--line);
  border-top: 0;
  padding: 16px 14px 18px;
}
@media (max-width: 979px) {
  .readings { margin-top: 16px; border-top: 1px solid var(--line); }
}
.reading-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(165px, 1fr));
  gap: 14px clamp(18px, 3vw, 42px);
}
.reading dt { font-family: var(--mono); font-size: 0.75rem; color: var(--ink-3); }
.reading dd {
  margin: 5px 0 0;
  font-family: var(--mono);
  font-size: 0.9375rem;
  color: var(--ink);
  line-height: 1.35;
  font-variant-numeric: tabular-nums;
}
.reading dd b { font-weight: 400; color: var(--violet-deep); }
.legend {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  gap: 9px 24px;
  font-size: 0.875rem;
  color: var(--ink-2);
}
.legend li { display: flex; align-items: center; gap: 9px; }
.swatch { width: 11px; height: 11px; flex: none; background: var(--keep-bg); border: 1px solid var(--violet); }
.swatch-amber { background: var(--hold-bg); border-color: var(--hold-ink); }
.swatch-plain { background: var(--paper); border-color: var(--line-4); }
.reading-note { margin-top: 16px; max-width: 72ch; font-size: 0.9375rem; line-height: 1.6; color: var(--ink-2); }
.reading-note code { color: var(--violet-deep); }

/* ---------- Field tables: mono term, grotesk definition ---------- */
.spec { margin-top: clamp(26px, 3.4vw, 40px); border-top: 1px solid var(--ink); }
.spec > div {
  border-bottom: 1px solid var(--line);
  padding: 15px 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: 5px;
}
@media (min-width: 760px) {
  .spec > div { grid-template-columns: minmax(120px, 168px) minmax(0, 1fr); gap: 0 clamp(20px, 3vw, 40px); padding: 17px 0; }
}
.spec dt {
  font-family: var(--mono);
  font-size: 0.8125rem;
  color: var(--violet-deep);
  padding-top: 0.28em;
}
.spec dd { color: var(--ink-2); max-width: 66ch; }

.caveat {
  margin-top: 24px;
  max-width: 64ch;
  padding-left: 14px;
  border-left: 2px solid var(--hold-ink);
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--ink-2);
}

/* ---------- Deliverables ---------- */
.deliverables { margin-top: clamp(26px, 3.4vw, 40px); border-top: 1px solid var(--ink); }
.deliverables li { border-bottom: 1px solid var(--line); padding: 19px 0 21px; }
.deliverables h3 { color: var(--ink); }
.deliverables p { margin-top: 8px; color: var(--ink-2); max-width: 64ch; }
@media (min-width: 900px) {
  .deliverables li {
    display: grid;
    grid-template-columns: minmax(200px, 268px) minmax(0, 1fr);
    gap: 0 clamp(20px, 3vw, 40px);
    align-items: start;
  }
  .deliverables p { margin-top: 0; }
}

/* ---------- Formats: magic bytes, read straight off the spec ---------- */
.ftab-wrap { --scroll-bg: var(--paper); margin-top: clamp(24px, 3vw, 36px); max-width: 620px; overflow-x: auto; border: 1px solid var(--line); }
.ftab { width: 100%; min-width: 340px; font-family: var(--mono); font-size: 0.8125rem; text-align: left; }
.ftab th,
.ftab td { padding: 10px 14px; border-bottom: 1px solid var(--line); white-space: nowrap; }
.ftab thead th { background: var(--paper-2); color: var(--ink-3); font-weight: 400; border-bottom-color: var(--ink); }
.ftab tbody th { color: var(--ink); font-weight: 400; }
.ftab td { color: var(--ink-2); }
.ftab .asc { color: var(--violet-deep); }
.ftab tbody tr:last-child th,
.ftab tbody tr:last-child td { border-bottom: 0; }

/* ---------- The corpus inventory ----------
   Mono field, mono count, grotesk definition. Same furniture as .ftab, but
   the third column holds prose, so it wraps and the table fills the measure.
   scripts/update_site_stats.py owns this markup and rewrites it from the
   database. The class names here are the ones it emits: do not rename them. */
.inv-asof { margin-top: clamp(24px, 3vw, 34px); }
.inv-wrap { --scroll-bg: var(--paper); margin-top: 10px; border: 1px solid var(--line); }
.inv { width: 100%; text-align: left; }
.inv th,
.inv td { padding: 13px 16px; border-bottom: 1px solid var(--line); vertical-align: baseline; }
.inv thead th {
  background: var(--paper-2);
  border-bottom-color: var(--ink);
  font-family: var(--mono);
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--ink-3);
  white-space: nowrap;
}
.inv tbody th {
  font-family: var(--mono);
  font-size: 0.8125rem;
  font-weight: 400;
  color: var(--violet-deep);
  width: 25%;
  overflow-wrap: anywhere;
}
.inv tbody td { color: var(--ink-2); font-size: 0.9375rem; line-height: 1.6; }
.inv-n {
  font-family: var(--mono);
  font-size: 0.9375rem;
  color: var(--ink);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  width: 1%;
}
.inv-n.zero { color: var(--hold-ink); }
.inv tbody tr.has-sub th,
.inv tbody tr.has-sub td { border-bottom: 0; padding-bottom: 5px; }
.inv tbody tr.sub th { color: var(--ink-3); padding-top: 0; padding-left: 34px; }
.inv tbody tr.sub td { padding-top: 0; }
.inv tbody tr.sub .inv-n { color: var(--ink-2); }
.inv tbody tr:last-child th,
.inv tbody tr:last-child td { border-bottom: 0; }
.inv tbody tr { transition: background-color 0.2s ease; }
@media (hover: hover) {
  .inv tbody tr:not(.sub):hover,
  .inv tbody tr.has-sub:hover + tr.sub { background: var(--paper-2); }
}
@media (max-width: 899px) {
  .inv, .inv tbody, .inv tr, .inv th, .inv td { display: block; width: auto; }
  .inv thead { display: none; }
  .inv tbody tr { border-bottom: 1px solid var(--line); padding: 14px 16px; }
  .inv tbody tr.has-sub { border-bottom: 0; padding-bottom: 0; }
  .inv tbody tr.sub { padding-top: 11px; padding-left: 34px; }
  .inv tbody tr:last-child { border-bottom: 0; }
  .inv tbody th, .inv tbody td { width: auto; padding: 0; border-bottom: 0; }
  .inv tbody tr.sub th { padding-left: 0; }
  .inv-n { width: auto; margin-top: 3px; }
  .inv tbody td:last-child { margin-top: 7px; }
}
.inv-more { margin-top: 20px; max-width: 64ch; }

/* ---------- What it is not ---------- */
.nots { margin-top: clamp(30px, 4vw, 44px); border-top: 1px solid var(--ink); }
.nots li { border-bottom: 1px solid var(--line); padding: 19px 0 21px; color: var(--ink-2); }
.not-name {
  display: block;
  font-family: var(--serif);
  font-size: 1.1875rem;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.008em;
  margin-bottom: 7px;
}
.not-text { display: block; max-width: 64ch; }
@media (min-width: 900px) {
  .nots li { display: grid; grid-template-columns: minmax(200px, 268px) minmax(0, 1fr); gap: 0 clamp(20px, 3vw, 40px); align-items: start; }
  .not-name { margin-bottom: 0; }
}

/* ---------- Release ---------- */
.release { margin-top: clamp(26px, 3.4vw, 38px); }
.release-a { display: flex; flex-direction: column; gap: 18px; }
@media (min-width: 780px) {
  .release-a { flex-direction: row; align-items: flex-end; justify-content: space-between; flex-wrap: wrap; gap: 18px clamp(32px, 5vw, 72px); }
}
.release-b { margin-top: clamp(28px, 3.4vw, 40px); }
.release-field { font-family: var(--mono); font-size: 0.75rem; color: var(--ink-3); }
.release-date {
  margin-top: 8px;
  font-family: var(--serif);
  font-size: clamp(2.25rem, 5vw, 3.25rem);
  font-weight: 400;
  letter-spacing: -0.022em;
  line-height: 1.02;
  color: var(--ink);
  padding-bottom: 14px;
  border-bottom: 2px solid var(--violet);
}
.release-note { max-width: 40ch; flex: 1 1 280px; }
.plan { margin-top: 20px; border-top: 1px solid var(--ink); }
.plan li {
  border-bottom: 1px solid var(--line);
  padding: 15px 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: 4px;
  color: var(--ink-2);
}
@media (min-width: 680px) {
  .plan li { grid-template-columns: minmax(150px, 214px) minmax(0, 1fr); gap: 0 clamp(20px, 3vw, 36px); }
}
.plan-key { font-family: var(--mono); font-size: 0.8125rem; color: var(--violet-deep); padding-top: 0.28em; }
.plan span:last-child { max-width: 62ch; }

/* ---------- Footer ---------- */
.site-footer {
  position: relative;
  flex-shrink: 0;
  border-top: 1px solid var(--ink);
  padding-top: clamp(28px, 3.4vw, 48px);
  padding-bottom: calc(40px + env(safe-area-inset-bottom, 0px));
}
.footer-grid { display: grid; grid-template-columns: 1fr; gap: 26px; }
@media (min-width: 900px) {
  .footer-grid { grid-template-columns: minmax(260px, 400px) minmax(0, 1fr); gap: 0 clamp(28px, 4vw, 64px); }
  .footer-b { border-left: 1px solid var(--line); padding-left: clamp(24px, 3vw, 44px); }
}
.footer-mark { display: flex; align-items: center; gap: 11px; }
.footer-mark img { display: block; width: 26px; height: 26px; }
.footer-mark span { font-family: var(--serif); font-size: 1.1875rem; font-weight: 500; color: var(--ink); }
.footer-line {
  margin-top: 22px;
  font-family: var(--serif);
  font-size: clamp(1.375rem, 2.4vw, 1.875rem);
  font-weight: 400;
  line-height: 1.12;
  letter-spacing: -0.014em;
  color: var(--ink);
  max-width: 24ch;
}
.footer-legal { font-size: 0.8125rem; line-height: 1.65; color: var(--ink-2); max-width: 66ch; }
.footer-legal + .footer-legal { margin-top: 12px; }
.footer-legal:target { background: var(--wash); box-shadow: 0 0 0 6px var(--wash); }

/* Pointer to long-form analysis. Deliberately quiet: it is a link, not a banner. */
.footer-reading { margin-top: 24px; padding-top: 16px; border-top: 1px solid var(--line); }
.footer-reading .lbl {
  display: block;
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  margin-bottom: 7px;
}
.footer-reading a { font-size: 0.9375rem; line-height: 1.5; }

/* ---------- Waitlist ----------
   Fixed to the bottom as a status line, in the same register as the section
   rail, not as a marketing banner. Works with JavaScript disabled: it is a
   plain form POST. The field names are read by the server: do not change
   them, and keep the honeypot positioned off screen rather than hidden, or
   a bot will stop filling it. */
.wl {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 45;
  background: var(--paper);
  border-top: 1px solid var(--ink);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
.wl-in {
  width: 100%;
  max-width: calc(var(--wrap) + var(--gutter) * 2);
  margin: 0 auto;
  padding: 10px max(var(--gutter), env(safe-area-inset-left, 0px)) 10px max(var(--gutter), env(safe-area-inset-right, 0px));
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 16px;
}
.wl-q { font-family: var(--mono); font-size: 0.75rem; color: var(--ink-3); white-space: nowrap; }
.wl-q b { color: var(--ink); font-weight: 400; }
.wl form { display: flex; flex: 1 1 20rem; min-width: 0; gap: 8px; }
.wl input[type="email"] {
  flex: 1 1 auto;
  min-width: 0;
  background: var(--paper-2);
  color: var(--ink);
  border: 1px solid var(--line-3);
  padding: 8px 10px;
  font-family: var(--mono);
  font-size: 0.875rem;
  line-height: 1.2;
  transition: border-color 0.2s ease;
}
.wl input[type="email"]::placeholder { color: var(--ink-3); }
.wl input[type="email"]:hover { border-color: var(--line-4); }
.wl button {
  background: var(--ink);
  color: var(--paper);
  border: 1px solid var(--ink);
  padding: 8px 18px;
  font-family: var(--sans);
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.2;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color 0.25s ease, border-color 0.25s ease;
}
.wl button:hover,
.wl button:focus-visible { background: var(--violet-deep); border-color: var(--violet-deep); }
.wl-note { font-size: 0.75rem; line-height: 1.5; color: var(--ink-3); flex: 1 1 100%; margin: 0; }
.wl .hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
@media (min-width: 760px) {
  .wl-note { flex: 0 1 auto; margin-left: auto; }
}

/* ---------- Privacy notice ----------
   Not a consent banner: the site sets no cookies and there is nothing to
   consent to. It says what is stored in this browser and where the data
   wording is. Carries the hidden attribute in the markup; site.js shows it
   until it is closed, and without scripts it never appears. Never a modal.
   It sits above the waitlist line, clear of it at every width. */
.notice {
  position: fixed;
  left: 16px;
  bottom: calc(var(--wl-h) + 12px);
  z-index: 40;
  width: calc(100% - 32px);
  max-width: 34rem;
  padding: 16px 18px 12px;
  border: 1px solid var(--line-3);
  background: var(--paper);
  color: var(--ink-2);
  box-shadow: var(--notice-shadow);
  font-size: 0.875rem;
  line-height: 1.5;
}
.notice[hidden] { display: none; }
.notice-label {
  margin-bottom: 6px;
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.04em;
  color: var(--ink-3);
}
.notice-actions { display: flex; align-items: center; justify-content: space-between; gap: 8px 20px; margin-top: 10px; }
.notice-actions a { font-size: 0.875rem; font-weight: 600; padding-block: 4px; }
.notice-close {
  min-width: 44px;
  min-height: 32px;
  margin-right: -6px;
  padding: 4px 10px;
  border: 1px solid var(--line-3);
  background: none;
  font-family: var(--sans);
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.2;
  color: var(--ink);
  cursor: pointer;
  transition: color 0.2s ease, border-color 0.2s ease;
}
.notice-close:hover,
.notice-close:focus-visible { color: var(--violet-deep); border-color: var(--violet); }
.notice.is-out { animation: none; opacity: 0; transform: translateY(12px); transition: opacity 0.3s ease, transform 0.35s var(--ease); }
/* On a very short screen (a laptop at 400% zoom) the notice keeps to one
   short row: the label and the text give way to the link and the button. */
@media (max-height: 460px) {
  .notice { padding: 6px 12px; font-size: 0.8125rem; }
  .notice-label,
  .notice-text { display: none; }
  .notice-actions { margin-top: 0; }
}

/* ---------- Motion ----------
   Rules of the house, the reference's:
   1. Every rule that hides content before animating it sits inside
      (prefers-reduced-motion: no-preference).
   2. Anything that waits for a script to reveal it also needs the .js class,
      which site.js sets as its first statement and removes again on error.
   3. Load animations (the headline, the lede) are pure CSS and run with
      scripts off.
   4. Only transform and opacity are animated. Nothing loops: every animation
      has an end state. */
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes rise { from { transform: translateY(110%); } to { transform: translateY(0); } }
@keyframes rise-soft { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: translateY(0); } }
@keyframes grow-x { from { transform: scaleX(0); } to { transform: scaleX(1); } }
/* The drawn figure. Paths carry pathLength="1000", so one dash pattern suits
   every path whatever its real length. The dash array is dropped on the last
   frame so a finished line is a plain solid stroke at every zoom level. */
@keyframes draw {
  from { stroke-dasharray: 1000 1000; stroke-dashoffset: 1000; }
  99.9% { stroke-dasharray: 1000 1000; stroke-dashoffset: 0; }
  to { stroke-dasharray: none; stroke-dashoffset: 0; }
}
@keyframes pop { from { opacity: 0; transform: scale(0.6); } to { opacity: 1; transform: scale(1); } }

/* Delay steps for the drawing: .dN is N tenths of a second. A lookup table of
   classes, because a strict CSP forbids the inline style attribute that an
   animation-delay per element would otherwise need. */
.d1 { --d: 0.1s; } .d2 { --d: 0.2s; } .d3 { --d: 0.3s; } .d4 { --d: 0.4s; }
.d5 { --d: 0.5s; } .d6 { --d: 0.6s; } .d7 { --d: 0.7s; } .d8 { --d: 0.8s; }
.d9 { --d: 0.9s; } .d10 { --d: 1s; } .d11 { --d: 1.1s; } .d12 { --d: 1.2s; }
.d13 { --d: 1.3s; } .d14 { --d: 1.4s; } .d15 { --d: 1.5s; } .d16 { --d: 1.6s; }

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

  /* The privacy notice rises in once site.js shows it. */
  .notice { animation: rise-soft 0.8s var(--ease) both; }

  /* The headline: words are wrapped in the markup (.w > .wi) and rise in
     reading order. Pure CSS. Words after the thirteenth share one delay. */
  .w { display: inline-block; overflow: clip; overflow-clip-margin: 0.08em; vertical-align: top; padding-bottom: 0.16em; margin-bottom: -0.16em; }
  .wi { display: inline-block; }
  .title-set .wi { animation: rise 0.95s var(--ease) both; animation-delay: calc(var(--k, 13) * 45ms + 120ms); }
  .title-set .w:nth-child(1) .wi { --k: 0; }
  .title-set .w:nth-child(2) .wi { --k: 1; }
  .title-set .w:nth-child(3) .wi { --k: 2; }
  .title-set .w:nth-child(4) .wi { --k: 3; }
  .title-set .w:nth-child(5) .wi { --k: 4; }
  .title-set .w:nth-child(6) .wi { --k: 5; }
  .title-set .w:nth-child(7) .wi { --k: 6; }
  .title-set .w:nth-child(8) .wi { --k: 7; }
  .title-set .w:nth-child(9) .wi { --k: 8; }
  .title-set .w:nth-child(10) .wi { --k: 9; }
  .title-set .w:nth-child(11) .wi { --k: 10; }
  .title-set .w:nth-child(12) .wi { --k: 11; }
  .title-set .w:nth-child(13) .wi { --k: 12; }

  .hero .lede { animation: rise-soft 0.9s var(--ease) 0.62s both; }
  .hero-note { animation: rise-soft 0.9s var(--ease) 0.78s both; }
  .hero > .rail > .rail-mark { animation: fade 0.8s ease 0.1s both; }

  /* Scroll reveals */
  .js [data-reveal] { opacity: 0; transform: translateY(16px); transition: opacity 0.8s ease, transform 0.9s var(--ease); transition-delay: calc(var(--i, 0) * 90ms); }
  .js [data-reveal].is-in { opacity: 1; transform: none; }

  /* Top borders that draw: the border is replaced by a pseudo rule. */
  .js .draws-top { position: relative; border-top-color: transparent; }
  .js .draws-top::after { content: ""; position: absolute; left: 0; right: 0; top: -1px; height: 1px; background: var(--ink); transform: scaleX(0); transform-origin: left center; transition: transform 1.3s var(--ease-draw); }
  .js .draws-top.is-in::after { transform: scaleX(1); }

  .js .site-footer { border-top-color: transparent; }
  .js .site-footer::before { content: ""; position: absolute; left: 0; right: 0; top: -1px; height: 1px; background: var(--ink); transform: scaleX(0); transform-origin: left center; transition: transform 1.3s var(--ease-draw); }
  .js .site-footer.is-in::before { transform: scaleX(1); }

  /* The drawing inks itself in as it enters the viewport, and stops. Hidden
     only behind .js, so with scripts off it is a finished drawing and never a
     blank box, and only inside this media query, so a reader who asked for
     less motion is handed the finished drawing too. */
  .js .figure-draw .dr { stroke-dasharray: 1000 1000; stroke-dashoffset: 1000; }
  .js .figure-draw .fd,
  .js .figure-draw .pp { opacity: 0; }
  .js .figure-draw.is-in .dr { animation: draw var(--t, 0.9s) var(--ease-draw) var(--d, 0s) both; }
  .js .figure-draw.is-in .fd { animation: fade 0.7s ease var(--d, 0s) both; }
  .js .figure-draw.is-in .pp { transform-box: fill-box; transform-origin: center; animation: pop 0.5s var(--ease) var(--d, 0s) both; }
  /* Hairlines are shorter, so they draw faster. Without fill-box above, an SVG
     element scales about the viewBox origin and the pip flies in from the
     corner instead of growing where it sits. */
  .js .figure-draw .vr-hair { --t: 0.6s; }

  /* The rail mark of a band draws its own hairline on the way in. */
  @media (min-width: 1080px) {
    .js .band .rail-body,
    .js .site-footer .rail-body { position: relative; border-left-color: transparent; }
    .js .band .rail-body::before,
    .js .site-footer .rail-body::before { content: ""; position: absolute; left: -1px; top: 0; bottom: 0; width: 1px; background: var(--line); transform: scaleY(0); transform-origin: center top; transition: transform 1.1s var(--ease-draw) 0.1s; }
    .js .band.is-in .rail-body::before,
    .js .site-footer.is-in .rail-body::before { transform: scaleY(1); }
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    transition-delay: 0s !important;
    scroll-behavior: auto !important;
  }
}

/* ---------- Responsive ---------- */
/* A page with no waitlist line reserves nothing for it. The article pages
   get this from analysis.css; the analysis hub carries class="no-wl" on
   <html> instead. Element plus class outranks :root, so this wins over the
   breakpoint values below at every width. */
html.no-wl { --wl-h: 0px; }

@media (max-width: 1000px) { :root { --wl-h: 88px; } }
@media (max-width: 560px) { :root { --wl-h: 116px; } }
@media (max-width: 430px) { :root { --wl-h: 132px; } }
@media (max-width: 680px) {
  :root { --header-h: 60px; }
  body { font-size: 1rem; }
  .wordmark { font-size: 1.25rem; }
  .masthead .shell { align-items: flex-start; }
  .masthead-end { width: 100%; justify-content: space-between; border-top: 1px solid var(--line); padding-top: 8px; }
  .state { justify-content: start; }
  .notice { left: 12px; width: calc(100% - 24px); padding: 12px 14px 8px; font-size: 0.8125rem; }
  .notice-label { display: none; }
  .notice-actions { margin-top: 6px; }
}
@media (max-width: 420px) {
  .artefact-head { font-size: 0.6875rem; }
  .theme-toggle { width: 32px; height: 32px; margin-right: -6px; }
}

/* ---------- Print ---------- */
@media print {
  .masthead, .skip-link, .wl, .notice, .theme-toggle { display: none !important; }
  /* Paper is white whatever the screen shows. */
  :root:root,
  :root:root[data-theme] {
    --paper: #ffffff;
    --paper-2: #f7f7f5;
    --ink: #14151a;
    --ink-2: #43464f;
    --ink-3: #626570;
    --line: #dcdde2;
    --line-2: #ececef;
    --line-3: #b9bbc4;
    --line-4: #8a8d98;
    --violet: #5a3ea8;
    --violet-deep: #43297f;
    --wash: #efeaf9;
    --on-violet: #ffffff;
    --shade: rgba(20, 21, 26, 0.1);
    --code-comment: #5f626d;
    --code-keyword: #5a3ea8;
    --code-string: #2a5b4c;
    --code-number: #8a4412;
    --code-title: #1d4f80;
    --code-attr: #43297f;
    --keep-ink: #43297f;
    --keep-bg: #efeaf9;
    --hold-ink: #8a4412;
    --hold-bg: #fbefe2;
    --wl-h: 0px;
    color-scheme: light;
  }
  *,
  *::before,
  *::after { animation: none !important; transition: none !important; }
  .js [data-reveal] { opacity: 1 !important; transform: none !important; }
  /* A reader who prints from a tab that never scrolled to the drawing still
     gets the drawing, not an empty frame. */
  .js .figure-draw .fd,
  .js .figure-draw .pp { opacity: 1 !important; transform: none !important; }
  .js .figure-draw .dr { stroke-dasharray: none !important; stroke-dashoffset: 0 !important; }
  .js .draws-top::after,
  .js .site-footer::before,
  .js .band .rail-body::before,
  .js .site-footer .rail-body::before { transform: none !important; }
  .page { padding-bottom: 0; }
  body { font-size: 11pt; }
  .band { break-inside: auto; }
  .artefact, .listing, .readings, .inv-wrap, .ftab-wrap, .figure-draw { break-inside: avoid; }
  .dump, .src { white-space: pre-wrap; overflow: visible; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-family: var(--sans); font-size: 0.8em; color: var(--ink-3); }
}
