/* tokens.css — generated from ../../design-system/tokens.json by tools/tokens-to-css.
   Do not hand-edit: change tokens.json and run the tool again. */

@font-face {
  font-family: "Source Sans 3";
  src: url("fonts/SourceSans3-Variable.woff2") format("woff2");
  font-weight: 200 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Source Sans 3";
  src: url("fonts/SourceSans3-VariableItalic.woff2") format("woff2");
  font-weight: 200 900;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: "Source Serif 4";
  src: url("fonts/SourceSerif4-Variable.woff2") format("woff2");
  font-weight: 200 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Source Serif 4";
  src: url("fonts/SourceSerif4-VariableItalic.woff2") format("woff2");
  font-weight: 200 900;
  font-style: italic;
  font-display: swap;
}

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

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

:root,
[data-theme="light"] {
  --surface: #faf7f2;
  --surface-raised: #f2ece2;
  --surface-sunken: #ebe3d6;
  --ink: #221f1a;
  --ink-muted: #544e47;
  --ink-faint: #665f57;
  --rule: #ded5c7;
  --rule-strong: #bcb2a1;
  --border-control: #8a8073;
  --accent-mw: #2c5c3f;
  --accent-ld: #96421a;
  --on-accent: #fbf9f5;
  --focus: #0b5ea8;
  --highlight: #f7e7c4;
  --type-article: #16456b;
  --type-note: #1c5b51;
  --type-postcard: #7a4570;
  --type-backstage: #7d6112;
  --tint-article: #e1ebf3;
  --tint-note: #ddedea;
  --tint-postcard: #f3e7f1;
  --tint-backstage: #f4ead1;
  --tint-mw: #e3ebe4;
  --tint-ld: #f8e7db;
  --state-ok: #1f5a33;
  --state-danger: #a02338;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --surface: #191816;
    --surface-raised: #232220;
    --surface-sunken: #121110;
    --ink: #f1ece4;
    --ink-muted: #b5aea4;
    --ink-faint: #968e84;
    --rule: #36332e;
    --rule-strong: #514c45;
    --border-control: #767069;
    --accent-mw: #79c396;
    --accent-ld: #ec9257;
    --on-accent: #161513;
    --focus: #6cb8f5;
    --highlight: #3d3521;
    --type-article: #8dc0ea;
    --type-note: #5fc3b0;
    --type-postcard: #cf9cc5;
    --type-backstage: #c8a34f;
    --tint-article: #1a2733;
    --tint-note: #14282a;
    --tint-postcard: #2b2130;
    --tint-backstage: #2c2616;
    --tint-mw: #1e2c24;
    --tint-ld: #33241a;
    --state-ok: #74c58c;
    --state-danger: #f0879c;
  }
}

[data-theme="dark"] {
  --surface: #191816;
  --surface-raised: #232220;
  --surface-sunken: #121110;
  --ink: #f1ece4;
  --ink-muted: #b5aea4;
  --ink-faint: #968e84;
  --rule: #36332e;
  --rule-strong: #514c45;
  --border-control: #767069;
  --accent-mw: #79c396;
  --accent-ld: #ec9257;
  --on-accent: #161513;
  --focus: #6cb8f5;
  --highlight: #3d3521;
  --type-article: #8dc0ea;
  --type-note: #5fc3b0;
  --type-postcard: #cf9cc5;
  --type-backstage: #c8a34f;
  --tint-article: #1a2733;
  --tint-note: #14282a;
  --tint-postcard: #2b2130;
  --tint-backstage: #2c2616;
  --tint-mw: #1e2c24;
  --tint-ld: #33241a;
  --state-ok: #74c58c;
  --state-danger: #f0879c;
}

: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;
  --space-10: 128px;
  --radius-sm: 3px;
  --radius-md: 5px;
  --radius-lg: 8px;
  --radius-pill: 999px;
  --border-hairline: 1px;
  --border-medium: 2px;
  --border-focus: 3px;
  --measure: 66ch;
  --measure-wide: 78ch;
  --column: 40rem;
  --page-max: 58rem;
  --tap-min: 44px;
  --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --font-sans: "Source Sans 3", Seravek, Calibri, ui-sans-serif, sans-serif;
  --font-serif: "Source Serif 4", Charter, Georgia, ui-serif, serif;
}

.wordmark {
  font-family: var(--font-sans);
  font-size: 21px;
  line-height: 1;
  font-weight: 600;
  letter-spacing: -0.012em;
}

.display {
  font-family: var(--font-sans);
  font-size: 42px;
  line-height: 1.08;
  font-weight: 600;
  letter-spacing: -0.02em;
}

.title-1 {
  font-family: var(--font-sans);
  font-size: 32px;
  line-height: 1.16;
  font-weight: 600;
  letter-spacing: -0.015em;
}

.title-2 {
  font-family: var(--font-sans);
  font-size: 25px;
  line-height: 1.25;
  font-weight: 600;
  letter-spacing: -0.008em;
}

.title-3 {
  font-family: var(--font-sans);
  font-size: 20px;
  line-height: 1.3;
  font-weight: 600;
}

.title-4 {
  font-family: var(--font-sans);
  font-size: 17px;
  line-height: 1.35;
  font-weight: 600;
  letter-spacing: 0.005em;
}

.lede {
  font-family: var(--font-sans);
  font-size: 22px;
  line-height: 1.5;
  font-weight: 400;
}

.body {
  font-family: var(--font-sans);
  font-size: 19px;
  line-height: 1.65;
  font-weight: 400;
}

.body-sm {
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.6;
  font-weight: 400;
}

.caption {
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.5;
  font-weight: 400;
}

.pull-quote {
  font-family: var(--font-serif);
  font-size: 26px;
  line-height: 1.4;
  font-weight: 400;
  font-style: italic;
}

.quote {
  font-family: var(--font-serif);
  font-size: 19px;
  line-height: 1.6;
  font-weight: 400;
}

.code-inline {
  font-family: var(--font-mono);
  font-size: 0.9em;
  line-height: 1.5;
  font-weight: 400;
}

.code-block {
  font-family: var(--font-mono);
  font-size: 15px;
  line-height: 1.7;
  font-weight: 400;
}

.meta {
  font-family: var(--font-mono);
  font-size: 13px;
  line-height: 1.4;
  font-weight: 500;
  letter-spacing: 0.055em;
}

/* McAllister Web — shared component stylesheet.
   Loads after tokens.css. Every value here resolves to a token; there are no
   literal colours, sizes or radii below except 0, 50% and 100%.

   Breakpoints are CONTAINER queries against the `site` container, which
   `body`/`.page` establishes. In a real page that is identical to a viewport
   query; in a prototype frame it means a narrowed frame gets the real mobile
   layout instead of a squashed desktop one.

   Two sites, one sheet. Put data-site="mw" (mcallister.ws) or data-site="ld"
   (logicaldisconnect.org) on <html> or <body>; that is the only thing that
   differs between them. */

/* ---------------------------------------------------------------- site accent */

[data-site="mw"] {
  --accent: var(--accent-mw);
  --accent-tint: var(--tint-mw);
}

[data-site="ld"] {
  --accent: var(--accent-ld);
  --accent-tint: var(--tint-ld);
}

/* Fallback so a component never renders unaccented if data-site is missing. */
:root {
  --accent: var(--accent-mw);
  --accent-tint: var(--tint-mw);
}

/* ---------------------------------------------------------------- foundations */

/* A real page puts class="page" on <body>; a prototype frame puts it on the
   element standing in for the viewport. Either way it is the `site` container. */
.page {
  margin: 0;
  container-name: site;
  container-type: inline-size;
  background: var(--surface);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 19px;
  line-height: 1.65;
  -webkit-text-size-adjust: 100%;
  text-rendering: optimizeLegibility;
}

