/* ============================================================
   machine layer — the whole style system
   Five sections. Everything editable from one file.
   1 tokens · 2 base · 3 layout · 4 nav · 5 components
   ============================================================ */

/* --- 0 fonts --------------------------------------------------
   JetBrains Mono, self-hosted (IBM Plex until 2026-10-07). Not loaded from a
   third party: a site arguing that pages should not depend on things they do
   not control should not phone a font CDN on every view.

   Latin subset only, one variable file covering the weight axis -- 40KB for
   the whole typographic system, and the same file fastfoodvisors.xyz and
   0xsaddy.com serve.

   font-display: swap, deliberately. It costs a reflow when the fonts land,
   and the alternative hides text until they do, which is the same class of
   mistake as a page that renders blank without JavaScript.
   ------------------------------------------------------------- */
/* 2026-10-07: JetBrains Mono replaces IBM Plex Mono, so this site shares one
   face with fastfoodvisors.xyz and 0xsaddy.com. One variable file (wght
   100-800, Latin, OFL) instead of two statics; same .6em advance, so every
   width below that was derived from Plex's advance still holds.
   2026-10-08: rebuilt from the upstream variable TTF (v2.211) with the same
   Latin set plus the Arrows block, so the footer's ↗ draws in this face
   instead of a fallback. U+2514 (└) is in the file from a reverted try at
   the closer marker; harmless, and it keeps the three sites' files identical. */
@font-face {
  font-family: "JetBrains Mono";
  src: url("/assets/fonts/JetBrainsMono-latin-wght.woff2") format("woff2");
  font-weight: 100 800; font-style: normal; font-display: swap;
}
/* --- 1 tokens ------------------------------------------------
   auto follows the clock (theme.js): light 7:00-19:00, dark
   otherwise. The OS preference below is only the no-JavaScript
   fallback; an explicit choice stored in localStorage wins in
   BOTH directions, which is why [data-theme="light"] is restated.
   ------------------------------------------------------------- */
:root {
  /* Paper and ink are the shared core of the world -- the same values as
     0xsaddy.com (receipt paper / near-black ink) and fastfoodvisors.xyz's dark
     floor and cream (2026-10-07). Secondary and line are that ink at stated
     opacities, flattened to solid hex here because this file's contrast notes
     are written against solid values. Accents: the world's ketchup and mustard
     (from 2026-10-08; teal before). */
  --bg:    #efe9dc;
  --fg:    #1a1712;
  /* Darkened from #6b6b66. On plain background that was 5.17:1, comfortable
     -- but --dim text sitting inside a tinted .ev block composites against
     #f1f1f0 instead, which took it to 4.74:1. Passing, with 5% of margin on
     the most-used secondary colour in the mode most readers get by default.
     4.74 -> 5.26 costs nothing visible and still reads clearly secondary
     against --fg at 16.48:1. Dark mode needed no change: its equivalent
     already clears by 0.69. */
  /* 2026-10-07: now ink at .69 on the shared paper (#5c5851): 5.37:1 on the
     tinted .ev surface (#e5e0d3), 5.85:1 on plain -- the margin above kept. */
  --dim:   #5c5851;
  /* ink at .25: 1.70:1 against paper, the same visibility the old #c4c4bc had */
  --line:  #bab4aa;
  /* the surface code sits on, at both scales. Over the area of a <pre> it
     reads as a field on its own; on an inline chip it is far too faint to
     delimit anything, which is why <code> also takes a --line border. The
     fill is still worth carrying there: it makes the chip an object rather
     than an outline, and it is what ties the two to one surface. */
  --tint:  rgba(0,0,0,.04);
  /* Two colours, one job each -- the world's pair, shared with 0xsaddy.com and
     fastfoodvisors.xyz since 2026-10-08. Mustard marks the label tier at rest
     (--accent: it names things); ketchup marks whatever is responding right now
     -- hover, focus, :target (--accent-live, --accent-rule: it acts). Nothing that is
     merely true gets it. Links are NOT in either list any more: a link at
     rest is structure and wore the live value, which was the one place the
     interaction colour sat on something that was not interacting. Its
     underline says what it is, and says it in greyscale. The evidence rule stays neutral
     at rest because "this is evidence" is a category, and the tint plus
     the rule already say it twice; in accent, nine of them down a page
     would be the most branded thing on a site whose brief is no branding.
     Targeted, it earns the colour, because that is a response.

     Teal until 2026-10-08, chosen then by elimination (eleven candidates; see
     git history for the reasoning). It gave way to ketchup and mustard so the
     three sites read as one system, and because the roles were already the
     same: this file's quiet/live split is 0xsaddy.com's names/acts split.
     Colour-vision cost, stated: in light the two are equal in luminance
     (1.02:1) and differ by hue alone, which red-yellow deficiencies blur. That
     is acceptable only because they never mark the same kind of thing in the
     same place -- mustard sits on labels at rest, ketchup appears on
     interaction -- and because colour never carries a meaning alone (below).

     Never the only signal, and less so than before: accent against --fg is
     2.6:1 light (both colours) and 1.8:1 / 2.5:1 dark (mustard / ketchup), because on a dark background the text and
     any bright accent are both light. Luminance ratio understates hue
     separation there, which is exactly why a link is an underline before it
     is anything else and :target keeps its shape change. Colour reinforces here, it never
     carries.

     One value per mode, no derived scale -- both clear body-text contrast
     (5.29:1 light, 12.77:1 dark), so prominence is carried by AREA, which
     is free: a 2px rule, 12px text and a focus ring all read differently at
     the same colour. A second value would need a contrast failure to
     justify it, and there isn't one.

     No exceptions. Code samples used to take accent on their quoted
     values and links used to take it at rest; both use contrast instead
     now, so the live value appears only on things that are responding. */
  /* Three tokens, two hues. --accent (mustard) marks structure at rest --
     category heads, evidence labels. --accent-live (ketchup) marks what is
     responding -- a link under the pointer, a focused control, a targeted
     block. They appear together on every page, which is what makes the pair
     readable as a rule rather than as two decorations.

     Three, not two, because text and rules have different requirements.
     Text has to clear 4.5:1 to be readable, and on a pale ground anything
     that clears it is dark. A 1px rule is not text: it needs to be seen, not
     read, so in light it takes a brighter ketchup (#d0473f, 3.73:1 -- more
     visible than the teal rule it replaces at 3.04:1). In dark there is no
     tension between vivid and legible, so live and rule are the same there.
     Contrast, light / on tinted .ev: mustard 5.62 / 5.16, ketchup 5.53 / 5.08.
     Dark: mustard 8.97 / 8.25, ketchup 6.34 / 5.83. Same values as
     0xsaddy.com. */
  --accent: #755600;
  --accent-live: #a23737;
  --accent-rule: #d0473f;

  /* ONE width. Fixed, never `ch`. A custom property holding `ch` is
     substituted at the point of USE, so it resolves against each element's
     own font and size. This has now been the same bug three times: 72ch
     computed to 648px on the grid and 1080px on the lede, and 66ch made a
     13px category head narrower than the 15px rows under it. If a width is
     meant to be shared, it cannot be written in a font-relative unit.

     ONE width now, not two. The pair existed because sans prose wanted a
     narrower measure than mono code; the site is one family, so the reason
     went with the essays. It is set by the longest line of code: 585px of
     mono at 13px (.6em advance: Plex then, JetBrains Mono now -- identical),
     plus 17px of padding and rule, plus headroom, and
     build.py warns when a sample outgrows it. Prose runs ~73 mono
     characters at it, the top of the readable band and comfortable at
     these paragraph lengths.

     It is declared in exactly one place -- on .wrap -- and every component
     fills it. Twelve components each carried their own copy, which is how
     a 13px label and a 15px row ended up different widths off the same
     token. Widening the site, or putting a border on something, is now one
     edit rather than twelve. */
  /* The one width on the site, set by the widest code sample: 74 characters
     plus the block's padding either side, so no sample scrolls. In ch, so it
     resolves against the 13px body where .wrap uses it (~610px) and follows
     the type if the size ever moves. Prose runs ~78ch at this width. */
  --measure: calc(74ch + 2 * var(--s2));
  /* Two leadings for two ways of reading: prose is read at length, and
     everything else -- titles, labels, the index, code -- is scanned. A
     third, for display sizes, went with the display sizes. */
  --leading:         1.6;   /* prose — read at length */
   /* titles, labels, index, code — scanned */
  /* One size, as on 0xsaddy.com. The scale was 25 / 19 / 15 / 13: a
     headline, a lede, body and a fine print step. It is 13 throughout now,
     and the hierarchy those steps carried is carried by treatment instead --
     primary bold for titles, primary for statements, --dim italic for the
     author's voice, mustard for names, ketchup for acts. A new size is a new
     decision, not a tidy-up. */
  --size:    13px;

  /* Two faces, split by function rather than by hierarchy. Structure is
     mono -- nav, headings, indexes, evidence markers, data. The reading
     layer is sans. Headings stay mono deliberately: they pair with the
     section index, and a face change would break the pairing. */
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  /* --sans is gone: the site is one family now, and a mono/sans
     pair was two voices for one register. */

  /* Vertical rhythm. Twenty-five distinct spacing values were in use across
     eight components -- .15 and .2 and .3 doing the same job in three
     places, 1.5 and 1.75 and 2 and 2.5 in four more. Nobody chose that; it
     accumulated. Five steps, each double the last, and everything snaps to
     one of them.

     Sizes stay in em where they are meant to scale with their own text --
     the code chip's padding, the index's hanging indent, the evidence
     marker's trailing space. Those are six values that should not be on
     this scale and are not. */
  --s1: .5rem;    /*  4px — inside a list item or a control */
  --s2: 1rem;     /*  8px — between tight siblings */
  --s3: 2rem;     /* 16px — paragraph rhythm, block padding */


}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:#080808; --fg:#e8e4dc; --dim:#9a9792; --line:#4d4c4a;
    --tint: rgba(255,255,255,.05);
    --accent: #c9ab48;
    --accent-live: #e9695f;
    --accent-rule: #e9695f;
  }
}

