/* --------------------------------------------------------------------------
   Palette. The figures are external SVG files that carry their own copy of
   these five hexes, plus a dark set, in a <style> block each. So the hexes here
   must stay identical to the ones in src/assets/figures/*.svg and in
   scripts/externalise-figures.py, which is the only place the two sets are
   written down together.

   The figures used to read these as var(--slate, #6B7178) while inline, which
   themed them for free. That stopped being worth it: inline SVG put 37.5KB into
   every feed fetch, 29% of the file, and feed readers sanitise <svg> away.
   -------------------------------------------------------------------------- */
:root {
  --paper: #f4f1ec;
  --ink: #263238;
  --slate: #6b7178;
  --stone: #c9b89a;
  --accent: #b98a5e;

  --rule: color-mix(in srgb, var(--slate) 22%, transparent);
  --measure: 46rem; /* ~736px — set by the figures' 720px viewBox, not by taste */

  --serif: ui-serif, Charter, "Iowan Old Style", Georgia, "Times New Roman", serif;
  --sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

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

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--serif);
  font-size: 1.125rem;
  line-height: 1.65;
  text-rendering: optimizeLegibility;
}

.wrap {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 0 1.25rem;
}

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

.site-head {
  padding: 2.5rem 0 1.25rem;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 2.5rem;
}

.site-head__id {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  text-decoration: none;
  color: var(--ink);
}
.site-head__avatar {
  flex: none;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  /* The photo is the only raster thing up here, and its background is close to
     the paper colour, so without a hairline it floats loose on the page. */
  box-shadow: 0 0 0 1px var(--rule);
}
.site-head__name {
  display: block;
  font-family: var(--sans);
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: 0.01em;
}
.site-head__tagline {
  display: block;
  font-family: var(--sans);
  font-size: 0.8125rem;
  color: var(--slate);
  margin-top: 0.05rem;
}
.site-head__id:hover .site-head__name {
  color: var(--accent);
}

.site-head__nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1rem;
  font-family: var(--sans);
  font-size: 0.875rem;
  margin-top: 1.1rem;
  color: var(--slate);
}
.site-head__nav a {
  color: var(--slate);
  text-decoration: none;
}
.site-head__nav a:hover {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

.site-foot {
  font-family: var(--sans);
  font-size: 0.8125rem;
  color: var(--slate);
  border-top: 1px solid var(--rule);
  margin-top: 5rem;
  padding: 1.5rem 0 3rem;
}
.site-foot a {
  color: var(--slate);
}

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

h1,
h2,
h3 {
  line-height: 1.25;
  letter-spacing: -0.012em;
}

h1 {
  font-size: 2rem;
  margin: 0 0 0.35rem;
}
h2 {
  font-size: 1.375rem;
  margin: 2.75rem 0 0.75rem;
}
h3 {
  font-size: 1.125rem;
  margin: 2rem 0 0.5rem;
}

p,
ul,
ol {
  margin: 0 0 1.25rem;
}

a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
}

blockquote {
  margin: 1.75rem 0;
  padding-left: 1.1rem;
  border-left: 2px solid var(--stone);
  color: var(--slate);
  font-style: italic;
}

hr {
  border: 0;
  border-top: 1px solid var(--rule);
  margin: 3rem 0;
}

table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--sans);
  font-size: 0.9375rem;
  margin: 0 0 1.5rem;
}
th,
td {
  text-align: left;
  padding: 0.5rem 0.6rem;
  border-bottom: 1px solid var(--rule);
  vertical-align: top;
}
th {
  font-weight: 600;
  color: var(--slate);
}

code {
  font-family: var(--mono);
  font-size: 0.875em;
  background: color-mix(in srgb, var(--stone) 26%, transparent);
  padding: 0.1em 0.32em;
  border-radius: 3px;
}

pre {
  overflow-x: auto;
  padding: 1rem 1.1rem;
  border: 1px solid var(--rule);
  border-radius: 4px;
  background: color-mix(in srgb, var(--stone) 14%, transparent);
  font-size: 0.875rem;
  line-height: 1.55;
}
pre code {
  background: none;
  padding: 0;
  font-size: inherit;
}

/* ---------- figures ----------
   Two class names are supported on purpose. The archive was written with
   bench-hero / bench-fig; new posts use hero / fig. Both resolve here so a post
   can be renamed when it is rewritten rather than all at once. */

