/* s35-craft.css — cross-cutting craft layer (founder review, items 12/13/14).
   OWNER: craft agent. CSS ONLY. Loads after every section stylesheet and before
   s35-a11y.css so the accessibility floor still wins on colour.

   THIS FILE CHANGES SIZE, SPACING, RAILS, Z-ORDER AND TEXTURE STRENGTH ONLY.
   It sets no colour. The orchestrator regenerates s35-a11y.css after this wave,
   and every rule below moves the geometry the contrast solver has to solve for,
   never the solved value itself.

   Contents
     1. RAIL TOKENS          item 13 — two deliberate rails, published as tokens
     2. RAIL SWEEP           item 13 — labels hang, content indents, all 10 pages
     3. TYPOGRAPHY FLOORS    item 14 — 16 / 13 / 12 px floors + measure caps
     4. CRT FALLOFF          item 12 — one overlay becomes a five-step ladder
     5. PHONE                every rule above re-checked at 390px
     6. HEADING LINE BREAKS  a heading breaks where the design breaks it
     7. RAG                  short blocks break like type, not like a textarea
   ========================================================================== */


/* ==========================================================================
   1. RAIL TOKENS  (brief item 13)

   "Use two deliberate rails: PROMPT / LABEL RAIL x = A, CONTENT RAIL
    x = A + 28–36px."

   A is the .wrap content edge — 130px at 1440, --gutter in from the wrap
   border box. Every section kicker and every section heading measured at
   exactly 130.0 before this file. The wordmark did NOT — it measured 126.19 —
   and 1b below puts it on the rail with them.
   The rails are published as tokens so the hero (agent B) and section one
   (agent C) land on the same grid without re-deriving it.

   The indent drops to 20px on the phone: 32px of a 342px column is 9% of the
   measure, and the founder's 28–36px is a desktop figure.
   ========================================================================== */
:root{
  --rail-indent:32px;
  --rail-prompt:0px;
  --rail-content:var(--rail-indent);
  /* the same two rails measured from the .wrap BORDER box, for elements that
     set padding rather than margin */
  --rail-prompt-x:var(--gutter);
  --rail-content-x:calc(var(--gutter) + var(--rail-indent));
  /* item 14 gave a 50–62 character band; RE-RULED 2026-08-27: leads fill the
     column. Measured at 52ch, every section lead and hero sub set at 52–56% of
     its content column at 1280+ — a narrow strip beside a half-empty field,
     complained about page after page (rsync §03 was the last). The fix is the
     pair of levers together: lead/sub type steps up ~15% at desktop (site.css
     §20) and the measure opens to 58ch. `ch` is the advance of "0", ~1.3x the
     average character advance in Space Grotesk: 58ch measures ~72–78 real
     characters (spaces included) on real leads — under the ~80 ceiling — and
     at the stepped-up lead size the line fills ~70% of the column instead of
     ~54%. Mobile is untouched: the phone column is narrower than any cap. */
  --measure-hero:58ch;   /* measures ~72-78 characters */
  --measure-body:58ch;   /* measures ~72-78 characters */
}
@media (max-width:760px){
  :root{ --rail-indent:20px; }
}

/* --- 1b. THE WORDMARK ON THE PROMPT RAIL ----------------------------------
   The header bar is ONE element carrying both `.wrap` and `.nav`, so the
   `.site-header .nav` padding rule REPLACES the wrap gutter instead of adding
   to it, and the wordmark lands inside the pill instead of on the rail.
   Measured on index.html before this rule, against a prompt rail at 130
   (1440) and 24 (390):
       1440   paper 126.19 · riso 127.19 · terminal 126.19 · brutal 128.19 ·
              acid 126.19 · olo 126.19 · night 126.19 · verified 107.59
        390   paper 14.59 · riso 15.59 · terminal 14.59 · brutal 16.59 ·
              acid 14.59 · olo 14.59 · night 14.59 · verified 1.59

   The inset has to total one gutter measured from the pill's BORDER box, and
   the border sits inside that box, so the border width comes out of the
   padding. `--nav-bw` restates the border each theme paints on
   `.site-header .nav`; the three themes that paint anything other than 1px
   restate it here, because this file may not write theirs. verified sets the
   padding shorthand itself at a higher specificity, so its declaration is
   repeated rather than inherited.

   padding-LEFT only. The pill's right inset is deliberately asymmetric (the
   CTA carries its own padding) and the narrow-width rule in site.css still
   owns it. */
.site-header .nav{
  --nav-bw:1px;
  padding-left:calc(var(--gutter) - var(--nav-bw));
}
html[data-theme="riso"]   .site-header .nav{ --nav-bw:2px; }
html[data-theme="brutal"] .site-header .nav{ --nav-bw:3px; }
html[data-theme="verified"] .site-header .nav{
  --nav-bw:0px;
  padding-left:calc(var(--gutter) - var(--nav-bw));
}
/* The rail costs the bar 9.4px of left inset that the narrow rule in site.css
   had taken back (.85rem), and at 320 the terminal CTA alone is 170px of a
   318px bar, so the pill overflowed by 5px and put 4px of horizontal scroll on
   all ten pages. The slack is the gap between the bar's items: 12.8px x 3 =
   38.4px, and dropping it to 8px buys 14.4px back, which is more than the 5px
   owed. Measured at 320 terminal after this rule: nav scrollWidth 308 of 318,
   document 320 of 320. Only below 360px — 390 never overflowed. */
@media (max-width:360px){
  .site-header .nav{ gap:.5rem; }
}


