/* 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 ---------------------------------------------------- */

/* A page shorter than the window showed a white band below the footer.
   `.page` (on <body>) carries the surface background, and a body
   background normally paints the whole canvas, but `.page` is also a
   size container (`container-type: inline-size`), and containment on
   <body> stops that propagation, so the canvas behind <html> stayed the
   browser default. Painting <html> directly fills the window; --surface
   resolves there because data-theme and data-site are on <html>. */
html {
  background: var(--surface);
}

/* 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);
}

/* The home h1 once had `max-width: 18ch` (map section B), which wrapped
   any name longer than 18 characters; it now takes the generic measure
   above (D62). */

/* D62: on mw home the portrait's figure comes from Markdown (a titled
   image) inside div.portrait, so reset the figure's UA margin. */
.portrait > figure {
  margin: 0;
}

/* 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);
}

/* Rows a home-page "more" link jumps to (D77) keep a little air above. */
:is(.stream, .photostrip) > li[id] {
  scroll-margin-top: var(--space-6);
}

/* A note's or postcard's own name (D75): its label or title, under the
   meta row. */
.note__title {
  margin: 0 0 var(--space-5);
}

.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,
.postcard > picture > 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, .prevnext) {
  margin-top: var(--space-6);
  padding-top: 0;
  border-top: 0;
}

/* Older/newer (partials/prevnext.html, D69): the last block of the foot
   group, same container step as `.backlinks`; the direction line uses
   `.backlinks__title`'s mono caps, the title `.backlinks__link`'s weight.
   Two columns, newer on the right; one column on a narrow screen. */
.prevnext {
  max-width: var(--measure);
  margin-top: var(--space-8);
  padding-top: var(--space-5);
  border-top: var(--border-hairline) solid var(--rule-strong);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-5);
}

.prevnext__link {
  display: grid;
  gap: var(--space-2);
  align-content: start;
  color: var(--ink);
  text-decoration: none;
}

.prevnext__link--next {
  grid-column: 2;
  text-align: right;
}

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

.prevnext__title {
  font-weight: 600;
  font-size: 17px;
  justify-self: start;
  border-bottom: var(--border-hairline) solid var(--rule-strong);
}

.prevnext__link--next .prevnext__title {
  justify-self: end;
}

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

/* ---- 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);
}

/* An untitled row's date is its permalink (D6), and a note row shows
   its whole body, links included; bundle.css styles neither, so both
   fell back to the browser's blue (first seen with the legacy import's
   linked notes). The date keeps the meta row's muted ink; body links
   look like `.link`. */
.entry__permalink {
  color: inherit;
  text-decoration-line: underline;
  text-decoration-thickness: var(--border-hairline);
  text-underline-offset: 0.18em;
  text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
}

.entry__permalink:hover {
  color: var(--accent);
  text-decoration-color: var(--accent);
}

/* Postcard captions (page and row) can carry links too since D69 lets a
   captioned photo post become a Postcard; same look. */
:is(.entry__body, .postcard figcaption, .entry--postcard figcaption) a {
  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);
}

:is(.entry__body, .postcard figcaption, .entry--postcard figcaption) a:hover {
  text-decoration-thickness: var(--border-medium);
  text-decoration-color: var(--accent);
}

/* 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;
}

/* D70: Kevin's review note at the top of an entry: the standing note's
   frame, highlighted with the accent. */
.review {
  max-width: var(--measure);
  margin-block: var(--space-6);
  padding: var(--space-4);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  font-size: 17px;
  line-height: 1.6;
}

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

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

.review__body {
  margin: 0;
  color: var(--ink);
}

/* ---- 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);
}

/* D70: the archive row names its type with the badge (a span here, not
   a link); a fixed width keeps the titles in one column. */
.archive-list__type {
  flex: none;
  width: 6.5rem;
}

/* D70: the stream's "Read the article" line under an article's summary. */
.entry__read {
  display: flex;
  gap: var(--space-3);
  align-items: baseline;
  margin: var(--space-2) 0 0;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--ink-faint);
}

/* 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; }
  .archive-list__type { width: auto; }
  .prevnext { grid-template-columns: 1fr; }
  .prevnext__link--next { grid-column: 1; text-align: left; }
  .prevnext__link--next .prevnext__title { justify-self: start; }
}

/* ---- Career timeline: /career/'s `roles` section, plain Markdown ----- */

/* career.md writes each role as `## Role · where`, a dates line, then
   prose, so there is no li per role: the .timeline spine (bundle.css)
   stays, its grid gap doesn't (it would space every paragraph), and each
   heading after the first carries the gap instead. Sizes and colours are
   bundle.css's timeline__role / __when / __learned. */
