/* ============================================================================
   Architecting the Writing Room - writingroom.efrederick.net

   Dark by design, not by toggle. Light text on a dark ground is the whole
   brief, so there is no prefers-color-scheme branch: this site looks the same
   everywhere on purpose.

   Two deliberate departures from literally inverting black and white:

   1. The ground is #0e0f11, not #000, and body text is #e4e1dc, not #fff. At
      maximum contrast the glyph edges bloom into the background and the text
      smears on a long read. Backing both ends off a few percent still reads
      as black-and-white but survives 4,000 words.
   2. Body copy is a serif. These are long technical essays, and a serif holds
      the eye across a 68ch measure better than a UI sans does.

   Accent is brass, NOT the red of the fiction site. Two different rooms.
   ========================================================================= */

:root {
  --bg:            #0e0f11;
  --bg-raised:     #16181c;
  --bg-code:       #131519;

  --text:          #e4e1dc;   /* body copy - warm off-white, not #fff */
  --text-head:     #f4f2ef;   /* headings sit a touch brighter */
  --text-dim:      #b3aea7;   /* secondary prose, theses, ledes */
  --text-mute:     #8b9096;   /* meta, dates, captions */

  --accent:        #d8a657;   /* brass */
  --accent-bright: #f0bd72;
  --accent-dim:    #8a6c3a;

  --border:        #24272c;
  --border-strong: #33373e;

  --serif: Charter, "Bitstream Charter", "Iowan Old Style", Georgia, Cambria,
           "Times New Roman", serif;
  --sans:  ui-sans-serif, -apple-system, "Segoe UI", Roboto, Helvetica, Arial,
           sans-serif;
  --mono:  ui-monospace, "Cascadia Mono", "SFMono-Regular", Consolas,
           "Liberation Mono", Menlo, monospace;

  /* Deliberately rem, not ch. `ch` is relative to the element's own font size,
     so a shared `68ch` gave the lede (1.15rem), the series note (0.98rem), the
     entry thesis (1rem) and body copy (1.0625rem) four DIFFERENT column widths
     and the left-hand text edges stopped lining up down the page. 44rem is one
     width for every prose column regardless of its size. */
  --measure: 44rem;
}

/* Justified body copy. Every run of prose gets this; headings, meta, captions,
   nav and code do not. Hyphenation is not optional alongside it - at a 68ch
   measure, unhyphenated justification pulls rivers of white space through the
   text as soon as a `docker-compose` or `character_state` lands near a line
   end. `text-wrap: pretty` cleans up the last line where supported. */
.page-hero p.lede,
.series-note p,
.entry .thesis,
.article-content > p:not(.dateline),
.article-content li,
.article-content blockquote p,
.gate-final p,
.justified {
  text-align: justify;
  hyphens: auto;
  -webkit-hyphens: auto;
  overflow-wrap: break-word;
  text-wrap: pretty;
}

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

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

/* Background texture: faint falling code built from this room's own schema
   (see build-background.py). Painted by a fixed pseudo-element rather than
   `background-attachment: fixed`, which misbehaves on iOS. It sits at z-index
   -1, so ordinary flow content paints over it with no stacking context games.
   html carries the flat ground colour so there is never a flash of white
   before the image resolves. */
html { background-color: var(--bg); }

body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: url("/img/bg-code.webp");
  background-size: cover;
  background-position: center top;
  background-repeat: no-repeat;
}

body {
  margin: 0;
  background: transparent;
  color: var(--text);
  font-family: var(--serif);
  font-size: 1.0625rem;
  line-height: 1.75;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.container {
  width: 100%;
  max-width: 1040px;
  margin: 0 auto;
  padding: 0 2rem;
}

a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-bright); }

img { max-width: 100%; height: auto; }

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

/* Scoped to .site-nav, not bare `nav`. These rules used to match any <nav> on
   the page, so the generated table of contents - which is correctly a <nav>
   for accessibility - inherited the header's flex row, uppercase and
   letter-spacing, and rendered its sub-lists sideways. */
.site-nav {
  border-bottom: 1px solid var(--border);
  background: var(--bg);
  position: sticky;
  top: 0;
  z-index: 10;
}

.site-nav .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  min-height: 64px;
  flex-wrap: wrap;
}

.nav-name {
  font-family: var(--sans);
  font-size: 0.78rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-head);
  white-space: nowrap;
}
.nav-name:hover { color: var(--accent-bright); }