/* ==========================================================================
   2. RAIL SWEEP  (brief item 13)

   Enumerated across all 10 served pages: every top-level section that is not a
   hero carries exactly one `> .wrap` column, and inside it the kicker, the h2
   and the lead all measured at the SAME x (130). That is one rail doing two
   jobs, which is why the page read as accidental rather than composed.

   The whole column moves to the content rail as a unit (padding on the wrap),
   so grids, cards and figures inside a section keep their internal geometry
   and simply arrive 32px further in. The label is then pulled back out to the
   prompt rail with a negative margin. Two rails, one rule each.

   MATCHED SECTIONS (all `main > section` on the 10 pages that are not heroes):
     .section       76 elements   8 pages   (incl. every .pr-sec/.pc-sec/.mp-sec,
                                             which all carry .section as well)
     .pr-model       1 element    product
     .pc-cta         1 element    changes
     .p35-close      1 element    agents
   The heroes (.hero/.hero33/.pr-hero/.pc-hero/.mp-hero/.pp-hero/.gp-hero/
   .pv-hero/.dm-hero/.p35-hero) are deliberately NOT matched: agent B owns the
   hero column and consumes the tokens directly.
   ========================================================================== */
main :is(.section,.pr-model,.pc-cta,.p35-close) > .wrap{
  padding-left:var(--rail-content-x);
  /* audit.html's #start wrap is a flex item: its min-width:auto let the extra
     padding push the min-content width past the viewport and put 17px of
     horizontal scroll on the phone. min-width:0 lets the column shrink the way
     every other .wrap already does. */
  min-width:0;
}
/* labels hang back out to the prompt rail. .kicker is the sitewide label class
   (93 elements, 10 pages); .section-head is its container and must not indent
   twice. */
main :is(.section,.pr-model,.pc-cta,.p35-close) > .wrap .kicker{
  margin-left:calc(-1 * var(--rail-indent));
}
/* the terminal prompt is a rail marker, not part of the sentence. themes.css
   prints `> ` inline before every h1/h2, which pushed the heading TEXT right by
   the width of two mono characters at heading size — 36px at h2, 88px at h1,
   and a different amount at every breakpoint. Hanging it puts the glyph on the
   prompt rail and the heading text on the content rail, exactly.
   h1 is left alone: the hero prompt is a separate column owned by agent B. */
html[data-theme="terminal"] main :is(.section,.pr-model,.pc-cta,.p35-close) > .wrap h2{
  position:relative;
}
html[data-theme="terminal"] main :is(.section,.pr-model,.pc-cta,.p35-close) > .wrap h2::before{
  position:absolute;left:calc(-1 * var(--rail-indent));top:0;
}


/* ==========================================================================
   3. TYPOGRAPHY FLOORS  (brief item 14)

   "The terminal identity should come from font, rhythm, labels, states,
    borders, cursor and graph. It should not depend on making text difficult
    to read."

   Measured before this file, at 1440, terminal, over all 10 pages:
     45 selectors set prose below 11.5px — the smallest was 9.5px
     180 <li> carrying up to 653 characters set at 10.72–14.4px
     88 bare <p> carrying up to 601 characters set at 11.2px
     26 figure captions carrying up to 332 characters set at 11.2px

   The founder gives two floors — body 16–18px, metadata 11–12px. The site has
   a third, large population between them: subordinate copy that still carries
   whole sentences (captions, notes, fine print, card lists). Dropping it to the
   metadata floor would leave 600-character paragraphs at 12px; raising it to
   the body floor would flatten the hierarchy the cards depend on. It gets its
   own floor at 13px, between the founder's two, so that NOTHING on the site
   sets below 12px and no reading copy sets below 16px.

     BODY        16px   bare p, dd, blockquote, .lead        (founder: 16–18)
     SECONDARY   13px   li, captions, notes, fine print       (new, see above)
     METADATA    12px   labels, values, stamps, strips        (founder: 11–12)
     NAV         13px   .nav-links a                          (founder: 12–13)
     HERO BODY   17–19px already met (18.4–19.4px measured) — untouched

   Every rule below is a FLOOR. The `max(<floor>, 1em)` form is used wherever
   the small size is inherited rather than declared, so a list inside a 17px
   column keeps 17px and only the ones that were too small move.
   -------------------------------------------------------------------------- */

/* --- 3a. BODY 16px: structural prose elements with no smallness class ------
   These four carry no font-size rule of their own anywhere in the build, so
   their computed size is always inherited and `max(16px,1em)` is a true floor:
   a paragraph inside a 19px column keeps 19px, one inside an 11px card rises to
   16px. Any class rule that sets a size beats these on specificity, which is
   how .hero-sub, .lead, .fine and the label classes keep their own sizes. */
p,
li,
dd,
blockquote{ font-size:max(16px, 1em); }
p{ line-height:1.6; }
li{ line-height:1.55; }

/* --- 3b. SECONDARY 13px: sentence-carrying subordinate copy ---------------
   Every one of these DECLARES its own size, so the floor must be a literal
   value — `1em` would resolve against the parent column and blow a 700-word
   caption up to body size. The list is the complete set of classes measured
   below 13px whose longest string runs 120 characters or more, over all 10
   pages: they are sentences, not labels. The longest string measured per class
   is in the comment.

   `!important` is not decoration here. s35-evidence.css and the page
   stylesheets set several of these from ID-scoped rules (`#evidence .rx-src`),
   and no selector this file can write outranks an id. The floor has to hold
   from one place or it does not hold at all. The four hero classes and the one
   card class another agent owns are floored WITHOUT it, further down, so their
   owner can still override. */
main :is(
  .fig-cap,        /* 324 chars */
  .frame-cap,      /* 468 */
  .sx-cap,         /* 263 */
  .fine,           /* 872 */
  .form-note,      /* 237 */
  .foot-line,      /* 138 */
  .mp-note,        /* 346 */
  .mp-rec-foot,    /* 209 */
  .mp-rung-note,   /* 236 */
  .pc-recs-fine,   /* 204 */
  .pc-cap,         /* 138 */
  .au-treecap,     /* 340 */
  .pr-shotnote,    /* 344 */
  .ev-cap,         /* 205 */
  .rx-nums-note,   /* 710 */
  .p35-note,       /* 266 */
  .count-note,     /* 302 */
  .cl-state-gloss  /* 128 */
){ font-size:13px !important; line-height:1.55; }
/* the footer sits outside main and carries the same class */
.site-footer .foot-line{ font-size:13px !important; line-height:1.55; }
/* bare captions inherit their size; this is a true floor and needs no weight */
figcaption{ font-size:max(13px, 1em); line-height:1.55; }

