/*
 * site.css — the design system, ported from the Claude Design export.
 *
 * Every value here is a var() from tokens.css. That is not tidiness: it is what
 * makes a contrast violation catchable once, in the token layer, instead of
 * forty times, one page at a time.
 *
 * Layout is CSS grid with hard 2px rules, the way the export draws it — the
 * page reads as a ruled sheet, not as floating cards. Rails collapse to stacked
 * blocks below 60rem; nothing is hidden, it just reorders.
 */

@import url("/css/fonts.css");

/* ── Page transitions ──────────────────────────────────────────────────────
 * FUNDAMENTALS §D: "View Transitions API for page-to-page, as progressive
 * enhancement. It must degrade to an instant, correct navigation in a browser
 * without it." This is that, and it is the F4 line that was never built.
 *
 * Cross-document, declared in CSS rather than driven from site.js. That matters
 * for the same reason everything else here is build-time: a navigation must not
 * depend on a script having loaded. A browser that does not implement this rule
 * ignores it and navigates instantly, which is the correct fallback and needs no
 * feature detection anywhere.
 *
 * The motion is the export's `viewIn`, unchanged: a soft fade with a 14px rise,
 * on a curve that leaves quickly and settles. The old page only fades — the two
 * pages moving in opposite directions reads as a slideshow, and §D's bar is iOS,
 * where the outgoing view gets out of the way rather than performing.
 *
 * The masthead is deliberately NOT part of it. In the export the animation is on
 * the content column and the header sits outside it, so a bar that is identical
 * on every page stays put and only its breadcrumb changes. Naming it takes it
 * out of the root snapshot and gives it its own, quicker cross-fade.
 *
 * The first load of a session animates nothing: there is no old page to
 * transition from. That is what keeps this clear of §D's "nothing animates the
 * LCP element".
 */
@view-transition {
  navigation: auto;
}

::view-transition-old(root) {
  animation: viewOut var(--motion-base) var(--ease-view) both;
}

::view-transition-new(root) {
  animation: viewIn var(--motion-slow) var(--ease-view) both;
}

@keyframes viewIn {
  from {
    opacity: 0;
    transform: translateY(var(--view-shift));
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes viewOut {
  to {
    opacity: 0;
  }
}

/* The name itself is declared on .masthead further down, next to the sticky
   positioning it belongs with, rather than duplicated up here. */
::view-transition-old(masthead),
::view-transition-new(masthead) {
  animation-duration: var(--motion-fast);
  animation-timing-function: var(--ease-view);
}

/* Reduced motion is honoured at the token layer — the media query in tokens.css
   zeroes the three durations, so these animations resolve to 0s. The rule below
   is the belt to that braces: a view transition is a full-screen event, and a
   0s animation is still an animation. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

/* ── Reset ─────────────────────────────────────────────────────────────── */

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

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

/* The shell is a flex column that is never shorter than the viewport, so a
   short page still pins its colophon and handbook band to the bottom edge
   instead of leaving the canvas showing under them. svh, not vh, because on
   mobile vh is the *largest* viewport height — with the URL bar retracted —
   which would leave the page one toolbar taller than the screen on load. */
body {
  margin: 0;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100svh;
  background: var(--page-bg);
  color: var(--color-ink);
  font-family: var(--font-body);
  font-size: var(--size-body);
  line-height: var(--leading-body);
  transition: background-color var(--motion-base) var(--ease-enter);
}

/* Absorbs the slack. 1 0 auto rather than 1 1 auto: main grows to fill a short
   page but is never squeezed below its content on a long one. The last block
   inside it grows too, so the column rules of a ruled section run all the way
   down to the colophon instead of stopping mid-page over blank canvas. */
.main {
  display: flex;
  flex-direction: column;
  flex: 1 0 auto;
}

.main > :last-child {
  flex: 1 0 auto;
}

p {
  text-wrap: pretty;
}

h1,
h2,
h3 {
  text-wrap: balance;
}

img,
svg {
  max-width: 100%;
}

a {
  color: inherit;
  text-decoration: none;
  transition:
    color var(--motion-fast) var(--ease-enter),
    background-color var(--motion-fast) var(--ease-enter),
    box-shadow var(--motion-fast) var(--ease-enter);
}

a:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* Focus is never removed, only made unmissable. */
:focus-visible {
  outline: var(--rule-weight) solid var(--color-accent);
  outline-offset: 2px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 99;
  background: var(--color-invert-bg);
  color: var(--color-invert-fg);
  padding: var(--space-3) var(--space-4);
}

.skip-link:focus {
  left: 0;
}

/* ── Shared type ───────────────────────────────────────────────────────── */

.eyebrow {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--size-eyebrow);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--color-muted);
}

.eyebrow--accent {
  color: var(--color-accent);
  font-size: var(--size-meta);
}

.meta {
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  color: var(--color-muted);
}

.small {
  font-size: var(--size-small);
  line-height: var(--leading-snug);
  color: var(--color-muted);
}

.lede {
  margin: 0;
  font-size: var(--size-lead);
  line-height: var(--line-unit);
  max-width: var(--measure-lead);
  color: var(--color-body);
}

