/* Generated from specs/DESIGN.md by tools/tokens.py. Do not edit.
 * Change a value in the spec and rebuild. That is the whole point:
 * there is one place for a number to be wrong.
 */

:root {
  --c-carbon: #14100C;
  --c-soot: #2B231B;
  --c-stone: #6B5D4B;
  --c-sand: #C9B896;
  --c-plaster: #E8DBC2;
  --c-linen: #F6EEDC;
  --c-night: #0B0F16;
  --c-blue-deep: #12294F;
  --c-blue: #2A5596;
  --c-blue-lit: #7FA9E8;
  --c-green: #1F5540;
  --c-green-lit: #64AD86;
  --c-red: #8E3524;
  --c-red-lit: #DB7355;
  --c-ochre: #7E5316;
  --c-orpiment: #F0C64A;

  --S: 2;
  --px: calc(1px * var(--S));          /* one art pixel */
  --u: calc(4px * var(--S));           /* sub-unit, 4 art px */
  --tile: calc(16px * var(--S));       /* tile, 16 art px */

  --sp-unit: calc(8px * var(--S) / 2);
  --sp-tile: calc(32px * var(--S) / 2);
  --sp-xs: calc(4px * var(--S) / 2);
  --sp-sm: calc(8px * var(--S) / 2);
  --sp-md: calc(16px * var(--S) / 2);
  --sp-lg: calc(24px * var(--S) / 2);
  --sp-xl: calc(32px * var(--S) / 2);
  --sp-xxl: calc(48px * var(--S) / 2);
  --sp-register: calc(64px * var(--S) / 2);
  --sp-gutter: calc(16px * var(--S) / 2);
  --sp-margin: calc(16px * var(--S) / 2);
  --measure: 68ch;
  --wall: calc(640px * var(--S) / 2);

  --duration-instant: 80ms;
  --duration-fast: 160ms;
  --duration-normal: 320ms;
  --duration-travel: 480ms;
  --duration-hold: 0ms;
  --ease-step-2: steps(2, end);
  --ease-step-4: steps(4, end);
  --ease-step-8: steps(8, end);
  --ease-fade: cubic-bezier(0.22, 1, 0.36, 1);
  --distance-nudge: var(--u);
  --distance-step: var(--tile);
  --distance-travel: calc(var(--tile) * 4);
  --ambient-slow: 166ms;
  --ambient-fast: 100ms;

  /* Daylight. The wall lit from a doorway. */
  --primary: #2A5596;
  --action: #2A5596;
  --action-hover: #12294F;
  --on-action: #F6EEDC;
  --secondary: #8E3524;
  --tertiary: #7E5316;
  --neutral: #E8DBC2;
  --ground: #E8DBC2;
  --surface: #F6EEDC;
  --surface-deep: #C9B896;
  --on-surface: #14100C;
  --on-surface-muted: #6B5D4B;
  --border: #14100C;
  --shadow: #2B231B;
  --focus-inner: #14100C;
  --focus-outer: #F0C64A;
  --error: #8E3524;
  --success: #1F5540;
}

@media (min-width: 600px) { :root { --S: 3; } }
@media (min-width: 1100px) { :root { --S: 4; } }