.wrap {
  max-width: var(--page-max);
  margin-inline: auto;
  padding-inline: var(--space-4);
}

@container site (min-width: 40rem) {
  .wrap {
    padding-inline: var(--space-5);
  }
}

/* One focus ring, everywhere, in a colour that belongs to neither site. */
.page :where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: var(--border-focus) solid var(--focus);
  outline-offset: var(--space-1);
  border-radius: var(--radius-sm);
}

.skip-link {
  position: absolute;
  left: var(--space-4);
  top: var(--space-1);
  z-index: 10;
  transform: translateY(-200%);
  background: var(--surface-raised);
  color: var(--ink);
  border: var(--border-hairline) solid var(--border-control);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  font-size: 16px;
  text-decoration: none;
}

.skip-link:focus-visible {
  transform: translateY(0);
}

/* ---------------------------------------------------------------- links */

.link {
  color: var(--accent);
  text-decoration-line: underline;
  text-decoration-thickness: var(--border-hairline);
  text-underline-offset: 0.18em;
  text-decoration-color: color-mix(in srgb, var(--accent) 45%, transparent);
}

.link:hover {
  text-decoration-thickness: var(--border-medium);
  text-decoration-color: var(--accent);
}

/* ---------------------------------------------------------------- header */

.site-header {
  border-bottom: var(--border-hairline) solid var(--rule);
  background: var(--surface);
}

.site-header__inner {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  min-height: 64px;
  padding-block: var(--space-3);
}

.site-wordmark {
  display: inline-flex;
  flex-direction: column;
  gap: var(--space-1);
  white-space: nowrap;
  color: var(--ink);
  text-decoration: none;
  margin-right: auto;
}

.site-wordmark::after {
  content: "";
  display: block;
  height: var(--border-medium);
  width: 100%;
  background: var(--accent);
}

.site-wordmark__sub {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.055em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

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

.nav__list {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  list-style: none;
  margin: 0;
  padding: 0;
}

.nav__link {
  display: inline-block;
  padding-block: var(--space-2);
  font-size: 17px;
  color: var(--ink-muted);
  text-decoration: none;
  border-bottom: var(--border-medium) solid transparent;
}

.nav__link:hover {
  color: var(--ink);
  border-bottom-color: var(--rule-strong);
}

.nav__link[aria-current="page"] {
  color: var(--ink);
  font-weight: 600;
  border-bottom-color: var(--accent);
}

/* The other site's link: same row, marked as a departure, never a mega-menu. */
.nav__crosslink {
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
  text-decoration: none;
  border-bottom: var(--border-hairline) solid var(--rule);
  padding-bottom: 2px;
}

.nav__crosslink:hover {
  color: var(--ink);
  border-bottom-color: var(--accent);
}


/* Mobile: a plain disclosure button and a plain list. No animation, no overlay. */
.nav__toggle {
  display: none;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap-min);
  padding-inline: var(--space-3);
  background: none;
  border: var(--border-hairline) solid var(--border-control);
  border-radius: var(--radius-md);
  color: var(--ink);
  font: inherit;
  font-size: 16px;
  cursor: pointer;
}

@container site (max-width: 47.99rem) {
  .site-header__inner {
    flex-wrap: wrap;
  }

  .nav__toggle {
    display: inline-flex;
  }

  .nav {
    order: 3;
    width: 100%;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
  }

  .nav[data-collapsed="true"] {
    display: none;
  }


  .nav__list {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    border-top: var(--border-hairline) solid var(--rule);
  }

  .nav__link {
    display: flex;
    align-items: center;
    min-height: var(--tap-min);
    padding-inline: var(--space-1);
    border-bottom: var(--border-hairline) solid var(--rule);
  }

  .nav__link[aria-current="page"] {
    box-shadow: inset var(--border-medium) 0 0 var(--accent);
    padding-left: var(--space-4);
  }
}

/* ---------------------------------------------------------------- theme switcher */

/* Three words and an underline. It used to be a bordered capsule with a filled
   thumb, which read as the loudest control on a page whose loudest thing should
   be the writing. This version is the same radio group with the furniture
   removed: the current theme is the one in `ink` with an accent rule under it. */

.theme-switch {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  border: 0;
  min-inline-size: 0;
}

.theme-switch__legend {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.theme-switch__hint {
  margin-right: var(--space-2);
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.055em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.theme-switch__option {
  position: relative;
  display: inline-flex;
}

.theme-switch__input {
  position: absolute;
  inset: 0;
  opacity: 0;
  margin: 0;
  cursor: pointer;
}

.theme-switch__label {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  padding-inline: var(--space-2);
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
  border-bottom: var(--border-medium) solid transparent;
  cursor: pointer;
}

.theme-switch__label:hover {
  color: var(--ink);
}

.theme-switch__input:checked + .theme-switch__label {
  color: var(--ink);
  border-bottom-color: var(--accent);
}

.theme-switch__input:focus-visible + .theme-switch__label {
  outline: var(--border-focus) solid var(--focus);
  outline-offset: var(--space-1);
  border-radius: var(--radius-sm);
}

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


.site-footer {
  margin-top: var(--space-9);
  border-top: var(--border-hairline) solid var(--rule-strong);
  padding-block: var(--space-7) var(--space-8);
  color: var(--ink-muted);
  font-size: 16px;
}

.site-footer__cols {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
}

.site-footer h2 {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.055em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 var(--space-3);
}

.site-footer ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
}

/* Bottom left, two stacked rows: the theme switcher, then the colophon line.
   This is the switcher's permanent home at every width. */
.site-footer__colophon {
  margin-top: var(--space-7);
  padding-top: var(--space-5);
  border-top: var(--border-hairline) solid var(--rule);
  display: grid;
  justify-items: start;
  gap: var(--space-4);
  color: var(--ink-faint);
  font-size: 15px;
}

.site-footer__colophon p {
  margin: 0;
}

/* ---------------------------------------------------------------- photographs */

/* A standalone photograph outside prose: the portrait beside an introduction,
   the single image at the top of an About page. */
.portrait {
  margin: 0;
}

.portrait img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: var(--radius-lg);
  background: var(--surface-sunken);
}

.portrait figcaption {
  margin-top: var(--space-3);
  font-size: 15px;
  line-height: 1.5;
  color: var(--ink-muted);
}

/* Text with a photograph beside it. One column on a phone, text first. */
.intro {
  display: grid;
  gap: var(--space-6);
  align-items: start;
}

@container site (min-width: 44rem) {
  .intro {
    grid-template-columns: minmax(0, 1fr) 13rem;
    gap: var(--space-7);
  }
}

/* A row of small photographs, each linking to its own Photo entry. */
.photostrip {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
}

.photostrip a {
  display: block;
  text-decoration: none;
}

.photostrip img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius-md);
  background: var(--surface-sunken);
}

.photostrip a:hover img {
  outline: var(--border-medium) solid var(--accent);
  outline-offset: 2px;
}

.photostrip__place {
  display: block;
  margin-top: var(--space-2);
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
}

/* ---------------------------------------------------------------- content-type badge */

/* Every badge is a link to that type's index. It keeps the badge's look and
   gains a hover and a focus state; it must never start looking like a button. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 2px var(--space-2);
  border: var(--border-hairline) solid transparent;
  border-radius: var(--radius-md);
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.055em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* The mark is a shape as well as a colour, so the four types stay apart in
   greyscale and for anyone who can't see the hue. The word is always present. */
a.badge {
  text-decoration: none;
}

a.badge:hover {
  border-color: currentColor;
}

.badge::before {
  content: "";
  width: 7px;
  height: 7px;
  flex: none;
  background: currentColor;
}