.timeline--prose {
  display: block;
}

.timeline--prose > h2 {
  margin: 0;
  font-size: 19px;
  font-weight: 600;
}

.timeline--prose > h2:not(:first-child) {
  margin-top: var(--space-6);
}

.timeline--prose > h2 + p {
  margin: var(--space-1) 0 0;
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
}

.timeline--prose > p:not(h2 + p) {
  margin: var(--space-2) 0 0;
  max-width: var(--measure);
  font-size: 17px;
  line-height: 1.6;
  color: var(--ink-muted);
}

/* ---- /now/'s closing line: the `closing` section, plain Markdown ----- */

/* now.md writes it as an inline Markdown link, which can't carry the
   `.link` class the template's hand-written <a> used; this repeats
   bundle.css `.link` / `.link:hover` for links inside it. */
.now-closing a {
  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);
}

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

/* ---- Notes lists in Markdown: /uses/, /colophon/, /contact/ (D65) --- */

/* A `.notes-md` block holds plain Markdown: optional `## Group` headings,
   each over a list of `- **Term** detail` rows. These rules give it the
   look of the detail-list--notes component it replaced; sizes and
   colours are bundle.css's. */
.notes-md > h2 {
  margin: 0 0 var(--space-3);
  font-size: 17px;
  line-height: 1.35;
  font-weight: 600;
  letter-spacing: 0.005em;
}

.notes-md > ul,
.links-md > ul,
.doors-md > ul {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: var(--measure-wide);
  border-top: var(--border-hairline) solid var(--rule);
}

.notes-md > ul:not(:last-child) {
  margin-bottom: var(--space-7);
}

.notes-md li {
  padding-block: var(--space-3);
  border-bottom: var(--border-hairline) solid var(--rule);
  font-size: 16px;
  color: var(--ink-muted);
}

.notes-md li > strong:first-child {
  display: block;
  margin-bottom: var(--space-1);
  font-size: 17px;
  font-weight: 400;
  color: var(--ink);
}

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

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

/* ---- Links in Markdown: /links/ (D65) --------------------------------- */

/* links.md writes `## Group` then loose list items, `[Name](url)
   `host` *relation*` over a paragraph of why: the linklist look. */
.links-md > h2 {
  margin: 0 0 var(--space-4);
  font-size: 20px;
  line-height: 1.3;
  font-weight: 600;
}

.links-md > ul + h2 {
  margin-top: var(--space-8);
}

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

.links-md li > p:first-child {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3);
  margin: 0 0 var(--space-2);
}

.links-md li > p:first-child > a {
  font-size: 19px;
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
  border-bottom: var(--border-hairline) solid var(--rule-strong);
}

.links-md li > p:first-child > a:hover {
  border-bottom-color: var(--accent);
}

.links-md li > p:first-child > code {
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
}

.links-md li > p:first-child > em {
  font-family: var(--font-mono);
  font-size: 12px;
  font-style: normal;
  font-weight: 500;
  letter-spacing: 0.055em;
  text-transform: uppercase;
  color: var(--accent);
}

.links-md li > p + p {
  margin: 0;
  max-width: var(--measure);
  font-size: 17px;
  line-height: 1.6;
  color: var(--ink-muted);
}

/* ---- Doors in Markdown: section layout `doors` (D65) ------------------ */

/* `- [Label](href) hint` rows; `- *[Label](href)* hint` is the quiet
   aside door. The link's ::after covers the whole row, so the row is the
   click target as in the doors component this replaced. A paragraph
   before the list is the subtitle. */
.doors-md > p {
  max-width: var(--measure);
  margin: 0 0 var(--space-4);
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink-muted);
}

/* The list holds the two columns and each row is a subgrid of them, so
   every hint lines up after the longest label. */
.doors-md > ul {
  display: grid;
  grid-template-columns: minmax(12rem, max-content) 1fr;
  column-gap: var(--space-5);
}

.doors-md li {
  position: relative;
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
  align-items: baseline;
  padding-block: var(--space-4);
  border-bottom: var(--border-hairline) solid var(--rule);
  font-size: 16px;
  color: var(--ink-muted);
}

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

.doors-md li a {
  font-size: 19px;
  font-weight: 600;
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: var(--border-hairline);
  text-underline-offset: 0.18em;
}

.doors-md li a::after {
  content: "";
  position: absolute;
  inset: 0;
}

.doors-md li:has(> em:first-child) {
  border-bottom: 0;
}

.doors-md li > em a {
  color: var(--ink-muted);
  font-weight: 400;
}