/* Lamplight. The same wall after dark. Roles swap, components do not. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='day']) {
    --ground: #0B0F16;
    --surface: #14100C;
    --surface-deep: #2B231B;
    --on-surface: #F6EEDC;
    --on-surface-muted: #C9B896;
    --border: #C9B896;
    --shadow: #0B0F16;
    --primary: #7FA9E8;
    --secondary: #DB7355;
    --tertiary: #F0C64A;
    --focus-inner: #F0C64A;
    --focus-outer: #14100C;
  }
}
:root[data-theme='night'] {
  --ground: #0B0F16;
  --surface: #14100C;
  --surface-deep: #2B231B;
  --on-surface: #F6EEDC;
  --on-surface-muted: #C9B896;
  --border: #C9B896;
  --shadow: #0B0F16;
  --primary: #7FA9E8;
  --secondary: #DB7355;
  --tertiary: #F0C64A;
  --focus-inner: #F0C64A;
  --focus-outer: #14100C;
}

.chip-carbon { background: var(--c-carbon); }
.chip-soot { background: var(--c-soot); }
.chip-stone { background: var(--c-stone); }
.chip-sand { background: var(--c-sand); }
.chip-plaster { background: var(--c-plaster); }
.chip-linen { background: var(--c-linen); }
.chip-night { background: var(--c-night); }
.chip-blue-deep { background: var(--c-blue-deep); }
.chip-blue { background: var(--c-blue); }
.chip-blue-lit { background: var(--c-blue-lit); }
.chip-green { background: var(--c-green); }
.chip-green-lit { background: var(--c-green-lit); }
.chip-red { background: var(--c-red); }
.chip-red-lit { background: var(--c-red-lit); }
.chip-ochre { background: var(--c-ochre); }
.chip-orpiment { background: var(--c-orpiment); }

/* Type. Pixel tokens step up at the desktop breakpoint. */
.t-wordmark {
  font-family: 'Silkscreen', 'Atkinson Hyperlegible Next', 'Courier New', monospace;
  font-size: 48px;
  line-height: 56px;
  font-weight: 700;
  letter-spacing: 2px;
}
.t-display {
  font-family: 'Silkscreen', 'Atkinson Hyperlegible Next', 'Courier New', monospace;
  font-size: 32px;
  line-height: 40px;
  font-weight: 700;
  letter-spacing: 1px;
}
.t-heading-lg {
  font-family: 'Silkscreen', 'Atkinson Hyperlegible Next', 'Courier New', monospace;
  font-size: 24px;
  line-height: 32px;
  font-weight: 700;
  letter-spacing: 1px;
}
.t-heading-md {
  font-family: 'Silkscreen', 'Atkinson Hyperlegible Next', 'Courier New', monospace;
  font-size: 16px;
  line-height: 24px;
  font-weight: 700;
  letter-spacing: 1px;
}
.t-label {
  font-family: 'Silkscreen', 'Atkinson Hyperlegible Next', 'Courier New', monospace;
  font-size: 16px;
  line-height: 24px;
  font-weight: 400;
  letter-spacing: 1px;
}
.t-metric {
  font-family: 'Silkscreen', 'Atkinson Hyperlegible Next', 'Courier New', monospace;
  font-size: 24px;
  line-height: 32px;
  font-weight: 700;
  letter-spacing: 1px;
  font-feature-settings: 'tnum' 1;
  font-variant-numeric: tabular-nums;
}
.t-metric-sm {
  font-family: 'Silkscreen', 'Atkinson Hyperlegible Next', 'Courier New', monospace;
  font-size: 16px;
  line-height: 24px;
  font-weight: 700;
  letter-spacing: 1px;
  font-feature-settings: 'tnum' 1;
  font-variant-numeric: tabular-nums;
}
.t-body-lead {
  font-family: 'Atkinson Hyperlegible Next', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  font-size: 20px;
  line-height: 32px;
  font-weight: 400;
}
.t-body {
  font-family: 'Atkinson Hyperlegible Next', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  font-size: 17px;
  line-height: 28px;
  font-weight: 400;
}
.t-body-strong {
  font-family: 'Atkinson Hyperlegible Next', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  font-size: 17px;
  line-height: 28px;
  font-weight: 700;
}
.t-body-sm {
  font-family: 'Atkinson Hyperlegible Next', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  font-size: 15px;
  line-height: 24px;
  font-weight: 400;
}
.t-caption {
  font-family: 'Atkinson Hyperlegible Next', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  font-size: 14px;
  line-height: 22px;
  font-weight: 400;
}
@media (min-width: 1100px) {
  .t-wordmark { font-size: 64px; line-height: 72px; }
}
@media (min-width: 1100px) {
  .t-display { font-size: 48px; line-height: 56px; }
}
@media (min-width: 1100px) {
  .t-heading-lg { font-size: 32px; line-height: 40px; }
}
@media (min-width: 1100px) {
  .t-heading-md { font-size: 24px; line-height: 32px; }
}
@media (min-width: 1100px) {
  .t-label { font-size: 16px; line-height: 24px; }
}
@media (min-width: 1100px) {
  .t-metric { font-size: 32px; line-height: 40px; }
}
@media (min-width: 1100px) {
  .t-metric-sm { font-size: 16px; line-height: 24px; }
}