/* --- 3c. METADATA 12px: labels, values, stamps, chips, separators ----------
   The complete set of classes measured below 12px whose longest string runs
   under 120 characters — 112 selectors over the 10 pages, from 9.50px up.
   Three classes are deliberately absent:
     .mp-mono, .branch-where   sized in `em` and measured 10.4–17.0px in
                               different rows; a literal floor would SHRINK
                               their larger uses, so they keep their own scale
     .kicker                   floored just below without !important, because
                               three pages set a 16.3–18.2px hero kicker from a
                               more specific rule that must keep winning
   ------------------------------------------------------------------------- */
main :is(
  .ag-cmp-cap,.ag-col-h,.ag-cwd,.ag-from,.ag-h,.ag-meta,.ag-tag,.ans,
  .au-r-foot,.cl-eg-t,.cl-fan-cap,.cl-klab,.cl-lab,.cl-ok,.cl-out,.cl-state,
  .cl-trust-lab,.ev-band-lab,.ev-h-id,.ev-h-promise,.ev-h-result,.ev-miss-k,
  .fig-ex,.foot-more-l,.frame-url,.gp-hint,.gs-hint-t,.gs-hint-x,.gs-panel-h,
  .gs-panel-sub,.gs-read-for,.gs-read-h,.gs-state,.hg-note-s,.l-bad,.l-good,
  .l-sig,.mp-col-h,.mp-col-sub,.mp-cov,.mp-k-sep,.mp-keep,.mp-kind,
  .mp-kind-lab,.mp-out-lab,.mp-rec-src,.mp-state-cost,.p35-bound-h,
  .p35-chips-cap,.p35-eg,.p35-eg-t,.p35-ret-cap,.p35-ret-h,.p35-state,
  .p35-surf-h,.p35-surf-s,.pc-cap-t,.pc-chip,.pc-col-h,.pc-cta-sep,
  .pc-guard-lab,.pc-ic,.pc-k-sep,.pc-klab,.pc-lab,.pc-out,.pc-radius-cap,
  .pc-radius-h,.pc-recs-h,.pc-strip-s,.pc-strip-t,.pc-tier-n,.pc-unc-fine,
  .pc-unc-lab,.pp-chip,.pp-door-k,.pp-dot,.pp-flab,.pp-ok,.pp-pm-lab,
  .pp-stage,.pr-lab,.pr-rl,.pr-src,.pv-hint,.rail-beat,.rx-art,.rx-dim,
  .rx-flag,.rx-n,.rx-stage,.rx-tag,.s35-arrives,.s35d-lab,.s35d-tag,.s35h-n,
  .s35h-tech-lab,.s5-eg,.s5-eg-t,.s5-rec-id,.s5-rec-k,.s5-rec-v,.sep,
  .stop-week,.sx-win-p,.sx-win-t,.tgrn,.tile-n,.tred,.tsig
){ font-size:12px !important; }

/* 3c-ii. the same floor, WITHOUT !important, for the five elements another
   agent owns: four in the hero (agent B) and one on the requirement card
   (agent C). This file loads after both of theirs, so the floor still lands
   today; the moment either of them sets a deliberate size, theirs wins.
   B and C: these were 9.50px (.hero-mach), 10.24px (.facts.hero-ev), 10.56px
   (.hero-cue), 10.88px (.pctl-key) and 11.52px (.rq-lab) — all under the
   founder's 11–12px metadata floor. Re-measure the strip width after this. */
.hero-mach,
.facts.hero-ev,
.hero-cue,
.pctl-key,
.rq-lab,
.rq-chip{ font-size:12px; }

/* 3c-iii. the kicker. site.css sets .74rem (11.84px) twice — once plain and
   once as `.row .kicker` — and audit/graph/prove each set a larger hero kicker
   from a more specific rule. Matching those two selectors exactly floors the
   93 section kickers and leaves the three page kickers alone. */
.kicker{ font-size:12px; }
main .row .kicker{ font-size:12px; }

/* 3c-iv. the safety net. These inline elements carry no size of their own, so
   `max(12px,1em)` is a true floor: inside a floored label they inherit 12px or
   more and nothing moves; inside anything smaller they stop at 12px. Any class
   rule outranks them, which is why 3b and 3c above had to be explicit. */
span,b,i,em,strong,code,cite,small,label,dt,th,td,button,a{
  font-size:max(12px, 1em);
}

/* 3c-v. the last 109 elements. Every one of them is won by a DESCENDANT rule
   — `.pp-rows dt`, `.mp-cp > b`, `.lead-form label` — which outranks both the
   class list and the safety net. These thirteen selectors are the complete
   remainder, read back out of the cascade rather than guessed: at 1440,
   terminal, they were the only HTML text on the 10 pages still under 12px. */
main :is(
  .mp-cp > b,          /* 18 elements @ 11.20px */
  .pp-rows dt,         /* 12 @ 11.84 */
  .s35-descent .n,     /* 11 @ 11.52 */
  .s35-loop .n,
  .lead-form label,    /* 11 @ 11.52 */
  .p35-row-f dt,       /*  9 @ 10.88 */
  .pr-fields li > span,/*  9 @ 11.52 */
  .pc-rc-l dt,         /*  8 @ 10.56 */
  .p35-field dt,       /*  4 @ 11.52 */
  .mp-tbl thead th,    /*  3 @ 10.88 */
  .pp-quote cite,      /*  1 @ 10.56 */
  .au-radius dt        /*  1 @ 11.52 */
){ font-size:12px !important; }
main .pp-fig figcaption{ font-size:13px !important; }   /* 142 chars @ 11.20 */

/* the footer is outside <main>, so the class lists above never reached it.
   s35-a11y.css also sets .foot-more-l and loads after this file, which is why
   the floor here has to carry weight. */