/* ── Masthead ──────────────────────────────────────────────────────────── */

.masthead {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--page-bg);
  /* Taken out of the root snapshot so the bar does not rise 14px on every
     navigation — it is the same bar on every page. Its own group cross-fades
     the breadcrumb, which is the only part of it that changes. See the page
     transition block at the top of this file. */
  view-transition-name: masthead;
}

.masthead__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-5);
  padding: var(--space-3) var(--space-8);
  border-bottom: var(--rule-weight) solid var(--rule-strong);
  font-size: var(--size-ui);
}

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

.wordmark:hover {
  text-decoration: none;
}

/* The logotype. Height-sized so the mark beside it and the name keep their
   proportion at any type scale; width follows the 7.2:1 ratio of the artwork. */
.wordmark__logotype {
  height: var(--wordmark-height);
  width: auto;
  display: block;
}

.logo rect,
.logo polygon {
  fill: var(--logo-rest);
  transition: fill var(--motion-base) var(--ease-enter);
}

/* The customisable-totem system, saying hello. Colour is decorative here: the
   wordmark beside it carries the name, so nothing is lost without it. */
.wordmark:hover .logo__a {
  fill: var(--logo-a);
}
.wordmark:hover .logo__b {
  fill: var(--logo-b);
}
.wordmark:hover .logo__c {
  fill: var(--logo-c);
}
.wordmark:hover .logo__d {
  fill: var(--logo-d);
}
.wordmark:hover .logo__e {
  fill: var(--logo-e);
}
.wordmark:hover .logo__f {
  fill: var(--logo-f);
}

.nav {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
  /* The switcher is a sibling now, so the free space has to be claimed here or
     `space-between` would strand the links in the middle of the bar. An auto
     margin eats it first, which keeps the links and the switcher one group on
     the right — exactly where they sat when the switcher was inside the nav. */
  margin-left: auto;
}

.lang {
  display: inline-flex;
  gap: var(--space-1);
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  color: var(--color-muted);
}

.lang [aria-current] {
  color: var(--color-ink);
}

.breadcrumb {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Wraps rather than overflows: the trail carries a totem title of unknown
     length and the tools are fixed width, so at 320px one of them has to give.
     It is the trail, onto its own line. */
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  padding: var(--space-2) var(--space-8);
  border-bottom: var(--rule-weight) solid var(--rule-strong);
  font-size: var(--size-ui);
  color: var(--color-dim);
}

.breadcrumb__trail {
  min-width: 0;
}

/* A step below the controls it sits next to: "1 / 49" is a label reporting
   where you are, not a third thing to press. */
.breadcrumb__meta {
  font-family: var(--font-mono);
  font-size: var(--size-ui);
  color: var(--color-muted);
  white-space: nowrap;
}

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

/* One metric for the three boxes in the route bar. They were drawn as two
   unrelated controls — the search box a hairline field, the arrows a 2px black
   button two thirds its height — which is why the cluster never lined up. Now
   the size is the bar's own --size-ui, so "Search", "47 / 49" and the arrows are
   one text; and the border is the structural --rule-weight, kept in the quiet
   field colour so the weight matches the bar without the black of a rule.
   Both keep their own hover state, which is where the strong border and the
   inversion still live. */
.searchbtn,
.pagerbtns a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--control-height);
  padding: 0 var(--space-3);
  box-shadow: inset 0 0 0 var(--rule-weight) var(--rule-field);
  font-family: var(--font-mono);
  font-size: var(--size-ui);
  white-space: nowrap;
}

/* The gap is not optional here: the label is three flex items now — "Type", the
   cap, "to search" — and a flex container eats the whitespace that used to space
   them, so without it the string renders as "Type/to search". */
.searchbtn {
  gap: var(--space-1);
  color: var(--color-muted);
}

.searchbtn:hover {
  text-decoration: none;
  color: var(--color-ink);
  box-shadow: inset 0 0 0 var(--rule-weight) var(--rule-strong);
}

/* Key caps, everywhere a key is named: the search button and the dialog's hint
   line. Square, because nothing in this sheet is round — the cap wears the same
   inset field rule the controls in the route bar wear, one weight down, so it
   reads as a key inside a control rather than a control inside a control. */
kbd {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--key-size);
  height: var(--key-size);
  padding: 0 var(--space-1);
  box-shadow: inset 0 0 0 var(--rule-hairline) var(--rule-field);
  font-family: var(--font-mono);
  font-size: var(--size-eyebrow);
}

.searchbtn kbd {
  transition: box-shadow var(--motion-fast) var(--ease-enter);
}

.searchbtn:hover kbd {
  box-shadow: inset 0 0 0 var(--rule-hairline) var(--rule-strong);
}

.pagerbtns {
  display: flex;
  gap: var(--space-1);
  font-family: var(--font-mono);
  font-size: var(--size-ui);
}

.pagerbtns a {
  color: var(--color-muted);
}

.pagerbtns a:hover {
  text-decoration: none;
  background: var(--color-invert-bg);
  color: var(--color-invert-fg);
}

/* ── Search dialog ─────────────────────────────────────────────────────── */

