/* 1dp global stylesheet entry.
   build.js resolves these @imports inline, so the browser fetches ONE
   stylesheet with no request waterfall. Order matters: tokens first. */
/* --- tokens/fonts.css --- */
/* 1dp fonts — IBM Plex family. Nerdy, warm, retro-adjacent.
   Self-hosted (latin subset, woff2) rather than the design snapshot's Google
   Fonts @import: no third-party request on first paint, no privacy handoff,
   and the build stays offline-capable. Files live in /fonts/, SIL OFL 1.1
   (see fonts/LICENSE.txt). Weights match what the system actually uses:
   mono 400/500/600 + 400 italic, sans 400/500/600. */

@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/fonts/plex-mono-400.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/fonts/plex-mono-500.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/fonts/plex-mono-600.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("/fonts/plex-mono-400-italic.woff2") format("woff2");
}

@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/fonts/plex-sans-400.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/fonts/plex-sans-500.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/fonts/plex-sans-600.woff2") format("woff2");
}

/* --- tokens/colors.css --- */
/* 1dp color system — warm papyrus greys, one calculator-key orange accent,
   and an LCD "screen" surface for segment displays / terminal.
   Light is default; dark via [data-theme="dark"] on <html> (or any ancestor). */

:root {
  color-scheme: light;
  /* base ramp — warm paper */
  --paper-0: #ECE8DD;   /* page */
  --paper-1: #F5F2E9;   /* raised card */
  --paper-2: #E1DCCC;   /* inset well / key bed */
  --paper-3: #D5CFBC;   /* deeper inset */
  --ink-1: #262319;     /* primary text */
  --ink-2: #5C574A;     /* secondary */
  --ink-3: #928C7B;     /* tertiary / hints */
  --line-1: #CFC9B6;    /* default border */
  --line-2: #B9B29C;    /* strong border */

  /* accent — calculator-key orange */
  --accent: #C4581F;
  --accent-hover: #B04E1B;
  --accent-press: #9A4417;
  --accent-ink: #F7F3E8;      /* text on accent */
  --accent-soft: #EBD9C4;     /* tinted chip bg */
  /* AA-safe accent roles: text = links/small accent text on paper or chip;
     bed = accent surface carrying --accent-ink labels. #C4581F stays for
     decorative/large uses (focus ring, borders, 2xl numerals, segments). */
  --accent-text: #9A4417;         /* 5.3:1 on paper, 4.8:1 on chip */
  --accent-text-hover: #7E3712;
  --accent-bed: #9A4417;          /* accent-ink on it: 6.3:1 */
  --accent-bed-hover: #8A3D14;
  --accent-bed-edge: #6F310F;

  /* LCD screen (segment displays, terminal) */
  --screen-bg: #C8CDB4;
  --screen-ink: #2A2F22;
  --screen-ghost: rgba(42, 47, 34, 0.10);  /* unlit segments */
  --screen-line: #AEB49A;

  /* semantic */
  --surface-page: var(--paper-0);
  --surface-card: var(--paper-1);
  --surface-inset: var(--paper-2);
  --text-primary: var(--ink-1);
  --text-secondary: var(--ink-2);
  --text-tertiary: var(--ink-3);
  --border-default: var(--line-1);
  --border-strong: var(--line-2);
  --focus-ring: 0 0 0 2px var(--paper-0), 0 0 0 4px var(--accent);
  --ok: #4A6741;
  --warn: #A32B1C;   /* true red, hue-distinct from --accent; AA at any size:
                        5.9 page / 6.4 card / 5.2 inset (F-1097) */
}

[data-theme="dark"] {
  color-scheme: dark;
  --paper-0: #1B1912;
  --paper-1: #26231A;
  --paper-2: #131109;
  --paper-3: #0D0B06;
  --ink-1: #EAE5D6;
  --ink-2: #A9A290;
  --ink-3: #6F695A;
  --line-1: #3A362A;
  --line-2: #4E4938;

  --accent: #E0763A;
  --accent-hover: #E98A50;
  --accent-press: #C9662E;
  --accent-ink: #1B1912;
  --accent-soft: #3D2C1D;
  --accent-text: #E98A50;         /* 5.2:1 on dark chip */
  --accent-text-hover: #F09A66;
  --accent-bed: #E0763A;          /* dark beds already pass */
  --accent-bed-hover: #E98A50;
  --accent-bed-edge: #C9662E;

  --screen-bg: #171A11;
  --screen-ink: #C4D3A2;
  --screen-ghost: rgba(196, 211, 162, 0.09);
  --screen-line: #2A2E1E;

  --ok: #8FAE83;
  --warn: #E88A78;   /* 7.0 page / 6.2 card / 7.5 inset (F-1097) */

  /* re-declare semantic aliases so they re-resolve against the dark ramp
     (var() on :root resolves at computed-value time, then inherits resolved) */
  --surface-page: var(--paper-0);
  --surface-card: var(--paper-1);
  --surface-inset: var(--paper-2);
  --text-primary: var(--ink-1);
  --text-secondary: var(--ink-2);
  --text-tertiary: var(--ink-3);
  --border-default: var(--line-1);
  --border-strong: var(--line-2);
  --focus-ring: 0 0 0 2px var(--paper-0), 0 0 0 4px var(--accent);
}