figure,
.hero,
.fig,
.bench-hero,
.bench-fig {
  margin: 2rem 0;
}

/* The figures are <img> now, so nothing here can reach inside them: the sans
   stack for their labels and the dark palette both live in each file's own
   <style> block. An <img>-referenced SVG is an isolated document — it inherits
   no font, and a custom property set out here is not visible to it. */
figure img,
.fig img,
.bench-fig img,
.hero img,
.bench-hero img {
  display: block;
  width: 100%;
  height: auto;
}

figcaption {
  font-family: var(--sans);
  font-size: 0.8125rem;
  color: var(--slate);
  margin-top: 0.5rem;
}

/* ---------- post list ---------- */

.post-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.post-list > li {
  display: flex;
  gap: 1.25rem;
  padding: 1.4rem 0;
  border-bottom: 1px solid var(--rule);
}

/* The hero, small, to the left of the writing. Fixed width rather than a
   fraction: the heroes are all one ratio, so a fixed width keeps every row in
   the list the same shape even when one post has a four-line description. */
.post-thumb {
  flex: none;
  display: block;
  width: 200px;
  line-height: 0;
}
.post-thumb img {
  width: 100%;
  height: auto;
  border-radius: 2px;
  box-shadow: 0 0 0 1px var(--rule);
}
/* Without this the long descriptions refuse to wrap and push the row wide. */
.post-entry {
  min-width: 0;
}

.post-list h2 {
  font-size: 1.1875rem;
  margin: 0 0 0.25rem;
}
.post-list h2 a {
  color: var(--ink);
  text-decoration: none;
}
.post-list h2 a:hover {
  color: var(--accent);
}
.post-list p {
  margin: 0;
  color: var(--slate);
  font-size: 1rem;
}
/* Date and tags on one line. Declared after the rule above so it wins without
   needing !important. */
.post-list .post-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem 0;
  margin-top: 0.5rem;
}

time {
  font-family: var(--sans);
  font-size: 0.8125rem;
  color: var(--slate);
  letter-spacing: 0.02em;
}

.post-head {
  margin-bottom: 2.25rem;
}

/* ---------- small screens ---------- */

@media (max-width: 34rem) {
  body {
    font-size: 1.0625rem;
  }
  h1 {
    font-size: 1.625rem;
  }
  .site-head {
    padding-top: 1.75rem;
  }
  /* A 200px thumbnail beside text on a phone leaves neither enough room, so the
     hero goes full width above the writing instead of beside it. */
  .post-list > li {
    display: block;
  }
  .post-thumb {
    width: 100%;
    margin-bottom: 0.8rem;
  }
}

/* ---------- dark ----------
   The figures do NOT follow from here. Each one carries its own
   prefers-color-scheme block, because a custom property declared out here is
   invisible inside an <img>-referenced SVG — and worse, Chrome and Safari do
   not re-evaluate custom properties in that context under a media query at
   all, which is why the files use literal values in selector rules instead.
   Change a hex here and you must change it in the figures too. */

@media (prefers-color-scheme: dark) {
  :root {
    --paper: #1b1f23;
    --ink: #e8e3d9;
    --slate: #9aa1a8;
    --stone: #6a6253;
    --accent: #c9a071;
  }
}

/* Avatar on the About page. Square source (a GitHub avatar), so a circle reads
   better than a rounded rectangle at this size. */
.avatar {
  float: right;
  width: 104px;
  height: 104px;
  border-radius: 50%;
  margin: 0.2rem 0 1rem 1.5rem;
  border: 1px solid var(--rule);
}

@media (max-width: 34rem) {
  .avatar {
    float: none;
    display: block;
    margin: 0 0 1.25rem;
  }
}

/* ---------- The Brief ----------
   A book section, kept visually distinct from the post stream without being a
   different site. */

.brief-crumb {
  font-family: var(--sans);
  font-size: 0.8125rem;
  color: var(--slate);
  margin-bottom: 1.5rem;
}

.brief-cover {
  display: block;
  margin: 0 auto 2rem;
}

.brief-toc {
  list-style: none;
  counter-reset: ch;
  margin: 2.5rem 0 0;
  padding: 0;
  border-top: 1px solid var(--rule);
}
.brief-toc > li {
  counter-increment: ch;
  border-bottom: 1px solid var(--rule);
}
.brief-toc > li::before {
  content: counter(ch);
  font-family: var(--sans);
  font-size: 0.75rem;
  color: var(--stone);
  display: inline-block;
  width: 1.75rem;
}
.brief-toc a {
  color: var(--ink);
  text-decoration: none;
  display: inline-block;
  padding: 0.7rem 0;
}
.brief-toc a:hover {
  color: var(--accent);
}