.search {
  width: min(38rem, 92vw);
  margin-top: 12vh;
  padding: 0;
  border: 0;
  background: var(--color-canvas);
  color: var(--color-ink);
  box-shadow: inset 0 0 0 var(--rule-weight) var(--rule-strong);
}

.search::backdrop {
  background: var(--color-invert-bg);
  opacity: 0.32;
}

.search__form {
  margin: 0;
}

.search__input {
  width: 100%;
  border: 0;
  border-bottom: var(--rule-weight) solid var(--rule-strong);
  outline: none;
  padding: var(--space-4) var(--space-5);
  background: var(--color-canvas);
  color: var(--color-ink);
  font-family: var(--font-body);
  font-size: var(--size-lead);
}

.search__results {
  display: flex;
  flex-direction: column;
  max-height: 52vh;
  overflow: auto;
  margin: 0;
  padding: 0;
  list-style: none;
}

.search__hit {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-5);
  border-bottom: var(--rule-hairline) solid var(--rule-hair);
  font-size: var(--size-list);
}

.search__hit:hover,
.search__hit[data-active] {
  text-decoration: none;
  background: var(--page-bg);
}

.search__kind {
  font-family: var(--font-mono);
  font-size: var(--size-eyebrow);
  color: var(--color-muted);
  white-space: nowrap;
}

/* A full-text hit is a title over the sentence it matched in, so it stacks
   where a jump-list row sits on one line. Same row, two shapes: the empty
   field lists places, a query lists passages. */
.search__hit--rich {
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-1);
}

.search__excerpt {
  font-size: var(--size-eyebrow);
  line-height: var(--leading-snug);
  color: var(--color-muted);
  /* Two lines. An excerpt long enough to need scrolling is a page, not a hint
     about which page. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* The matched words. A background swap would have to clear contrast against
   every one of the 42 totem tints; weight and colour carry it on all of them. */
.search__excerpt mark {
  background: none;
  color: var(--color-ink);
  font-weight: var(--weight-strong);
}

.search__empty {
  padding: var(--space-3) var(--space-5);
  font-size: var(--size-list);
  color: var(--color-muted);
}

.search__hints {
  display: flex;
  gap: var(--space-1);
  margin: 0;
  padding: var(--space-2) var(--space-5);
  font-family: var(--font-mono);
  font-size: var(--size-eyebrow);
  color: var(--color-muted);
}

/* ── Page header ───────────────────────────────────────────────────────── */

.pagehead {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 20rem;
  gap: var(--space-9);
  align-items: start;
  padding: var(--space-9) var(--space-8) var(--space-8);
  border-bottom: var(--rule-weight) solid var(--rule-strong);
}

.pagehead--solo {
  grid-template-columns: minmax(0, 1fr);
}

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

.pagehead__title {
  margin: 0;
  font-family: var(--font-head);
  font-size: var(--size-hero);
  line-height: var(--leading-hero);
  font-weight: var(--weight-body);
  letter-spacing: var(--track-head);

  /* A single long word at hero size overflows a 320px viewport on its own, and
     nothing else on the page has to be wrong for it to happen. "Acknowledgements"
     measured 353px in a 320px viewport and failed check:responsive, which is
     WCAG 1.4.10: at hero size the threshold is about sixteen characters, so this
     is a trap any future page title can walk into rather than a fact about one
     word. `hyphens` breaks it at a legal point and reads correctly in both
     languages, since <html lang> is set; `overflow-wrap` is the backstop for a
     word the hyphenation dictionary does not know. */
  hyphens: auto;
  overflow-wrap: break-word;
}

.pagehead__aside {
  display: flex;
  flex-direction: column;
  padding-top: var(--space-2);
}

.pagehead__fact {
  margin: 0;
  font-size: var(--size-small);
  line-height: var(--line-unit);
}

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-1) 0 0;
}

/* ── Buttons ───────────────────────────────────────────────────────────── */

.btn {
  display: inline-block;
  font-size: var(--size-meta);
  font-weight: var(--weight-strong);
  padding: var(--space-3) var(--space-5);
  box-shadow: inset 0 0 0 var(--rule-weight) var(--rule-strong);
}

.btn:hover {
  text-decoration: none;
}

.btn--solid {
  background: var(--color-invert-bg);
  color: var(--color-invert-fg);
}

.btn--solid:hover {
  background: var(--color-canvas);
  color: var(--color-ink);
}

.btn--outline {
  background: var(--color-canvas);
  color: var(--color-ink);
}

.btn--outline:hover {
  background: var(--color-invert-bg);
  color: var(--color-invert-fg);
}

.btn--invert {
  background: var(--color-invert-fg);
  color: var(--color-ink);
  box-shadow: none;
}

.btn--small {
  font-size: var(--size-ui);
  padding: var(--space-2) var(--space-4);
}

/* ── Forms ─────────────────────────────────────────────────────────────── */

/* The form on paper. The handbook band has its own set further down because it
   draws on the inverted palette and has to fit on one line inside a docked bar;
   this is the same drawing in the page's own colours, stacked, for a form with
   a textarea in it. The two share the honeypot rule (.hp) and nothing else,
   which is honest: they are not the same component, they are the same language.

   Ruled, not boxed. An inset hairline instead of a border, so focus can thicken
   the line without moving anything on the page — a field that grows a 2px
   border on focus shifts every field under it. */