/* Both faces are SIL OFL 1.1. The licence files ship beside them at
 * /assets/fonts/, which is what OFL clause 2 asks for. Neither font
 * declares a Reserved Font Name, so subsetting them is permitted.
 */
@font-face {
  font-family: 'Silkscreen';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/silkscreen-400.ca496a22cdc6.woff2') format('woff2');
  unicode-range: U+0000-0024, U+0027-007D, U+007F-10FFFF;
}
@font-face {
  font-family: 'Silkscreen';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/assets/fonts/silkscreen-700.f38347166850.woff2') format('woff2');
  unicode-range: U+0000-0024, U+0027-007D, U+007F-10FFFF;
}
@font-face {
  font-family: 'Atkinson Hyperlegible Next';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/assets/fonts/atkinson-hyperlegible-next-var.f13b3874a751.woff2') format('woff2');
}

/* Register, the component layer.
 *
 * Tokens come from specs/DESIGN.md through tools/tokens.py and are
 * prepended to this file at build time. Nothing here restates a value the
 * spec already carries; everything here is behaviour tokens cannot express.
 *
 * Four rules govern the whole file, from DESIGN.md:
 *   no blur, anywhere, for anything
 *   no rounded corner, ever
 *   things move in steps, they fade smoothly
 *   no seventeenth colour, including as an opacity variant
 */

/* --- derived values -------------------------------------------------- */

:root {
  /* Two art pixels. 4px at 2x, 6px at 3x, 8px at 4x. Every panel border
     and the rule are drawn at this width. */
  --border-w: calc(2px * var(--S));

  /* Depth. Solid rectangles offset by whole sub-units, zero blur. */
  --shadow-raised: var(--u) var(--u) 0 var(--shadow);
  --shadow-lifted: calc(var(--u) * 2) calc(var(--u) * 2) 0 var(--shadow);

  /* Bevel. One art pixel of light top and left, dark bottom and right.
     Reversed for a sunk surface. Built from the surface's own ramp, so it
     costs no new colour. */
  --bevel-raised:
    inset var(--px) var(--px) 0 var(--c-linen),
    inset calc(var(--px) * -1) calc(var(--px) * -1) 0 var(--c-stone);
  --bevel-sunk:
    inset var(--px) var(--px) 0 var(--c-stone),
    inset calc(var(--px) * -1) calc(var(--px) * -1) 0 var(--c-linen);
}

:root[data-theme="night"],
:root:not([data-theme="day"]) {
  --bevel-raised:
    inset var(--px) var(--px) 0 var(--c-stone),
    inset calc(var(--px) * -1) calc(var(--px) * -1) 0 var(--c-night);
  --bevel-sunk:
    inset var(--px) var(--px) 0 var(--c-night),
    inset calc(var(--px) * -1) calc(var(--px) * -1) 0 var(--c-stone);
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme="night"]) {
    --bevel-raised:
      inset var(--px) var(--px) 0 var(--c-linen),
      inset calc(var(--px) * -1) calc(var(--px) * -1) 0 var(--c-stone);
    --bevel-sunk:
      inset var(--px) var(--px) 0 var(--c-stone),
      inset calc(var(--px) * -1) calc(var(--px) * -1) 0 var(--c-linen);
  }
}