:root[data-theme="dark"] {
  --bg:#080808; --fg:#e8e4dc; --dim:#9a9792; --line:#4d4c4a;
  --tint: rgba(255,255,255,.05);
  --accent: #c9ab48;
  --accent-live: #e9695f;
  --accent-rule: #e9695f;
}

/* --- 2 base --------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; }

body {
  background: var(--bg);
  color: var(--fg);
  font: var(--size)/var(--leading) var(--mono);
  padding: 0 5vw;
}


/* The underline is the whole signal, and now it is unambiguous: nothing
   else inside running text carries one. That was not true until <em> and
   its dotted rule were removed -- prose had two kinds of line under text
   meaning different things, which is why the hue was here doing the work
   of telling them apart.

   So the colour goes. It was reinforcement for a signal that no longer
   needs any, and against --dim italic prose the live accent outranked the
   sentence it sat in: on a page whose point is the argument, the brightest
   thing was an aside. Inherit puts a link at the contrast of whatever it
   interrupts -- --dim in prose, --fg in .stated -- and the underline says
   what it is.

   What that buys is the token split meaning what its names say. --accent
   marks structure at rest -- the label tier, the evidence rule -- and
   --accent-live marks what is responding right now. A link sitting still
   is structure, not a response, and it was wearing the LIVE value: the one
   place on the site where the interaction colour appeared on something
   that was not interacting. It now appears on hover, focus and :target and
   nowhere else, which is what it was named for.

   It also unifies the hover rule. Every link on the site is now neutral at
   rest, so colour is free on every one of them, and the special case for
   prose links -- underline weight instead of hue, because hue was spent --
   goes away with the thing that spent it.

   Deliberately reinforcement rather than discovery: the pointer cursor is
   what tells a reader something is clickable. This confirms which one they
   are on, which matters when a link is two words inside running text, and
   it works in greyscale and for a colour-blind reader. */
a {
  color: inherit;
  text-underline-offset: .2em;
  text-decoration-thickness: 1px;
}
a:focus-visible { text-decoration-thickness: 2px; }
/* Three inline marks, one job each, and the whole of the system:

     <a>       you can go there          accent + solid underline
     <code>    a literal -- copy, type   tint chip
               or grep it
     <strong>  the load-bearing part     below

   Anything already inside quotation marks gets NO mark: the quotes were
   doing it, and eleven runs carried both.

   <em> is gone. It was not one mark, it was three -- 21 words being named
   rather than used, 9 quotations already in quote marks, 2 clauses -- and
   two of those three were jobs <strong> and <code> were already doing
   elsewhere. One paragraph of llms-txt put `you` and `your` in a chip and
   reader, model, asks under a dotted rule, in the same sentence, for the
   same reason: strings to search your own file for. Its dotted underline
   also had to be told apart from the solid one on links at a glance, so
   three meanings shared an appearance. Underline now means exactly one
   thing on this site. */
/* One emphasis per voice, and both are the same move: title weight plus
   one step up in ink. Underline is not emphasis -- it means a link and
   nothing else.

     primary copy (the record: .stated, .ev, definitions)
       bold mustard. Primary has no brighter ink to step up to, so it takes
       the accent.
     secondary copy (the author's italic --dim prose)
       bold primary, still italic. The voice is kept; the clause steps up to
       full ink.

   :not(.stated) because .stated is a main > p too, and is primary. */
strong { font-weight: 600; color: var(--accent); }
main > p:not(.stated) strong { color: var(--fg); }

/* Accent rather than --fg: a focus ring the same colour as the text it
   surrounds is the one state a keyboard reader cannot afford to miss. */
:focus-visible { outline: 2px solid var(--fg); outline-offset: 2px; }