.site-nav ul {
  list-style: none;
  display: flex;
  gap: 1.75rem;
  margin: 0;
  padding: 0;
  font-family: var(--sans);
  font-size: 0.76rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.site-nav ul li a { color: var(--text-mute); }
.site-nav ul li a:hover,
.site-nav ul li a.active { color: var(--accent-bright); }

/* --------------------------------------------------------------- hero ---- */

.page-hero { padding: 5rem 0 2.5rem; border-bottom: 1px solid var(--border); }

/* On the index the hero sits inside .top-region, which owns the rule so it
   spans both columns. Standalone heroes (about) keep their own. */
.top-region .page-hero { padding: 5rem 0 0; border-bottom: none; }

.hero-eyebrow {
  font-family: var(--sans);
  font-size: 0.72rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 1.1rem;
}

.page-hero h1 {
  font-size: clamp(2rem, 5vw, 3.1rem);
  font-weight: 400;
  line-height: 1.15;
  letter-spacing: 0.01em;
  color: var(--text-head);
  margin: 0 0 1.2rem;
}

.page-hero p.lede {
  font-size: 1.15rem;
  line-height: 1.7;
  color: var(--text-dim);
  margin: 0;
}

/* INDEX WIDTHS: prose here fills its column edge to edge - no reading measure.
   Article pages keep the measure: a 4,000-word essay needs a shorter line than
   a three-line thesis does.

   ---------------------------------------------------------------- index ---- */

/* Masthead + reading note on the left, Latest rail on the right. One rule
   under the whole region rather than under each column. */
.top-region {
  display: grid;
  grid-template-columns: 1fr 15rem;
  gap: 3.5rem;
  border-bottom: 1px solid var(--border);
  padding-bottom: 2.75rem;
}
.top-main { min-width: 0; }

.latest { padding-top: 5.4rem; min-width: 0; }

.rail-head {
  font-family: var(--sans);
  font-size: 0.72rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 1rem;
  padding-bottom: 0.7rem;
  border-bottom: 1px solid var(--border);
}

.latest-list { list-style: none; margin: 0; padding: 0; }
.latest-list li { margin-bottom: 1.15rem; line-height: 1.4; }
.latest-list a {
  display: block;
  color: var(--text-head);
  font-size: 0.98rem;
  margin-bottom: 0.2rem;
}
.latest-list a:hover { color: var(--accent-bright); }
.latest-date {
  font-family: var(--sans);
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-mute);
}

.feed-link {
  display: inline-block;
  margin-top: 0.4rem;
  font-family: var(--sans);
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-mute);
}
.feed-link:hover { color: var(--accent-bright); }

/* Section headers between the top region and the series list. */
.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
  padding: 3rem 0 0;
}
.section-head h2 {
  font-family: var(--sans);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-head);
  margin: 0;
}
.section-count {
  font-family: var(--sans);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-mute);
}

.series-note {
  padding: 2rem 0 0;
  color: var(--text-dim);
  font-size: 0.98rem;
}
.series-note strong { color: var(--text); font-weight: 600; }

.entry-list { padding: 1.25rem 0 5rem; list-style: none; margin: 0; }

.entry {
  border-top: 1px solid var(--border);
  padding: 2.25rem 0;
  display: grid;
  grid-template-columns: 12rem 1fr;
  gap: 2rem;
  align-items: start;
}
.entry:last-child { border-bottom: 1px solid var(--border); }

/* The rail carries the plate and the meta. Deliberately narrow: the thesis is
   what the index is for, and a full-width card image would outrank it. An
   entry with no artwork just renders the meta, which reads as intentional
   rather than as a hole in a grid. */
.entry-rail { min-width: 0; }

/* Meta sits above the plate so the No./date line starts on the same baseline
   in every row, whether or not that entry has artwork. */
.entry-thumb {
  display: block;
  margin-top: 0.7rem;
  border: 1px solid var(--border);
  line-height: 0;
}
.entry-thumb img {
  width: 100%;
  height: auto;
  display: block;
  filter: saturate(0.85) brightness(0.9);
  transition: filter 0.25s ease;
}
.entry:hover .entry-thumb img { filter: none; }
.entry-thumb:hover { border-color: var(--accent-dim); }

.entry-meta {
  font-family: var(--sans);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-mute);
  line-height: 1.9;
}
.entry-no { display: block; color: var(--accent-dim); font-size: 0.8rem; }