/* --- base ------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  background: var(--ground);
}

body {
  margin: 0;
  padding: 0;
  background: var(--ground);
  color: var(--on-surface);
  font-family: "Atkinson Hyperlegible Next", system-ui, -apple-system, sans-serif;
  font-size: 17px;
  line-height: 28px;
  /* The plain path prints full URLs as link text, and a URL is one
     unbreakable word. Without this it pushes the page sideways at 360px. */
  overflow-wrap: break-word;
}

/* Antialiasing is off for the carved voice and on for the spoken one.
 * The global "antialias everything" advice does not survive contact with a
 * bitmap face. Constitution Principle V records this as the exception. */
.t-wordmark, .t-display, .t-heading-lg, .t-heading-md,
.t-label, .t-metric, .t-metric-sm, .pixel {
  -webkit-font-smoothing: none;
  -moz-osx-font-smoothing: unset;
  font-smooth: never;
  text-rendering: optimizeSpeed;
}

.prose, p, li, dd, blockquote {
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, .sprite, .tiled { image-rendering: pixelated; }

/* --- layout ----------------------------------------------------------- */

.wall {
  max-width: var(--wall);
  margin-inline: auto;
  padding-inline: var(--sp-margin);
}

.column { max-width: var(--measure); }

/* --- type ------------------------------------------------------------- */

h1, h2, h3, h4, h5, h6 { margin: 0; font-weight: inherit; }
p, ul, ol, dl, blockquote, table { margin: 0 0 var(--sp-md); }
ul, ol { padding-left: var(--sp-lg); }

.t-label, .t-label * { text-transform: uppercase; }

blockquote {
  margin-inline: 0;
  padding: var(--sp-sm) var(--sp-md);
  border-left: var(--border-w) solid var(--tertiary);
  color: var(--on-surface-muted);
}

code {
  font-family: "Silkscreen", "Courier New", monospace;
  font-size: 0.9em;
}

/* --- links and the focus ring ---------------------------------------- */

/* Blue is the only interactive colour. If it is blue, it does something. */
a { color: var(--primary); text-underline-offset: 3px; text-decoration-thickness: 1px; }
a:hover { text-decoration-thickness: var(--px); }

/* Two rings, drawn with box-shadow rather than outline so the corners stay
 * square. The pair inverts between themes, so it is visible on any surface
 * in either one. Tested against a worst-case tiled background in the
 * rendering spike. */
:focus { outline: none; }
:focus-visible {
  outline: none;
  box-shadow:
    0 0 0 var(--border-w) var(--focus-inner),
    0 0 0 calc(var(--border-w) * 2) var(--focus-outer);
  position: relative;
  z-index: 1;
}

/* --- registers -------------------------------------------------------- */

/* `spacing.register` separates registers. It is not padding inside each
 * one: applied both ways it doubled, and Home's sourced claim fell below
 * the fold on a 360 by 640 viewport, which breaks the seven-second
 * contract in world-design.md. Separation between, modest padding within. */
.register { padding-block: var(--sp-lg); }
.register + .register { margin-block-start: var(--sp-register); }

.masthead { padding-block: var(--sp-lg) var(--sp-sm); }
.masthead .role { margin: 0; color: var(--on-surface-muted); }

/* The section index. Marks on the wall, not a bulleted list. */
.index { padding-block: var(--sp-sm); }
.index ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--sp-md);
  list-style: none;
  padding: 0;
  margin: 0;
}
.index li { display: block; }

.doorways, .contact-links {
  list-style: none;
  padding: 0;
  display: grid;
  gap: var(--sp-md);
}

.doorway h4 { margin-bottom: var(--sp-xs); }
.doorway p { margin-bottom: var(--sp-xs); }
.doorway p:last-child { margin-bottom: 0; }

.band { margin-block: var(--sp-xl); }
.claim { color: var(--on-surface-muted); }

.crumb { padding-block: var(--sp-sm); }
.foot { margin-block-start: var(--sp-register); border-top: var(--border-w) solid var(--border); }
.foot p { margin-bottom: var(--sp-xs); }