/* The link hover, and the rule for all of them: every :hover on this site
   is inside an (hover: hover) query. Components may own theirs -- .crumb,
   .pager and .list each keep a one-line gate beside the rule it guards --
   but none of them may go ungated. This block used to claim it was the
   only place a hover could be written, which was never true of .crumb or
   .pager and had quietly stopped being true of .list, where two ungated
   rules were lighting a row on tap and leaving it lit.

   Gated on (hover: hover) because a touch browser has no hover to give and
   fakes one anyway: it applies :hover on tap and leaves it applied until
   something else is tapped, so a nav item stayed accent after you had
   already navigated away and come back. Ungated, these rules describe a
   state the device cannot leave.

   :focus is deliberately NOT in here. It lives with each component, because
   a tablet with a keyboard has no hover and still needs focus, and gating
   the two together would have taken keyboard navigation out with the bug.

   One rule for every link now: neutral at rest, --accent-live on hover.
   That used
   to need a split -- nav links took accent because their colour was free,
   prose links thickened their underline because theirs was spent on being
   accent already. Nothing is spent any more, so the exception went with it
   and --accent-live means one thing everywhere: this is responding to you.

   The underline still thickens as well. It is the half that survives
   greyscale and colour blindness, and dropping it would leave hover as a
   hue change alone -- the exact failure the accent token's own comment
   warns about. */
@media (hover: hover) {
  a:hover,
  .theme:hover { color: var(--accent-live); }

  a:hover { text-decoration-thickness: 2px; }
}

/* Motion is decoration here; anyone who has asked for less gets none. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
}

.skip { position: absolute; left: -9999px; }
.skip:focus {
  position: fixed; left: 1rem; top: 1rem; z-index: 9;
  background: var(--bg); border: 1px solid var(--fg); padding: var(--s1) var(--s2);
}

/* --- 3 layout -------------------------------------------------- */
.wrap {
  display: grid;
  grid-template-columns: minmax(0, var(--measure));
  gap: var(--s3);
  max-width: var(--measure);
  margin: 0 auto;
  padding: var(--s3) 0 calc(var(--s3) * 2);
}
/* 10% on wide screens, matching the portfolio. Everything inside scales
   together -- type, spacing, the measure, and the code samples that set it
   -- so nothing pinned to anything else comes unpinned. It is comfort at
   reading distance on a large display, where the column is otherwise a
   small block of 13px mono in the middle of a lot of nothing. */
@media (min-width: 64rem) { .wrap { zoom: 1.1; } }

@media (max-width: 60rem) {
  /* minmax(0, 1fr), never a bare 1fr. A bare 1fr is minmax(auto, 1fr), and
     `auto` means "at least as wide as my content" -- so one wide <pre> grows
     the column past the viewport and takes the whole page with it. The
     desktop rule above already guards this; this one did not, which put a
     horizontal scrollbar on every narrow screen. */
  .wrap { grid-template-columns: minmax(0, 1fr); gap: var(--s3); padding-top: var(--s3); }
}

/* --- 4 masthead -----------------------------------------------
   One line: whose site this is, and the theme control. The rail that
   used to sit here listed the categories on every page -- which was the
   index restated, beside a document that already carries a crumb back
   to it. Its width token, its mobile row, and the measured argument for
   the marker gutter in that row went with it. */
/* No margin-bottom: .wrap is a grid with gap --s3, so a margin here
   stacked with it and put 64px under a one-line header. Two mechanisms
   doing one job; the grid owns the gaps between its children. */
.masthead {
  display: flex; align-items: baseline; justify-content: space-between;
}
/* A span, not a link, and no hover. It is the site saying where you are --
   identity, not navigation -- and the way to the index is [back], one line
   below it on every document and the only thing in the crumb. A masthead
   that also went there offered the same destination twice within 40px, so
   the one that answered the pointer was the one that did not need to.
   text-decoration: none is gone with the anchor; a span has none to remove. */
.brand { color: var(--dim); }

/* theme control — text, not iconography. Three states, cycled.
   Sits in the masthead rather than floating: a fixed corner button would
   be chrome over the content, and the masthead is already the one line
   that is not the record.
   Set one step down from the nav links (--eyebrow, not 14px) so a control
   does not read as a seventh destination. */
.theme {
  background: none; border: 0; padding: var(--s1); margin: calc(var(--s1) * -1);
  font: inherit;
  /* Here and not in the --size list up in base, because `font: inherit` is
     a SHORTHAND: it resets line-height, it inherits body's 1.7 along with
     the family, and it sits later in the file at equal specificity, so the
     base rule lost silently. Same trap as the note at .contents below, and
     .theme is the element it has now caught three times. It has to be
     restated after the shorthand that clears it. */
  line-height: var(--leading);
  color: var(--dim); cursor: pointer;
  transition: color .15s ease;
}

/* No narrow-width margin here any more, and the absence is measurable.
   There was `@media (max-width: 60rem) { .theme { margin-top: --s2 } }`,
   and its comment described a layout that no longer exists -- "in a row
   the control is part of the same band as the links" is about the rail,
   which was deleted, and "the standoff it earns under a column" is the
   column the rail stood in. The rule outlived both.

   What it did once the rail was gone: pushed the control 16px down inside
   a flex row aligned on the baseline, which pulls the whole band down with
   it. The masthead measured 42.2px tall for one 13px line, against 28.8px
   above 60rem where the rule never applied -- so the header sat 16px
   lower on every viewport under 960px, and the top of the page read as
   loose on exactly the screens with the least room. Desktop was right the
   whole time and narrow screens were carrying a rail's spacing for a rail
   that is not there.

   The frame's rhythm is the grid's: --s3 of .wrap padding above the
   header, --s3 of gap below it, the same step between main and the footer.
   Nothing here needs to add to that. */

.theme::before { content: "[" attr(data-mode) "]"; }

/* --- 5 components ---------------------------------------------- */

/* h1 is an eyebrow, not a headline — the lede below it is what the page is
   about, and it carries the weight. Small, dim, spaced. */
/* The h1 is what the page is about: the playbook on the index, the
   finding on a document. One display rule for both.

   It used to be an eyebrow, from when the h1 was the site name -- which
   left .claim setting only a width, so every finding title on the site
   rendered as a 13px uppercase label. The site name is identity, not
   subject, and it lives in the masthead now. */
h1 {
  /* body size, title weight -- 0xsaddy.com's title treatment. Hierarchy is
     carried by weight and colour, not by size. inherit, not omitted: the
     UA sheet gives h1 2em and h2 1.5em. */
  font-size: inherit; font-weight: 600;
  /* --s1 to the statement under it: label and title are one unit, but a
     wrapped bold title run straight into a regular statement blurred into
     one paragraph. The gap separates them without a second size. */
  margin: 0 0 var(--s1);
  /* No display cap, and no text-wrap: balance.

     The cap was here on the principle that a headline should not run the
     full body measure -- but that measure is 26em, pinned by the widest
     code sample, so 24em shaved 2em off something already short and its
     only real effect was stranding "AI." on a line of its own. Removing
     it fixed the orphan.

     Balance was added to fix that orphan and outlived it. It optimises
     for equal line widths, not for sense, so at intermediate widths it
     broke mid-phrase into two near-identical lines that read as a block
     rather than a heading. Ordinary wrapping is more predictable, and
     responsive headline behaviour should be a deliberate decision if we
     make one. */
}

/* h2 is the skim layer. It was the same size as body text, which is why the
   argument was invisible unless you read every word. */
/* Unnumbered. Headings carried a counter until the numbering moved to the
   evidence -- one bracket vocabulary, one referent. A section ordinal only
   said where you were, which the index says better. */