.site-footer :is(.foot-line,.foot-more-l){ font-size:13px !important; }
/* .foot-more already sets .82rem = 13.12px; keep it rather than shave 0.12px */
.site-footer .foot-more{ font-size:max(13px, .82rem) !important; }

/* --- 3d. NAV 13px --------------------------------------------------------- */
.nav-links a{ font-size:max(13px, .82rem); }

/* --- 3e. MEASURE: one token, sitewide -------------------------------------
   (The old 50–62 character band is superseded by the 2026-08-27 ruling in §1:
   the tokens now carry it, and this section only ROUTES them.) These generic
   rules carry the SAME specificity as site.css's originals and simply land
   later in the cascade, so every class-level opt-out in the section
   stylesheets still wins over them and only the generic measure moves. The
   hero measure needs (0,2,1) to clear per-hero caps at (0,2,0). */
p{ max-width:var(--measure-body); }
.lead{ max-width:var(--measure-body); }
/* the generic rules above only reach the paragraphs site.css was already
   capping. Measured, the running copy INSIDE cards had no cap at all and set
   at 66–79 characters on all 10 pages, above the founder's ceiling. `main p`
   (0,0,2) reaches those without touching li/dd, which sit in row grids where a
   max-width would fight the column, and every class-level `max-width` in the
   section stylesheets still outranks it. */
main p{ max-width:var(--measure-body); }
/* the prose classes that were setting their own width and so never saw either
   rule. Enumerated: every selector on the 10 pages carrying 180+ characters
   that measured over 64 characters per line. The widest was .fine at 147. */
main :is(
  .lead,.fine,.pp-pm-txt,.pp-note,.pp-after,.pp-fval,.cl-p,.pr-sub,.pr-td,
  .au-rule,.au-treecap,.mp-miss,.mp-state-d,.mp-note,.mp-rung-note,
  .rx-reconcile,.rx-nums-note,.pc-unc-say,.pc-tier-say,.pc-body,.pc-recs-p,
  .p35-note,.ev-cap,.fig-cap,.shot-branch,.pr-shotnote
){ max-width:var(--measure-body); }
main .hero .hero-sub,
main .hero-sub{ max-width:var(--measure-hero); }

/* --- 3f. SPACING AND CASING FOR FOUR NOTES THAT HAD NO OWNER --------------
   These five rules are not floors. They are the layout half of four small
   notes that were, until now, styled from the generated contrast sheet — a
   colour sheet that loads last, so its spacing and its sizes were also
   outranking every floor in this file. That sheet is colour-only now, and the
   non-colour half had nowhere to go. It goes here, because this is the layer
   that owns type and spacing.

   Sizes are written as `max(13px, ...)` for the same reason the footer rules
   above are: the value on the right is the size the note is drawn at, the 13px
   is the floor it may never fall through. Both notes were rendering at the
   inherited body size with the sheet gone — 16.96px in main, which is button
   copy, not fine print — so both get smaller here, and neither goes below the
   secondary tier.

   .portal-note is measured at 13px rather than at its old .78rem (12.48px): it
   also sits inside .foot-line, which this file floors at 13px, and a note
   nested in that line must not undercut the line it qualifies.
   .tree-note keeps its .84rem. Its old max-width:66ch is deliberately NOT
   restored — 3e caps main prose at var(--measure-body), and that cap is the
   one that governs now.
   .foot-more a carries the gap BETWEEN the footer's five outbound links. It is
   listed with .foot-more-l because without it the row sets solid: the label
   and all five link texts run together into one string.

   .foot-more is a FLEX row, not inline flow, for two reasons found at 1920:
   (1) the anchors sit with no whitespace between them in the markup, so inline
   flow had no soft-wrap points BETWEEN links — the only break opportunities
   were the spaces INSIDE link labels, which is how "The rsync / audit" and
   "Correctness / Labels" broke mid-name. Flex items wrap between themselves
   regardless of source whitespace, and white-space:nowrap makes each label
   atomic. (2) it is a <p>, so 3e's `p{max-width:var(--measure-body)}` (52ch
   ~ 440px at 13px mono) was capping it — three cramped lines beside ~1400px
   of dead space. The prose measure is for sentences; this is a nav strip, so
   it opts out the same way site.css §20 opts .foot-line out, and both footer
   rows now share the same measure: the full .wrap column. */
.tree-note{ margin:.7rem 0 0; font-size:max(13px, .84rem); line-height:1.55; }
.portal-note{ display:inline-block; margin-left:.5rem; font-size:max(13px, .78rem); }
.foot-more{ margin:.9rem 0 0; line-height:1.9; max-width:none;
  display:flex; flex-wrap:wrap; align-items:baseline; }
.foot-more a{ margin-right:.9rem; white-space:nowrap; }
.foot-more-l{ margin-right:.6rem; text-transform:uppercase; letter-spacing:.08em;
  white-space:nowrap; }