/* Doorway shape by tier, so the Work register is not a card grid.
 * world-design.md fixes the heights in tiles. */
@media (min-width: 600px) {
  .doorways { grid-template-columns: repeat(2, 1fr); }
  .shape-full { grid-column: 1 / -1; }
}

.rule {
  height: var(--border-w);
  background: var(--border);
  border: 0;
  margin: var(--sp-lg) 0;
}

/* The frieze. A patterned band one tile tall spanning the wall.
 * `chequer` is drawn here with hard colour stops rather than as an image:
 * it is exact on the grid, it costs no request, and it recolours with the
 * theme. `lotus` and `cord` are sprite tiles and arrive with the asset
 * generator in Phase 3.3. */
.frieze {
  height: var(--tile);
  background:
    repeating-conic-gradient(
      var(--surface-deep) 0% 25%,
      var(--ground) 0% 50%
    ) 0 0 / calc(var(--tile) / 2) calc(var(--tile) / 2);
  border-block: var(--border-w) solid var(--border);
}

/* --- panels ----------------------------------------------------------- */

/* A border and a shadow together is the rule, not a stack. Never nest a
 * panel inside a panel: that is a layout that has not been decided yet. */
.panel {
  background: var(--surface);
  color: var(--on-surface);
  border: var(--border-w) solid var(--border);
  padding: var(--sp-lg);
  box-shadow: var(--bevel-raised), var(--shadow-raised);
}

.panel-sunk {
  background: var(--surface-deep);
  color: var(--on-surface);
  border: var(--border-w) solid var(--border);
  padding: var(--sp-md);
  box-shadow: var(--bevel-sunk);
}

.panel-lifted { box-shadow: var(--bevel-raised), var(--shadow-lifted); }

/* The notch. One tile removed from a corner, marking the world you are in.
 * One notch, one meaning, never decorative. */
.notch {
  clip-path: polygon(
    0 0, calc(100% - var(--tile)) 0, 100% var(--tile), 100% 100%, 0 100%
  );
}

/* --- buttons ---------------------------------------------------------- */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  min-width: 48px;
  padding: 0 var(--sp-md);
  border: var(--border-w) solid var(--border);
  background: var(--surface);
  color: var(--on-surface);
  font-family: "Silkscreen", "Courier New", monospace;
  font-size: 16px;
  line-height: 24px;
  letter-spacing: 1px;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  box-shadow: var(--bevel-raised), var(--shadow-raised);
  transition: background-color var(--duration-fast) var(--ease-fade);
}

/* `action`, not `primary`. A link sits on the page ground and has to
 * lighten after dark; a button carries its own surface and must not.
 * Bound to `primary`, this painted linen on blue-lit at 2.08:1 every
 * night. See DESIGN.md, Buttons. */
.button-primary { background: var(--action); color: var(--on-action); }

/* Press moves the element down and right by one sub-unit and removes the
 * shadow. That is a button being pressed, rather than a button pretending. */
.button:active {
  transform: translate(var(--u), var(--u));
  box-shadow: var(--bevel-sunk);
  transition-duration: var(--duration-instant);
}

@media (hover: hover) {
  .button:hover { background: var(--surface-deep); }
  .button-primary:hover { background: var(--action-hover); color: var(--on-action); }
}

/* --- measured numbers ------------------------------------------------- */

/* The most important component here, because the site's argument runs
 * through it. Never animates, never counts up, never appears without its
 * source. */
.metric-readout {
  display: block;
  background: var(--surface-deep);
  border: var(--border-w) solid var(--border);
  padding: var(--sp-md);
  box-shadow: var(--bevel-sunk);
  max-width: max-content;
}

/* Size, family, and figures come from the .t-metric-sm token, which is
 * generated from the spec. All this rule adds is the warmth. */
.metric { color: var(--tertiary); }