h2 {
  font-size: inherit; font-weight: 600;
  margin: calc(var(--s3) * 2) 0 var(--s2);
  scroll-margin-top: var(--s3);   /* anchored headings are share targets */
}

/* The lede is a title, not prose: one sentence, display size, received
   rather than read. It belongs with the structure, and it sits directly
   under a mono eyebrow -- in sans the face changed twice in three lines
   for no reason a reader could infer.

   Sans now carries exactly one thing: sustained reading. */
/* the second line of the lede: the same voice at lower emphasis. It is
   the caveat and the claim in one sentence, and it leads the page rather
   than sitting in the note, so a reader -- or a model -- that takes only
   the top of the page still gets the limit with the offer. */
/* No cap of its own: paragraphs fill the measure like everything else.
   The narrower cap here existed for Plex Sans, which fitted far more into
   the column than the mono it was sized for -- 87 characters a line. In
   mono the same width is ~73, inside the readable band. */
p { margin: var(--s2) 0; }

/* Inline code: a literal you could copy, type or grep, delimited so a run of
   it reads as one object rather than a stretch of loud prose. Without any
   mark it fell through to the browser default and read as a slab of
   oversized text mid-sentence.

   A 1px --line box, not a tint. The tint could not be seen: composited
   against the page it came out 1.12:1 in dark and 1.09:1 in light, where
   --line -- the value this site uses everywhere it means "a boundary you
   can see" -- is 2.33:1 and 1.69:1. Raising the alpha does not reach it
   either; .13 still only gets to 1.43:1 dark, and by then it is a
   highlighter. The border gets there at the site's own established value,
   with no new number to justify.

   NOT the accent, which was the other candidate. The accent token's own
   comment rules it out: "in accent, nine of them down a page would be the
   most branded thing on a site whose brief is no branding" -- written about
   the evidence rule, and llms-txt carries seven chips with five in one
   sentence. Accent at rest marks block-level categories, the label tier and
   the evidence rule; a chip is a run inside a sentence.

   The 3px radius is the only rounded corner on the site, and it is what
   separates a chip from a highlighter mark. Horizontal padding is generous,
   vertical is not: the border costs 2px, taking the chip from 26.5px to
   28.5px inside .stated's 31.3px line box, which is the tightest case on
   the site and still clears.

   .92em keeps the chip from setting wider than the text it interrupts;
   mono runs ~6% wide per character and the padding adds to that.

   A chip does not wrap at all now -- see inline-block below -- so
   box-decoration-break has nothing to decide. The earlier claim here, that
   a broken box was "what it is", was wrong: `structured data` split across
   two lines reads as two terms, and the open edges read as a rendering
   fault rather than as one term continuing. */
code {
  font-family: var(--mono);
  font-size: .92em;
  background: var(--tint);
  border: 1px solid var(--line);
  padding: .1em .35em;
  border-radius: 3px;
  /* A chip is one token and must not be readable as two, so the box never
     splits across a line: inline-block keeps it whole and moves it down
     intact instead. max-width is what pays for that -- <script
     type="application/ld+json"> sets 381px against a 338px measure on a
     phone, and an inline-block that cannot fit would push the page into a
     horizontal scroll rather than wrap. Clamped, it wraps INSIDE its own
     closed box, which is the one honest way to show a literal too long for
     the column.

     line-height is the price of inline-block and this is the receipt. An
     inline box that outgrows its line simply overflows; an inline-BLOCK
     pushes the lines apart, and .stated's steps went from a flat 31.3px to
     31.3 / 32.1 / 33.6 / 34.3 on the old scale. At 13px and 1.6 the line
     box is 20.8px; 1.2 puts the chip's box at 18.7px (12px chip text, its
     padding and border) and keeps it under the line, so the rhythm stays
     flat. Re-measure if the size or the leading moves. */
  display: inline-block;
  max-width: 100%;
  line-height: 1.2;
}

/* evidence — the one idea worth a component, and now it means exactly one
   thing: this was observed, on the subject named in the footer.

   It used to hold two kinds. A [reasoned] variant sat in the same box with
   a quieter border, which gave an inference the visual authority of a
   measurement -- the failure the credence page names, committed by the
   component that carries the page's own claims. Inference is prose now.
   With one kind left, the [measured] label was restating the box, so the
   block carries only its number.

   The TINT says "this is evidence" and gives the block a shape the eye
   catches without reading -- which is what makes the page scannable down
   either track, prose or evidence, while skipping the other.

   Same size and same face as the prose, deliberately. Setting evidence
   smaller would mark it as supporting detail, and it is the opposite --
   it is the part that makes the rest worth reading. */
/* A rule and an indent, not a box. Four edges said "container"; one edge
   and a set-in measure say "quoted" -- which is what an observation is,
   and it is subordinate to the prose that reasons about it.

   Colour alone was tried and cannot carry this: it only separates where
   there is prose to separate from, and several documents run stated ->
   evidence -> takeaway with no connective paragraph at all. The block
   needs a mark that does not depend on its neighbours. */
.ev {
  border-left: 2px solid var(--accent);
  padding-left: var(--s2);
  /* No TOP margin, mirroring what used to be no bottom margin: the citation
     is part of this unit and carries the separation for both, and it is
     above now, so it owns the gap above and this owns the gap below. Given
     --s3 on both sides, the citation's --s1 would collapse against it and
     the label would sit 32px off the block it names. */
  margin: 0 0 var(--s3);
  /* Every block is its own share target, and the target is the block --
     but the label naming it is a sibling ABOVE, so a scroll margin of --s3
     would land it just past the viewport edge. 2 × --s3 clears the label
     (18px), its --s1 gap, and still leaves the --s3 standoff. */
  scroll-margin-top: calc(var(--s3) * 2);
}
.ev p { margin: var(--s1) 0; }
.ev p:first-child { margin-top: 0; }
.ev p:last-child { margin-bottom: 0; }
.ev .data, .ev pre { margin: var(--s2) 0; }

/* the block a reader was sent to. Live, not structure -- it is the answer
   to a navigation, and it matched the resting border exactly until the
   accent split, so the rule did nothing. */
/* the block you were linked to: the rule it already has, lit */
.ev:target { border-left-color: var(--accent-rule); }

/* note — a caveat the reader should carry into every other page, so it is
   set apart rather than left as one more paragraph they may skim past.
   Outlined and not coloured: this is not a warning, and an alert colour
   would overstate it.

   Outlined and not FILLED either, which is the distinction that matters
   here. A tint was tried and reverted. It made both callouts one mechanism,
   which was the argument for it, and that turned out to be the argument
   against it: .ev is a measurement and .note is a caveat ABOUT the
   measurements, one level up, and a shared fill flattens that. So the fill
   is the category:

     filled  + 2px --fg left rule   .ev    a measurement, attached to the flow
     no fill + 1px --dim box        .note  a remark on them, set beside it

   Rejected on the way here: a second fill colour. The only two available
   both break something -- accent-tinted contradicts the rule that accent
   means THIS RESPONDS TO YOU, and any other hue introduces a colour that
   means nothing anywhere else on the site.

   Losing the fill makes this border the only thing marking the note, which
   sets its contrast. The rule for every rule on this site: a border that is
   the ONLY delimiter takes --dim and clears 3:1 -- this box, and the <pre>
   left rule. A border confirming a boundary that whitespace already makes
   may stay faint at --line -- .next above 64px of gap, the mobile rail
   above 32px. The difference is whether removing the line would lose
   information or only tidiness. At --line this box was 1.33:1 and carrying;
   at --dim it is 5.74:1. */