.brief-download {
  font-family: var(--sans);
  font-size: 0.875rem;
  margin-top: 2rem;
}

.brief-nav {
  display: flex;
  justify-content: space-between;
  gap: 1.5rem;
  font-family: var(--sans);
  font-size: 0.875rem;
  border-top: 1px solid var(--rule);
  margin-top: 3.5rem;
  padding-top: 1.25rem;
}
.brief-nav__next {
  margin-left: auto;
  text-align: right;
}

/* ---------- tags ----------
   Small, quiet, and deliberately not pills: five tags across the archive means
   they are a filter, not a badge collection. */

.post-tags {
  display: inline flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  list-style: none;
  margin: 0 0 0 0.75rem;
  padding: 0;
  font-family: var(--sans);
  font-size: 0.75rem;
}
.post-tags a {
  color: var(--slate);
  text-decoration: none;
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 0.1rem 0.5rem;
}
.post-tags a:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.tag-all {
  font-family: var(--sans);
  font-size: 0.875rem;
  margin-top: 2rem;
}

/* ---------- post margin: table of contents and sidenotes ----------
   The prose column stays at the measure, which is set by the figures' 720px
   viewBox and is not negotiable. The margin hangs to its right and holds two
   things: the table of contents at the top, then sidenotes as they come up.

   Both are right floats with `clear: right`, so they stack in document order
   and cannot overlap. A sticky table-of-contents rail in its own grid column
   reads better, but a sidenote floating into that same strip lands on top of
   it, and the fix for that is a third column and ten more rem of page width.

   Below 64rem there is no room beside the prose, so the floats are switched off
   and both fold back into the text column. That 64rem is written out in the two
   media queries below rather than held in a custom property, because a media
   query cannot read one. */

:root {
  --margin-w: 13rem;
  --margin-gap: 2.5rem;
}

.wrap--wide {
  max-width: calc(var(--measure) + var(--margin-w) + var(--margin-gap));
}

.post-body .prose {
  max-width: var(--measure);
}

.post-margin,
.sidenote {
  font-family: var(--sans);
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--slate);
}

.post-toc .section-label {
  font-family: var(--sans);
  font-size: 0.6875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--slate);
  margin: 0 0 0.6rem;
}
.post-toc ul,
.post-toc ol {
  list-style: none;
  counter-reset: toc;
  margin: 0;
  padding: 0;
}
.post-toc li {
  counter-increment: toc;
  margin: 0;
}
/* The number is what makes a block of links read as a table of contents and
   not a list of related reading. */
.post-toc a::before {
  content: counter(toc);
  display: inline-block;
  width: 1.25em;
  color: var(--stone);
  font-variant-numeric: tabular-nums;
}
.post-toc a {
  display: block;
  color: var(--slate);
  text-decoration: none;
  padding: 0.22rem 0 0.22rem 0.75rem;
  border-left: 1px solid var(--rule);
}
.post-toc a:hover {
  color: var(--accent);
  border-left-color: var(--accent);
}

/* Emitted inside its paragraph and pulled out of it, so that with no margin to
   pull into it simply stays put and reads as a parenthetical after the claim. */
.sidenote {
  display: block;
  border-left: 2px solid var(--stone);
  padding-left: 0.7rem;
  margin: 0.6rem 0;
}

@media (min-width: 64rem) {
  .post-margin,
  .sidenote {
    float: right;
    clear: right;
    width: var(--margin-w);
    /* Out past the prose and into the margin: its own width plus the gap. */
    margin-right: calc((var(--margin-w) + var(--margin-gap)) * -1);
  }
  .post-margin {
    margin-top: 0.4rem;
    margin-bottom: 1.5rem;
  }
  .sidenote {
    margin-top: 0.3rem;
    margin-bottom: 1rem;
    border-left: 0;
    padding-left: 0;
  }
}

/* No margin to float into, so the contents sits above the prose and the
   sidenotes stay inline where they were written.

   The contents stays a vertical list here rather than becoming a wrapping row.
   A row saved four lines of height and cost the reader the ability to tell
   where one entry ended: these headings are long enough that "The map — three
   loops, not a pipeline" and "The hole" shared a line and read as one item. */