/* --- tokens/typography.css --- */
/* 1dp typography — IBM Plex Mono leads (headlines, numbers, UI chrome),
   IBM Plex Sans for body copy. Segment digits are drawn by the SegmentDisplay
   component, never a font. */

:root {
  --font-mono: "IBM Plex Mono", "SFMono-Regular", Menlo, monospace;
  --font-sans: "IBM Plex Sans", "Helvetica Neue", Arial, sans-serif;

  /* scale */
  --text-xs: 12px;
  --text-sm: 13.5px;
  --text-base: 15.5px;
  --text-lg: 18px;
  --text-xl: 22px;
  --text-2xl: 28px;
  --text-3xl: 38px;
  --text-4xl: 54px;
  --text-hero: clamp(44px, 7vw, 84px);

  --leading-tight: 1.1;
  --leading-snug: 1.3;
  --leading-body: 1.55;

  --tracking-wide: 0.08em;   /* mono labels, ALL-CAPS eyebrows */
  --tracking-tight: -0.01em; /* large display sizes */
}

/* --- tokens/spacing.css --- */
/* 1dp spacing + radii — 4px base grid. */
:root {
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;
  --space-9: 96px;

  /* radii — calculator-key rounding, nothing bubbly */
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 14px;
  --radius-screen: 8px;  /* LCD panels */
  --radius-pill: 999px;

  --measure: 64ch;       /* max body-copy width */
}

/* --- tokens/effects.css --- */
/* 1dp effects — the "key" elevation system.
   Interactive things are calculator keys: a hard 2px bottom edge, no blur.
   Press = the key travels down (translateY 1-2px, edge shrinks). */
:root {
  --edge-color: var(--line-2);
  --shadow-key: 0 2px 0 var(--edge-color);          /* resting key */
  --shadow-key-press: 0 0 0 var(--edge-color);      /* pressed */
  --shadow-card: 0 2px 0 var(--line-1);             /* raised card */
  --shadow-screen: inset 0 2px 4px rgba(0,0,0,0.18);/* recessed LCD */
  --ease-key: cubic-bezier(0.2, 0, 0.4, 1);
  --dur-key: 90ms;      /* press feedback */
  --dur-ui: 180ms;      /* hovers, toggles */
  --dur-flicker: 720ms; /* segment power-on */
}

/* --- site.css --- */
/* 1dp site stylesheet — the shared vocabulary every page uses.
 *
 * The design snapshot expressed all of this as inline styles on x-dc templates.
 * Here it is classes, so a page is readable HTML and a change lands everywhere
 * at once. Everything below reads tokens only — no literal colors, no literal
 * radii — which is what makes both themes automatic.
 *
 * Page tasks: prefer composing these classes over new inline styles. If a page
 * genuinely needs something new, add it here so the next page can reuse it.
 */

/* ---------- the one token this stylesheet declares ---------- */

/* `--text-tertiary`, the design system's hint grey, reads at 2.4-3.5:1 on
 * every paper surface in BOTH themes — under the 4.5:1 WCAG AA asks of text
 * below 24px, and everything that used it here is 12px meta text: the
 * landing well's placeholder, the footer copy, the board meta, the `#008`
 * ids, the comment bylines (measured in F-1091; Lighthouse failed
 * color-contrast on the live site). `tokens/` is upstream and byte-identical
 * to the design snapshot, so the grey itself is not ours to darken — but
 * which ink the quiet layer points at IS ours.
 *
 * So quiet text is the SECONDARY ink (5.2-7.4:1 on every surface, both
 * themes), and the layering that grey used to carry is carried by size,
 * weight and the mono face instead — which is how `.eyebrow` always did it.
 * `.muted` and `.faint` consequently render the same; that is the trade, not
 * an oversight. If the design system ever ships a tertiary that clears AA,
 * this one line moves back and test/contrast.test.mjs will notice.
 *
 * Declared twice for the reason tokens/colors.css re-declares its semantic
 * aliases: var() resolves against the element it is declared on, so a
 * :root-only alias would freeze at the light value whenever a SUBTREE rather
 * than <html> carries data-theme="dark".
 */
:root {
  --text-quiet: var(--text-secondary);
}
[data-theme="dark"] {
  --text-quiet: var(--text-secondary);
}

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

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

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--surface-page);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--leading-body);
  transition: background var(--dur-ui) linear, color var(--dur-ui) linear;
  -webkit-font-smoothing: antialiased;
}