.entry-body { min-width: 0; }

.entry h2 {
  font-size: 1.4rem;
  font-weight: 400;
  line-height: 1.3;
  margin: 0 0 0.7rem;
}
.entry h2 a { color: var(--text-head); }
.entry h2 a:hover { color: var(--accent-bright); }

.entry .thesis { margin: 0; color: var(--text-dim); font-size: 1rem; line-height: 1.7; }

.entry .entry-foot {
  font-family: var(--sans);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  color: var(--text-mute);
  margin-top: 0.85rem;
  text-transform: uppercase;
}

/* ------------------------------------------------------------ article ---- */

.article-header { padding: 3.5rem 0 0; }

.article-header .kicker {
  font-family: var(--sans);
  font-size: 0.72rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 0.9rem;
}

.article-header h1 {
  font-size: clamp(1.9rem, 4.2vw, 2.9rem);
  font-weight: 400;
  line-height: 1.2;
  color: var(--text-head);
  margin: 0 0 0.7rem;
  max-width: min(22ch, 100%);
}

.article-header .subtitle {
  font-size: 1.15rem;
  color: var(--text-dim);
  font-style: italic;
  margin: 0 0 1.4rem;
  max-width: var(--measure);
}

.article-header .byline {
  font-family: var(--sans);
  font-size: 0.73rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--text-mute);
  border-top: 1px solid var(--border);
  padding-top: 1rem;
}

.article-header-img {
  width: 100%;
  max-height: 440px;
  object-fit: cover;
  display: block;
  margin-top: 2rem;
  border: 1px solid var(--border);
}

.article-content { padding: 3rem 0 2rem; max-width: var(--measure); }

.article-content > p { margin: 0 0 1.55rem; }

/* Section headings are small caps in the body serif rather than uppercase
   sans. Small caps sit at x-height, so they mark a section without shouting
   the way full uppercase does, and they keep the heading in the same voice as
   the prose under it. Letter-spacing is widened because small caps set tight
   look cramped. */
.article-content h2 {
  font-family: var(--serif);
  font-variant: small-caps;
  font-size: 1.35rem;
  font-weight: 400;
  letter-spacing: 0.07em;
  color: var(--text-head);
  margin: 3rem 0 1.1rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--border);
}

.article-content h3 {
  font-family: var(--serif);
  font-variant: small-caps;
  font-size: 1.1rem;
  font-weight: 400;
  letter-spacing: 0.06em;
  color: var(--accent);
  margin: 2.2rem 0 0.8rem;
}

.article-content blockquote {
  margin: 0 0 1.55rem;
  padding: 0.2rem 0 0.2rem 1.4rem;
  border-left: 2px solid var(--accent-dim);
  color: var(--text-dim);
  font-style: italic;
}
.article-content blockquote p:last-child { margin-bottom: 0; }

.article-content ul,
.article-content ol { margin: 0 0 1.55rem; padding-left: 1.4rem; }
.article-content li { margin-bottom: 0.55rem; }

.article-content hr {
  border: 0;
  border-top: 1px solid var(--border);
  margin: 2.75rem 0;
}

.article-content strong { color: var(--text-head); font-weight: 600; }

/* Dates a position rather than a fact, so a reader years out knows whether it
   still holds. Sits under a heading, above the statement it stamps. */
.article-content p.dateline {
  font-family: var(--sans);
  font-size: 0.74rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-mute);
  margin: -0.4rem 0 1.3rem;
}

/* Code. These essays carry docker-compose, SQL and schema blocks, so this is
   load-bearing rather than decorative. Blocks scroll inside themselves so the
   page body never scrolls sideways. */
.article-content code {
  font-family: var(--mono);
  font-size: 0.86em;
  background: var(--bg-code);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 0.12em 0.36em;
  color: var(--accent-bright);
}

.article-content pre {
  background: var(--bg-code);
  border: 1px solid var(--border);
  border-left: 2px solid var(--accent-dim);
  padding: 1.1rem 1.25rem;
  overflow-x: auto;
  margin: 0 0 1.55rem;
  font-size: 0.86rem;
  line-height: 1.6;
}
.article-content pre code {
  background: none;
  border: 0;
  padding: 0;
  color: var(--text);
  font-size: 1em;
}