.form {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  max-width: var(--measure-form);
}

.form__field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* Visible, not off-screen. The band hides its label because it has one field in
   a bar with no room; a form with three fields and a textarea has both the room
   and the obligation — a placeholder is an example, and it stops existing at the
   first keystroke, which is exactly when a person needs to know what the field
   was for. */
.form__label {
  font-size: var(--size-eyebrow);
  font-weight: var(--weight-strong);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--color-body);
}

.form__input {
  min-height: var(--control-height);
  padding: var(--space-2) var(--space-3);
  border: 0;
  background: var(--color-canvas);
  box-shadow: inset 0 0 0 var(--rule-hairline) var(--color-muted);
  color: var(--color-ink);
  font-family: var(--font-body);
  font-size: var(--size-meta);
  transition: box-shadow var(--motion-fast) var(--ease-enter);
}

.form__input::placeholder {
  color: var(--color-muted);
  opacity: 1;
}

.form__input:hover,
.form__input:focus {
  box-shadow: inset 0 0 0 var(--rule-hairline) var(--color-ink);
}

/* `resize: vertical`, not none: taking away a control someone is using to read
   their own paragraph is not a design decision worth making. */
.form__textarea {
  resize: vertical;
  line-height: var(--leading-snug);
}

.form__consent {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  font-size: var(--size-meta);
  line-height: var(--leading-snug);
  color: var(--color-body);
}

.form__consent a {
  text-decoration: underline;
  text-underline-offset: 3px;
}

.form__actions {
  margin: 0;
}

/* .btn was written for anchors; a <button> inherits none of the page's type and
   draws a chrome border of its own. */
.form .btn {
  border: 0;
  cursor: pointer;
  font-family: var(--font-body);
}

.form__status:empty {
  display: none;
}

.form__status {
  margin: 0;
  font-size: var(--size-meta);
  line-height: var(--leading-snug);
  color: var(--color-ink);
}

/* ── Sections ──────────────────────────────────────────────────────────── */

.section {
  border-bottom: var(--rule-weight) solid var(--rule-strong);
}

.section--split {
  display: grid;
  grid-template-columns: var(--rail-width) minmax(0, 1fr);
}

.section__label {
  border-right: var(--rule-weight) solid var(--rule-strong);
  padding: var(--space-7) var(--space-6) var(--space-8);
}

.section__note {
  margin: var(--space-3) 0 0;
  font-size: var(--size-meta);
  line-height: var(--leading-snug);
  color: var(--color-body);
}

.section__body {
  padding: var(--space-7) var(--space-8) var(--space-8);
}

.section:not(.section--split) > .section__body {
  padding-inline: var(--space-8);
}

.section__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  padding-bottom: var(--space-2);
}

.section__head h2 {
  margin: 0;
  font-family: var(--font-head);
  font-size: var(--size-h2);
  font-weight: var(--weight-bold);
  letter-spacing: var(--track-h2);
}

/* ── The numbered index ────────────────────────────────────────────────── */

/* Multi-column, not a two-track grid. A grid with auto-flow row fills across
   — 01 next to 02, 03 next to 04 — so the numbering read left-to-right and the
   list stopped being a list. Columns fill down and balance: 01–21 on the left,
   22–42 on the right, and the split moves on its own the day a totem is added
   or dropped rather than being a number written twice. */
.index {
  columns: 2;
  column-gap: var(--space-8);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* A row split across the column break would put a number at the foot of one
   column and its title at the head of the next. */
.index__row {
  break-inside: avoid;
}

.index__row a {
  display: grid;
  position: relative;
  grid-template-columns: 2.125rem minmax(0, 1fr) auto;
  gap: var(--space-3);
  align-items: baseline;
  padding: var(--space-2) var(--space-2) var(--space-2) 0;
  border-top: var(--rule-hairline) solid var(--rule-hair);
  font-size: var(--size-list);
  transition:
    background-color var(--motion-fast) var(--ease-enter),
    border-color var(--motion-fast) var(--ease-enter),
    box-shadow var(--motion-fast) var(--ease-enter);
}

/* The hovered row lifts off the page tint onto paper and gets ruled top and
   bottom: its own hairline goes strong, and a shadow draws the edge below it —
   a second border there would move every row after it down by a pixel. The
   number and the theme take the accent, the title takes full ink. All of it is
   paint, none of it is layout, so the whole state costs no reflow.
   Replaces a bare colour swap that was invisible against a list of forty-two. */
.index__row a:hover,
.index__row a:focus-visible {
  text-decoration: none;
  background-color: var(--color-canvas);
  border-top-color: var(--rule-strong);
  box-shadow: 0 var(--rule-hairline) 0 var(--rule-strong);
  /* The shadow only drew that bottom edge on the last row of a column. Every
     other row has a neighbour underneath whose own hairline paints later, in
     tree order, straight over it — so the hover showed one rule where it was
     meant to show two, and the last row of each column looked like the odd one
     out. z-index moves the hovered row into the positive-index paint step, past
     every sibling, so its shadow lands on top of the hairline instead of under
     it. Still paint, still no reflow. */
  z-index: 1;
}

.index__n,
.index__tag {
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  color: var(--color-muted);
}

.index__n,
.index__tag,
.index__title {
  transition: color var(--motion-fast) var(--ease-enter);
}

.index__row a:hover .index__n,
.index__row a:focus-visible .index__n,
.index__row a:hover .index__tag,
.index__row a:focus-visible .index__tag {
  color: var(--color-accent);
}

.index__row a:hover .index__title,
.index__row a:focus-visible .index__title {
  color: var(--color-ink);
}

.index__tag {
  font-size: var(--size-eyebrow);
}

/* ── Totem ─────────────────────────────────────────────────────────────── */

.totem,
.post {
  display: grid;
  grid-template-columns: var(--rail-width) minmax(0, 1fr) var(--rail-narrow);
}

.rail {
  padding: var(--space-6) var(--space-6) var(--space-8);
}

.rail--index,
.rail--byline {
  border-right: var(--rule-weight) solid var(--rule-strong);
}

.rail--related {
  border-left: var(--rule-weight) solid var(--rule-strong);
}

.rail__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: var(--space-3) 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--size-ui);
  color: var(--color-dim);
}