/* The browser's own [hidden] rule is a bare `display: none`, so ANY class that
   sets display beats it — `.form { display: flex }` kept a submitted form on
   screen underneath its own confirmation panel (caught in the browser, F-1077).
   Scripts toggle `hidden`; this makes that mean what it says. */
[hidden] {
  display: none !important;
}

/* Link text takes --accent-text, never --accent (F-1096). The key orange is
   3.60:1 on the light page — under AA — and every page is mostly links.
   See docs/contrast.md for which accent role goes where. */
a {
  color: var(--accent-text);
  text-decoration: underline;
  text-underline-offset: 3px;
}
a:hover {
  color: var(--accent-text-hover);
}

/* The focus ring is an OUTLINE, and that is load-bearing (F-1089). It used to
   be `box-shadow: var(--focus-ring)`, which lost the cascade on every button
   on the site: each key-style class draws its 2px key edge with box-shadow at
   the SAME specificity as this rule and later in the sheet, so .btn, .vote,
   .btn-quiet and friends silently overwrote the ring — WCAG 2.4.7 failed for
   every control while links (no box-shadow of their own) looked fine.
   An outline cannot collide with the key edge, follows each element's own
   border-radius, and survives forced-colors mode, which a box-shadow ring
   does not. The corollary: NOTHING else in this stylesheet may declare
   `outline` — test/focus-ring.test.mjs resolves the cascade for every
   focusable control and fails if a later rule takes the ring away. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

h1,
h2,
h3 {
  font-family: var(--font-mono);
  font-weight: 600;
  letter-spacing: var(--tracking-tight);
  line-height: var(--leading-tight);
  margin: 0;
  text-wrap: balance;
}

h1 {
  font-size: var(--text-hero);
}
h2 {
  font-size: var(--text-3xl);
}
h3 {
  font-size: var(--text-xl);
  font-weight: 500;
  letter-spacing: normal;
}

p {
  margin: 0;
  text-wrap: pretty;
}

@keyframes blink {
  0%,
  49% {
    opacity: 1;
  }
  50%,
  100% {
    opacity: 0;
  }
}

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

.wrap {
  max-width: 1080px;
  margin: 0 auto;
  padding-inline: var(--space-5);
}

.section {
  border-top: 1px solid var(--border-default);
}

.section > .wrap {
  padding-block: var(--space-8);
}

/* A single-column page — support, legal, anything that is one conversation
   rather than a grid. The design's narrow measure, 760px. */
.wrap-narrow {
  max-width: 760px;
}

.stack {
  display: flex;
  flex-direction: column;
}

.prose {
  max-width: var(--measure);
  color: var(--text-secondary);
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: var(--space-3);
  z-index: 10;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  padding: var(--space-2) var(--space-4);
  background: var(--surface-card);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  text-decoration: none;
}
.skip-link:focus {
  left: var(--space-5);
}

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

.mono {
  font-family: var(--font-mono);
}

/* ALL-CAPS is allowed here and nowhere else (brand book: sentence case). */
.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-secondary);
}

/* An h1 that leads a page rather than a hero. The hero size is for the
   landing page's one big statement; a page title sits at heading scale. */
.page-title {
  font-size: var(--text-3xl);
}

.muted {
  color: var(--text-secondary);
}
.faint {
  color: var(--text-quiet);
}

/* ---------- the segment motif ---------- */

/* The recessed LCD bezel around the logo lockup. seg.js sets the padding
   inline so the bezel scales with the digits. */
.seg-box {
  display: inline-flex;
  background: var(--screen-bg);
  border: 1px solid var(--screen-line);
  border-radius: var(--radius-screen);
  box-shadow: var(--shadow-screen);
}

/* A recessed LCD panel: segment displays, the intake terminal, hero numbers. */
.screen {
  background: var(--screen-bg);
  color: var(--screen-ink);
  border: 1px solid var(--screen-line);
  border-radius: var(--radius-screen);
  box-shadow: var(--shadow-screen);
  font-family: var(--font-mono);
  padding: var(--space-5);
}

.screen .eyebrow {
  color: inherit;
  opacity: 0.65;
}

/* ---------- keys (buttons + button-shaped links) ---------- */

/* Elevation is a calculator key: a hard 2px bottom edge, no blur. Pressing
   travels the key down 2px and removes the edge. Never a scale or a fade. */
.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: var(--text-base);
  padding: 12px 22px;
  color: var(--text-primary);
  background: var(--surface-card);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  box-shadow: 0 2px 0 var(--border-strong);
  text-decoration: none;
  cursor: pointer;
  transition: transform var(--dur-key) var(--ease-key), box-shadow var(--dur-key) var(--ease-key),
    background var(--dur-ui) var(--ease-key);
}
.btn:hover {
  background: var(--surface-inset);
  color: var(--text-primary);
}
.btn:active {
  transform: translateY(2px);
  box-shadow: 0 0 0 var(--border-strong);
}