/* margin: 0 was right when a note held one paragraph. At four it welded
   them into a wall.

   1rem, not the .5rem the evidence blocks use. That gap suits .ev, where
   one or two paragraphs continue a single statement; this note holds four
   independent ones -- scope, subject, notation, standing -- and at 14px on
   a 24px line, 8px of separation reads as little more than a line break.
   1rem is the gap open prose already uses, and it matches the box's own
   vertical padding, so the block keeps one rhythm rather than two.

   First and last stay flush via p + p, so the box does not grow at its
   edges -- only between the statements. */

/* label/value data. Was being set in <pre> because monospace made the
   columns line up -- but a code block cannot wrap, so on a narrow screen
   the second column either scrolled or fell off. A grid does the aligning
   on wide screens and simply stacks below 40rem, which is what tabular
   prose should do. <pre> is now reserved for code. */
/* The one component where the measure measurably binds: a definition
   column shares its row with a term column and loses that width from its
   own, so it wraps where nothing else does. Kept explicit for that reason,
   even though it now names the same token as everything else. */
.data {
  display: grid; grid-template-columns: auto 1fr;
  gap: var(--s1) var(--s3); margin: var(--s3) 0;
  max-width: var(--measure);
}
.data dt { font-family: var(--mono); color: var(--dim); }
.data dd { font-family: var(--mono); }
@media (max-width: 40rem) {
  .data:not(.defs) { grid-template-columns: 1fr; gap: 0; }
  .data:not(.defs) dt { margin-top: var(--s2); }
}

/* .defs — a definition list that is not tabular.
   The grid sizes its first column to the longest term, which is right for
   data and wrong for definitions: "understanding" set the column, so
   "reach" and "credence" sat with two centimetres of nothing before their
   own text and the pairs stopped reading as pairs. These flow instead —
   term, rule, definition, one per line — so every line starts at the same
   left edge and the terms still scan as a list.
   The <div> wrappers are what HTML5 added <dl> grouping for. */
.data.defs { display: block; }
.data.defs div { margin: 0 0 var(--s2); }
.data.defs dt { display: block; font-weight: 600; color: var(--fg); }
.data.defs dd { display: block; }

/* code samples. A field, not a rule and not a box.

   A full border was tried and rejected, for two reasons. One has since
   expired: it "read identically to .note", and .note no longer exists. The
   other still stands -- the lines here are mostly far shorter than the
   column, so a border drew a hard right edge through nothing.

   A fill is not that edge. A border asserts where the content ends; a
   surface just extends under it, and the code's ragged right stays ragged.
   So the objection to the box does not carry over, and what the fill adds
   is something the hairline could not: overflow-x is live on this element,
   and a field shows a reader the region that scrolls. A rule on the left
   marks only where the block begins.

   The alpha is the same value that was invisible on an inline <code> chip,
   1.12:1 against the page in both modes. Nothing about the colour changed
   -- the area did, and the accent token's comment already names that as
   the channel: "prominence is carried by AREA, which is free". A tint over
   40px of chip is nothing; over a 656px block it is a surface. The chip
   needed an edge because it interrupts a sentence and a reader has to see
   where it stops mid-line; a block's extent is its own lines.

   Square, with no radius. The chip keeps the only rounded corner on the
   site, and the two now say different things: rounded is an object sitting
   inside a line, square is a surface sitting under one.

   --s2 all round. It was 4px vertical and 0 on the right, which a fill
   cannot use -- text would have run to the edge of the field. Every sample
   still clears the column at the wider padding; the longest is 74
   characters against a budget of 78, and build.py warns before that
   changes.

   overflow-x is a safety net, not a layout: at 13px the column holds about
   81 characters and every sample is written to stay inside 78. A code
   block a reader has to drag sideways is a badly set code block, and the
   fix belongs in the sample rather than in the CSS. */
pre {
  font-family: var(--mono);
  background: var(--tint);
  padding: var(--s2);
  overflow-x: auto;
  margin: var(--s3) 0;
  color: var(--dim);
  overscroll-behavior-x: contain;   /* do not hand the drag to the page */
}

/* One tier, applied at build time, and no colour. The block sits at --dim
   -- tags, keys, braces, punctuation and comments are all scaffolding a
   reader copies verbatim -- and only the values come up to full contrast,
   because those are the parts that are theirs to set.

   The values used to be accent. Two things were wrong with that. It marked
   every quoted run, so "headline" and "Page - what it is" were lit
   identically and the colour meant only "between quotes". And it spent the
   site's one accent on a category, inside a block, where it read as
   interactive text that was not. Contrast does the same job here and costs
   nothing, so accent now means one thing everywhere with no exception. */
pre .s { color: var(--fg); }

/* footer — a citation, not navigation. Names the subject every numbered
   observation came from, and the period, because these findings are about systems
   that change monthly and a reader who knows when should discount
   accordingly. Quieter than the record: it is not part of the argument. */
/* two sentences doing different jobs: a citation, and a pointer. Inline
   they only separated where the text happened to wrap. */
footer p { margin: 0; }
footer p + p { margin-top: var(--s1); }

/* The footer is a region of its own -- a citation, not the record -- so a
   rule marks it, --s3 either side: the .wrap grid's gap above, this padding
   below. The same boundary every category on the index takes, and the one
   0xsaddy.com's footer takes. It replaces 2 × --s3 of bare space. */
footer {
  color: var(--dim);
  border-top: 1px solid var(--line);
  padding-top: var(--s3);
}
/* Nothing here any more, and the absence is the point. This block used to
   hold `footer a { color: --dim }` to keep the live accent out of a
   citation that would otherwise have been the loudest thing on the page at
   the moment it matters least. Links inherit now, footer is already --dim,
   so the override says what the cascade already said.

   Its hover went to --fg rather than accent on the same reasoning. That
   reasoning was about a REST state -- chrome should not wear the brand
   colour while sitting still -- and accent no longer describes a rest
   state at all. A footer link answering the mouse in the same colour as
   every other link is the rule, not an exception to it, so the global
   a:hover carries it. */
/* :focus-visible, like every other focus style that paints colour --
   .pager, .list, and a:focus-visible in base. Bare :focus fires on a tap
   too, which is the touch half of the hover problem arriving through a
   different selector. .skip keeps bare :focus and is the one exception
   that earns it: a skip link is only ever reached by Tab, and it has to
   appear whenever it is focused or it is not a skip link. */
footer a:focus-visible { color: var(--accent-live); }

/* ↗ ends a link that leaves the site -- the mark 0xsaddy.com uses, with its
   treatment: --dim, regular weight, --s1 off the word, ketchup and a 2px
   nudge with the link. build.py adds it to every link in page content that
   leaves the site, and the footer's two carry it in the template.
   inline-block so the link's underline stops at the word. */