.article-content table {
  width: 100%;
  border-collapse: collapse;
  margin: 0 0 1.55rem;
  font-family: var(--sans);
  font-size: 0.88rem;
  display: block;
  overflow-x: auto;
}
.article-content th,
.article-content td {
  border: 1px solid var(--border);
  padding: 0.55rem 0.75rem;
  text-align: left;
  vertical-align: top;
}
.article-content th { color: var(--text-head); background: var(--bg-raised); font-weight: 600; }

.article-content img { border: 1px solid var(--border); display: block; margin: 0 0 1.55rem; }

/* ----------------------------------------------------------- article foot */

.article-foot {
  border-top: 1px solid var(--border);
  padding: 2rem 0 5rem;
  max-width: var(--measure);
}

.prevnext {
  display: flex;
  justify-content: space-between;
  gap: 2rem;
  flex-wrap: wrap;
  font-family: var(--sans);
  font-size: 0.8rem;
  line-height: 1.5;
}
.prevnext .slot { max-width: 20rem; }
.prevnext .slot.next { text-align: right; margin-left: auto; }
.prevnext .lbl {
  display: block;
  font-size: 0.68rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-mute);
  margin-bottom: 0.3rem;
}

.back-link {
  display: inline-block;
  margin-top: 2rem;
  font-family: var(--sans);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-mute);
  border-bottom: 1px solid var(--border);
  padding-bottom: 0.15rem;
}
.back-link:hover { color: var(--accent-bright); border-color: var(--accent-dim); }

/* -------------------------------------------------------------- plate ---- */

/* A framed image sitting inside prose. Capped well under the text measure so
   it reads as a plate set into the page rather than a banner interrupting it. */
.article-content .plate {
  margin: 2rem auto 2.25rem;
  max-width: 420px;
  text-align: center;
}
.article-content .plate img {
  width: 100%;
  height: auto;
  display: block;
  margin: 0;
  border: 1px solid var(--border);
}
.article-content .plate figcaption {
  font-family: var(--sans);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-mute);
  margin-top: 0.6rem;
}



/* ---------------------------------------------------------------- toc ---- */

/* Generated by the `toc` transform in .eleventy.js from the page's own
   headings. Breaks out past the reading measure and runs in columns, because
   a 38-entry single-column list would be a page of its own before the page
   starts. */
.toc {
  width: min(60rem, calc(100vw - 4rem));
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent-dim);
  background: var(--bg-raised);
  padding: 1.4rem 1.6rem 1rem;
  margin: 2rem 0 2.5rem;
}
.toc .rail-head { border-bottom: none; padding-bottom: 0; margin-bottom: 0.9rem; }

.toc-list {
  columns: 2;
  column-gap: 3rem;
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: toc;
}
.toc-list > li {
  break-inside: avoid;
  margin-bottom: 1rem;
  counter-increment: toc;
}
.toc-list > li > a {
  font-family: var(--sans);
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--text-head);
  display: block;
}
.toc-list > li > a::before {
  content: counter(toc) ". ";
  color: var(--accent-dim);
  font-weight: 400;
}
.toc-list > li > a:hover { color: var(--accent-bright); }

.toc-list ul {
  list-style: none;
  margin: 0.3rem 0 0;
  padding: 0 0 0 1.1rem;
  border-left: 1px solid var(--border-strong);
}
.toc-list ul li { margin: 0.2rem 0; }
.toc-list ul a {
  font-family: var(--sans);
  font-size: 0.78rem;
  color: var(--text-mute);
  line-height: 1.45;
  display: block;
}
.toc-list ul a:hover { color: var(--accent-bright); }

/* Anchored headings shouldn't land under the sticky nav. */
.article-content h2[id],
.article-content h3[id] { scroll-margin-top: 5rem; }

/* --------------------------------------------------------- diagrams ---- */

/* The data-plane diagram is inline SVG rather than an image so it takes the
   site's own colours from these variables, stays sharp at any zoom, and keeps
   its <title>/<desc> readable to a screen reader. It is allowed to break out
   past the reading measure, because a diagram is not prose. */
.diagram, .diagram-img {
  margin: 2.25rem 0 2.5rem;
  max-width: none;
  /* Break out of the reading measure. .article-content is left-aligned inside
     .container, so a width wider than the measure grows rightward into the
     container's own width rather than off the page. The vw term keeps it
     inside the viewport once the measure stops being the limiting factor. */
  width: min(60rem, calc(100vw - 4rem));
}
.diagram svg { width: 100%; height: auto; display: block; }
.diagram-img img {
  width: 100%;
  height: auto;
  display: block;
  border: 1px solid var(--border);
  margin: 0;
}
.diagram figcaption, .diagram-img figcaption {
  font-family: var(--sans);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  color: var(--text-mute);
  margin-top: 0.7rem;
}