.badge--article {
  color: var(--type-article);
  background: var(--tint-article);
  border-color: color-mix(in srgb, var(--type-article) 28%, transparent);
}

.badge--article::before {
  border-radius: 50%;
}

.badge--note {
  color: var(--type-note);
  background: var(--tint-note);
  border-color: color-mix(in srgb, var(--type-note) 28%, transparent);
}

.badge--note::before {
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
}

.badge--postcard {
  color: var(--type-postcard);
  background: var(--tint-postcard);
  border-color: color-mix(in srgb, var(--type-postcard) 28%, transparent);
}

.badge--postcard::before {
  border-radius: 1px;
}

.badge--backstage {
  color: var(--type-backstage);
  background: var(--tint-backstage);
  border-color: color-mix(in srgb, var(--type-backstage) 28%, transparent);
}

.badge--backstage::before {
  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
}

/* ---------------------------------------------------------------- stream */

.stream {
  list-style: none;
  margin: 0;
  padding: 0;
}

.entry {
  padding-block: var(--space-5);
  border-bottom: var(--border-hairline) solid var(--rule);
}

.entry__meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
}

.entry__title {
  font-size: 20px;
  line-height: 1.3;
  font-weight: 600;
  margin: 0 0 var(--space-2);
}

.entry__title a {
  color: var(--ink);
  text-decoration: none;
}

.entry__title a:hover {
  text-decoration: underline;
  text-decoration-thickness: var(--border-medium);
  text-underline-offset: 0.18em;
  text-decoration-color: var(--accent);
}

.entry__summary {
  margin: 0;
  max-width: var(--measure-wide);
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink-muted);
}

/* Note: the text is the entry. No title, tighter block, quieter frame. */
.entry--note .entry__body {
  max-width: var(--measure);
  margin: 0;
  font-size: 18px;
  line-height: 1.6;
}

.entry--note .entry__body p {
  margin: 0 0 var(--space-3);
}

.entry--note .entry__body p:last-child {
  margin-bottom: 0;
}

/* Photo: image first, caption and place under it. */
.entry--postcard .entry__figure {
  margin: 0 0 var(--space-3);
}

.entry--postcard img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-lg);
  background: var(--surface-sunken);
}

.entry__place {
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
}

/* Backstage: the project name leads, the update follows. */
.entry--backstage .entry__project {
  font-weight: 600;
  color: var(--ink);
}

.entry--backstage .entry__body {
  margin: 0;
  max-width: var(--measure);
  font-size: 17px;
  line-height: 1.6;
  color: var(--ink-muted);
}

/* ---------------------------------------------------------------- filter */

.filter {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding-block: var(--space-4);
  border-bottom: var(--border-hairline) solid var(--rule);
}

.filter__legend {
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.055em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-right: var(--space-2);
}

.filter__btn {
  min-height: 34px;
  padding-inline: var(--space-3);
  background: none;
  border: var(--border-hairline) solid var(--border-control);
  border-radius: var(--radius-md);
  color: var(--ink-muted);
  font: inherit;
  font-size: 15px;
  cursor: pointer;
}

.filter__btn:hover {
  color: var(--ink);
}

.filter__btn[aria-pressed="true"] {
  background: var(--accent-tint);
  border-color: var(--accent);
  color: var(--accent);
  font-weight: 600;
}

.filter__count {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--ink-faint);
}

/* ---------------------------------------------------------------- tags and chips */

.taglist {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  list-style: none;
  margin: 0;
  padding: 0;
}

.tag {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--ink-muted);
  text-decoration: none;
  border-bottom: var(--border-hairline) solid var(--rule-strong);
}

.tag::before {
  content: "#";
  color: var(--ink-faint);
}

.tag:hover {
  color: var(--ink);
  border-bottom-color: var(--accent);
}

/* A reference to a PROJECT, which is a different animal from a topic tag: it
   leaves Logical Disconnect for that project's page on mcallister.ws. No `#`,
   and a trailing arrow, so the two are never confused at a glance. */
.project-ref {
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--ink-muted);
  text-decoration: none;
  border-bottom: var(--border-hairline) solid var(--rule-strong);
  white-space: nowrap;
}

.project-ref::after {
  content: "\2197";
  margin-left: 2px;
}

.project-ref:hover {
  color: var(--ink);
  border-bottom-color: var(--accent);
}

/* The chip is the heavier form, used only on a topic page's header. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border: var(--border-hairline) solid var(--border-control);
  border-radius: var(--radius-md);
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--ink);
  text-decoration: none;
}

.chip__count {
  color: var(--ink-faint);
}

/* ---------------------------------------------------------------- more link */

/* The small "see all" link at the right of a section heading. It is not a tag
   (no `#`) and not a button; it is the quietest way to say there is more. */
.more-link {
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--ink-muted);
  text-decoration: none;
  border-bottom: var(--border-hairline) solid var(--rule-strong);
  white-space: nowrap;
}

.more-link:hover {
  color: var(--ink);
  border-bottom-color: var(--accent);
}

/* ---------------------------------------------------------------- prose */

.prose {
  max-width: var(--measure);
  font-size: 19px;
  line-height: 1.65;
  color: var(--ink);
}

.prose > * {
  margin-block: 0 var(--space-4);
}

.prose h2 {
  font-size: 25px;
  line-height: 1.25;
  font-weight: 600;
  letter-spacing: -0.008em;
  margin-block: var(--space-7) var(--space-3);
}

.prose h3 {
  font-size: 20px;
  line-height: 1.3;
  font-weight: 600;
  margin-block: var(--space-6) var(--space-3);
}

.prose ul,
.prose ol {
  padding-left: var(--space-5);
}

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

.prose li::marker {
  color: var(--ink-faint);
}

.prose a {
  color: var(--accent);
  text-decoration-line: underline;
  text-decoration-thickness: var(--border-hairline);
  text-underline-offset: 0.18em;
}

.prose a:hover {
  text-decoration-thickness: var(--border-medium);
}

.prose code {
  font-family: var(--font-mono);
  font-size: 0.88em;
  background: var(--surface-sunken);
  border-radius: var(--radius-sm);
  padding: 0.1em 0.3em;
}

.prose pre {
  background: var(--surface-sunken);
  border: var(--border-hairline) solid var(--rule);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  overflow-x: auto;
  font-family: var(--font-mono);
  font-size: 15px;
  line-height: 1.7;
}

.prose pre code {
  background: none;
  padding: 0;
  font-size: inherit;
}

.prose blockquote {
  margin-inline: 0;
  padding-left: var(--space-5);
  border-left: var(--border-medium) solid var(--rule-strong);
  font-family: var(--font-serif);
  font-size: 19px;
  line-height: 1.6;
  color: var(--ink-muted);
}

.prose figure {
  margin-inline: 0;
  margin-block: var(--space-6);
}

/* Code and figures step outside the measure by one space-6 on EACH side, so
   they read as wider than the text without pulling the column off-centre. */
@container site (min-width: 48rem) {
  .prose pre,
  .prose figure {
    margin-inline: calc(-1 * var(--space-6));
  }
}

.prose figure img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-lg);
  background: var(--surface-sunken);
}

.prose figcaption {
  margin-top: var(--space-3);
  font-size: 15px;
  line-height: 1.5;
  color: var(--ink-muted);
}

.prose mark {
  background: var(--highlight);
  color: var(--ink);
  padding: 0 0.15em;
}

.pull-quote-block {
  max-width: var(--measure);
  margin-block: var(--space-7);
  padding-left: var(--space-5);
  border-left: var(--border-focus) solid var(--accent);
  font-family: var(--font-serif);
  font-size: 26px;
  line-height: 1.4;
  font-style: italic;
  color: var(--ink);
}