.ext {
  display: inline-block; margin-left: var(--s1);
  color: var(--dim); font-weight: 400;
  transition: transform .15s ease;
}
@media (hover: hover) {
  a:hover .ext { color: var(--accent-live); transform: translate(2px, -2px); }
}
@media (hover: none) {
  a:active .ext { color: var(--accent-live); }
}
a:focus-visible .ext { color: var(--accent-live); }

/* ================================================================
   THE RECORD
   Colour has exactly one job: it marks the label tier -- the mono-caps
   line that names a block. Category heads and evidence labels -- and the
   pager's own PREV and NEXT, which are the same kind of line one register
   down. Nothing else takes it: links, focus and current
   state are carried by underline, weight and a gutter marker, which
   work in greyscale and for a colour-blind reader.
   Evidence-only was the earlier rule and it underworked -- on a 28-row
   index it coloured four counts and left the rows undifferentiated.
   ================================================================ */

/* --- the index ------------------------------------------------- */
/* A gloss: a line that says how to read what follows. The lede has them
   and so does every category head -- the same component, so one rule.
   Named rather than selected by adjacency: `.lede + p` could only ever
   match one, and the page needs two -- how far to trust the record, and
   what the number means, which are different statements. Italic is free to mean this and only
   this: <em> on this site is a dotted underline, not a slant.

   The slant is synthesised. Only mono 400 and 600 are loaded, both normal,
   so the browser obliques the roman. A true italic would be a third font
   file for two lines of text a page, which is not the trade. */
.gloss, .mood, main > p { font-style: italic; color: var(--dim); }

/* the label tier: every line that names a block -- category heads,
   evidence labels, the pager's prev and next, and the category eyebrow a
   pager cell takes when it crosses. Lowercase as written, untracked, in
   colour (mustard for a category, --dim for chrome).

   Title weight, as on 0xsaddy.com. On the index the category heads are the
   scan layer -- they prime the buckets before the rows are read -- and one
   weight on both page shapes beats a per-page exception. Over a doc's bold
   h1, colour is what separates the label from the title it names.

   Regular weight was tried (2026-10-08) and reverted for exactly that:
   the index lost its buckets to save a doc header a little weight. */
.lbl { font-weight: 600; }

/* THE RHYTHM. 0xsaddy.com's vertical system: three steps, 8 / 16 / 32,
   and one leading (1.6). Each step names one relationship everywhere; a gap
   not on this list arrived by margin collapse or is a mistake.

     0      one unit
              .eyebrow to h1              a label and what it names
              .cat-head to .mood          a head and its own definition

     --s1   inside a unit
              h1 to .stated, .gloss       title and its restatement
              a row's text to its rules   (so rows sit --s2 apart, as
                                           0xsaddy's entries do)
              .ev-meta to its .ev         a citation and what it cites
              footer p to footer p        two halves of one note
              .pager .lbl to its cell

     --s2   paragraph rhythm
              p to p                      one voice, continuing
              .gloss to .state            the claim in words, then numbers
              .mood to its rows           a head and the list under it
              code and .ev insets

     --s3   block to block
              prose to .ev, to pre        sibling to sibling
              either side of a rule       .stated, .state, each category,
                                          the pager, the footer
              .wrap gap and frame


   The older comments in this file argue spacing with pixel measurements
   taken on the previous scale (15px body, 1.7 and 1.4 leadings, a five-
   step 4-64 scale). Their token names have been carried onto this scale;
   their pixel figures have not been re-measured. */
/* A list, not a table (2026-10-08). Every row used to sit between two
   rules -- thirty-odd hairlines on one page, so a rule meant nothing. Now
   the rule has one job on the index, a new category starts here, and rows
   are separated by space, as 0xsaddy.com separates its entries.

   Plain bullets with a hanging indent: "-" on the content edge, a wrapped
   title aligned under its own first word. 0xsaddy.com's key column was
   tried and rejected here -- its key carries data ("01", "fields"), and
   this one would have carried only a dash, at 64-96px a row, with titles
   wrapping to three lines on a phone.

     .cat-head / .mood      one unit, no gap
     .mood to the list      --s2
     row to row             --s1
     list, rule, next head  --s3 either side */
.cat + .cat {
  margin-top: var(--s3); padding-top: var(--s3);
  border-top: 1px solid var(--line);
}
.cat-head { color: var(--accent); margin: 0; }
/* the definition, on its own line under the head */
.mood { margin: 0; padding-bottom: var(--s2); }

.list { list-style: none; }
.list li + li { margin-top: var(--s1); }
.list a { display: block; text-decoration: none; color: var(--fg); }
/* The bullet: one character cell plus one space, 2ch, so a wrapped line
   starts under the title's first letter. inline-block with a fixed width
   because the closer's "↳" is not in JetBrains Mono and draws in a system
   face whose advance is not 1ch -- the width holds the title's edge anyway. */
.list .t { display: block; padding-left: 2ch; text-indent: -2ch; }
.list .t::before {
  content: "-"; display: inline-block; width: 2ch; text-indent: 0;
  color: var(--dim);
}
/* a closer: this concludes the rows above it. The word "Conclusion:" stays
   in the title -- it is what llms.txt, the schema and every <title> publish,
   so the mark agrees rather than carries it. */
.list li.closer .t::before { content: "\21B3"; }
/* the whole row answers, bullet included */
.list a:focus-visible, .list a:focus-visible .t::before { color: var(--accent-live); }
@media (hover: hover) {
  .list a:hover, .list a:hover .t::before { color: var(--accent-live); }
}
/* an empty category's one line: dim, unlinked, no bullet -- it sits where
   entries sit and has to be visibly not one */
.list li.empty { color: var(--dim); }


/* --- a document ------------------------------------------------ */
/* One control, in the bracket form [auto] established. No rule under it:
   it held a border when it also carried the category, and that context
   moved to the eyebrow below. */
/* A control, not a label -- so it takes the theme toggle's register
   rather than .lbl's caps and 600 weight. [back] and [auto] are the same
   kind of thing and should look it. */
.crumb { margin-bottom: var(--s3); }

/* The document's category, over its title -- the same accent label the
   index uses for its category heads, so the two surfaces name a category
   the same way. */
/* roman, not italic: it is a label, and main > p would otherwise sweep it
   into the author's voice along with the prose */
.eyebrow { color: var(--accent); font-style: normal; margin: 0; }

/* Chrome, like the brand and the footer: navigation is marked by position,
   not by hue and not by an underline. A crumb is unmistakably a link where
   it sits, so it keeps an explicit colour and drops the rule that a{} would
   otherwise give it.

   This block used to exist to keep the live accent OUT -- a{} put it at the
   top and bottom of every document, and that was the one thing chrome could
   not wear. a{} inherits now, so the override earns its place on the
   underline alone.

   Its private hover was dropped on the assumption that the global a:hover
   would take over, and it did not: `a:hover` is (0,1,1) -- one pseudo-class,
   one type -- and `.crumb a` is also (0,1,1), so the tie goes to source
   order and this rule is 600 lines later. [back] answered the pointer with
   nothing at all until this was restored. `.crumb a:hover` is (0,2,1) and
   settles it on specificity rather than on where it happens to sit.

   Hue alone, and it is the one register where that is right. Everywhere
   else hover also thickens an underline, because colour is never allowed to
   be the only signal -- but chrome has no underline at rest to thicken, and
   [back] is already bracketed. The brackets are the affordance; the hue is
   the response. [auto] does exactly this, and the two are the same kind of
   control by the note above.

   The pager was here too and has left: it is a two-row index now rather
   than chrome, so it takes the index row's --fg and its lit brackets. */