.btn-sm {
  font-size: var(--text-sm);
  padding: 9px 16px;
}

/* An accent surface carrying an --accent-ink label is a BED, not the accent
   (F-1096): --accent-ink on --accent is 3.98:1, and no label colour fixes it —
   even pure white on #C4581F is 4.41. The bed is the darker orange, so the
   label reads at 6.3:1, and its edge/hover steps down behind it. */
.btn-accent {
  color: var(--accent-ink);
  background: var(--accent-bed);
  border-color: var(--accent-bed-edge);
  box-shadow: 0 2px 0 var(--accent-bed-edge);
}
.btn-accent:hover {
  background: var(--accent-bed-hover);
  color: var(--accent-ink);
}
.btn-accent:active {
  box-shadow: 0 0 0 var(--accent-bed-edge);
}

.btn-icon {
  width: 38px;
  height: 38px;
  padding: 0;
  justify-content: center;
  font-size: 16px;
}

/* ---------- surfaces ---------- */

.card {
  background: var(--surface-card);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  padding: var(--space-5);
}

.badge {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-secondary);
  background: var(--surface-inset);
  border: 1px solid var(--border-default);
  border-radius: 4px;
  padding: 2px 8px;
}

.badge-accent {
  color: var(--accent-ink);
  background: var(--accent-bed);
  border-color: var(--accent-bed-edge);
}

/* ---------- forms ---------- */

:root {
  /* Fields are recessed like the LCD panels, only shallower — the same idiom
     as the token file's own --shadow-screen (an alpha-black inset reads
     correctly in both themes). The one non-token value in this file, and it
     is a shadow, never a surface or an ink. */
  --shadow-field: inset 0 1px 2px rgb(0 0 0 / 0.06);
}

.form {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  max-width: 560px;
}

.field-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4);
}

.field-label {
  display: block;
  margin-bottom: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-secondary);
}

/* One class for every control: an input, a select and a textarea that do not
   agree pixel for pixel look like a bug even when nothing is wrong. */
.input {
  width: 100%;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  line-height: var(--leading-body);
  color: var(--text-primary);
  background: var(--surface-inset);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-field);
  padding: 9px 12px;
  transition: border-color var(--dur-ui) linear, box-shadow var(--dur-ui) linear;
}
.input::placeholder {
  color: var(--text-quiet);
}
/* The accent border is the field's own focus signal; the ring itself comes
   from :focus-visible above (F-1089) — a second box-shadow ring here would
   draw twice, and an `outline: none` here would erase the real one. */
.input:focus {
  border-color: var(--accent);
}

.textarea {
  resize: vertical;
  min-height: 8em;
}

/* The native arrow is the one piece of chrome that refuses to take a token,
   so it is replaced by one that can. */
.select {
  appearance: none;
  padding-right: 34px;
  cursor: pointer;
}

.select-wrap {
  position: relative;
}

.select-arrow {
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-quiet);
  pointer-events: none;
}

.form-error {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--warn);
}

/* Present to a bot, absent to a person: off-screen rather than display:none,
   which the cruder form fillers skip. */
.honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* ---------- the filed ticket ---------- */

.ticket {
  max-width: 560px;
  padding: var(--space-5) var(--space-6);
}

.ticket__number {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
  min-height: 56px;
}

.ticket__note {
  font-size: var(--text-sm);
  line-height: 1.7;
}

/* ---------- nav ---------- */

.site-nav > .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  padding-block: var(--space-5);
}

.site-nav__brand {
  display: inline-flex;
  text-decoration: none;
  color: inherit;
}

.site-nav__links {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

.site-nav__link {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--text-secondary);
  text-decoration: none;
}
.site-nav__link:hover {
  color: var(--text-primary);
  text-decoration: underline;
  text-underline-offset: 3px;
}
/* The page you are on is stated, not decorated. */
.site-nav__link[aria-current="page"] {
  color: var(--text-primary);
}

/* ---------- footer ---------- */

.site-footer {
  border-top: 1px solid var(--border-default);
}

.site-footer > .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  flex-wrap: wrap;
  padding-block: var(--space-6);
}

/* The tertiary grey survives here and nowhere else: this color is the
   currentColor the footer's segment MARK is drawn in — a logotype, which WCAG
   exempts from contrast (1.4.3, 1.4.11), and which is meant to recede. The
   copy sitting next to it is text and takes the quiet ink below. */
.site-footer__brand {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  color: var(--text-tertiary);
}

.site-footer__copy {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-quiet);
}

.site-footer__links {
  display: flex;
  gap: var(--space-4);
}