/* ==========================================================================
   4. CRT FALLOFF  (brief item 12)

   "Technically, keep media and cards above the scanline overlay rather than
    applying the texture globally."

   WHY THE OVERLAY COULD NOT BE OUTRANKED WHERE IT IS. themes.css paints the
   tube as `body::after`, position:fixed, z-index:40. site.css sets
   `main,footer{position:relative;z-index:1}`, and .hero33 adds
   isolation:isolate. Everything the visitor reads is sealed inside a
   z-index-1 stacking context underneath the overlay, so no z-index written in
   a section stylesheet can lift a screenshot, a card or a paragraph over it.
   Two section owners hit this independently and were both right.

   The fix is to move the tube INSIDE that stacking context. body::after stops
   printing stripes; `main::after` prints them instead, one document-tall layer
   at z-index 3. Inside main's own context z-index is free again — the only
   positioned descendants on any of the 10 pages top out at z-index 2 — so
   z-index 4 is a clean floor above the tube that media can stand on.

   WHY A TEXTURE *BEHIND* THE COPY WAS NOT ENOUGH, MEASURED. The first cut put
   the whole tube behind the content at z-index -1, which makes glyphs perfectly
   clean. On this theme it also makes the tube disappear: the page ground is
   #050807, mean luminance 15/255, and compositing black at .045 over it lands
   inside one 8-bit step. Measured over a 1300x700 band on audit.html: ground
   layer on 15.4634, off 15.4595 — a 0.03% difference, i.e. nothing. The tube is
   only visible where the pixels are bright, which is exactly why the founder
   sees the stripes on the type and not on the background. So the ladder has to
   modulate the layer that crosses the type, and the way to protect a screenshot
   is to lift it, not to dim the world behind it.

   THE LADDER, and how each step is built:

     HERO                    base + a hero-only boost, masked so the tube fades
                             toward the fold instead of ending on a line
     GRAPH ZONE              base + a stripe on the innermost figure/plate,
                             printed OVER the drawing (see 4e)
     PRODUCT OBJECT          base + a stripe on the card's own surface, under
                             its labels and values (item 9, "less scanline
                             interference", "clearer difference between labels
                             and values")
     LONG BODY COPY          the base layer alone
     SCREENSHOT/MEDIA        the frame is lifted to z-index 4, above the tube.
                             The frame keeps its own object stripe; the image
                             inside it is covered by nothing at all

   TWO DIFFERENT NUMBERS, AND THE ONE THAT WAS FICTION.
   The alphas below are stated as a fraction of the reference stripe (see
   REFERENCE): 15 / 35 / 70 / 100. That is an honest statement about the CSS
   and nothing else. It was ALSO being read as a ladder of perceived texture,
   and as that it was wrong — a fraction of an alpha is not a fraction of a
   visible effect, because black at low alpha over a #050807 ground lands
   inside one 8-bit step and only shows where the pixels underneath are
   bright. A zone's measured texture is therefore a function of how much lit
   type and line-work it contains, not of its alpha alone. So the perceived
   ladder is measured, not derived, and it is written down here:

   MEASURED. Mean |dG| per pixel, one zone at a time, taken by screenshotting
   the zone and re-screenshotting it with the alpha variables set to 0 —
   first all four at once ("all"), then one at a time to attribute the effect
   to a layer. terminal, 1440x900, deviceScaleFactor 1, `#live` (a headless
   load WITHOUT that fragment renders the QA-frozen page and measures nothing
   real), animations paused so the two frames differ only by the stripe. The
   noise floor — two frames taken with no change at all — is 0.000 to 0.006
   on every zone below, so every figure here is signal.

     zone                      region        all   base  object  graph   hero
     HERO      index .hero     1440x738    1.880  0.538   0.458  0.516  1.942
     HERO      product .pr-hero 1440x675   1.944  0.816   0.721  0.851  2.330
     GRAPH     #stale grid     1148x207    1.142  0.246   0.061  0.966  0.075
     CARD      index .rq-card   643x697    1.072  0.492   0.583  0.002  0.003
     CARD      method .mp-rec  1148x873    0.998  0.402   0.596  0.000  0.000
     GRAPH     #intent stage    421x452    0.572  0.123   0.086  0.567  0.094
     BODY      method long p    565x271    0.418  0.418   0.000  0.000  0.000
     MEDIA     product img      910x569    0.011  0.000   0.011  0.000  0.000
     MEDIA     pubproof img    1146x716    0.006  0.000   0.006  0.000  0.000

   READ IT THIS WAY. The hero is the loudest zone by 1.6x and media is clean
   to within a rounding error, which is what the brief asked for. Between
   them the ordering is NOT the alpha ordering: the card lands between the two
   graph figures (1.07 against 1.14 and 0.57), because the graph step's effect
   depends on how bright the particular drawing is — a wide lit grid takes
   0.966 from it and a small dark stage takes 0.567 — while the card carries
   a large evenly-lit surface. Long body copy is the floor at 0.418, and it is
   the floor everywhere: every zone above it contains the base layer too.
   Do not raise anything here without re-running the measurement. Raising the
   graph step would push the graph zone into the hero's band and cost the
   line-work; raising base or object would put texture over body copy and over
   media, which the brief forbids.

   PHASE. Every layer here is document-anchored (background-attachment stays
   `scroll`) and the hero boost shares main's origin, so two overlapping stripe
   layers hold a constant phase and cannot beat against each other under
   scroll. The old overlay was viewport-fixed and slid across the type as the
   page moved; that sliding is the shimmer the founder saw on the screenshot.

   REFERENCE. themes.css printed rgba(0,0,0,.30) over 1 of every 3 device
   pixels. The percentages on the four alphas below are fractions of THAT
   alpha. They are not fractions of a visible effect; the table above is.
   -------------------------------------------------------------------------- */
:root{
  --crt-period:3px;
  /* alpha, as a fraction of the reference stripe. NOT a texture ladder —
     the measured texture each zone receives is the table above. */
  --crt-base:.055;     /*  15% of reference — long body copy, and the floor    */
  --crt-object:.115;   /*  35% of reference — on the card surface, under it    */
  --crt-graph:.165;    /*  70% of reference — over the base: 1-(1-.055)(1-.165) = .211 */
  --crt-hero:.267;     /* 100% of reference — over the base: 1-(1-.055)(1-.267) = .307 */
}

/* 4a. the global overlay keeps the phosphor bloom and stops printing stripes.
   Same specificity as themes.css, later file. theme-verified.css kills
   body::after outright for its own theme and is unaffected. */
html[data-theme="terminal"] body::after{
  background-image:radial-gradient(120% 90% at 50% 0%,rgba(43,255,111,.055),transparent 60%);
}

/* 4b. the tube, rebuilt inside main. `main` and `.site-footer` are both
   position:relative, so inset:0 spans the whole document column. z-index 3
   clears every positioned descendant on all 10 pages (highest measured: 2)
   and leaves 4 free for media. */