.rail__list a {
  display: grid;
  grid-template-columns: 1.625rem minmax(0, 1fr);
  gap: var(--space-2);
}

.rail--related .rail__list a {
  display: block;
}

.rail__list a[aria-current="page"] {
  font-weight: var(--weight-bold);
  color: var(--color-ink);
}

.rail__n {
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  color: var(--color-muted);
}

.rail__name {
  margin: var(--space-2) 0 0;
  font-size: var(--size-list);
  font-weight: var(--weight-strong);
}

.rail__meta {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  color: var(--color-muted);
}

.totem__body,
.post__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: var(--space-8) var(--space-8) var(--space-9);
}

.totem__title,
.post__title {
  margin: 0;
  font-family: var(--font-head);
  font-size: var(--size-h1);
  line-height: var(--leading-tight);
  font-weight: var(--weight-bold);
  letter-spacing: var(--track-head);
}

.totem__stamp {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  color: var(--color-muted);
}

.pager {
  display: flex;
  justify-content: space-between;
  gap: var(--space-6);
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: var(--rule-weight) solid var(--rule-strong);
  font-size: var(--size-meta);
  font-weight: var(--weight-strong);
  color: var(--color-accent);
}

.pager__next {
  text-align: right;
}

.post__back {
  margin: var(--space-4) 0 0;
  padding-top: var(--space-4);
  border-top: var(--rule-hairline) solid var(--rule-soft);
  font-size: var(--size-meta);
  font-weight: var(--weight-strong);
}

/* ── Prose ─────────────────────────────────────────────────────────────── */

.prose {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  max-width: var(--measure);
  color: var(--color-body);
}

.prose > * {
  margin: 0;
}

/* Intro prose in a hero reads at lede size and on the lede measure: it is the
   standfirst, not the body copy, and the export sets it a step larger. */
.prose--intro {
  gap: var(--line-unit);
  max-width: var(--measure-lead);
  font-size: var(--size-lead);
  line-height: var(--line-unit);
}

.prose h2 {
  margin-top: var(--space-2);
  font-family: var(--font-head);
  font-size: var(--size-h3);
  font-weight: var(--weight-bold);
  letter-spacing: var(--track-h2);
  color: var(--color-ink);
}

.prose ul,
.prose ol {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
  padding-left: var(--space-5);
}

.prose a {
  color: var(--color-accent);
  font-weight: var(--weight-strong);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ── Blocks ────────────────────────────────────────────────────────────── */

.quote {
  margin: 0;
  background: var(--color-canvas);
  border-left: var(--space-1) solid var(--color-accent);
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.quote__body {
  font-size: var(--size-quote);
  line-height: var(--leading-snug);
  font-style: italic;
  color: var(--color-ink);
}

.quote__body > p {
  margin: 0;
}

.quote__cite {
  font-size: var(--size-ui);
  font-style: normal;
  color: var(--color-cite);
}

.note {
  border: var(--rule-hairline) solid var(--rule-soft);
  background: var(--color-canvas);
  padding: var(--space-4) var(--space-5);
  font-size: var(--size-meta);
  line-height: var(--leading-snug);
  color: var(--color-body);
}

.note--warning {
  border-left: var(--space-1) solid var(--color-accent);
}

.note__label {
  margin: 0 0 var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--size-eyebrow);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--color-muted);
}

.note__body > * {
  margin: 0 0 var(--space-3);
}

.note__body > :last-child {
  margin-bottom: 0;
}

.figure {
  margin: 0;
}

.figure__frame img {
  display: block;
  height: auto;
}

.figure__caption {
  margin-top: var(--space-2);
  font-size: var(--size-small);
  color: var(--color-muted);
}

/* ── Stacked lists ─────────────────────────────────────────────────────── */

.stack {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  list-style: none;
}

.stack > li {
  border-top: var(--rule-hairline) solid var(--rule-hair);
}

.stack > li:first-child {
  border-top: 0;
}