.site-footer__links a {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-secondary);
}

/* ---------- page sections ---------- */

/* A section heading and the eyebrow above it are a unit: the eyebrow sits
   tight to the heading, the heading holds the space below. */
.eyebrow + .section__title {
  margin-top: var(--space-2);
}
.eyebrow + h1 {
  margin-top: var(--space-4);
}

.section__title {
  margin-bottom: var(--space-6);
}
.section__title--tight {
  margin-bottom: var(--space-4);
}

/* The hero owns the top of the page, so it carries no border (the nav is
   directly above it) and breathes wider at the bottom than a normal section. */
.section-hero > .wrap {
  padding-block: var(--space-8) var(--space-9);
}

.hero {
  display: grid;
  grid-template-columns: 1.2fr 1fr;
  gap: var(--space-7);
  align-items: center;
}

/* --text-hero tops out at 84px, which is wider than the 1.2fr column can hold:
   "Useful software." would wrap and the deliberate break before "One dollar."
   would stop meaning anything. The headline is sized to keep its two lines. */
.hero__title {
  font-size: clamp(32px, 4.4vw, 52px);
  margin-bottom: var(--space-5);
}

.hero__lede {
  font-size: var(--text-lg);
  max-width: 44ch;
  margin-bottom: var(--space-6);
}

.hero__panel {
  padding: var(--space-6) var(--space-6) var(--space-5);
}

/* Digits are right-aligned in the panel the way a calculator right-aligns its
   readout — the decimal point lands where you expect it. */
.hero__digits {
  display: flex;
  justify-content: flex-end;
}

/* A row of tiny labels across an LCD panel's top or bottom edge. */
.screen__labels {
  display: flex;
  justify-content: space-between;
}
.screen__labels:first-child {
  margin-bottom: var(--space-5);
}
.screen__labels:last-child {
  margin-top: var(--space-5);
}

/* A row of keys. */
.actions {
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
}

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

.values {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-6);
}

/* The number is the personality; it gets the accent, the title does not.
   It keeps the KEY orange rather than --accent-text (F-1096): at 28px/600 it
   is large text, where WCAG 1.4.3 asks 3:1 and --accent clears at 3.60. The
   exemption is deliberate and the contrast suite asserts the size that earns
   it — shrink this below 24px and it needs --accent-text like any other text. */
.value__num {
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: var(--text-2xl);
  color: var(--accent);
  margin-bottom: var(--space-3);
}

.value__title {
  font-size: var(--text-lg);
  font-weight: 500;
  letter-spacing: normal;
  margin-bottom: var(--space-2);
}

.value__body {
  font-size: var(--text-sm);
}

/* ---------- app cards ---------- */

.apps {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-5);
}

.app-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.app-card__name {
  font-size: var(--text-xl);
  margin-bottom: var(--space-2);
}

.app-card__blurb {
  font-size: var(--text-sm);
}

/* Pins the key to the bottom edge however tall the blurbs are, so a row of
   cards has one CTA line rather than three. */
.app-card__cta {
  margin-top: auto;
}

.chips {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

/* The screenshot shelf: a recessed well on --surface-inset. Empty, it states
   what is coming; filled, it holds a device (brand book: screenshots live
   inside a plain device outline, never bleeding onto the paper). */
.well {
  height: 220px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: var(--surface-inset);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-screen);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-quiet);
  text-align: center;
}

/* The device stands on the shelf and is cropped by it. Shrinking a whole
   phone into 220px would make the screenshot an illegible thumbnail; showing
   the top of it at a readable size says more about the app. */
.well--device {
  align-items: flex-start;
  padding-top: var(--space-5);
}

.device {
  display: block;
  flex: none;
  width: 156px;
  background: var(--surface-card);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}

.device img {
  display: block;
  width: 100%;
  height: auto;
}

/* ---------- problems teaser ---------- */

.teaser {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-7);
  align-items: start;
}

.teaser__lede {
  max-width: 44ch;
  margin-bottom: var(--space-5);
}

.teaser__list {
  gap: var(--space-3);
  margin-bottom: var(--space-6);
}

.problem-row {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
}

.problem-row__id {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-quiet);
}

.problem-row__title {
  font-size: var(--text-sm);
  flex: 1;
}

.problem-row__votes {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--text-secondary);
  white-space: nowrap;
}

/* ---------- terminal vignette ---------- */

.terminal {
  font-size: var(--text-sm);
  line-height: 1.8;
}

.terminal__head {
  margin-bottom: var(--space-4);
}

.terminal__prompt {
  opacity: 0.65;
}

.terminal__reply {
  opacity: 0.85;
}

.cursor {
  animation: blink 1.1s step-start infinite;
}

/* ---------- app subpage (/catstickers) ---------- */

/* The design gives the screenshot and information bands a slightly tighter
   rhythm than a full content section. */