html[data-theme="terminal"] main::after,
html[data-theme="terminal"] .site-footer::after{
  content:"";position:absolute;inset:0;z-index:3;pointer-events:none;
  background-image:repeating-linear-gradient(0deg,
    rgba(0,0,0,var(--crt-base)) 0 1px,transparent 1px var(--crt-period));
}

/* 4c. the hero boost — full tube in the hero, masked down toward the fold so
   the step into the graph zone below is a fade, not an edge (item 2: "graph
   returning gradually toward the right and bottom"). .hero33 is already
   position:relative + isolation:isolate; the three static heroes are given
   position:relative here and nothing inside them is positioned against main. */
html[data-theme="terminal"] :is(.pr-hero,.pc-hero,.mp-hero){ position:relative; }
html[data-theme="terminal"] :is(.hero,.pr-hero,.pc-hero,.mp-hero)::after{
  content:"";position:absolute;inset:0;z-index:2;pointer-events:none;
  background-image:repeating-linear-gradient(0deg,
    rgba(0,0,0,var(--crt-hero)) 0 1px,transparent 1px var(--crt-period));
  -webkit-mask-image:linear-gradient(to bottom,#000 0 74%,rgba(0,0,0,.62) 90%,rgba(0,0,0,.24) 100%);
          mask-image:linear-gradient(to bottom,#000 0 74%,rgba(0,0,0,.62) 90%,rgba(0,0,0,.24) 100%);
}

/* 4d. PRODUCT OBJECTS — 35% of the reference alpha; measured 0.583 (index
   .rq-card) and 0.596 (method .mp-rec) mean |dG| attributable to this layer.
   Enumerated by query, not by guesswork: every element inside main whose
   computed background-color is neither transparent nor the page ground, on all
   10 pages. 37 class selectors cover all 52 measured surfaces. Four matches are
   excluded on purpose:
     img    media, 0% by definition
     a, li  chips and buttons, too small to carry texture
     .stage the only surface that already owns a background-image (a dot grid);
            overriding background-image would delete it, so .stage keeps its own
            texture and its inner .fig-scroll carries the graph step instead
   The stripe prints over the card's colour and UNDER its content, so it is the
   card surface that gains atmosphere and never the values printed on it. */
html[data-theme="terminal"] main :is(
  .tile,.tiles,.rows,.frame,.lead-form,
  .mp-plate,.mp-two,.mp-rec,.mp-tscroll,.mp-rung,.mp-clause,.mp-receipt,
  .pp-rec,.pp-card,.pp-pm,.pp-offer,
  .pc-rec,.pc-cmp,.pc-guard,.pc-receipt,.pc-radius,.pc-sec-key,
  .gp-q,.gp-shot,.gs-card,.rq-card,.gs-read,
  .cl-rec,.ev-miss,.s35d-docket,.ag-col,.p35-surf,.p35-rec,.p35-ret,
  .sx-win,.au-radius,.au-tree
){
  background-image:repeating-linear-gradient(0deg,
    rgba(0,0,0,var(--crt-object)) 0 1px,transparent 1px var(--crt-period));
}

/* 4e. GRAPH ZONES — 70% of the reference alpha; measured 0.966 on the wide
   lit #stale grid and 0.567 on the small dark #intent stage, i.e. this one
   step's effect swings 1.7x with the brightness of the drawing under it.
   A graph on a tube should be scanned like the tube,
   so this step goes OVER the drawing rather than under it: measured, a stripe
   printed behind the art reads 22–52% depending only on how bright that
   particular figure happens to be, because a 17% black stripe on a #050807
   ground lands inside one 8-bit step. Over the art it reads the value it is
   set to, on every figure.
   `:not(:has(...))` keeps the rule off the outer wrappers — the deepest chain
   on the site is .fig > .mp-plate > .fig-scroll and printing at three levels
   would compound past the reference. All six of these pseudo-elements are
   unused across the build; the innermost container is given position:relative
   so the overlay can be inset to it.
   The rule is scoped to non-hero sections on purpose: a figure inside a hero
   is already carrying the 100% step, and measured, adding the graph step on
   top of it took graph.html's hero figure to 146% of the reference. */
html[data-theme="terminal"] main :is(.section,.pr-model,.pc-cta,.p35-close)
  :is(.fig,.fig-scroll,.mp-plate,.gs-stage,.pp-fig,.s5-grid):not(
     :has(:is(.fig,.fig-scroll,.mp-plate,.gs-stage,.pp-fig,.s5-grid))){
  position:relative;
}
html[data-theme="terminal"] main :is(.section,.pr-model,.pc-cta,.p35-close)
  :is(.fig,.fig-scroll,.mp-plate,.gs-stage,.pp-fig,.s5-grid):not(
     :has(:is(.fig,.fig-scroll,.mp-plate,.gs-stage,.pp-fig,.s5-grid)))::after{
  content:"";position:absolute;inset:0;z-index:2;pointer-events:none;
  background-image:repeating-linear-gradient(0deg,
    rgba(0,0,0,var(--crt-graph)) 0 1px,transparent 1px var(--crt-period));
}

/* 4f. MEDIA — 0%; measured 0.011 and 0.006, which is the rounding error on
   the frame edge and nothing on the image. This is the one place the founder's "keep media above the
   overlay" is taken literally, and inside main it is finally possible.
   The FRAME is lifted rather than the image: every screenshot on the site is
   an absolutely-positioned <img> inside a figure, and lifting the frame works
   whether or not the image is positioned. The frame keeps the 35% chrome it
   got from 4d; the image on top of it is covered by nothing.
   Measured across the 10 pages: 9 images, in .rq-shot, .pr-shot (x4),
   .pp-shot (x2), figure.frame (x1) and .gp-shot (x1). */
html[data-theme="terminal"] main :is(.frame,.gp-shot,.rq-shot,.pr-shot,.pp-shot){
  position:relative;z-index:4;
}
/* belt and braces for a future screenshot dropped in without a frame */
html[data-theme="terminal"] main :is(img,video,iframe,picture,object,embed){
  z-index:4;background-image:none;
}

/* ==========================================================================
   6. A HEADING BREAKS WHERE THE DESIGN BREAKS IT, AND NOWHERE ELSE

   "For the majority of headers on the page we can avoid having a double line."

   THE RULE. An authored two-part heading gets two lines. A one-sentence
   heading gets one line. A break in the middle of a sentence is the defect.
   The authored break lives in the markup — the two spans of

     <h2 class="s35-h2"><span class="a">…</span><span class="b">…</span></h2>

   — and that split is preserved. What is removed is the SECOND break, the one
   no one asked for.

   WHY IT HAPPENED. Nothing was too big. Measured over all 10 pages x 8 themes
   at 1440, 46 headings wrapped while the column they sit in still had room to
   the right of them: the constraint was a `max-width` on the heading or on the
   block that holds it, not the size of the type.

     .section-head          62ch / 64ch / 66ch / 60ch / 52ch   57 headings
     h1                     20ch                                4
     .pr-body h2            22ch                                8
     .gs-intent .s35-h2     34ch                                1  (the example)
     .gs-intent .s35-beat-h 32ch                                1
     #start .cl-ask         28ch                                1
     .p35-close-h           22ch                                1
     .au-offer-line         22ch                                1

   Measured need vs room for the example: the two sentences want 876px, the
   column is 1148px wide, and the heading was capped at 620px.

   6a. THE SECTION HEAD IS NOT A MEASURE. Enumerated across the 10 pages, every
   child of every `.section-head` is a <p> or the heading itself — 67 .lead,
   64 .kicker, 63 headings, and five one-off notes. Not one <div>, <ul> or <ol>
   is inside one, so no layout block widens with it. Every one of those <p>
   already carries a measure of its own (.lead and the notes declare one; §3e
   caps the rest at var(--measure-body)), so the cap on the container was doing
   nothing for the prose and everything to the heading. It comes off, and the
   body measure below is re-proved at 1440/1280/1024 rather than assumed.
   (0,2,1) so it clears the eight `.X .section-head` rules at (0,2,0). */
main .wrap :is(.section-head,.au-sub){ max-width:none; }
/* Three .lead paragraphs were NOT capped by a rule of their own — s35-compound
   gives two of them 64ch and p35-agents gives the third a wide head — and the
   container was the only thing holding them in. Measured at 1440 they widened
   from 75 to 82, 69 to 76 and 65 to 68 characters when the cap came off, which
   is a body-measure regression and the reason this rule exists. It hands them
   back to §3e's measure, which is the cap the rest of the site's leads already
   sit at. Nothing else inside a .section-head moved: the kickers' boxes grew
   but their text did not (same characters, same one line, and .kicker::before
   is a 7px dot at the flex start, not a rule across the box), and the five
   one-off notes keep the width their own authors wrote.
   (0,3,1) to clear `.cp .section-head .lead` at (0,3,0). */
main .wrap .section-head > .lead{ max-width:var(--measure-body); }

/* 6b. A HEADING'S MEASURE IS THE COLUMN IT SITS IN.
   `!important` is the policy, not a shortcut: after this rule a heading cannot
   be given a shape by capping its width, because a cap cannot tell a wanted
   break from an unwanted one. A heading that wants two lines says so in the
   markup, with two spans or a <br>, and gets exactly two.

   It cannot sprawl: `.wrap` is capped at 1228px on every page and at every
   viewport, so a section heading can never take more than the 1148px column —
   63 characters at the 30.4px section size, 27 at the 73.6px hero size.
   Measured after this rule, over 10 pages x 8 themes, the widest heading line
   on the site is 1173px: the audit hero H1 in riso, inside the 1180px hero
   column. No heading line exceeds its column at any width.

   `balance` is not hiding a size problem here — 6a and the ceiling in 6c are
   what stop the wrapping. It is for the headings in the honest list below that
   genuinely cannot hold one line: a hero at 73.6px, a heading in a 436px grid
   column. Those break into two, and balance is what makes the two lines even
   instead of 90/10. On a heading that now fits, it does nothing at all. */
main :is(h1,h2,h3){ max-width:none !important; }
main :is(h1,h2,h3){ text-wrap:balance; }

/* 6c. THE TWO LOUD THEMES' CEILING.
   riso and brutal set h2 at 43.2px and 46.4px at 1440 — riso in Bricolage 800
   uppercase, which is the widest setting on the site. With 6a and 6b in place
   the remaining mid-sentence breaks were mostly theirs. Measured at 1440 over
   the 10 pages, headings rendering more lines than they were authored:

                    before 6   6a+6b   6a+6b+6c
       terminal        42        17       17
       paper           38        14       14
       riso            69        31       25
       brutal          70        36       25
       acid/olo/night  38        14       14
       verified        37        14       14

   The clamp ceiling is what bites at 1440; the middle term (3.4vw / 3.6vw) is
   what governs below 1270px, and it is untouched, so nothing moves on a laptop
   or a phone.

   The cut is bounded below by paper's 2.4rem: after it riso and brutal are
   still the two largest h2 on the site, in that order, and still out-rank
   their lead (19.2px) by more than 2:1. Cutting further stopped clearing
   headings — the next one down needs 34.5px — so it was not taken. */
html[data-theme="riso"]   h2{ font-size:clamp(1.9rem,3.4vw,2.45rem); }
html[data-theme="brutal"] h2{ font-size:clamp(2rem,3.6vw,2.5rem); }


/* ==========================================================================
   7. RAG — SHORT BLOCKS BREAK LIKE TYPE, NOT LIKE A TEXTAREA

   "Also weird wrapping in other places."

   The example is the PAY-* figure caption. Four lines is right for it; the rag
   is not:

     Fig. · EXAMPLE SERVICE · One requirement, walked out
     to everything that rests on it. The PAY-* service is     <- auxiliary
     invented for this drawing and the counts are             <- auxiliary
     illustrative.                                            <- one word

   Two measurable faults, both enumerated over the 10 pages x 8 themes:
     ORPHAN     the last line carries one word, or under 30% of the widest line
     WEAK END   a line that is not the last ends on a one- or two-letter word,
                a preposition, an article, a conjunction or an auxiliary verb

   7a. `pretty` is the orphan instrument. It re-flows the last few lines of a
   block so the final line cannot be left with one word, and it works at any
   length, which is why it goes on the running prose rather than `balance`.
   Every element here is prose that is read as sentences. */
main :is(p,li,dd,dt,figcaption,blockquote){ text-wrap:pretty; }

/* 7b. `balance` is the short-block instrument. It levels every line, which is
   what a caption, a note or a piece of fine print wants: they are read as one
   small object, not as running text. Measured on one six-line caption:

     last line   wrap 78px    pretty 109px    balance 367px   (measure 452px)

   `pretty` barely moves the last line of a block this short; `balance` fills
   it. That is the whole difference between the founder's caption and a caption.

   BALANCE HAS A CEILING, AND IT WAS CHECKED RATHER THAN ASSUMED. Measured in
   this engine, a block of seven lines or more is never balanced: the value
   computes, the engine declines, and the block falls back to plain `wrap`,
   losing the `pretty` it displaced. So the question is what share of these
   blocks the engine actually honours. Over 10 pages x 8 themes:

       1440   1344 instances at 6 lines or fewer,  48 over   97% honoured
       1024   1336                                 48        97%
        390   1248                                 96        93%

   It is honoured almost everywhere, at every width, so the rule is not gated
   on width and the four classes that do sometimes run long stay in it. The
   trade was measured on those four alone (144 instances at 1440):

       under `pretty`    32 of 144 end on a full last line, 16 orphans
       under `balance`  112 of 144,                          8 orphans

   .kicker is here because it is the label above the heading: one to four words
   everywhere on the site, and it must not break where it fits. */
main :is(
  .fig-cap,.ev-cap,.frame-cap,.frame-url,.p35-note,.pp-note,.tree-note,
  .au-treecap,.au-rule,.shot-branch,.pr-shotnote,.form-note,.portal-note,
  .mp-note,.mp-rung-note,.rx-nums-note,.pc-recs-p,.fine,.kicker
){ text-wrap:balance; }

/* 7c. THE MONO MEASURE. `ch` is the width of a "0", so 52ch is ~60 characters
   in Space Grotesk and exactly 52 in IBM Plex Mono. The caption classes are
   mono, so §3e's `var(--measure-body)` was handing them a 52-character measure
   where the same token gives the body copy 60 — eight characters short of the
   band, in the blocks least able to afford them, and the PAY-* caption was one
   of them at 41 characters average and 52 at the widest.

   --measure-mono restates the token in the units the mono blocks actually
   resolve: 58ch of mono is 58 characters. (2026-08-27: the sans measure
   opened to ~72–78 characters; the mono caption measure deliberately does
   NOT follow — captions are subordinate fine print, and 58 mono characters
   at 13px is already the top of comfortable for them. themes.css's 70ch
   .frame-cap step-up for the long portal captions stands.) It is a max-width,
   so a caption in a narrower figure column keeps the column.

   IT CUTS BOTH WAYS, ON PURPOSE. Three of these classes were ABOVE the band,
   not below it: .frame-cap measured 74–80 characters and comes down to 58, at
   the cost of two more lines. Lines are the cheaper thing to spend — 58 is the
   measure the rest of the site reads at, and 80 is not a caption. */
:root{ --measure-mono:58ch; }
main :is(.fig-cap,.ev-cap,.frame-cap,.au-treecap,.shot-branch,.pr-shotnote,.fine){
  max-width:var(--measure-mono);
}


/* ==========================================================================
   5. PHONE — 390x844
   The indent is already 20px from §1. The two remaining phone risks are the
   metadata floor pushing a nowrap strip past the viewport, and the section
   column losing measure. Both are checked at 390 in the craft probe; the strip
   is allowed to wrap rather than shrink below the floor.
   ========================================================================== */
@media (max-width:760px){
  .facts.hero-ev{ flex-wrap:wrap; }
  /* audit.html #start: .au-start is a grid whose track is sized by the longest
     unbreakable token in .form-note. At 11.84px that token fitted a 342px
     column; at the 13px reading floor, inside a column narrowed by the content
     rail, it did not, and the page took 17px of horizontal scroll. The same
     track already overflowed by 12px in brutal and 2px in riso BEFORE this
     file — the token has never been breakable. `anywhere` is the one value
     that feeds back into min-content sizing, so the track can shrink. */
  main .form-note{ overflow-wrap:anywhere; }
  /* four selectors drop below the 12px floor only at this width, from phone
     rules of their own: .hero-copy .kicker (.68rem), .mp-rec-f, .cl-fan-k. */
  .hero-copy .kicker{ font-size:12px; }
  main :is(.mp-rec-f,.cl-fan-k){ font-size:12px !important; }
  /* THE COLUMN DOES NOT INDENT ON THE PHONE. Measured: audit.html's #start
     grid is sized by the min-content width of its call-to-action, which at 390
     already fills the 342px column; taking 20px out of that column put 17px of
     horizontal scroll on the page. The rails still exist — they move onto the
     section head, where every child is text that can wrap, so the label still
     hangs left of the heading and nothing can be pushed off the screen. */
  main :is(.section,.pr-model,.pc-cta,.p35-close) > .wrap{
    padding-left:var(--gutter);
  }
  main :is(.section,.pr-model,.pc-cta,.p35-close) > .wrap .kicker{
    margin-left:0;
  }
  /* the prompt still hangs, into the page gutter rather than into a rail of
     its own, so the heading TEXT and the body share one edge. Measured, this
     is the only arrangement at 390 in which label, heading and body all agree:
     the wrap keeps its full 342px column and no grid track is squeezed. */
  html[data-theme="terminal"] main :is(.section,.pr-model,.pc-cta,.p35-close) > .wrap h2::before{
    left:-18px;
  }
}