/* The source mark. Ochre, beside every published number.
 *
 * Landed 2026-08-30, ruled 2026-08-29. Until now the caption printed inline
 * in both render modes, and on the home lead it cut the site's opening
 * sentence in half: "reads 171 hieroglyph signs at 98.2% [the project's own
 * README, checked 2026-08-28], inside a phone browser". It is a mark now,
 * with the sources collected under the region -- which is what
 * `docs/design-directions.md` called the proper fix and what Phase 3.1 was
 * deferring when it called the printed caption "the safe direction to be
 * wrong in".
 *
 * A letter rather than a numeral: every numeral on this site has to resolve
 * to a file under content/, and a footnote mark resolves to nothing because
 * it claims nothing. `tools/render.py` says the same thing at more length.
 *
 * Raised rather than `<sup>`, because superscript in a bitmap face at a
 * fraction of the size is a smudge. Full size, lifted, in ochre. */
/* Likewise: .t-caption carries the type, this carries the ochre. */
.source, .source-mark { color: var(--tertiary); text-transform: none; }

a.source-mark {
  text-decoration: none;
  padding: 0 2px 0 3px;
  vertical-align: super;
  line-height: 0;
}
a.source-mark:hover, a.source-mark:focus-visible { text-decoration: underline; }

.sources { color: var(--on-surface-muted); font-size: 14px; line-height: 22px; }
.sources ul { list-style: none; padding: 0; margin: 0; }
.sources > p { margin: 0 0 var(--sp-xs); }
.sources li + li { margin-top: var(--sp-xs); }
.sources .source-mark { display: inline-block; min-width: 12px; }

/* --- tags, nav, chrome ------------------------------------------------ */

.tag {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  padding: 0 var(--sp-sm);
  background: var(--surface-deep);
  color: var(--on-surface);
  border: var(--border-w) solid var(--border);
}

.nav-item {
  display: inline-flex;
  align-items: center;
  min-height: 48px;
  padding: 0 var(--sp-md);
  color: var(--on-surface);
  text-decoration: none;
}

.nav-item[aria-current] {
  background: var(--c-carbon);
  color: var(--c-orpiment);
}

/* Fixed to the top-left corner, visible from the first frame, first in tab
 * order. Not an icon, not hidden until focus, not "boring mode". */
.skip {
  display: inline-flex;
  align-items: center;
  min-height: 48px;
  padding: 0 var(--sp-md);
  background: var(--c-carbon);
  color: var(--c-orpiment);
  text-decoration: none;
  border: var(--border-w) solid var(--c-carbon);
}

.settings {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-sm);
  align-items: center;
}

.setting {
  display: inline-flex;
  align-items: center;
  min-height: 48px;
  padding: 0 var(--sp-sm);
  background: var(--surface);
  color: var(--on-surface);
  border: var(--border-w) solid var(--border);
  cursor: pointer;
  font-family: "Silkscreen", "Courier New", monospace;
  font-size: 16px;
  line-height: 24px;
  letter-spacing: 1px;
  text-transform: uppercase;
}

.setting[aria-pressed="true"],
.setting[data-active="true"] {
  background: var(--c-carbon);
  color: var(--c-orpiment);
}

/* --- portrait --------------------------------------------------------- */

/* A sunk panel with a one-tile margin. The portrait never crops to a
 * circle, because a circle in a bitmap is a staircase. */
.portrait-frame {
  display: inline-block;
  padding: var(--tile);
  background: var(--surface-deep);
  border: var(--border-w) solid var(--border);
  box-shadow: var(--bevel-sunk);
}

.portrait-frame img { display: block; image-rendering: pixelated; }

/* --- hit areas -------------------------------------------------------- */

/* 44 x 44 CSS pixels minimum for anything interactive, regardless of scale.
 * WCAG 2.2 AA asks for 24. The larger number is a house rule, because a
 * tile grid makes small targets tempting. */
a, button, [role="button"], input, select, summary {
  min-height: 44px;
}

a { min-height: 0; }          /* inline links inside prose are exempt */
a.hit, .nav-item, .skip, .button, .setting {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}