.section-tight > .wrap {
  padding-block: var(--space-7);
}

.app-hero > .wrap {
  display: flex;
  gap: var(--space-6);
  align-items: flex-start;
  flex-wrap: wrap;
  padding-block: var(--space-7) var(--space-8);
}

/* 132 @ 29px is the iOS icon's own corner ratio — the mark keeps its shape. */
.app-hero__icon {
  flex: none;
  width: 132px;
  height: 132px;
  border-radius: 29px;
  border: 1px solid var(--border-default);
  box-shadow: var(--shadow-card);
}

.app-hero__body {
  flex: 1;
  min-width: 280px;
}

.app-hero__eyebrow {
  margin-bottom: var(--space-2);
}

.app-hero__title {
  font-size: var(--text-4xl);
  margin-bottom: var(--space-3);
}

.app-hero__blurb {
  font-size: var(--text-lg);
  max-width: 52ch;
  margin-bottom: var(--space-5);
}

/* The store key and the fine print sit on one baseline, not one edge. */
.app-hero__actions {
  align-items: center;
}

.app-hero__meta {
  font-size: var(--text-xs);
}

.app-hero__note {
  margin-top: var(--space-4);
  font-size: var(--text-sm);
  max-width: 52ch;
}

/* 15.5px accent text in body copy, so the text role, not the key orange. */
.app-hero__price {
  color: var(--accent-text);
}

/* The App Store key before there is an App Store link: the key shape and its
   2px edge stay, the accent and the travel do not. Nothing here is clickable,
   so nothing here may look clickable. */
.btn-soon,
.btn-soon:hover {
  color: var(--text-secondary);
  background: var(--surface-inset);
  border-color: var(--border-strong);
  box-shadow: 0 2px 0 var(--border-strong);
  cursor: default;
}
.btn-soon:active {
  transform: none;
  box-shadow: 0 2px 0 var(--border-strong);
}

.shots {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-4);
}

.shot {
  margin: 0;
}

/* The brand book's device treatment, at full height rather than the landing
   page's cropped `.well` shelf: a plain outline on the inset well with the
   screenshot recessed inside it. No shadows, no perspective, no bezel art. */
.shot__frame {
  background: var(--surface-inset);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-screen);
  padding: var(--space-2);
}

.shot__frame img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-md);
}

.shot__label {
  margin-top: var(--space-3);
  color: var(--text-quiet);
  text-align: center;
}

.info__heading {
  font-size: var(--text-xl);
  font-weight: 500;
  letter-spacing: normal;
  margin-bottom: var(--space-5);
}

.info-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-4) var(--space-6);
  margin: 0;
}

.info-item {
  border-top: 1px solid var(--border-default);
  padding-top: var(--space-3);
}

.info-item dt {
  color: var(--text-quiet);
  margin-bottom: 2px;
}

.info-item__value {
  margin: 0;
  font-size: var(--text-sm);
}

.info-links {
  display: flex;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-top: var(--space-6);
  font-size: var(--text-sm);
}

/* ---------- the problem board (/problems) ---------- */

/* The intro sits tighter than a normal hero: the terminal below it is the
   thing to reach, not the headline. */
.problems-intro > .wrap {
  padding-block: var(--space-6);
}

.problems-intro .eyebrow + h1 {
  margin-top: var(--space-2);
  margin-bottom: var(--space-3);
  font-size: var(--text-4xl);
}

.problems-intro__lede {
  max-width: 56ch;
}

/* A section that carries no rule of its own — it belongs to the one above it. */
.section-flush > .wrap {
  padding-block: 0 var(--space-8);
}

/* ---------- the terminal ---------- */

.term {
  font-size: var(--text-sm);
}

.term .screen__labels:first-child {
  margin-bottom: var(--space-4);
}

/* Two labels, one row — until they no longer fit. Inherited from
   .screen__labels this row was nowrap with no gap, so at phone widths the two
   items met at 0px and the LEFT one wrapped inside its own flex item: the
   label broke mid-phrase while the status stayed pinned to the top-right
   corner, reading as two interleaved lines (measured at 375px, F-1092 — the
   overflow there is a hairline, 204px + 73px against 277px of room). Wrapping
   as a unit with a real gap drops the status onto its own line instead, and
   the label never breaks mid-phrase. */
.term__head {
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-4);
}

/* Bounded and scrolling: a long conversation must not push the input off the
   page, and the script pins this to the tail after every line. */
.term__log {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  line-height: 1.7;
  max-height: 340px;
  overflow-y: auto;
}

.term__line {
  white-space: pre-wrap;
}

/* The visitor's own lines sit back a step; the terminal is doing the talking. */
.term__line--me {
  opacity: 0.85;
}

.term__prefix,
.term__busy {
  opacity: 0.65;
}

.term__row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--screen-line);
}