.brand {
  display: grid;
  grid-template-columns: var(--brand-tracks);
  gap: var(--space-7);
  align-items: start;
  padding: var(--space-5) 0;
  position: relative;
  transition:
    background-color var(--motion-fast) var(--ease-enter),
    border-color var(--motion-fast) var(--ease-enter),
    box-shadow var(--motion-fast) var(--ease-enter);
}

.brand--brief {
  grid-template-columns: var(--brand-tracks-brief);
}

/* The whole row is the link. There is still exactly one <a> — the brand name —
   so the accessible name, the keyboard stop and the status-bar URL are the ones
   that were always there; its ::after just stretches the pointer target over the
   row. The price of the pattern is that the description stops being selectable
   text, which a table of four outbound links can afford. Keyboard parity comes
   from :has(), so focusing the name lights the same row hovering it does. */
.brand__name::after {
  content: "";
  position: absolute;
  inset: 0;
}

/* Three rows, three different hover states, none of them intended — the first
   row had no top rule to light because :first-child zeroes the border, the
   middle rows lost their bottom rule to the next row's hairline painting over
   the shadow, and only the last row (no neighbour, nothing painting later)
   showed the boxed row the state was designed as. Two fixes, and after them
   every row hovers the same: the hairline above the first row exists but is
   transparent, so there is something to paint; and z-index lifts the hovered
   row past its siblings so its shadow is the last thing drawn at that seam. */
.stack--brands > li:first-child {
  border-top: var(--rule-hairline) solid transparent;
}

.brand:hover,
.brand:has(.brand__name:focus-visible) {
  background-color: var(--color-canvas);
  border-top-color: var(--rule-strong);
  box-shadow: 0 var(--rule-hairline) 0 var(--rule-strong);
  z-index: 1;
}

.brand:hover .brand__name,
.brand:has(.brand__name:focus-visible) .brand__name {
  color: var(--color-accent);
}

.brand__name {
  font-family: var(--font-head);
  font-size: var(--size-h2);
  font-weight: var(--weight-bold);
  letter-spacing: var(--track-h2);
}

/* The one bit of movement in the row: the arrow leaves in the direction it
   points. Zeroed with the duration tokens under prefers-reduced-motion. */
.brand__arrow {
  display: inline-block;
  transition: transform var(--motion-fast) var(--ease-enter);
}

.brand:hover .brand__arrow {
  transform: translate(0.14em, -0.14em);
}

.brand__id p,
.brand__who p {
  margin: var(--space-1) 0 0;
}

/* The row is a row, not prose: it takes the small scale the rest of the ruled
   rows take, not the document body size it was inheriting — that made it the
   only 0.95rem text in a sheet of 0.875rem ones. Same metric as the .small in
   the audience column beside it, so the two stop competing; the hierarchy
   between them is carried by colour, the way it is everywhere else here. */
.brand__body {
  margin: 0;
  font-size: var(--size-small);
  line-height: var(--leading-snug);
  color: var(--color-body);
}

/* Retired brands. The live-brand grid at one step down in weight and colour:
   same tracks, same rules, no hover — there is nothing to click. The name drops
   from h2 to h3 and from ink to dim so the eye reads the section as past tense
   before it reads a word of it. */
.past {
  display: grid;
  grid-template-columns: var(--brand-tracks-brief);
  gap: var(--space-7);
  align-items: start;
  padding: var(--space-5) 0;
}

.past__name {
  margin: 0;
  font-family: var(--font-head);
  font-size: var(--size-h3);
  font-weight: var(--weight-bold);
  letter-spacing: var(--track-h2);
  color: var(--color-dim);
}

.past__id p {
  margin: var(--space-1) 0 0;
}

.past__id .past__name {
  margin: 0;
}

.ritual {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3) 0;
}

.stack--rituals {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: var(--space-8);
}

.stack--rituals > li:nth-child(2) {
  border-top: 0;
}

.ritual__title {
  font-size: var(--size-list);
  font-weight: var(--weight-strong);
}

.principles {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  margin: 0;
  padding: 0;
  list-style: none;
}

.principle {
  display: grid;
  grid-template-columns: 3.25rem minmax(0, 1fr);
  gap: var(--space-4);
}

.principle__n {
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  color: var(--color-muted);
}

.principle__title {
  margin: 0 0 var(--space-2);
  font-family: var(--font-head);
  font-size: var(--size-h2);
  font-weight: var(--weight-body);
  letter-spacing: var(--track-h2);
}

.principle p {
  margin: 0;
  max-width: var(--measure);
  color: var(--color-body);
}

.pairs {
  margin: 0;
}

.pair {
  display: grid;
  grid-template-columns: 13.75rem minmax(0, 1fr);
  gap: var(--space-6);
  align-items: baseline;
  padding: var(--space-3) 0;
  border-top: var(--rule-hairline) solid var(--rule-hair);
}

.pair:first-child {
  border-top: 0;
}

.pair dt {
  font-size: var(--size-list);
  font-weight: var(--weight-strong);
}

.pair dd {
  margin: 0;
  font-size: var(--size-meta);
  color: var(--color-body);
}

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