@media (max-width: 64rem) {
  .post-margin {
    display: block;
    border-bottom: 1px solid var(--rule);
    padding-bottom: 1rem;
    margin-bottom: 1.75rem;
  }
  /* At the top of the page it has to announce itself as a contents block
     rather than look like the first paragraph of the post. A tinted box, a
     label and numbers do that; the margin version needs none of them because
     its position already says what it is. */
  .post-margin {
    background: color-mix(in srgb, var(--stone) 13%, transparent);
    border: 1px solid var(--rule);
    border-radius: 4px;
    padding: 0.9rem 1rem;
  }
  .post-toc a {
    color: var(--ink);
    padding: 0.28rem 0;
    border-left: 0;
    display: flex;
    gap: 0.1rem;
  }
  .post-toc a::before {
    color: var(--slate);
    opacity: 0.75;
    flex: none;
  }
}

.backlinks {
  max-width: var(--measure);
  border-top: 1px solid var(--rule);
  margin-top: 3rem;
  padding-top: 1.25rem;
}
.backlinks .section-label {
  font-family: var(--sans);
  font-size: 0.6875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--slate);
  margin: 0 0 0.5rem;
}
.backlinks ul {
  list-style: none;
  margin: 0;
  padding: 0;
  font-family: var(--sans);
  font-size: 0.9375rem;
}
.backlinks li {
  margin: 0.2rem 0;
}

.post-confidence {
  font-family: var(--sans);
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--slate);
  background: color-mix(in srgb, var(--stone) 16%, transparent);
  border-radius: 3px;
  padding: 0.5rem 0.75rem;
  margin: 0.9rem 0 0;
  max-width: var(--measure);
}
.post-confidence span {
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-size: 0.6875rem;
  color: var(--ink);
  margin-right: 0.35rem;
}

/* The "updated" half of the date pair. Quieter than the original date, because
   the original is the one that says when the thinking happened. */
.post-updated {
  font-family: var(--sans);
  font-size: 0.8125rem;
  color: var(--slate);
  opacity: 0.75;
  margin-left: 0.5rem;
}
.post-updated::before {
  content: "·";
  margin-right: 0.5rem;
  opacity: 0.6;
}

/* ---------- search ---------- */

.list-controls {
  margin-bottom: 2rem;
}

/* The label is for screen readers only. The placeholder tells a sighted reader
   what the box is, and a visible "Search" label above a one-line filter row
   would be a second heading for a control that needs none. */
.visually-hidden,
.search__label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
/* The tag row below carries its own bottom spacing and rule but no top margin,
   so without this the box and the chips touch. */
.search {
  margin-bottom: 0.9rem;
}

.search input {
  width: 100%;
  font-family: var(--sans);
  font-size: 0.9375rem;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 0.45rem 0.9rem;
  appearance: none;
}
.search input::placeholder {
  color: var(--slate);
  opacity: 0.8;
}
.search input:focus-visible {
  outline: none;
  border-color: var(--accent);
}
.search__status {
  font-family: var(--sans);
  font-size: 0.8125rem;
  color: var(--slate);
  margin: 0.5rem 0 0;
  min-height: 1.2em;
}
.search-empty {
  font-family: var(--sans);
  font-size: 0.9375rem;
  color: var(--slate);
  border-bottom: 0 !important;
}

/* ---------- tag filter row ---------- */

.tag-filter {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  font-family: var(--sans);
  font-size: 0.8125rem;
  margin: 0 0 2.5rem;
  padding-bottom: 1.25rem;
  border-bottom: 1px solid var(--rule);
}
.tag-filter a {
  color: var(--slate);
  text-decoration: none;
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 0.2rem 0.7rem;
  white-space: nowrap;
}
.tag-filter a span {
  color: var(--stone);
  margin-left: 0.4rem;
  font-variant-numeric: tabular-nums;
}
.tag-filter a:hover {
  color: var(--accent);
  border-color: var(--accent);
}
/* aria-current is the state, so style from it rather than a second class —
   one source of truth for "this is the view you are on". */
.tag-filter a[aria-current="page"] {
  color: var(--paper);
  background: var(--slate);
  border-color: var(--slate);
}
.tag-filter a[aria-current="page"] span {
  color: var(--stone);
}

/* The lede is now several short lines rather than one dense sentence. */
.lede p {
  margin: 0 0 0.35rem;
}
.lede p:last-child {
  margin-bottom: 0;
}