@container site (max-width: 47.99rem) {
  .doors-md > ul { display: block; }
  .doors-md li { display: block; }
  .doors-md li > a,
  .doors-md li > em { display: block; margin-bottom: var(--space-1); }
}

/* ---- D71. Post images: a default size cap, tap to expand ---------------- */

/* Every image in a post (page or stream row) is capped at --post-img-max
   tall and never wider than its column; it keeps its aspect ratio and is
   never scaled up. `image_size: small | large | full` in a post's front
   matter changes the cap for that post (a class on its article or row).
   The bundle's width: 100% on postcard and figure images is undone here
   so the cap can't stretch them. */
:root {
  --post-img-max: 30rem;
}

.img-size--small { --post-img-max: 18rem; }
.img-size--large { --post-img-max: 45rem; }
.img-size--full { --post-img-max: none; }

.prose img,
.entry__body img,
.postcard > img,
.postcard > picture > img,
.entry__figure img {
  max-width: 100%;
  max-height: var(--post-img-max);
  height: auto;
  object-fit: contain;
}

.postcard > img,
.postcard > picture > img,
.entry__figure img,
.prose figure img {
  width: auto;
}

/* site.js marks an image that's shown smaller than its file: a click or
   Enter expands it to its full resolution, as wide as the page column
   and as tall as the screen allow (wider than the text measure on a
   wide screen); again collapses it. */
.is-zoomable {
  cursor: zoom-in;
}

.is-zoomable.is-expanded {
  width: auto;
  height: auto;
  max-width: calc(100cqi - 2 * var(--space-4));
  max-height: calc(100svh - 2 * var(--space-4));
  cursor: zoom-out;
}

@container site (min-width: 40rem) {
  .is-zoomable.is-expanded {
    max-width: min(calc(var(--page-max) - 2 * var(--space-5)), calc(100cqi - 2 * var(--space-5)));
  }
}

/* A long URL in an old post's text wraps instead of widening the page. */
.prose,
.entry__body {
  overflow-wrap: break-word;
}

/* "Also on" (partials/syndication.html): where else this post lives.
   The foot group's quiet first line, above Older/Newer. */
.syndication {
  max-width: var(--measure);
  margin-top: var(--space-6);
  font-size: 15px;
  color: var(--ink-muted);
}

/* A media <picture> is a layout-transparent wrapper: its <img> keeps
   every rule written for a bare post image. */
picture {
  display: contents;
}

/* ---- The postcard map (D82, sites/ld/pages/postcard-map.html) ----- */

.postcard-map {
  height: min(70vh, 36rem);
  border: var(--border-hairline) solid var(--rule-strong);
  border-radius: var(--radius-md);
  background: var(--surface-sunken);
  overflow: hidden;
}

/* A town's pin is a dot; an exact spot's is a larger ringed dot. A
   number in the dot counts postcards sent from the same point. */
.postcard-map__pin {
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 2px solid var(--surface);
  border-radius: 50%;
  background: var(--accent);
  color: var(--surface);
  font: 600 11px/1 var(--font-mono);
  cursor: pointer;
  box-shadow: 0 0 0 1px var(--ink-muted);
}

.postcard-map__pin:not(:empty) {
  width: 22px;
  height: 22px;
}

.postcard-map__pin--exact {
  width: 24px;
  height: 24px;
  box-shadow: 0 0 0 3px var(--accent), 0 0 0 4px var(--surface);
}

.postcard-map__pin:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

.postcard-map__popup .maplibregl-popup-content {
  padding: var(--space-3);
  font: 15px/1.4 var(--font-sans);
  background: var(--surface);
  color: var(--ink);
  border-radius: var(--radius-sm);
}

/* maplibre-gl.css loads after this file, so out-specify its tip rule. */
.postcard-map .postcard-map__popup .maplibregl-popup-tip {
  border-top-color: var(--surface);
}

.postcard-map__list {
  display: grid;
  gap: var(--space-3);
  max-height: 22rem;
  overflow-y: auto;
}

.postcard-map__card {
  display: grid;
  gap: var(--space-1);
  color: inherit;
  text-decoration: none;
}

.postcard-map__card img {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius-sm);
}

.postcard-map__name {
  font-weight: 600;
}

.postcard-map__card time {
  font-size: 13px;
  color: var(--ink-muted);
}

.postcard-map__card:hover .postcard-map__name,
.postcard-map__card:focus-visible .postcard-map__name {
  color: var(--accent);
  text-decoration: underline;
}

.site-footer__map {
  text-decoration: none;
}

/* ---- Shoeboxes (D84): sites/ld/pages/shoebox-index.html,
   pages/shoebox.html, partials/shoebox-card.html and
   partials/postcard-family-nav.html ---------------------------------- */