.step {
  display: grid;
  grid-template-columns: 2.5rem minmax(0, 1fr) 6.25rem;
  gap: var(--space-4);
  align-items: baseline;
  padding: var(--space-3) 0;
  border-top: var(--rule-hairline) solid var(--rule-hair);
  font-size: var(--size-list);
}

.step:first-child {
  border-top: 0;
}

.step__n {
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  color: var(--color-muted);
}

.position {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 16rem;
  gap: var(--space-7);
  align-items: start;
  padding: var(--space-5) 0;
}

.position__role {
  margin: 0 0 var(--space-2);
  font-family: var(--font-head);
  font-size: var(--size-h2);
  font-weight: var(--weight-bold);
  letter-spacing: var(--track-h2);
}

.position__role .meta {
  font-weight: var(--weight-body);
}

.position__main p {
  margin: 0;
  max-width: var(--measure-lead);
  color: var(--color-body);
}

.position__looking {
  margin: var(--space-2) 0 0;
  padding-left: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--size-small);
  color: var(--color-muted);
}

.position__meta {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
}

.position__meta p {
  margin: 0;
}

.postcard a {
  display: grid;
  grid-template-columns: 7.5rem minmax(0, 1fr) 7.5rem;
  gap: var(--space-7);
  align-items: start;
  padding: var(--space-5) 0;
}

.postcard a:hover {
  text-decoration: none;
}

.postcard a:hover .postcard__title {
  color: var(--color-accent);
}

.postcard__main {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.postcard__title {
  font-family: var(--font-head);
  font-size: var(--size-h2);
  font-weight: var(--weight-bold);
  letter-spacing: var(--track-h2);
  transition: color var(--motion-fast) var(--ease-enter);
}

.postcard__read {
  text-align: right;
}

/* ── Colophon and handbook band ────────────────────────────────────────── */

/* The colophon closes the ruled sheet, so it carries its own top rule instead
   of borrowing the bottom border of whatever section happens to precede it —
   a short page, or one ending in a block that has no bottom rule, was leaving
   the footer floating loose. The last block in main drops its own bottom
   border so the seam is one 2px rule everywhere, never two stacked. */
.main > :last-child {
  border-bottom: 0;
}

.colophon {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-3) var(--space-5);
  padding: var(--space-5) var(--space-8);
  border-top: var(--rule-weight) solid var(--rule-strong);
  font-size: var(--size-ui);
  color: var(--color-muted);
}

/* The conformance claim sits between the credit and the legal links. It is
   ordinary footer text, not a badge: a badge implies certification by someone
   else, and this is self-assessed. */
.colophon__a11y {
  flex: 1;
  text-align: center;
}

.colophon a {
  color: inherit;
}

.handbook {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-8);
  background: var(--color-invert-bg);
  color: var(--color-invert-fg);
}

.handbook__pitch {
  flex: 1 1 18rem;
  margin: 0;
  font-size: var(--size-meta);
}

/* The capture, in the space the three facts used to take. It is the second
   flexible item in the band, so on a wide screen it sits beside the pitch and
   on a narrow one it takes its own line under it — which is what it should do,
   being the thing the block is for. */
.handbook__form {
  flex: 1 1 20rem;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0;
}

/* The honeypot. Not `display:none`: some bots skip what is display-none and
   fill what is merely off-screen, and the ones worth catching are the second
   kind. `aria-hidden` and `tabindex="-1"` on the markup keep it out of the
   accessibility tree, so nothing announces it and nothing tabs into it. */
.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* The form stacks now that it carries a consent line and a status line: three
   rows, not one, and the input row keeps its own horizontal arrangement. */
.handbook__consent {
  flex: 1 1 100%;
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  font-size: var(--size-eyebrow);
  line-height: var(--leading-snug);
  color: var(--color-invert-dim);
}

.handbook__consent a {
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* aria-live, so the answer is announced rather than only drawn. Empty until
   there is something to say — an empty live region is silent. */
.handbook__status:empty {
  display: none;
}

.handbook__status {
  flex: 1 1 100%;
  margin: 0;
  font-size: var(--size-eyebrow);
  color: var(--color-invert-fg);
}

/* Off-screen, not absent. Same device as .skip-link, and for the same reason:
   the control needs a name a screen reader can read, and the placeholder is an
   example rather than a name — it is gone by the second keystroke. */
.handbook__label {
  position: absolute;
  left: -9999px;
}

/* Ruled, not boxed: an inset hairline instead of a border, so the field is the
   same one-line drawing as .handbook__close beside it and its focus state can
   thicken the line without moving anything. */
.handbook__input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: var(--control-height);
  padding: var(--space-2) var(--space-3);
  border: 0;
  background: none;
  box-shadow: inset 0 0 0 var(--rule-hairline) var(--color-invert-dim);
  color: var(--color-invert-fg);
  font-family: var(--font-body);
  font-size: var(--size-meta);
  transition: box-shadow var(--motion-fast) var(--ease-enter);
}

.handbook__input::placeholder {
  color: var(--color-invert-dim);
  opacity: 1;
}

.handbook__input:hover,
.handbook__input:focus {
  box-shadow: inset 0 0 0 var(--rule-hairline) var(--color-invert-fg);
}

/* .btn was written for anchors; a <button> inherits none of the page's type
   and draws a chrome border of its own. */