/* Table of contents: a plain nested list, collapsible, never sticky. */
.toc {
  max-width: var(--measure);
  margin-block: var(--space-6);
  padding: var(--space-4) var(--space-5);
  background: var(--surface-raised);
  border-radius: var(--radius-lg);
  font-size: 16px;
}

.toc > summary {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.055em;
  text-transform: uppercase;
  color: var(--ink-faint);
  cursor: pointer;
}

.toc ol {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
}

.toc ol ol {
  margin-top: var(--space-2);
  padding-left: var(--space-4);
}

/* Footnotes. */
.footnotes {
  max-width: var(--measure);
  margin-top: var(--space-8);
  padding-top: var(--space-5);
  border-top: var(--border-hairline) solid var(--rule);
  font-size: 16px;
  color: var(--ink-muted);
}

.footnotes ol {
  padding-left: var(--space-5);
}

.footnote-ref {
  font-family: var(--font-mono);
  font-size: 0.8em;
  vertical-align: super;
  line-height: 0;
  text-decoration: none;
  color: var(--accent);
}

.footnote-ref:target,
.footnotes li:target {
  background: var(--highlight);
}

/* ---------------------------------------------------------------- revision note */

.revision {
  max-width: var(--measure);
  margin-block: var(--space-6);
  border: var(--border-hairline) solid var(--rule-strong);
  border-radius: var(--radius-md);
  background: var(--surface-raised);
  font-size: 16px;
  line-height: 1.6;
}

.revision__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: var(--border-hairline) solid var(--rule);
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
}

.revision__label {
  color: var(--ink);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.055em;
}

.revision__body {
  padding: var(--space-4);
  margin: 0;
  color: var(--ink);
}

/* The diff lines: a sign, a colour, and indentation — three signals, not one. */
.revision__diff {
  margin: 0;
  padding: 0 var(--space-4) var(--space-4);
  list-style: none;
  font-family: var(--font-mono);
  font-size: 14px;
  line-height: 1.7;
}

.revision__diff li {
  padding-left: var(--space-4);
  text-indent: calc(-1 * var(--space-4));
}

.revision__diff li::before {
  display: inline-block;
  width: var(--space-4);
  text-indent: 0;
  font-weight: 500;
}

.revision__diff .is-added {
  color: var(--state-ok);
}

.revision__diff .is-added::before {
  content: "+";
}

.revision__diff .is-removed {
  color: var(--state-danger);
}

.revision__diff .is-removed::before {
  content: "\2212";
}

/* ---------------------------------------------------------------- standing */

/* How far a piece has been reviewed. Not a content type — a property that any
   type can carry, and one that goes UP over time: an imported post that gets
   re-read becomes checked, and if the re-reading changed anything it also gets
   a RevisionNote. The absence of a standing means current work. */

.standing {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 1px var(--space-2);
  border: var(--border-hairline) solid var(--border-control);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.055em;
  text-transform: uppercase;
  color: var(--ink-muted);
  white-space: nowrap;
}

.standing--checked {
  color: var(--state-ok);
  border-color: color-mix(in srgb, var(--state-ok) 45%, transparent);
}

/* The block at the top of an entry. Outlined, where RevisionNote is filled —
   the two sit together on some pages and must not read as one thing. */
.standing-note {
  max-width: var(--measure);
  margin-block: var(--space-6);
  padding: var(--space-4);
  border: var(--border-hairline) solid var(--border-control);
  border-radius: var(--radius-md);
  font-size: 16px;
  line-height: 1.6;
}

.standing-note__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3);
  margin: 0 0 var(--space-3);
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
}

.standing-note__label {
  color: var(--ink);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.055em;
}

.standing-note--checked .standing-note__label {
  color: var(--state-ok);
}

.standing-note__body {
  margin: 0;
  color: var(--ink-muted);
}

/* ---------------------------------------------------------------- backlinks */

.backlinks {
  max-width: var(--measure);
  margin-top: var(--space-8);
  padding-top: var(--space-5);
  border-top: var(--border-hairline) solid var(--rule-strong);
}

.backlinks__title {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.055em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 var(--space-4);
}

.backlinks ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-4);
}

.backlinks li {
  display: grid;
  gap: var(--space-2);
}

.backlinks__link {
  color: var(--ink);
  font-weight: 600;
  font-size: 17px;
  text-decoration: none;
  border-bottom: var(--border-hairline) solid var(--rule-strong);
  justify-self: start;
}

.backlinks__link:hover {
  border-bottom-color: var(--accent);
}

.backlinks__context {
  margin: 0;
  font-size: 16px;
  color: var(--ink-muted);
}

/* ---------------------------------------------------------------- widget: latest from LD */

.widget {
  background: var(--surface-raised);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
}

.widget__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  padding-bottom: var(--space-3);
  border-bottom: var(--border-hairline) solid var(--rule);
}

.widget__title {
  margin: 0;
  font-size: 17px;
  font-weight: 600;
  letter-spacing: 0.005em;
}

.widget__more {
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--accent-ld);
  text-decoration: none;
  border-bottom: var(--border-hairline) solid transparent;
}

.widget__more:hover {
  border-bottom-color: var(--accent-ld);
}

.widget ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-4);
}

.widget__item {
  display: grid;
  gap: var(--space-2);
}

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

.widget__link {
  color: var(--ink);
  font-size: 17px;
  line-height: 1.4;
  text-decoration: none;
  border-bottom: var(--border-hairline) solid var(--rule-strong);
}

.widget__link:hover {
  border-bottom-color: var(--accent-ld);
}

.widget__date {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--ink-faint);
}

/* ---------------------------------------------------------------- backstage strip */

.backstage-strip {
  background: var(--surface-raised);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.backstage-strip__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-5);
  background: var(--tint-backstage);
  color: var(--type-backstage);
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.055em;
  text-transform: uppercase;
}

.backstage-strip__feed {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 0.18em;
}

.backstage-strip ol {
  list-style: none;
  margin: 0;
  padding: var(--space-2) var(--space-5) var(--space-5);
}

.backstage-strip li {
  padding-block: var(--space-4);
  border-bottom: var(--border-hairline) solid var(--rule);
}

.backstage-strip li:last-child {
  border-bottom: 0;
  padding-bottom: 0;
}

.backstage-strip__date {
  display: block;
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
  margin-bottom: var(--space-2);
}

.backstage-strip__body {
  margin: 0;
  font-size: 17px;
  line-height: 1.6;
  max-width: var(--measure);
}

/* ---------------------------------------------------------------- project status */

.status {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.055em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

/* Four filled pips of four: the position in the ladder is the signal, the word
   confirms it, and neither depends on colour alone. */
.status__track {
  display: inline-flex;
  gap: 3px;
}

.status__pip {
  width: 6px;
  height: 12px;
  background: var(--rule-strong);
  border-radius: 1px;
}

.status__pip.is-on {
  background: currentColor;
}

.status--idea {
  color: var(--ink-faint);
}

.status--prototype {
  color: var(--type-backstage);
}

.status--beta {
  color: var(--accent);
}

.status--launched {
  color: var(--state-ok);
}

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

/* Link-style by default: these sites do not have a "CTA". */
.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap-min);
  padding-inline: 0;
  background: none;
  border: 0;
  color: var(--accent);
  font: inherit;
  font-size: 17px;
  font-weight: 600;
  text-decoration: underline;
  text-decoration-thickness: var(--border-hairline);
  text-underline-offset: 0.18em;
  cursor: pointer;
}

.btn:hover {
  text-decoration-thickness: var(--border-medium);
}

.btn::after {
  content: "\2192";
  font-weight: 400;
  text-decoration: none;
}