/* The Postcards · Shoeboxes · Map switcher: a quiet mono line, the
   current page in ink with an accent rule under it. */
.family-nav__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.04em;
}

.family-nav__link {
  display: inline-block;
  padding: var(--space-1) 0;
  color: var(--ink-faint);
  text-decoration: none;
  border-bottom: var(--border-medium) solid transparent;
}

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

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

/* The index grid of cards. */
.shoebox-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-7) var(--space-6);
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
}

.shoebox-card__link {
  display: grid;
  gap: var(--space-2);
  color: var(--ink);
  text-decoration: none;
}

.shoebox-card__title {
  margin-top: var(--space-3);
  font-weight: 600;
  font-size: 18px;
  line-height: 1.3;
  justify-self: start;
  border-bottom: var(--border-hairline) solid transparent;
}

.shoebox-card__link:hover .shoebox-card__title {
  border-bottom-color: var(--accent);
}

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

/* The stack: the cover photo with two cards tucked behind it, tilted a
   few degrees each way. The page's one bit of whimsy. They fan out a
   little on hover, but only when motion is welcome. */
.stack {
  position: relative;
  display: block;
  isolation: isolate;
  margin: var(--space-2) var(--space-2) 0;
}

.stack::before,
.stack::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border: var(--border-hairline) solid var(--rule-strong);
  border-radius: var(--radius-md);
  background: var(--surface-raised);
}

.stack::before { transform: rotate(-3deg); }
.stack::after { transform: rotate(2deg) translate(2px, 1px); }

.stack img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border: var(--border-hairline) solid var(--rule-strong);
  border-radius: var(--radius-md);
  background: var(--surface-sunken);
}

@media (prefers-reduced-motion: no-preference) {
  .stack::before,
  .stack::after {
    transition: transform 160ms ease-out;
  }

  .shoebox-card__link:hover .stack::before { transform: rotate(-6deg) translate(-4px, 2px); }
  .shoebox-card__link:hover .stack::after { transform: rotate(4deg) translate(6px, 2px); }
}

.shoebox-card__link:focus-visible .stack img {
  outline: var(--border-focus) solid var(--focus);
  outline-offset: 3px;
}

/* A shoebox page: every photo whole (never cropped), captioned under. */
.shoebox-photos {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-7) var(--space-6);
  grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
  align-items: start;
}

.shoebox-photo {
  margin: 0;
  scroll-margin-top: var(--space-6);
}

.shoebox-photo__link {
  display: block;
}

.shoebox-photo img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-md);
  background: var(--surface-sunken);
}

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

.shoebox-photo:target img {
  outline: var(--border-focus) solid var(--accent);
  outline-offset: 3px;
}

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

.shoebox-photo__caption p {
  margin: 0;
}

.shoebox-photo__caption p + p {
  margin-top: var(--space-2);
}

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

/* The photo viewer (pages/shoebox.html shoebox-viewer, site.js
   initShoeboxViewer): the whole window, dark in either theme so the
   photo carries it; the photo fits whole, the caption sits over its
   foot in a small dark box around the words, and the buttons sit on
   the edges. Sized to the dynamic viewport and kept inside the safe
   area, so a phone's toolbars and home bar never cover the caption. */
html:has(dialog.viewer[open]) {
  overflow: hidden;
}

.viewer {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  height: 100dvh;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: #0a0a0a;
  color: #f2f2f2;
  overscroll-behavior: contain;
  outline: none;
}

.viewer::backdrop {
  background: #0a0a0a;
}

.viewer__stage {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-6) 4.5rem;
  /* Sideways drags are the viewer's (a swipe); pinch-zoom stays the
     browser's. */
  touch-action: pinch-zoom;
  user-select: none;
  -webkit-user-select: none;
}

.viewer__stage picture {
  display: contents;
}

.viewer__img {
  display: block;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}

.viewer__caption {
  position: absolute;
  left: 50%;
  bottom: calc(var(--space-5) + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  width: max-content;
  max-width: min(40rem, calc(100% - 10rem));
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-md);
  background: rgb(0 0 0 / 0.62);
  font: 16px/1.5 var(--font-sans);
  text-align: center;
  pointer-events: none;
}

.viewer__caption[hidden] {
  display: none;
}

.viewer__caption p {
  margin: 0;
}

.viewer__caption p + p {
  margin-top: var(--space-2);
}

.viewer__caption .shoebox-photo__meta {
  color: rgb(242 242 242 / 0.7);
}

.viewer__count {
  position: absolute;
  top: calc(var(--space-4) + env(safe-area-inset-top, 0px));
  left: var(--space-5);
  margin: 0;
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: rgb(242 242 242 / 0.75);
}