/* SVG internals. Classes rather than presentation attributes so the palette
   lives in one place with everything else. */
.dg-band {
  font-family: var(--sans);
  font-size: 11px;
  letter-spacing: 0.18em;
  fill: var(--text-mute);
  text-transform: uppercase;
}
.dg-node rect {
  fill: var(--bg-raised);
  stroke: var(--border-strong);
  stroke-width: 1;
}
.dg-access rect { stroke: var(--accent-dim); }
.dg-db rect { fill: var(--bg-code); }
.dg-host {
  fill: none;
  stroke: var(--border);
  stroke-width: 1;
  stroke-dasharray: 3 4;
}
.dg-t {
  font-family: var(--sans);
  font-size: 15px;
  font-weight: 600;
  fill: var(--text-head);
}
.dg-s {
  font-family: var(--sans);
  font-size: 12px;
  fill: var(--text-dim);
}
.dg-headfill { fill: var(--accent-dim); }
.dg-muted rect { opacity: 0.45; }
.dg-muted .dg-t, .dg-muted .dg-s { opacity: 0.5; }
.dg-arrow {
  stroke: var(--accent-dim);
  stroke-width: 1.5;
  fill: none;
  marker-end: url(#dg-head);
}


/* ------------------------------------------------------------ tables ---- */

/* Wide content (the gate tables) breaks out of the reading measure the same
   way the diagrams do, and scrolls inside itself if the viewport is narrower
   than the table needs. */
.wide {
  width: min(60rem, calc(100vw - 4rem));
  margin: 1.5rem 0 2rem;
  overflow-x: auto;
}
.wide table { margin: 0; display: table; width: 100%; }

table.gates { border-collapse: collapse; font-family: var(--sans); font-size: 0.86rem; }
table.gates th,
table.gates td {
  border: 1px solid var(--border);
  padding: 0.6rem 0.8rem;
  text-align: left;
  vertical-align: top;
  line-height: 1.55;
}
table.gates thead th {
  background: var(--bg-raised);
  color: var(--text-head);
  font-weight: 600;
  font-size: 0.74rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  white-space: nowrap;
}
table.gates td { color: var(--text-dim); }
table.gates td strong { color: var(--text-head); }
table.gates .num { text-align: right; white-space: nowrap; color: var(--accent); }
table.gates .num code { background: none; border: 0; padding: 0; color: inherit; font-size: 0.94em; }
table.gates tbody tr:nth-child(even) td { background: rgba(255, 255, 255, 0.012); }

/* Gate identifiers read as labels, not prose. */
/* The one gate that is not a table row, because it is not mechanical. Given
   its own block so it reads as the terminal step rather than an appendix to
   the scored gates above it. */
.gate-final {
  border: 1px solid var(--border-strong);
  border-left: 3px solid var(--accent);
  background: var(--bg-raised);
  padding: 1.5rem 1.6rem 0.4rem;
  margin: 1.5rem 0 2rem;
}
.gate-final p { margin: 0 0 1.2rem; }
.gate-final p:last-child { margin-bottom: 1.2rem; }
.gate-final-id {
  font-family: var(--sans);
  font-size: 0.72rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-mute);
  margin-bottom: 1rem !important;
}
.gate-final-id .gid { margin-right: 0.5rem; }

/* Marks a row as something other than a base table. */
.tag {
  font-family: var(--sans);
  font-size: 0.62rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-mute);
  border: 1px solid var(--border-strong);
  border-radius: 2px;
  padding: 0.05rem 0.3rem;
  margin-left: 0.35rem;
  vertical-align: 0.08em;
}

/* Table-name cells shouldn't wrap mid-identifier. */
table.gates td > code:first-child { white-space: nowrap; }

.gid {
  font-family: var(--mono);
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--accent);
  white-space: nowrap;
}

/* --------------------------------------------------------- disclosure ---- */

/* Mirrors VoT-Site's .ai-disclosure rule exactly, with one substitution: the
   accent there is --red-light, which is the fiction's colour. Brass is the
   equivalent role here. */