/* The one bordered form, for a real submit. Still not a pill. */
.btn--framed {
  padding-inline: var(--space-4);
  border: var(--border-hairline) solid var(--accent);
  border-radius: var(--radius-md);
  background: var(--accent);
  color: var(--on-accent);
  text-decoration: none;
}

.btn--framed:hover {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--surface);
  text-decoration: none;
}

.btn--framed::after {
  content: none;
}

.btn--quiet {
  color: var(--ink-muted);
  font-weight: 400;
  text-decoration-color: var(--rule-strong);
}

.btn--quiet:hover {
  color: var(--ink);
}

.btn--quiet::after {
  content: none;
}

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

.form {
  max-width: 32rem;
  display: grid;
  gap: var(--space-5);
}

.form__intro {
  margin: 0;
  max-width: var(--measure);
  font-size: 17px;
  line-height: 1.6;
  color: var(--ink-muted);
}

.field {
  display: grid;
  gap: var(--space-2);
}

.field__label {
  font-size: 17px;
  font-weight: 600;
}

.field__hint {
  font-size: 15px;
  line-height: 1.5;
  color: var(--ink-muted);
}

.field__input,
.field__textarea,
.field__select {
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-4);
  background: var(--surface);
  color: var(--ink);
  border: var(--border-hairline) solid var(--border-control);
  border-radius: var(--radius-md);
  font: inherit;
  font-size: 17px;
}

.field__textarea {
  min-height: 8rem;
  line-height: 1.6;
  resize: vertical;
}

.field__input::placeholder,
.field__textarea::placeholder {
  color: var(--ink-faint);
}

.field--invalid .field__input,
.field--invalid .field__textarea {
  border-color: var(--state-danger);
  border-width: var(--border-medium);
}

.field__error {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  font-size: 15px;
  font-weight: 600;
  color: var(--state-danger);
}

.field__error::before {
  content: "\26A0";
  font-weight: 400;
}

.form__success {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border: var(--border-hairline) solid var(--state-ok);
  border-radius: var(--radius-md);
  font-size: 16px;
  color: var(--state-ok);
}

.form__success::before {
  content: "\2713";
}

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

/* ---------------------------------------------------------------- link list */

/* A permanent collection of links where the reasoning is the content. Rows on
   hairlines, never cards: the paragraph under each link is the reason the page
   exists, and a card would make it look like a directory listing. */
.links-group + .links-group {
  margin-top: var(--space-8);
}

.links-group__title {
  margin: 0 0 var(--space-4);
  font-size: 20px;
  line-height: 1.3;
  font-weight: 600;
}

.linklist {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: var(--border-hairline) solid var(--rule);
}

.linklist > li {
  padding-block: var(--space-5);
  border-bottom: var(--border-hairline) solid var(--rule);
}

.linklist__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3);
  margin: 0 0 var(--space-2);
}

.linklist__link {
  font-size: 19px;
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
  border-bottom: var(--border-hairline) solid var(--rule-strong);
}

.linklist__link:hover {
  border-bottom-color: var(--accent);
}

.linklist__host {
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
}

/* Who this person is to Kevin. The one place a relationship is stated, and it
   is stated in the accent because it is the reason the link is on the page. */
.linklist__rel {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.055em;
  text-transform: uppercase;
  color: var(--accent);
}

.linklist__why {
  margin: 0;
  max-width: var(--measure);
  font-size: 17px;
  line-height: 1.6;
  color: var(--ink-muted);
}

/* ---------------------------------------------------------------- page patterns */

/* A heading with one quiet link opposite it. Used above every list that has
   somewhere fuller to go. */
.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-bottom: var(--space-4);
}

/* Router: the "Where to begin" doors. Rows on hairlines, not a grid of cards. */
.doors {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: var(--border-hairline) solid var(--rule);
}

.doors a {
  display: flex;
  align-items: baseline;
  gap: var(--space-5);
  flex-wrap: wrap;
  padding-block: var(--space-4);
  border-bottom: var(--border-hairline) solid var(--rule);
  text-decoration: none;
  color: var(--ink);
}

.doors a:hover {
  background: var(--accent-tint);
}

.doors__label {
  flex: none;
  min-width: 12rem;
  font-size: 19px;
  font-weight: 600;
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: var(--border-hairline);
  text-underline-offset: 0.18em;
}

.doors__hint {
  font-size: 16px;
  color: var(--ink-muted);
}

.doors__aside a {
  border-bottom: 0;
}

.doors__aside .doors__label {
  color: var(--ink-muted);
  font-weight: 400;
  font-style: italic;
}

/* DetailList: label-and-value rows. Stats, the content-type legend, /uses. */
.detail-list {
  margin: 0;
  max-width: var(--measure-wide);
  display: grid;
  gap: 0;
  border-top: var(--border-hairline) solid var(--rule);
}

.detail-list > div {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding-block: var(--space-3);
  border-bottom: var(--border-hairline) solid var(--rule);
}

.detail-list dt {
  font-size: 17px;
  color: var(--ink);
}

.detail-list dd {
  margin: 0;
  font-size: 16px;
  color: var(--ink-muted);
  text-align: right;
}

/* The numeric variant: figures in mono so columns of them line up. */
.detail-list--figures dt {
  color: var(--ink-muted);
}

.detail-list--figures dd {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* The wide variant: the value is prose, so it wraps under the label. */
.detail-list--notes > div {
  display: grid;
  gap: var(--space-1);
}

.detail-list--notes dd {
  text-align: left;
  max-width: var(--measure);
}

/* Picks: a titled link with a one-line hook. "Start here", featured reading. */
.picks {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-5);
  max-width: var(--measure-wide);
}

.picks__link {
  font-size: 20px;
  line-height: 1.3;
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
  border-bottom: var(--border-hairline) solid var(--rule-strong);
}

.picks__link:hover {
  border-bottom-color: var(--accent);
}

.picks__hook {
  margin: var(--space-2) 0 0;
  font-size: 16px;
  color: var(--ink-muted);
}

/* Timeline: roles down the page, each with one line on what it taught. The
   rule on the leading edge is the spine; there are no dots, because a dot per
   role implies events rather than spans. */
.timeline {
  list-style: none;
  margin: 0;
  padding: 0 0 0 var(--space-5);
  border-left: var(--border-hairline) solid var(--rule-strong);
  display: grid;
  gap: var(--space-6);
  max-width: var(--measure-wide);
}

.timeline__when {
  display: block;
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
  margin-bottom: var(--space-1);
}

.timeline__role {
  margin: 0;
  font-size: 19px;
  font-weight: 600;
}

.timeline__where {
  color: var(--ink-muted);
  font-weight: 400;
}

.timeline__learned {
  margin: var(--space-2) 0 0;
  max-width: var(--measure);
  font-size: 17px;
  line-height: 1.6;
  color: var(--ink-muted);
}

/* Archive: dense, scannable, one line per entry, grouped by year. */
.archive-year {
  margin-bottom: var(--space-7);
}

.archive-year__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  margin: 0 0 var(--space-3);
  padding-bottom: var(--space-2);
  border-bottom: var(--border-hairline) solid var(--rule-strong);
  font-family: var(--font-mono);
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0.055em;
  color: var(--ink);
}

.archive-year__count {
  color: var(--ink-faint);
  font-size: 13px;
}

.archive-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.archive-list li {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding-block: var(--space-2);
  font-size: 16px;
  line-height: 1.5;
}