/* Hover affordances never carry meaning, and they do not exist on a phone. */
@media (hover: none) {
  .nav-item:hover, .skip:hover { text-decoration: none; }
}

/* --- motion ----------------------------------------------------------- */

/* Three levels, not two. Reduce does not mean remove: the world keeps its
 * colour, its art, and its layout, and has simply stopped moving. */
:root[data-motion="reduced"] *,
:root[data-motion="reduced"] *::before,
:root[data-motion="reduced"] *::after {
  animation-duration: 1ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: var(--duration-instant) !important;
  transition-property: opacity, background-color, color, box-shadow !important;
  transform: none !important;
  scroll-behavior: auto !important;
}

@media (prefers-reduced-motion: reduce) {
  :root:not([data-motion="full"]) *,
  :root:not([data-motion="full"]) *::before,
  :root:not([data-motion="full"]) *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: var(--duration-instant) !important;
    transform: none !important;
    scroll-behavior: auto !important;
  }
}

/* The theme switch is never animated. A stepped transform caught mid-swap
 * looks broken, and the swap has nothing to communicate. */
:root, :root * { transition-property: none; }
.button, .setting, a { transition-property: background-color, color; }

/* --- print ------------------------------------------------------------ */

@media print {
  :root { --S: 2; }
  body { background: #fff; color: #000; font-size: 11pt; line-height: 1.5; }
  .skip, .settings, .frieze, nav { display: none; }
  .panel, .panel-sunk, .metric-readout { box-shadow: none; border: 1px solid #000; }
  a { color: #000; text-decoration: underline; }
  .metric, .source, .source-mark, .sources { color: #333; }
  h1, h2, h3 { page-break-after: avoid; }
}

/* --- the component gallery -------------------------------------------
 * Only used by /components/, which is a review surface rather than part
 * of the site. Kept here so it goes through the same tokens and the same
 * contrast check as everything else.
 */

.row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-md);
  align-items: flex-start;
  margin-bottom: var(--sp-md);
}

.swatches {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: var(--sp-sm);
  list-style: none;
  padding: 0;
}

.swatch { display: flex; align-items: center; gap: var(--sp-sm); }

.swatch-chip {
  display: block;
  width: var(--tile);
  height: var(--tile);
  border: var(--border-w) solid var(--border);
}

.portrait-placeholder {
  width: calc(var(--tile) * 3);
  height: calc(var(--tile) * 3);
  background: var(--surface);
  border: var(--border-w) solid var(--border);
}

.scale-row { align-items: center; }
.scale-box { display: block; background: var(--tertiary); }
.scale-px { width: var(--px); height: var(--tile); }
.scale-u { width: var(--u); height: var(--tile); }
.scale-tile { width: var(--tile); height: var(--tile); }

/* Inside a sunk surface the warm treatment steps back to the ink colour.
 *
 * In Daylight `surface-deep` is sand, and DESIGN.md's own forbidden-pairings
 * table says sand carries only carbon, soot, and blue-deep. Ochre on sand
 * computes to 3.44, under the 4.5 text floor, and no warm value in the
 * sixteen clears it. So the metric readout keeps its sunk panel and gives up
 * the warmth rather than the contrast.
 *
 * DESIGN.md's `source-mark` component token asks for ochre on surface-deep,
 * which is the pairing its own table forbids. Raised as Q12. The computed
 * table wins, the same way it did for Q11.
 */
.panel-sunk .metric, .metric-readout .metric,
.panel-sunk .source, .metric-readout .source,
.panel-sunk .source-mark, .metric-readout .source-mark,
.limits .metric, .limits .source {
  color: var(--on-surface);
}

/* An email address is not a label. Set in uppercase pixel type at 16px with
 * letter spacing, support@notmrrobot.com pushed a 320px viewport 10px wide,
 * and an address is something a visitor reads character by character before
 * copying it. The prose face is the right voice for it. */
.contact-links .nav-item { text-transform: none; letter-spacing: 0; }