.crumb a { color: var(--dim); text-decoration: none; }
@media (hover: hover) { .crumb a:hover { color: var(--accent-live); } }

/* the finding stated, capped at ~50 words. Marked by size rather than
   weight: mono's 400-to-600 step is coarse and reads as shouting over
   three lines, and the wider glyph pulls this to ~58ch on its own. */
/* The masthead's closing step: margin-top 0 so the h1 owns the gap above,
   and a RULE below, because this is where the masthead ends and the record
   begins and that boundary had been asked to live in whitespace alone.

   Whitespace could not hold it. The boundary has to beat the biggest gap
   inside the record, and that gap is --s3; the only step above it is 2 × --s3,
   which put 106px of nothing between the claim and the first quoted line.
   Too big was the honest reading. Dropping to --s3 then made the boundary
   40px against 42px gaps inside the record -- 0.95x, a boundary smaller
   than the divisions it was supposed to rank above. Two steps on the scale,
   one too loud and one inaudible, and no third step that would be
   systematic to invent.

   So the boundary stops being a distance and becomes a mark. A rule is
   categorical: it does not compete with --s3 on the same axis, it is
   simply a different kind of thing, and it reads at any spacing. Which
   means the space around it can be the comfortable one rather than the
   one big enough to win an argument -- 40px above the rule and 36px below
   it, where 2 × --s3 alone needed 106px and still read as a void.

   --s3 both sides, so the split is even and neither half claims the rule.
   The pager and the footer take the same rule, --s3 either side.

   One declaration for all three document shapes, and the :has() exception
   that used to sit below is gone with it. That rule existed because a
   label opening the record already filled part of an 2 × --s3 gap and a
   paragraph did not, so the same token rendered 106px on twenty documents
   and 77px on ten. With the rule carrying the boundary, the space no
   longer has to carry it, --s3 is right in front of all three openers, and
   the variance it was correcting does not arise. */
/* 1.65 is the one leading on the site that is not a token, and it is load-
   bearing rather than left over. It is set by the inline code chip, not by
   the prose: <code> is .92em plus .1em of vertical padding, which at this
   size makes the chip 28.8px -- taller than the line box at any leading
   below 1.65, so the chip's tint starts eating the line beneath it. Measured
   from the chip's bottom edge to the ink of the next line: 1.65 clears by
   5.6px, 1.5 by 2.4px, 1.45 by 1.5px, 1.4 by 0.5px, and 1.3 overlaps by
   1.8px. The claim at <code>'s own rule -- that vertical padding is small
   enough that the pill never pushes the lines apart -- is only true here,
   and only at this value.

   So it does not fold into a display leading, which this file once
   kept for "the lede and headings" and which this slot is otherwise the
   obvious candidate for. Reference pages carry code chips inside a .stated
   that runs to nine lines, and at 1.3 they collide. Anyone tidying this
   into the three-value set should re-measure the chip first; the tidy is
   what breaks it. */
.stated {
  /* body size and leading, primary ink: set apart from the italic --dim
     prose under it by voice and colour alone */
  margin: 0 0 var(--s3);
  padding-bottom: var(--s3);
  border-bottom: 1px solid var(--line);
}

/* prev and next: a region of its own, so a rule above it, --s3 either
   side -- the same boundary as the footer below it and every category on
   the index. Stacked in one column: each slot is the label, the category
   where it changes, then the title, one unit with no gaps, and the two
   slots sit --s2 apart. It was 0xsaddy.com's key/value row for a while,
   the only thing on the site using that indent, which it did not earn.

   The label is chrome, --dim; the mustard .eyebrow appears only where the
   target is in another category -- the word the reader meets over its h1
   on arrival. The whole slot answers hover and press in ketchup except
   the eyebrow, which keeps its mustard. */
.pager {
  display: grid; gap: var(--s2);
  margin-top: var(--s3); padding-top: var(--s3);
  border-top: 1px solid var(--line);
}
.pager a { display: block; color: var(--fg); text-decoration: none; }
.pager .lbl { display: block; color: var(--dim); }
.pager .eyebrow { display: block; color: var(--accent); margin: 0; }
.pager .t { display: block; }
.pager a:focus-visible, .pager a:focus-visible .lbl:not(.eyebrow) { color: var(--accent-live); }
@media (hover: hover) {
  .pager a:hover, .pager a:hover .lbl:not(.eyebrow) { color: var(--accent-live); }
}

/* Hover adds one underline step, everywhere: a prose link goes 1px to
   2px, and a list title -- an index row, a pager slot -- goes none to 1px.
   Colour is never the only signal, and since the rows lost their rules
   ketchup was all a row had. The title only: not the bullet, the label or
   the eyebrow. Thickness is restated because it does not inherit from the
   anchor; the offset does, and matches prose links. Same rule on
   0xsaddy.com. */
.list a:focus-visible .t, .pager a:focus-visible .t {
  text-decoration: underline; text-decoration-thickness: 1px;
}
@media (hover: hover) {
  .list a:hover .t, .pager a:hover .t {
    text-decoration: underline; text-decoration-thickness: 1px;
  }
}


/* --- evidence -------------------------------------------------- */
/* the label is the only coloured text on a page, and the block the only
   coloured rule. A block arguing against the page it sits on gets the
   same weight -- the word "Counter-evidence" carries that, and dimming
   it would demote the one thing that should not be. */
/* Metadata about the block, not a heading for it. Lowercase, --size, and
   quiet: the block's own rule and indent already say it is evidence, and
   with one block on most documents the word was neither counting nor
   disambiguating anything.

   All one tone. The status was briefly accent when determined, which
   read as an inconsistently coloured word rather than a credential --
   the distinction is in the words, not the colour. Nothing is dropped:
   an unmarked block would read as presumably fine rather than unchecked,
   which is the failure "a gap you leave gets filled by the reader" is
   about. */
/* Above the block, flush left, and quiet enough that it reads as the
   block's handle rather than as a heading over it.

   Three positions were tried. Under the quote at calc(--s2 + 2px) -- the
   quote's exact text column -- kept it attached but put it in the same
   column as the quoted lines, so a citation read as one more line of the
   quote, dimmer and smaller and structurally continuous with the thing it
   was meant to close. Split to the two ends of a row under the block fixed
   that and columned both halves, but it still asked a reader to reach the
   end of an observation before learning what kind of observation it was.

   Above answers both, and one thing neither did: the id is an address, and
   an address belongs at the door. #c-09 scrolls the block to the top of the
   viewport, so the label confirming the arrival was landing past the quote.

   The prominence this position used to have was never the position. It was
   an accent-coloured word -- "Evidence" -- set over a block that already
   said it was evidence twice, with a rule and a tint. Dim, --size,
   lowercase and scan-led, an id and a status do not lead the block; they
   let it start.

   The margins invert with the move: --s3 above separates it from whatever
   preceded, --s1 below binds it to its own block. Head, then body -- the
   same two steps as .eyebrow over h1, which is the same relationship. */