.archive-list__date {
  flex: none;
  width: 5.5rem;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

.archive-list__mark {
  flex: none;
  width: 9px;
  height: 9px;
  margin-top: 0.35em;
  background: currentColor;
}

.archive-list__link {
  color: var(--ink);
  text-decoration: none;
  border-bottom: var(--border-hairline) solid transparent;
}

.archive-list__link:hover {
  border-bottom-color: var(--accent);
}

/* FeedList: the Subscribe page. A row per feed, URL visible, because the URL
   is the thing a reader is going to copy into a reader. */
.feeds {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: var(--border-hairline) solid var(--rule);
  max-width: var(--measure-wide);
}

.feeds > li {
  display: grid;
  gap: var(--space-1);
  padding-block: var(--space-4);
  border-bottom: var(--border-hairline) solid var(--rule);
}

.feeds__name {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  flex-wrap: wrap;
  font-size: 18px;
  font-weight: 600;
}

.feeds__url {
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--accent);
  text-decoration: none;
  border-bottom: var(--border-hairline) solid var(--rule-strong);
  overflow-wrap: anywhere;
}

.feeds__url:hover {
  border-bottom-color: var(--accent);
}

.feeds__why {
  margin: 0;
  max-width: var(--measure);
  font-size: 16px;
  color: var(--ink-muted);
}

/* Screenshots on a project page. */
.shots {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}

.shots img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  border-radius: var(--radius-lg);
  background: var(--surface-sunken);
}

.shots figcaption {
  margin-top: var(--space-2);
  font-size: 15px;
  color: var(--ink-muted);
}

.shots figure {
  margin: 0;
}

/* ---------------------------------------------------------------- newsletter */

/* Built, placed, and switchable off in one line. Put data-newsletter="off" on
   the site root and every instance disappears without touching a template. */
[data-newsletter="off"] .newsletter,
[data-newsletter="off"] .newsletter-only {
  display: none;
}

/* `.newsletter-only` is the hook for anything that exists only because the
   list exists but is not itself a signup block — a footer link, a nav item. */

.newsletter {
  max-width: var(--measure);
  margin-block: var(--space-7);
  padding: var(--space-5);
  background: var(--surface-raised);
  border-radius: var(--radius-lg);
}

.newsletter__label {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.055em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 var(--space-3);
}

.newsletter__title {
  margin: 0 0 var(--space-3);
  font-size: 20px;
  line-height: 1.3;
  font-weight: 600;
}

.newsletter__body {
  margin: 0 0 var(--space-5);
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink-muted);
}

.newsletter__row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: flex-start;
}

.newsletter__row .field {
  flex: 1 1 15rem;
}

.newsletter__terms {
  margin: var(--space-4) 0 0;
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink-faint);
}

/* The compact form, for the foot of an article: one line of prose, one field. */
.newsletter--inline {
  margin-block: var(--space-8);
  padding: var(--space-4) var(--space-5);
  background: none;
  border-top: var(--border-hairline) solid var(--rule-strong);
  border-bottom: var(--border-hairline) solid var(--rule-strong);
  border-radius: 0;
  padding-inline: 0;
}

.newsletter--inline .newsletter__title {
  font-size: 17px;
}

.newsletter--inline .newsletter__body {
  margin-bottom: var(--space-4);
}

/* ---------------------------------------------------------------- address */

/* Neither site has a free-text contact form. The Contact page publishes an
   address instead: a form is a spam relay someone else eventually pays for,
   and an address is a thing a person can put in their own mail client. */
.address {
  display: grid;
  gap: var(--space-3);
  max-width: var(--measure);
  padding: var(--space-5);
  background: var(--surface-raised);
  border-radius: var(--radius-lg);
}