.ai-disclosure {
  font-size: 0.78rem;
  color: var(--accent);
  font-style: italic;
  text-align: center;
  border-top: 1px solid var(--border);
  padding-top: 1.25rem;
  margin-top: 0.5rem;
  margin-bottom: 1.5rem;
  line-height: 1.6;
}

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

footer {
  border-top: 1px solid var(--border);
  padding: 2.5rem 0 3.5rem;
  font-family: var(--sans);
  font-size: 0.76rem;
  color: var(--text-mute);
}
footer .container {
  display: flex;
  gap: 1.5rem;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
}
.footer-name { letter-spacing: 0.2em; text-transform: uppercase; color: var(--text-dim); }
.footer-links { display: flex; gap: 1.5rem; flex-wrap: wrap; }
.footer-links a { color: var(--text-mute); }
.footer-links a:hover { color: var(--accent-bright); }

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

@media (max-width: 900px) {
  .top-region { grid-template-columns: 1fr; gap: 0; padding-bottom: 2rem; }
  .latest {
    padding-top: 2.25rem;
    margin-top: 2rem;
    border-top: 1px solid var(--border);
  }
  .latest-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 1rem 2rem; }
  .latest-list li { margin-bottom: 0; }
}

@media (max-width: 720px) {
  .container { padding: 0 1.25rem; }

  /* Scaled to phone width the diagram's labels drop to about 5px and stop
     being readable. Hold it at a legible size and let it scroll inside its own
     box, exactly as the code blocks do - the page itself still never scrolls
     sideways. */
  .diagram { overflow-x: auto; }
  .diagram svg { min-width: 640px; }

  .toc-list { columns: 1; }

  /* Same treatment for the gate tables: squeezed into 311px the three-column
     ones become unreadable, so hold a legible width and scroll inside .wide. */
  .wide table { min-width: 560px; }
  .page-hero { padding: 3rem 0 2rem; }
  /* Single column, and the plate caps out well short of full width so 14 of
     them don't turn the index into a scroll of pictures. */
  .entry { grid-template-columns: 1fr; gap: 0.6rem; }
  .entry-thumb { max-width: 200px; margin-top: 0.5rem; }
  .entry-no { display: inline; margin-right: 0.6rem; }
  .entry-meta { line-height: 1.6; }
  .site-nav .container { min-height: 0; padding-top: 0.75rem; padding-bottom: 0.75rem; }
  .site-nav ul { gap: 1.1rem; }
  .prevnext .slot.next { text-align: left; margin-left: 0; }
}

/* About hero: text left, author portrait filling the empty right-hand space.
   Collapses to a single column on narrow screens, portrait first so it does
   not sit orphaned under the lede. */
.hero-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 260px;
  gap: 2.5rem;
  align-items: center;
}
.hero-split .hero-portrait img {
  width: 100%;
  height: auto;
  display: block;
  border: 1px solid var(--border);
  filter: saturate(0.92);
}
@media (max-width: 760px) {
  .hero-split { grid-template-columns: 1fr; gap: 1.5rem; }
  .hero-split .hero-portrait { max-width: 220px; order: -1; }
}

/* Tip button. Renders only when metadata.tip.url is set, so the site ships
   with it dark rather than pointing at a placeholder. Stripe Payment Link:
   a plain anchor to a Stripe-hosted page, so no keys, no script, and no
   card data ever touch this site. */
.tip-jar { margin: 2.5rem 0 0; text-align: center; }
.article-foot .tip-jar { margin: 2rem 0 0; }
.tip-btn {
  display: inline-block;
  padding: 0.7rem 1.4rem;
  border: 1px solid var(--accent, #d8a657);
  color: var(--accent, #d8a657);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  transition: background 0.18s ease, color 0.18s ease;
}
.tip-btn:hover { background: var(--accent, #d8a657); color: var(--bg, #0e0f11); }
.tip-note { margin: 0.7rem 0 0; font-size: 0.78rem; color: var(--text-dim); font-style: italic; }

/* Closing block: the tip button and the AI disclosure read as one statement at
   the end of every page, so they share a container and a rule above them. */
.container.closing {
  margin-top: 3rem;
  padding-top: 2rem;
  padding-bottom: 3rem;   /* breathing room before the footer rule */
  border-top: 1px solid var(--border);
  text-align: center;
}
.container.closing .tip-jar { margin: 0 0 1.4rem; }
.container.closing .ai-disclosure { margin: 0; }