/* The input is the LCD itself — no field chrome inside a screen. No
   `outline: none` here: that is the focus ring now (F-1089), and this rule
   sits later in the sheet at equal specificity, so it would win. */
.term__input {
  flex: 1;
  min-width: 0;
  padding: 4px 0;
  color: inherit;
  background: transparent;
  border: none;
  font-family: inherit;
  font-size: inherit;
  /* The example placeholder is longer than a phone-width field however wide we
     make the field, so make the truncation deliberate: an ellipsis rather than
     a word sliced down the middle (F-1092). Typed text still scrolls normally.  */
  text-overflow: ellipsis;
}
.term__input::placeholder {
  color: inherit;
  opacity: 0.5;
}

.term__key {
  font-family: inherit;
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  padding: 4px 12px;
  color: inherit;
  background: transparent;
  border: 1px solid var(--screen-line);
  border-radius: var(--radius-sm);
  cursor: pointer;
  opacity: 0.8;
}
.term__key:hover {
  opacity: 1;
}
.term__key[disabled] {
  cursor: default;
  opacity: 0.45;
}

.term__noscript {
  margin-top: var(--space-4);
  font-size: var(--text-sm);
}

/* ---------- the board ---------- */

.board__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-bottom: var(--space-5);
}

.board__head h2 {
  font-size: var(--text-2xl);
}

.board__meta {
  font-size: var(--text-xs);
}

/* The board is client-rendered: the HTML ships one "reading the board…" line
   and problems.js replaces it with cards once GET /api/problems answers. The
   reservation is what stops the footer being painted mid-page and then shoved
   off the bottom of the screen when the cards land (F-1094: 0.06 of /problems'
   0.09 CLS, the whole of its larger half).

   30rem is bounded on both sides, and both bounds matter:
   - big enough that on a desktop first screen everything below the board
     already starts off-screen, so nothing VISIBLE moves when cards arrive
     (measured at 1280x900 the board opens at y=666 and the footer needs only
     ~170px of reservation to clear the fold; 30rem covers viewports to
     ~1200px tall);
   - never TALLER than the shortest board we can ship, or the reservation
     causes the shift it was meant to prevent, upwards. The floor is the five
     seeded problems from migrations/0002 — ~573px at desktop widths, and
     taller on a phone where summaries wrap.
   A board that outgrows the reservation is free: cards simply paint into
   space that was already theirs. Measurements: docs/cls.md. */
.board {
  gap: var(--space-3);
  min-height: 30rem;
}

.board__note {
  font-size: var(--text-sm);
}

.problem {
  padding: var(--space-4) var(--space-5);
}

.problem__main {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
}

.problem__body {
  flex: 1;
  min-width: 220px;
}

.problem__meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.problem__id {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-quiet);
}

.problem__title {
  font-family: var(--font-mono);
  font-weight: 500;
  margin-top: 2px;
}

.problem__summary {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  margin-top: 2px;
}

.problem__keys {
  display: flex;
  gap: var(--space-2);
  flex: none;
  flex-wrap: wrap;
}

/* Whatever the server said went wrong, said where it went wrong. */
.problem__note {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--warn);
  margin-top: var(--space-3);
}

.problem__panel {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-default);
}

/* The upvote key: a calculator key that happens to hold a number. Its lit
   state is the accent, and `aria-pressed` — the server's truth — drives it. */
.vote {
  width: 44px;
  height: 44px;
  flex: none;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1.2;
  color: var(--text-primary);
  background: var(--surface-card);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  box-shadow: 0 2px 0 var(--border-strong);
  cursor: pointer;
  transition: transform var(--dur-key) var(--ease-key), box-shadow var(--dur-key) var(--ease-key),
    background var(--dur-ui) var(--ease-key);
}
.vote:active {
  transform: translateY(2px);
  box-shadow: 0 0 0 var(--border-strong);
}
.vote[aria-pressed="true"] {
  color: var(--accent-ink);
  background: var(--accent-bed);
  border-color: var(--accent-bed-edge);
  box-shadow: 0 2px 0 var(--accent-bed-edge);
}
.vote[aria-pressed="true"]:active {
  box-shadow: 0 0 0 var(--accent-bed-edge);
}

/* A key that is a secondary action on a card: the shape, one step quieter. */
.btn-quiet {
  font-size: var(--text-xs);
  font-weight: 400;
  padding: 8px 12px;
  color: var(--text-secondary);
  border-color: var(--border-default);
  box-shadow: 0 2px 0 var(--border-default);
}
.btn-quiet:active {
  box-shadow: 0 0 0 var(--border-default);
}
/* Subscribed. Stated in the ok colour, and it stops being a button to press. */
.btn-quiet.is-on,
.btn-quiet.is-on:hover {
  color: var(--ok);
  border-color: var(--ok);
  box-shadow: 0 2px 0 var(--ok);
  background: var(--surface-card);
  cursor: default;
}
.btn-quiet.is-on:active {
  transform: none;
  box-shadow: 0 2px 0 var(--ok);
}