.address__label {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.055em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.address__value {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 20px;
  line-height: 1.4;
  color: var(--ink);
  overflow-wrap: anywhere;
  user-select: all;
}

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

.address__status {
  font-size: 15px;
  color: var(--state-ok);
}

.address__status[hidden] {
  display: none;
}

/* ---------------------------------------------------------------- utilities */

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

.stack-4 > * + * {
  margin-top: var(--space-4);
}

.stack-5 > * + * {
  margin-top: var(--space-5);
}

.stack-7 > * + * {
  margin-top: var(--space-7);
}

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

/* Everything that moves, moves only here, and only by consent. */
@media (prefers-reduced-motion: no-preference) {
  .nav__link,
  .tag,
  .chip,
  .btn,
  .link,
  .widget__link,
  .backlinks__link,
  .filter__btn,
  .theme-switch__label {
    transition: color 120ms ease, background-color 120ms ease,
      border-color 120ms ease;
  }
}
/* Workbench theme additions: the prototype's inline `style=` attributes,
   turned into rules, per DESIGN-SPEC.md "Inline style= to class map".
   Loads after tokens.css + bundle.css (theme.yaml's css: order) so it can
   override on specificity or simply add what those two don't cover.

   Only the groups this slice's pages actually use are implemented so far
   (page shell, headings, ledes, article-page rhythm, the trailing
   responsive step-down). Later slices add stream rows, lists and the
   BEM/utility classes in the registry as those pages land — see the
   group letters below, which match DESIGN-SPEC.md's map exactly so that
   work is a lookup, not a re-derivation.

   No !important anywhere (DESIGN-SPEC.md "Inline style= to class map").
*/

/* ---- Foundational resets bundle.css assumes but never ships -----------

   Both additions below were found by diffing a real generated page against
   the prototype reference screenshots (not in DESIGN-SPEC.md's own map,
   since Fable wrote that map from the inline `style=` attributes alone,
   before any real page existed to render). Registered here per
   DESIGN-SPEC.md's own rule for this situation ("If bundle.css needs an
   override, put it in themes/workbench/css/site.css and note it in
   DESIGN-SPEC.md 'Known deviations'") and logged as a new decision there. */

/* bundle.css's sizing (`min-height: 64px` plus `padding-block` on
   `.site-header__inner`, and more like it) is written assuming
   `box-sizing: border-box` everywhere, matching every mainstream CSS
   reset. The prototype gets this for free from prototype/assets/
   prototype.css — explicitly rig-only, never shipped — so a real page
   built from just tokens.css + bundle.css never got it. Without it,
   `min-height` measures content alone, ignoring padding, and several
   components render visibly taller than the prototype (DESIGN-SPEC.md's
   decisions log has the details). */
*,
*::before,
*::after {
  box-sizing: border-box;
}

/* bundle.css sets `--accent`/`--accent-tint` in `[data-site="mw"]` and
   `[data-site="ld"]`, then redeclares the mw values again as a `:root`
   fallback "so a component never renders unaccented if data-site is
   missing" — but that fallback rule comes *after* the two `[data-site]`
   rules in the file, and `:root` and `[data-site="ld"]` have the same
   specificity, so on an element that is both (true here: DESIGN-SPEC.md
   puts data-site on `<html>` itself, which `:root` also always matches)
   the fallback wins the cascade tie by source order — every page renders
   with mw's green accent, ld included. The prototype never hits this: its
   router sets `data-site` on `.page`, one level below `<html>`, where
   there is no `:root` rule to tie against. Two higher-specificity rules
   restore the fallback's real intent without touching bundle.css. */
:root[data-site="mw"] {
  --accent: var(--accent-mw);
  --accent-tint: var(--tint-mw);
}

:root[data-site="ld"] {
  --accent: var(--accent-ld);
  --accent-tint: var(--tint-ld);
}

/* ---- A. Page shell ---------------------------------------------------- */

/* The wrap has *no* bottom padding (the prototype's literal
   `padding-block: var(--space-8) 0`, 26 uses). The one space-9 between a
   page's last block and the footer is `.site-footer`'s own
   `margin-top: var(--space-9)` (bundle.css, SiteFooter): the last
   block's bottom margin — 0 on an article, space-9 on a `ul.doors`, any
   value between — collapses into it through the padding-less wrap and
   main, exactly as it does in the prototype. Padding here instead would
   *add* to that margin (padding never collapses), stacking to 192px on
   every page — which is what shipped from M1 to M3, hidden from every
   visual diff by the stitch that drops precisely this trailing band
   (DESIGN-SPEC.md D33). So: no per-page bottom padding rules, no
   `:last-child { margin-bottom: 0 }`. */
main > .wrap {
  padding-block: var(--space-8) 0;
}

/* M3 listings: the 404 wrap starts at space-9, not the general space-8
   (map section A row 2). */
.page--404 main > .wrap {
  padding-top: var(--space-9);
}

/* ---- B. Headings outside prose ---------------------------------------- */

/* A site.css default never outranks a bundle.css component rule — the
   same principle `main :where(p) { margin: 0 }` (section D, D22) already
   follows for paragraphs. Written with `:where()` (specificity (0,0,1))
   so any component class (`.entry__title`, `.widget__title`,
   `.archive-year__head`, `.section-head > h*`, any `.mb-N` heading
   utility below) wins on its own specificity, no tie-break needed.
   Before this rule was `main :is(h1,h2,h3):not(.prose *)` at (0,1,2), a
   generic default that out-ranked every one of those component rules and
   rendered every titled row, section-head and widget title 8–16px taller
   than the design system says — DESIGN-SPEC.md D39 (points 1 and 3) /
   D43, the M4 fix-up's commit 1. */
main :where(h1, h2, h3):where(:not(.prose *)) {
  margin: 0 0 var(--space-4);
}

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

/* map section B: the home h1 (both sites) is narrower than the generic
   measure — 18ch reads correctly at both 16ch (ld) and 18ch (mw)'s
   prototype values, since neither changes a line break at any tested
   width (map's own note). */
.page--home h1.display {
  max-width: 18ch;
}

/* A div.section-head's own heading never carries the block's usual
   bottom margin — the section-head's flex gap (bundle.css) does the
   spacing to its sibling a.more-link/span.standing instead (map section
   B: "margin: 0 in .section-head | 12"). Now that the default above is
   `:where()`, this plain class selector already outranks it on
   specificity alone — no more routing through `main` to win a
   specificity tie (D40's workaround, itself citing D39 point 3; D43's
   fix-up commit 1 makes the general default safe instead). */
.section-head > :is(h1, h2, h3) {
  margin: 0;
}

/* ---- C. Ledes and intro lines ------------------------------------------ */

.lede {
  color: var(--ink-muted);
}

article > header .lede {
  margin: 0;
  max-width: var(--measure);
}

/* A lede (or any prose child) that is .prose's own last child needs no
   bottom margin — bundle.css's `.prose > *` already zeroes the top (map
   section C: "covers every margin: 0 variant"). */
.prose > :last-child {
  margin-bottom: 0;
}

/* map section C: p.body-sm's ink-muted/measure/margin trio (stream's own
   subtitle line, the archive intro, "Four doors") — new class,
   registered, used together with the design system's own `body-sm`. */
.subtitle {
  max-width: var(--measure);
  color: var(--ink-muted);
  margin: 0 0 var(--space-4);
}

/* ---- D. Vertical rhythm ------------------------------------------------ */

/* A bare <p> starts at zero so a spacing utility (added as pages need one)
   sets only the side it names; bundle.css's own component `p` rules (for
   example `.prose > *`) still win on specificity.

   This is also the deliberate answer for the component paragraphs
   bundle.css leaves without a margin rule (`.entry__meta`'s top,
   `.revision__head`, `.backstage-strip__head`, `.filter__count`,
   `.address__row`): the prototype renders those with the browser's default
   `1em` margin only because it has no reset; zero is the design, not the
   accident — DESIGN-SPEC.md D22. */
main :where(p) {
  margin: 0;
}

/* ---- E. Prose ------------------------------------------------------------ */

/* bundle.css spaces `.prose > *` but is silent on the paragraphs *inside*
   a blockquote, so without the reset above they carried the browser's
   19px default. Zero at both ends, so the left rule spans exactly the
   quoted text, and the prose paragraph rhythm between paragraphs of a
   multi-paragraph quote (D22). */
.prose blockquote > p {
  margin: 0 0 var(--space-4);
}

.prose blockquote > p:last-child {
  margin-bottom: 0;
}

/* ---- F. Entry pages ------------------------------------------------------ */

article > header {
  margin-bottom: var(--space-7);
}

/* Entry-page metas only — `header > .entry__meta` (article), `.wrap >
   .entry__meta` (note/postcard/backstage's un-wrapped meta) and
   `.wrap > article > .entry__meta` (note/postcard/backstage's own
   article-wrapped meta). A stream row is also `article > .entry__meta`
   but its `li.entry` ancestor is neither `header` nor `.wrap`, so it
   falls through to bundle.css's own `.entry__meta { margin-bottom:
   var(--space-3) }` instead of this rule's space-4 — before this
   narrowing, the old `:is(header, article, .wrap) > .entry__meta`
   selector matched every row's meta too (`li.entry > article >
   .entry__meta`) and rendered every stream row on both sites 4px taller
   than the design system says. DESIGN-SPEC.md D39 point 2 / D43, fix-up
   commit 1. */
:is(header, .wrap) > .entry__meta,
.wrap > article > .entry__meta {
  margin-bottom: var(--space-4);
}

article > .taglist {
  margin-top: var(--space-7);
}

.page--note article,
.page--backstage article {
  max-width: var(--measure);
}

/* Backstage's h1 is one of the "margin: 0 0 var(--space-5)" group (map
   section B), not the default h1/h2/h3 var(--space-4) — the prototype's
   own inline style on this exact fixture (D29). Section B's default is
   now `:where()`, so this plain class selector wins on specificity alone
   — no more routing through `main` to tie and win by source order (D29's
   own workaround, and the same fix as the section-head rule above; D39
   point 3 / D43, fix-up commit 1). */
.page--backstage h1 {
  margin-bottom: var(--space-5);
}

/* The backstage entry page once carried its own `main .wrap {
   padding-bottom: 0 }` and `p.mt-7 { margin-bottom: var(--space-9) }`
   pair (D29's M2 fix-up) to undo section A's stacking for this one
   route — the general fix above (D33) now does the same for every page,
   so that pair is gone. */

/* postcard's taglist sits closer to the figure than an article's/note's,
   and has nothing below it to add the general rule's mt-7 rhythm to. */
.page--postcard article > .taglist {
  margin-top: 0;
  margin-bottom: var(--space-6);
}

/* A backstage follow-up, DESIGN-SPEC.md's "backstage entry" skeleton —
   new variant, registered (map section F). */
.revision--followup {
  margin-top: var(--space-7);
}

/* `postcard-figure`: a new block, registered (map section F) — the
   prototype's postcard permalink figure has no bundle.css component of
   its own. Values verbatim from the prototype's inline style=. */
figure.postcard {
  margin: 0 0 var(--space-6);
  max-width: var(--measure-wide);
}

.postcard > img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-lg);
  background: var(--surface-sunken);
}

.postcard > figcaption {
  margin-top: var(--space-3);
  max-width: var(--measure);
  font-size: 17px;
  line-height: 1.6;
  color: var(--ink-muted);
}

/* `.photostrip img` (bundle.css) already sets width:100%/aspect-ratio
   4:3/object-fit:cover, but says nothing about height, leaving it to the
   `aspect-ratio` box-sizing algorithm — normally fine, except that this
   image now also carries width/height *attributes* (DESIGN-SPEC.md line
   776's accessibility floor, pages/postcard.html), and a browser's own
   attr(width)/attr(height)-derived aspect ratio can disagree with the
   author `aspect-ratio: 4/3` when a sibling postcard's real image isn't
   exactly 4:3 — a real per-fixture risk now that these attributes are
   real intrinsic sizes rather than absent. An explicit `height: auto`
   here (matching the `postcard > img` rule above, map section F) pins
   the used height to the CSS aspect-ratio unconditionally, the same way
   `postcard > img`'s own explicit height:auto already does for the one
   image that already carried width/height attributes before this
   change. Registered: Known deviations. */