.viewer__btn {
  position: absolute;
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgb(255 255 255 / 0.12);
  color: #fff;
  font: 2rem/1 var(--font-sans);
  cursor: pointer;
}

.viewer__btn:hover {
  background: rgb(255 255 255 / 0.25);
}

.viewer__btn:focus-visible {
  outline: var(--border-focus) solid #fff;
  outline-offset: 2px;
}

.viewer__btn[hidden] {
  display: none;
}

.viewer__close {
  top: calc(var(--space-3) + env(safe-area-inset-top, 0px));
  right: var(--space-3);
}

.viewer__prev,
.viewer__next {
  top: 50%;
  transform: translateY(-50%);
  font-size: 2.5rem;
}

.viewer__prev { left: var(--space-3); }
.viewer__next { right: var(--space-3); }

/* A phone: the photo runs edge to edge, swiping is the main way
   through, and the arrows shrink to faint buttons on its edges, so the
   caption has the full width under the photo. */
@media (max-width: 40rem) {
  .viewer__stage { padding: 3.5rem 0; }
  .viewer__caption {
    max-width: calc(100% - 2 * var(--space-4));
    font-size: 15px;
  }
  .viewer__prev,
  .viewer__next {
    width: 2.25rem;
    height: 2.25rem;
    font-size: 1.75rem;
    background: rgb(0 0 0 / 0.35);
  }
  .viewer__prev { left: var(--space-2); }
  .viewer__next { right: var(--space-2); }
}

/* Older · All shoeboxes · Newer: the post prevnext with a middle link. */
.prevnext--shoebox {
  max-width: none;
  grid-template-columns: 1fr auto 1fr;
  align-items: start;
}

.prevnext--shoebox .prevnext__link--prev { grid-column: 1; }
.prevnext--shoebox .prevnext__link--next { grid-column: 3; }

.prevnext__all {
  grid-column: 2;
  grid-row: 1;
  align-self: center;
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--ink-muted);
}

@container site (max-width: 47.99rem) {
  .prevnext--shoebox { grid-template-columns: 1fr; }
  .prevnext--shoebox .prevnext__link--prev,
  .prevnext--shoebox .prevnext__link--next,
  .prevnext__all { grid-column: 1; grid-row: auto; }
}

/* "More from this trip" on a postcard (pages/postcard.html, D84): a mini
   stack and two lines, one link. */
.shoebox-link {
  max-width: var(--measure);
  margin-top: var(--space-7);
}

.shoebox-link__a {
  display: inline-flex;
  align-items: center;
  gap: var(--space-5);
  color: var(--ink);
  text-decoration: none;
}

.stack--mini {
  flex: none;
  width: 6rem;
  margin: var(--space-1);
}

.shoebox-link__text {
  display: grid;
  gap: var(--space-1);
}

.shoebox-link__title {
  font-weight: 600;
  font-size: 17px;
  justify-self: start;
  border-bottom: var(--border-hairline) solid var(--rule-strong);
}

.shoebox-link__a:hover .shoebox-link__title {
  border-bottom-color: var(--accent);
}

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

.shoebox-link__a:focus-visible .stack img {
  outline: var(--border-focus) solid var(--focus);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: no-preference) {
  .shoebox-link__a:hover .stack::before { transform: rotate(-6deg) translate(-3px, 1px); }
  .shoebox-link__a:hover .stack::after { transform: rotate(4deg) translate(4px, 1px); }
}

/* A shoebox's pin on the postcard map (D84): a small square card with a
   second one tilted behind it, the stack in miniature. */
.postcard-map__pin--shoebox {
  position: relative;
  width: 20px;
  height: 16px;
  border-radius: var(--radius-sm);
  background: var(--type-postcard);
  isolation: isolate;
}

.postcard-map__pin--shoebox::before {
  content: "";
  position: absolute;
  inset: -2px;
  z-index: -1;
  border: 2px solid var(--surface);
  border-radius: var(--radius-sm);
  background: var(--type-postcard);
  box-shadow: 0 0 0 1px var(--ink-muted);
  transform: rotate(-12deg);
}

.postcard-map__count {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink-faint);
}

/* Two more-links side by side under a strip (partials/section-photos.html:
   All postcards, All shoeboxes; D84). */
.more-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
  margin: var(--space-4) 0 0;
}

/* A shoebox's "Postcards from this trip" (D84) often has one or two
   postcards: auto-fill keeps each at strip size instead of stretching
   one across the page. */
.photostrip--fill {
  grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
  max-width: var(--measure);
}