.badge-ok {
  color: var(--ok);
  background: transparent;
  border-color: var(--ok);
}

.badge-soft {
  color: var(--accent-text);
  background: var(--accent-soft);
  border-color: transparent;
}

.comments {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.comment {
  display: flex;
  gap: var(--space-3);
  align-items: baseline;
}

.comment__who {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-quiet);
  flex: none;
}

.comment__text {
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.comment-form,
.notify-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.comment-form .input {
  flex: 1;
  min-width: 180px;
}

.notify-row .input {
  flex: 1;
  max-width: 280px;
  min-width: 180px;
}

.notify-row__label {
  flex: none;
}

/* ---------- the document chrome (legal pages) ---------- */

.wrap-doc {
  max-width: 760px;
}

.doc {
  padding-block: var(--space-6) var(--space-9);
}

.site-nav-doc > .wrap {
  padding-block: var(--space-5);
}

.site-nav__back {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
}

.doc__eyebrow {
  margin-bottom: var(--space-2);
}

.doc__title {
  font-size: var(--text-3xl);
  margin-bottom: var(--space-5);
}

.doc__intro {
  max-width: var(--measure);
  margin-bottom: var(--space-6);
}

.doc__section {
  margin-bottom: var(--space-6);
}

.doc__heading {
  font-size: var(--text-lg);
  font-weight: 500;
  letter-spacing: normal;
  margin-bottom: var(--space-2);
}

.doc__body {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  max-width: var(--measure);
}

.doc__closing {
  margin-bottom: 0;
}

/* ---------- a11y helpers ---------- */

/* A heading that exists for the outline and the screen reader, not the eye —
   the design's screenshot band is captioned, not titled. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------- responsive ---------- */

@media (max-width: 900px) {
  .shots {
    grid-template-columns: repeat(2, 1fr);
  }
  .info-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* One column below 800px. Every desktop grid on the site collapses here, so a
   new page gets the mobile layout by using the same class. */
@media (max-width: 800px) {
  .hero,
  .teaser {
    grid-template-columns: 1fr;
    gap: var(--space-6);
  }
  .values,
  .apps {
    grid-template-columns: 1fr;
  }
  .hero__digits {
    justify-content: center;
  }
}

@media (max-width: 760px) {
  .site-nav > .wrap {
    flex-wrap: wrap;
    gap: var(--space-3);
  }
  .section > .wrap {
    padding-block: var(--space-7);
  }
  .section-hero > .wrap {
    padding-block: var(--space-7) var(--space-8);
  }
  /* Two half-width fields side by side stop being fields at phone width. */
  .field-row {
    grid-template-columns: 1fr;
  }
}

/* --text-hero's floor is 44px, which overflows a 375px viewport in mono.
   Phones get their own ceiling rather than a changed token. */
@media (max-width: 560px) {
  h1 {
    font-size: clamp(28px, 8.5vw, 44px);
  }
  .hero__panel {
    padding: var(--space-5);
  }
  .app-hero > .wrap {
    padding-block: var(--space-6) var(--space-7);
  }
  .app-hero__title {
    font-size: var(--text-3xl);
  }
  /* One screenshot per row, but a phone screenshot the width of a phone screen
     is a scroll hazard — hold it to a readable column. */
  .shots {
    grid-template-columns: 1fr;
    gap: var(--space-6);
  }
  .problems-intro .eyebrow + h1 {
    font-size: var(--text-3xl);
  }
  .problem {
    padding: var(--space-4);
  }
  /* The two card keys stop being a trailing pair and become their own row,
     each half of it — at 375px they would otherwise wrap one under the other
     against the right edge. */
  .problem__keys {
    width: 100%;
  }
  .problem__keys .btn {
    flex: 1;
    justify-content: center;
  }
  .term__key {
    padding: 4px 10px;
  }
  /* The ENTER key is the widest thing in the input row, and the row is the
     one place on a phone where every pixel is typing room (174px of it at
     375px before this, F-1092). Below this width the glyph carries the key on
     its own — the word goes visually hidden rather than away, so the button is
     still announced as "enter". Same declarations as .visually-hidden, which
     cannot be applied per-breakpoint from the markup. */
  .term__key-word {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  .shot {
    max-width: 280px;
    margin-inline: auto;
  }
  .info-grid {
    grid-template-columns: 1fr;
  }
}

/* Animation is sparse and meaningful — and off entirely when asked. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  /* Stated rather than inherited from the blanket rule above: an endlessly
     blinking cursor is exactly what "reduce" is asking us not to do. */
  .cursor {
    animation: none !important;
  }
}