.handbook__form .btn {
  flex: none;
  border: 0;
  cursor: pointer;
  font-family: var(--font-body);
}

/* The site's focus ring is the accent on paper, which is not a ring on a black
   band. Inside it, focus is drawn in the band's own foreground. */
.handbook :focus-visible {
  outline-color: var(--color-invert-fg);
}

/* ── The band, docked ──────────────────────────────────────────────────────
   Fixed to the bottom edge of the viewport, but only once site.js has said so.
   A bar that fixes itself in plain CSS is a bar you cannot dismiss without
   scripting either, and the whole reason a fixed bar is defensible here is that
   it comes with an exit. Undocked — no JS, or dismissed — the band is the
   static block at the end of the document it has always been.

   The shell reserves the band's measured height as padding, so the colophon can
   always be scrolled clear of it and nothing under it is unreachable. That plus
   the close button is what keeps this inside WCAG 1.4.10 at 400% zoom, where
   the band is the tallest thing on the screen. */
[data-band="docked"] body {
  padding-bottom: var(--handbook-height);
}

.handbook--docked {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: 20;
  transition:
    transform var(--motion-base) var(--ease-enter),
    opacity var(--motion-base) var(--ease-enter);
}

.handbook__close {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--control-height);
  min-height: var(--control-height);
  border: 0;
  background: none;
  box-shadow: inset 0 0 0 var(--rule-hairline) var(--color-invert-dim);
  font-family: var(--font-mono);
  font-size: var(--size-meta);
  color: var(--color-invert-dim);
  cursor: pointer;
}

.handbook__close:hover,
.handbook__close:focus-visible {
  color: var(--color-invert-fg);
  box-shadow: inset 0 0 0 var(--rule-hairline) var(--color-invert-fg);
}

/* Narrow screens only: the band starts out of frame and comes up once the
   reader is a third of the way down. Mirrors --bp-narrow, which site.js reads
   to decide the same thing. Desktop never gets the hidden state at all, so the
   band there is simply always up. */
@media (max-width: 60rem) {
  .handbook--docked.is-hidden {
    transform: translateY(100%);
    opacity: 0;
  }
}

/* No displacement when motion is unwelcome: the band fades in where it already
   is instead of travelling. The durations are zeroed at the token layer, so
   this is only about the distance. */
@media (prefers-reduced-motion: reduce) {
  .handbook--docked.is-hidden {
    transform: none;
  }
}

/* ── Responsive ────────────────────────────────────────────────────────────
   One breakpoint, at the width where a three-column ruled sheet stops being
   readable. Below it the rails become stacked blocks in source order: the
   totem index moves under the article rather than disappearing, because it is
   the navigation and hiding navigation on small screens is how you tell mobile
   readers they are second-class. */

@media (max-width: 60rem) {
  /* Two rows, explicitly placed: wordmark and language in the top corners, the
     links across the row underneath. Wrapping alone gave three rows, with the
     switcher orphaned under the links. */
  .masthead__bar {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
  }

  .wordmark {
    grid-column: 1;
    grid-row: 1;
  }

  .lang {
    grid-column: 2;
    grid-row: 1;
    justify-self: end;
  }

  .nav {
    grid-column: 1 / -1;
    margin-left: 0;
  }

  .pagehead,
  .section--split,
  .totem,
  .post,
  .brand,
  .past,
  .position,
  .pair,
  .postcard a,
  .stack--rituals {
    grid-template-columns: minmax(0, 1fr);
  }

  .index {
    columns: 1;
  }

  .pagehead,
  .section--split,
  .totem,
  .post {
    gap: 0;
  }

  .section__label,
  .rail--byline {
    border-right: 0;
    border-bottom: var(--rule-weight) solid var(--rule-strong);
  }

  /* The 42-link index is a sidebar on a wide screen and a wall on a narrow one:
     stacked, it buries the pager and the related rail under a full screen of
     links. It comes out. Nothing is lost — /totems/ is the same list, one tap
     away in the nav, and the pager plus the same-theme rail are the two moves
     a reader actually makes from inside a totem. */
  .rail--index {
    display: none;
  }

  .rail--related {
    border-left: 0;
    border-top: var(--rule-weight) solid var(--rule-strong);
  }

  /* Source order puts the index before the article; on a narrow screen the
     article should come first and the full index after it. */
  .totem__body,
  .post__body {
    order: -1;
  }

  .pagehead__aside {
    padding-top: var(--space-5);
    border-top: var(--rule-hairline) solid var(--rule-soft);
  }

  .pair,
  .postcard a {
    gap: var(--space-1);
  }

  .brand,
  .past,
  .position {
    gap: var(--space-4);
  }

  .postcard__read {
    text-align: left;
  }

  :root {
    --size-hero: 2.5rem;
    --size-h1: 1.875rem;
  }
}

@media (max-width: 30rem) {
  .masthead__bar,
  .breadcrumb,
  .colophon,
  .handbook {
    padding-inline: var(--space-4);
  }

  .section__label,
  .section__body,
  .rail,
  .totem__body,
  .post__body,
  .pagehead {
    padding-inline: var(--space-4);
  }
}