/* The whole record's citations once rendered in the author's italic while
   this rule's own comment said they were the footer's register exactly,
   same case included, and `font-style: normal` was sitting right here.
   This comment used to blame specificity -- `main > p` at (0,1,1) beating a
   bare class at (0,1,0) -- and that arithmetic is wrong. `main > p` is
   (0,0,2): two type selectors, and a child combinator adds nothing. A bare
   class wins it. .state, further down, is the live proof -- (0,1,0),
   declares `font-style: normal`, renders roman.

   The real cause was a stray closing brace upstream that dropped this rule
   and about sixty lines after it. check_css() in build.py exists because of
   it and would catch it now. The tag stays because `main > p.stated` and
   `main > p:not(.stated) strong` nearby need theirs for real reasons and a
   lone bare class among them reads as an oversight -- but nothing here
   depends on it, and a new rule at this size should not copy the pattern.
   .caveat above is a bare class for exactly that reason. */
main > p.ev-meta {
  color: var(--dim);
  font-style: normal; margin: var(--s3) 0 var(--s1);
  /* No tracking: this is the footer's register exactly -- same size, same
     colour, same case -- because it is the same kind of line. A citation
     saying what this is and where it came from. Tracking was tried and it
     was the only tracked lowercase text on the page, which widened it and
     made it read as more rather than less. */
}

/* The record is roman and --fg; the author is italic and --dim. Same rule
   on both surfaces: on the index the rows are the record and the glosses
   are the author, and on a document the claim and its evidence are the
   record while the prose between them is the author reasoning about it.
   The takeaway goes grey with the rest -- it is derived, not observed, and
   an exception for it was a positional rule standing in for meaning. */
main > p.stated, .ev p { color: var(--fg); font-style: normal; }

/* the lead, above the record: the one paragraph that is voice rather
   than record.

   The index's half of the pair .stated is on a document -- same slot, and
   now the same closing rule, for the same reason. It was margin 0 0 2 × --s3,
   carrying the masthead-to-record boundary in whitespace alone; measured,
   that boundary rendered 74.4px of ink-to-ink space against the 81.8px
   between one category and the next. 0.91x -- a boundary smaller than the
   divisions it ranks above, which is the same failure .stated's comment
   describes at 0.95x and answers with a mark instead of a distance. The
   mark now sits on .caveat, two lines further down, at the end of the
   masthead rather than in the middle of it.

   --s3, the sibling step, because this is the one gap in the masthead that
   crosses between kinds of thing. h1 to .gloss is prose to prose and
   .state to .caveat is one fine line to another; this is prose to data --
   15px dim italic to 13px --fg roman, a sentence to four counted facts.
   The table's rule for --s2 is that it "names no boundary between
   different kinds of thing", and that is a boundary.

   It was --s2, on the argument that the gloss and the state line are one
   statement in two registers and a token that means one relationship
   should be used every time the relationship recurs. The first half of
   that is true and it is not what the scale measures. Both lines do say
   how far to trust the record, but the scale responds to kind, not to
   content, and "in two registers" is the admission: a register change is
   exactly the boundary --s2 is defined not to name. So the masthead is two
   halves -- the claim in words, the record in numbers -- and it is spaced
   like two halves.

   --s3 was measured and rejected once, at 35.5px above the pair against
   33.7px below it to the rule, floating between them at 1.05x. That was
   true while .state and .caveat sat 4px apart: a tight two-line clot has
   no inside, so even brackets leave it belonging to neither neighbour. At
   --s2 the pair has 17.2px of its own against 35.5 and 33.7 outside --
   2.07x and 1.96x -- and even brackets around a group that coheres is a
   band rather than an orphan. .stated's rule is bracketed the same way for
   the same reason.

   Before it was --s2 it was --s1, chosen to give the masthead a descending
   shape, and the shape being the whole argument was the tell. */
/* --s2 now: the tagline and .state are one claim said twice, in words and
   then in numbers -- paragraph rhythm, not a block boundary. At --s3 the
   index header stacked four block gaps in a row and read as loose. */
.gloss { margin: 0 0 var(--s2); }

/* The record's dimensions, and .ev-meta's treatment moved to the index:
   --size, --dim, roman, scan leading. Not a new register -- this is
   metadata ABOUT the record, which is the slot .ev-meta already occupies
   on a document, and the pairing rule two hundred lines up says anything
   new at --size joins that list rather than inventing a tier.

   Roman, against `main > p`. The voice rule below is the reason: the
   record is roman and the author is italic, and four counted numbers are
   as far from the author's voice as anything on the page gets. It states
   the style rather than joining `main > p.stated`, which also claims --fg;
   the state line is metadata, and metadata is dim here and on a document.

   The masthead's closing rule, --s3 both sides so neither half claims it,
   same as .stated on a document. The rule sat on .caveat while that line
   was up here; the line went back down to the rows it decodes and the rule
   stayed where the masthead ends, which is this line.

   Everything above the rule is the frame -- what the record is, how far to
   trust it, how big it is -- and everything below is the record, opening
   with the note on how to read its numbers. That was true when the caveat
   was up here too; it is truer with one less thing in the frame.

   NO box, fill or badge, which were the other candidates for "these are
   live numbers". A tint alone is not visible on this site -- code's
   comment measures it at 1.12:1 dark and 1.09:1 light and rejects it for
   that reason -- and a tint plus a --line border is the code chip, which
   means "a literal you could copy, type or grep". Numbers that change when
   the record changes are the opposite of a literal. Boxing them would also
   claim a distinction the twenty-three counts in the rows do not get, and
   they are the same numbers. */
.state {
  font-style: normal; color: var(--fg);
  margin: 0 0 var(--s3);
  padding-bottom: var(--s3);
  border-bottom: 1px solid var(--line);
}

/* --- touch: press stands in for hover -----------------------------------
   With no hover, every "responding" state above would never appear on a
   phone. A press is the touch equivalent of responding right now, so :active
   takes the same states for its duration (2026-10-08). Nothing changes at
   rest: links stay structure, as the accent token's comment requires, and a
   32-row index does not turn ketchup. The grey system tap highlight is
   switched off so ketchup replaces it rather than stacking on it. iOS Safari
   applies :active only when the page has a touchstart listener; theme.js
   registers an empty, passive one for exactly that. */
@media (hover: none) {
  a, .theme { -webkit-tap-highlight-color: transparent; }
  a:active, .theme:active { color: var(--accent-live); }
  a:active { text-decoration-thickness: 2px; }
  .pager a:active .lbl:not(.eyebrow), .list a:active .t::before { color: var(--accent-live); }
  .list a:active .t, .pager a:active .t { text-decoration: underline; text-decoration-thickness: 1px; }
}