.photostrip img {
  width: 100%;
  height: auto;
}

/* ---- I. The foot group: `.relations` + `.backlinks` (DESIGN-SPEC.md
   D49/D50, Known deviations "M5, D49/D50") ---------------------------- */

/* The Relations component (partials/relations.html) has no bundle.css
   rule — the prototype predates builds_on/revisits — so this is an
   addition, not an override: `.relations` copies `.backlinks`'s container
   values and `.relations ul`/`li` copy its list reset and grid gaps
   verbatim (bundle.css "backlinks"). Same voice, no icon, no weight
   change; the heading is the whole distinction (D49). */
.relations {
  max-width: var(--measure);
  margin-top: var(--space-8);
  padding-top: var(--space-5);
  border-top: var(--border-hairline) solid var(--rule-strong);
}

.relations ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-4);
}

.relations li {
  display: grid;
  gap: var(--space-2);
}

/* The page foot's link blocks form one group under one hairline: the
   first nav.relations/nav.backlinks present carries the space-8 + rule
   step, every later one sits space-6 below with no rule and no padding.
   space-6 is the largest step that still reads as "same section, next
   heading" (D49). Specificity (0,2,0) beats bundle.css's `.backlinks`
   (0,1,0) without !important, and fires only when two or more foot
   blocks are adjacent — a page with just nav.backlinks is untouched. */
:is(.relations, .backlinks) + :is(.relations, .backlinks) {
  margin-top: var(--space-6);
  padding-top: 0;
  border-top: 0;
}

/* ---- Utilities: the mt-N/mb-N spacing steps (map section D), each one
   declaration, same family as bundle.css's own stack-N. Only the steps a
   real page uses exist; add a new one only when a page needs it. ---- */

.mt-3 {
  margin-top: var(--space-3);
}

.mt-5 {
  margin-top: var(--space-5);
}

.mt-6 {
  margin-top: var(--space-6);
}

.mt-7 {
  margin-top: var(--space-7);
}

.mt-8 {
  margin-top: var(--space-8);
}

.mb-3 {
  margin-bottom: var(--space-3);
}

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

.mb-5 {
  margin-bottom: var(--space-5);
}

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

.mb-7 {
  margin-bottom: var(--space-7);
}

.mb-8 {
  margin-bottom: var(--space-8);
}

.mb-9 {
  margin-bottom: var(--space-9);
}

/* A heading's own mb-N override loses to map section B's generic
   "main :is(h1,h2,h3):not(.prose *)" rule — same specificity trap D29
   already named for .page--backstage h1 ((0,1,2) beats a bare class's
   (0,1,0)). Routed through main :is(h1,h2,h3) here too, to tie that
   specificity and win on source order, rather than raising every mb-N/
   mt-N utility's own specificity globally — they are used on paragraphs,
   sections and divs elsewhere, where the generic heading rule never
   competes. Only the two steps a heading actually uses exist. */
main :is(h1, h2, h3).mb-3 {
  margin-bottom: var(--space-3);
}

main :is(h1, h2, h3).mb-5 {
  margin-bottom: var(--space-5);
}

/* M4: mw about's own h1 (map section B: "margin: 0 0 var(--space-6) (about
   h1) | 1 | .mb-6" — the one heading on the whole site that steps down
   further than the mb-5 group). Same specificity-tying reasoning as
   mb-3/mb-5 above. */
main :is(h1, h2, h3).mb-6 {
  margin-bottom: var(--space-6);
}

/* D1/D17: the topic page's own h1 (not inside a .section-head, unlike
   /topics/'s) and its per-type group h2s both use this step. */
main :is(h1, h2, h3).mb-4 {
  margin-bottom: var(--space-4);
}

/* ---- E. Stream rows ---------------------------------------------------- */

/* The build sets `is-last` on the final li.entry of every ul.stream;
   site.js moves it when the filter changes (D16). Replaces the
   prototype's per-row `style="border-bottom: 0"`. */
.entry.is-last {
  border-bottom: 0;
}

.entry .taglist {
  margin-top: var(--space-3);
}

.entry__figure .entry__summary {
  margin-top: var(--space-3);
}

/* M4: mw's `entry-project` row (home's "Three things I'm building",
   /projects/) spaces its status pill and its "Since …"/"Started …" span
   apart, the same way every other row type's own entry__meta layout is
   already handled by bundle.css's default `justify-content` — the
   prototype's own inline style on both mw routes' project rows. */
.entry--project .entry__meta {
  justify-content: space-between;
}

/* M4: the LatestFrom widget's own row paragraph (mw home only) — the
   prototype's inline `style="margin: 0"` on every `p.widget__line`. */
.widget__line {
  margin: 0;
}

/* ---- G. Lists ------------------------------------------------------------ */

.archive-list__mark--article {
  border-radius: 50%;
  color: var(--type-article);
}

.archive-list__mark--note {
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
  color: var(--type-note);
}

.archive-list__mark--postcard {
  border-radius: 1px;
  color: var(--type-postcard);
}

.archive-list__mark--backstage {
  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
  color: var(--type-backstage);
}

/* The postcards index's photostrip (and a topic page's Postcard group,
   D17) is denser than a teaser strip. */
.photostrip--large {
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
}

/* `.taglist__count` (D1, Known deviations: "an addition, not an
   override" — bundle.css has no such class, since the prototype has no
   /topics/ index). The `.chip__count` look, applied to a plain taglist
   row. */
.taglist__count {
  margin-left: var(--space-2);
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--ink-faint);
}

/* `.topic-name` (DESIGN-SPEC.md's "topic" page-kind row: `h1.title-1
   "Everything tagged <span.topic-name>"`) — a new, registered class for
   the tag's own mono treatment in the h1, replacing the prototype's
   inline `style="font-family:var(--font-mono);font-size:0.8em"`. */
.topic-name {
  font-family: var(--font-mono);
  font-size: 0.8em;
}

/* map section F's last row ("span mono 0.9em (colophon 'main', subscribe
   '/feed.xml') | 2 | <code>") — `bundle.css`'s own `.prose code` doesn't
   reach outside `.prose`, so both call sites (ld-subscribe's "By topic"
   why-text, M3; mw-colophon's "Deploy" row, M4) need this instead, with
   no ground (matching the prototype's own inline style, which set no
   background either). The rule was already documented in the map but
   never added to this file — ld-subscribe's own `<code>` has rendered
   unstyled since M3, unnoticed because the map row and the
   `ld-subscribe` `deviations.mjs` patch (retagging the reference's
   `<span>` to `<code>` too) both describe the *tag*, and neither the
   skeleton nor the visual-diff leg checks a tag's *computed style* — M4
   adding mw-colophon's own `<code>` is what surfaced the gap. */
main code:not(.prose *) {
  font-family: var(--font-mono);
  font-size: 0.9em;
}

/* ---- H. Trailing responsive step-down, verbatim from the prototype ---- */

@container site (max-width: 47.99rem) {
  .display { font-size: 32px; line-height: 1.16; }
  .lede { font-size: 20px; }
  .doors__label { min-width: 0; }
  .doors a { gap: var(--space-1); }
  .doors__hint { flex-basis: 100%; }
  .detail-list > div { gap: var(--space-2); }
  .archive-list__date { width: 4.5rem; }
}
