/* PANIM — components.css  (Direction B — "The Plate")
   Nav, hero, chapter opening, plates and figures, verse setting, buttons,
   sheets/modals, glossary card, toast, five-words terminal.
   Grid ranges (.col-margin / .col-text / .col-aside) come from site.css. */

/* ============================================================================
   BUTTONS
   No rounded corners, no fill, no glow. A button is a rule and a label — the
   same vocabulary as the rest of the page, at the same weight.
   ============================================================================ */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .6em;
  min-height: 44px;
  padding: .72em 1.25em;
  border: 1px solid var(--control-edge);
  color: var(--ink);
  font-family: var(--ui);
  font-size: var(--ap-lg); font-weight: 600;   /* on the scale, not a loose .8rem */
  letter-spacing: .01em;   /* sentence case: uppercase + wide tracking is the
                              stock "premium" button and reads as template chrome */
  background: var(--control-wash);
  transition: border-color var(--dur-fast) var(--ease),
              background-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
}
/* 🔴 HOVER IS THE ACCENT NOW, NOT BLACK, 2026-09-05. The author: "continue balck
   buttons are so ugly. they need redesign."
   Every control on this site used to invert to a slab of --ink on hover — a
   near-black rectangle, which is the default primary button of every template on
   the internet and the one gesture the page shares with none of its own ideas. The
   book's colour system is the dawn arc: night, fire, morning, changing where the
   book turns. A control that lights in the CHAPTER'S OWN accent is the page saying
   something it already believes; a control that goes black is chrome.
   ⚠️ IT IS A WASH AND A LINE, NOT A FILL. The label stays --ink, so nothing here
   depends on the accent's contrast against paper — 12% of any of the three accents
   moves the background by a hair and the text pair is unchanged at 14.6:1. The
   emphatic control (.btn-begin) is the one that fills, and it is measured. */
.btn:hover, .btn:focus-visible {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--ink);
}
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn[disabled] { opacity: .35; cursor: not-allowed; }

/* An icon button is chrome, not a control surface — no border at all, and no wash
   either. It inherited `background: var(--control-wash)` from .btn, so every one of
   them drew a grey chip: the player bar was a row of eight boxes with one outlined
   circle among them, which is exactly the "three unrelated button treatments" the
   cohesion note complains about. Chrome is ink on paper; the control surface belongs
   to .btn and .btn-solid, which is what makes those two read as controls at all. */
.btn-icon {
  border: 0; background: none; padding: .5em; min-width: 44px;
  color: var(--ink-soft);
}
.btn-icon:hover, .btn-icon:focus-visible { background: none; color: var(--ink); }

/* ============================================================================
   THE TRANSPORT BUTTON — one component, two sizes
   The player bar's play button and the Listening Room's were two separate builds
   of the same object: two circles, two borders, two icon-swap rules, two focus
   treatments, one 46px and one 74px. They already shared their SVG paths and their
   `.is-playing` class switch (2026-08-28); this makes them share everything else.
   An instance changes --transport-size and --transport-glyph and nothing else.
   ============================================================================ */
/* 🔴 REDRAWN 2026-09-07. The author, of the bar: "we need to find a better play
   button ours is not impressive it looks like chlkdrens coding… research a new
   jhigher quality play button like the circle that has the triangle."
   It WAS a circle with a triangle. What made it look homemade was five things, and
   every one of them is what separates a drawn icon from a typed one:
     1 THE CORNERS WERE SHARP. `M8 5.5v13l11-6.5z` is three straight lines meeting at
       three points, and a 60° point rendered at 21px is a needle. Every icon set
       worth borrowing from — Material, Feather, Phosphor, Lucide — rounds the joins.
       Here that is stroke-linejoin:round with the fill and the stroke both currentColor,
       which rounds the corners AND restores the size the rounding takes off.
     2 IT WAS GEOMETRICALLY CENTRED, WHICH LOOKS OFF-CENTRE. A triangle carries its
       visual mass at the back edge, so a play mark centred on its bounding box always
       reads as sitting too far left. The convention is to centre the CENTROID and let
       the bounding box run right — Material's own play_arrow puts its centroid 0.33
       units left of centre in a 24 grid and its box 1.5 units right. This is that,
       scaled: centroid on 22.0 in a 44 grid, box centre on 24.6.
     3 THE PROPORTIONS WERE THIN. The triangle covered about 46% of the circle's inner
       width; 51-53% is where a transport button stops looking like a diagram.
     4 THE RING WAS --ink AT 1px. A hairline of near-black around a near-black triangle
       is the shape of a wireframe. It takes the accent now, like every other control
       on this site, and it moves with the dawn arc.
     5 THERE WAS NO BODY. A 46px circle containing nothing but two hairlines has no
       button in it. A 14% wash of the same accent gives it one without filling it —
       which is the standing rule here (see .btn-begin: "the ring stays drawn, not
       filled"), and the reason that rule survives this round rather than being
       overturned by it. The FILL is what hover is for, and hover already did it.
   🛑 ONE MARK, THREE PLACES. .bb-glyph on the jacket and .hs-glyph on the five-minute
   card carry the same geometry in the same grid, so the play mark is one drawing at
   three sizes rather than three drawings. If the path changes here it changes there.
   ⚠️ MEASURED, --accent on the paper each of them sits on: 5.68:1 day / 8.71:1 night
   for the ring and the triangle, against a 3:1 requirement for a control. The 14%
   wash is decoration under an opaque glyph and carries no contrast debt of its own. */
.transport-play {
  --transport-size: 46px;
  /* ⚠️ THE GLYPH BOX IS THE BUTTON, NOT A FRACTION OF IT, AND THAT IS THE POINT OF
     THE 44-UNIT GRID. .bb-glyph and .hs-glyph draw their ring INSIDE the same
     viewBox (r=20.5 of 44); here the ring is the button's own border, so the SVG has
     to be laid over the whole button for the triangle to come out at the same size
     relative to it. Set to a fraction — 24px was the first try — the triangle lands
     at 24% of the circle instead of 49% and the button reads as a big empty ring
     with a chip in it, which is most of what "childrens coding" meant.
     ⚠️ MINUS THE BORDER, AND THAT 3px IS NOT COSMETIC. Set to the button's full size the
     SVG is a grid item wider than the content box it sits in, so the browser shrinks it
     to fit — measured 44px inside a 46px button — and the glyph comes out 4% small at
     one size and not at another. `preserveAspectRatio` keeps it undistorted, so the
     fault is silent. The content box is size − 2×1.5px of border; state that. */
  --transport-glyph: calc(var(--transport-size) - 3px);
  width: var(--transport-size); height: var(--transport-size);
  min-width: var(--transport-size); min-height: var(--transport-size);
  padding: 0;
  display: grid; place-items: center;
  border: 1.5px solid var(--accent); border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              transform 80ms var(--ease);
}
.transport-play svg {
  width: var(--transport-glyph); height: var(--transport-glyph);
  fill: currentColor; display: block;
  /* the stroke IS the rounding. Same paint as the fill, so the shape does not gain an
     outline — it gains a radius, and gains back the 1.5 units the radius eats. */
  stroke: currentColor; stroke-width: 3; stroke-linejoin: round;
}
/* the pause bars are rounded to the same radius the triangle's corners now have, so
   the two states are the same drawing hand. They are rects, so they do not want the
   join stroke the triangle needs. */
.transport-play .ico-pause rect { stroke: none; }
/* the state switch is a class, not textContent: the button holds both glyphs and
   shows one. js/player.js reflectPlaying and js/room.js both toggle .is-playing. */
.transport-play .ico-pause { display: none; }
.transport-play.is-playing .ico-pause { display: block; }
.transport-play.is-playing .ico-play { display: none; }
/* the same inversion every other control on the site uses. In the Room --ink and
   --paper are swapped by the token block in room.css, so the gesture is identical
   and the result is correctly the other way round. */
/* 🔴 THE SAME MOVE AS .btn ABOVE, 2026-09-05: it fills with the accent rather than
   with black. This one DOES fill, because a 46px circle with a 12% wash reads as
   nothing at all — so the pair that matters is --paper on --accent, measured at
   6.79:1 (night), 5.19:1 (fire) and 5.03:1 (morning) on the darkest day stock, and
   7.9 / 6.2 / 8.1 against the lightest night stock. In the Room --paper and --ink
   are swapped by the token block in room.css and the gesture comes out correctly
   the other way round, exactly as it did when this was ink. */
.transport-play:hover, .transport-play:focus-visible {
  background: var(--accent); border-color: var(--accent); color: var(--paper);
}
.transport-play:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.transport-play:active { transform: scale(.96); }

/* ============================================================================
   THE SKIP MARK — one component, two sizes
   🔴 PROMOTED OUT OF room.css, 2026-09-07. The Listening Room made this fix on
   2026-09-05 and the player bar never got it: "THE SKIP CONTROLS ARE DRAWN NOW, NOT
   TYPED. They were the strings '−15' and '+30' … the two cheapest-looking objects on
   the screen." Both of those strings were still sitting in the bar, on either side of
   the play button the author has now called "chlkdrens coding" — so the bar had one
   drawn control between two typed ones, which is most of why it reads as unfinished.
   🛑 IT IS THE ROOM'S DRAWING, NOT A SECOND ONE. A circle with a dash gap at twelve
   o'clock and an arrowhead in the gap; the number set inside the ring, aria-hidden,
   with the real instruction on the button's own label. An instance changes
   --skip-size and --skip-num and nothing else.
   ⚠️ THE GAP IS CUT BY stroke-dasharray AND NOT DRAWN AS AN ARC. A dashed circle
   stays perfectly round at every size and needs no second path to close it; a
   hand-written A-command drifts off the circle the moment the stroke width changes.
   ============================================================================ */
.skip-mark {
  --skip-size: 44px;
  --skip-num: .72rem;
  position: relative;
  width: var(--skip-size); height: var(--skip-size);
  min-width: var(--skip-size); min-height: var(--skip-size);
  padding: 0; border: 0; background: none;
  display: grid; place-items: center;
  color: var(--ink-soft);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease), transform 80ms var(--ease);
}
.skip-mark svg { width: 100%; height: 100%; display: block; }
.sm-n {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font-family: var(--ui); font-size: var(--skip-num); font-weight: 600;
  font-variant-numeric: lining-nums tabular-nums;
  letter-spacing: .02em;
  padding-top: .18em;   /* the arrowhead sits above the ring's centre line */
}
.skip-mark:hover, .skip-mark:focus-visible { color: var(--ink); }
.skip-mark:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.skip-mark:active { transform: scale(.94); }

/* THE ONE EMPHATIC ACTION ON THE PAGE.
   ⚠️ #begin-btn DOES NOT USE THIS ANY MORE — it is .btn-begin below. This class is
   still worn by the invitation modal's own door and by the completion modal's
   "next chapter", where a one-line slab is right and there is no place to print.
   It is painted in the accent rather than in near-black for the reason the .btn
   hover note above gives at length. Measured: --paper on each of the three accents
   clears 5:1 on every stock in both themes. */
.btn-solid { border-color: var(--accent); background: var(--accent); color: var(--paper); }
.btn-solid:hover, .btn-solid:focus-visible {
  background: color-mix(in srgb, var(--accent) 84%, var(--ink));
  border-color: color-mix(in srgb, var(--accent) 84%, var(--ink));
  color: var(--paper);
}

/* ============================================================================
   🔴 .btn-begin — THE JACKET'S ONE DOOR, REBUILT 2026-09-05
   The author, of the resume state: "could the resume button be any ugglier?
   That's so pathethic."
   It was .btn-solid printing `Continue: VII, 37:26` as one string of sans inside a
   near-black rectangle. Three faults in one object: the colour was the internet's
   default primary, the shape was a form submit, and the copy ran a verb, a roman
   numeral and a timecode together on one line as though they were one fact.
   THEY ARE TWO FACTS AND THEY GET TWO LINES — the verb, and under it the place.
   That is what a bookmark is, and a bookmark is what this button holds.
   🛑 THE FILL IS THE ACCENT AND IT MOVES WITH THE BOOK. On the jacket that is the
   night blue; js/motion.js walks it to fire and then to morning as the reader goes
   down the page, so the one filled control on the site is always painted in the
   light of wherever the reader is standing. Measured, --paper on --accent:
   6.79:1 night · 5.19:1 fire · 5.03:1 morning against the darkest DAY stock, and
   7.88 / 6.19 / 8.06 against the lightest NIGHT stock. All clear of 4.5.
   ⚠️ THE RING IS DRAWN, NOT FILLED, and it is the same gesture the second door's
   .hs-glyph makes — this book's controls point with a hairline circle. Do not give
   it a solid disc: two filled shapes inside one filled slab is a logo, not a button.
   ============================================================================ */
/* 🔴 IT STOPPED BEING A SLAB, 2026-09-06. The author: "the resume button is
   obnoxiously loud."
   He is right, and the fault was that it was solving the wrong problem. The round
   before, it had been a near-black rectangle and the fix was to paint it in the
   book's own accent instead — which fixed the COLOUR and left the SHAPE, a filled
   block sixty pixels tall sitting on cream paper directly under a photograph. On a
   page whose entire visual argument is ink on paper and one hairline, a filled block
   of any colour is the loudest thing on the screen by an order of magnitude.
   ⭐ IT DOES NOT NEED FILL TO BE PRIMARY. It is already the widest control on the
   jacket, the only one carrying two lines, and the only one with a drawn ring in it —
   three ranks of emphasis before colour is spent. So the fill becomes a wash, the
   border carries the accent at 2px, and the label goes back to --ink.
   ⚠️ 2px, NOT 1. The wash alone left the button's own edge indistinct against the
   paper — SC 1.4.11 wants 3:1 for a control's boundary, and doubling the accent rule
   is what keeps the shape legible now that it is not a block.
   ⚠️ MEASURED after the change, compositing the wash over the paper it actually sits
   on — day rgb(218,214,205), night rgb(18,17,16): the label 12.53:1 / 15.61:1, the
   place line 5.14 / 8.52, the accent ring and border 5.80 / 8.70. All clear of 4.5,
   and the ring clears 3 as a control boundary with room to spare.
   🛑 THE RING STAYS DRAWN, NOT FILLED. This book's controls point with a hairline
   circle; a solid disc inside a washed pill is a logo, not a button. */
.btn-begin {
  /* 🔴 AND THEN THE BOX WENT TOO, 2026-09-06. The author, clarifying: "i meant loud
     as in ugly lol."
     The wash version above was quieter and still a RECTANGLE, and the rectangle was
     the ugly part. This page is hairlines and paper — a bordered box is the one shape
     it never uses anywhere else, which is exactly why a control drawn as one looks
     like something pasted on from a different website.
     ⭐ SO IT IS MARKED THE WAY THE BOOK MARKS THINGS: one accent rule down the left
     edge, which is what the five-minute card does and what a change of voice gets
     throughout the text. The affordance is not lost with the box — it was never the
     box carrying it. It is the 40px drawn ring with a triangle in it, which is the
     one mark on this page that means "this plays".
     ⚠️ SO THE HOVER HAS TO DO REAL WORK NOW. With no resting background there is
     nothing to deepen, so hover paints the wash IN — the control gains a surface
     under the pointer rather than changing colour. That is also the only state where
     a boundary appears, which is fine: SC 1.4.11 is satisfied at rest by the ring
     (7.07:1 day / 8.57:1 night against a 3:1 requirement), not by an edge.
     ⚠️ MEASURED at rest, on the paper itself now that there is no wash under it —
     day rgb(239,235,225), night rgb(20,19,17): label 15.26:1 / 15.37:1, place line
     6.26 / 8.38, ring 7.07 / 8.57. All clear. */
  border: 0;
  border-left: 2px solid var(--accent);
  border-radius: 0;
  background: none;
  color: var(--ink);
  /* the label block is a two-line stack, so the vertical rhythm is set here rather
     than inherited from .btn's single-line padding */
  align-items: center;
  gap: .85em;
  padding: .85em 1.4em .85em 1.05em;
  min-height: 60px;
  text-align: left;
  /* the rule must not move when the wash arrives on hover */
  transition: background-color var(--dur-fast) var(--ease);
}
.btn-begin:hover, .btn-begin:focus-visible {
  /* the surface arrives; nothing inverts and nothing moves. A hover that flips a
     control the reader is already pointing at is the black-slab gesture in disguise. */
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border-left-color: var(--accent);
  color: var(--ink);
}
/* 🔴 34px → 40px AND THE SAME REDRAWN MARK THE TRANSPORT GOT, 2026-09-07 — the five
   faults are listed once, at .transport-play above, and this is the second of the
   three places they were shipping. The ring is still DRAWN and not filled: the note
   at the top of .btn-begin is unchanged and this round did not overturn it. What the
   ring gained is a body (a 14% wash of its own accent), a corner radius on the
   triangle, and four more pixels — a 34px mark inside a 60px control was reading as
   the button's punctuation rather than as its point. */
.bb-glyph { display: block; width: 40px; height: 40px; flex: 0 0 40px; }
.bb-glyph svg { display: block; width: 100%; height: 100%; }
/* the glyph is the one place the accent still speaks at full strength — it is the
   part of the button that says "this plays", and it is 40px of line art, not a block */
.bb-ring {
  fill: color-mix(in srgb, var(--accent) 14%, transparent);
  stroke: var(--accent); stroke-width: 1.5;
  transition: fill var(--dur-fast) var(--ease);
}
.bb-tri {
  fill: var(--accent); stroke: var(--accent);
  stroke-width: 3; stroke-linejoin: round;
}
/* ⚠️ THE HOVER IS ON THE BUTTON, NOT ON THE GLYPH, so the mark fills at the same
   moment the control gains its surface rather than a beat after it. */
.btn-begin:hover .bb-ring,
.btn-begin:focus-visible .bb-ring { fill: color-mix(in srgb, var(--accent) 26%, transparent); }
.bb-text { display: block; min-width: 0; }
.bb-label {
  display: block;
  font-family: var(--ui); font-size: .92rem; font-weight: 600;
  letter-spacing: .005em; line-height: 1.25;
}
/* ⚠️ THE PLACE LINE IS A SIBLING, NOT A SUFFIX, and it ships `hidden`. A reader with
   no saved position — or with localStorage unavailable, which is a private window on
   iOS — must get a complete one-line button rather than a two-line one with a gap
   where the second line would be. js/ui.js unhides it and fills it, and nothing else
   ever touches it.
   🛑 IT IS FULL --paper, AND THE DIMMED VERSION WAS MEASURED AND THROWN OUT. The
   obvious treatment is a quieter tint — --paper mixed toward the accent — and it
   fails: across the twelve accent/stock pairs in the two themes the best mix that
   still reads as quieter lands at 4.42:1 for 11.5px of text, under the 4.5 floor,
   with the fire and morning accents the worst of it. So the hierarchy is carried by
   SIZE, WEIGHT AND CASE instead of by colour — tracked uppercase apparatus under a
   sentence-case semibold label — which is how the rest of this book separates a
   line from the line above it anyway. */
.bb-place {
  display: block;
  margin-top: .18em;
  font-family: var(--ui); font-size: var(--ap-md); font-weight: 400;
  letter-spacing: .09em; text-transform: uppercase;
  line-height: 1.3;
  /* 🛑 --ink-soft IS SAFE HERE AND --paper WAS NOT SAFE ON THE OLD FILL. The note
     below the previous version of this rule explains why the dimmed line had to be
     full --paper: it was sitting on a solid accent, where every quieter mix measured
     under 4.5:1. On a 9% wash over paper that constraint is gone — --ink-soft is the
     token this page uses for a second line everywhere else, and on plain paper it
     measures 6.26:1 by day and 8.38:1 at night. Re-measure if a resting background
     is ever put back under this button. */
  color: var(--ink-soft);
  font-variant-numeric: lining-nums tabular-nums;
}
.bb-place[hidden] { display: none; }

/* ============================================================================
   NAV — a running head, not a floating capsule
   ============================================================================ */
#site-nav {
  position: fixed; top: 0; left: 0; right: 0; z-index: 800;
  background: var(--paper);
  border-bottom: 1px solid var(--rule);
  transition: transform var(--dur-med) var(--ease), opacity var(--dur-med) var(--ease);
  color: var(--ink);
}
#site-nav.nav-hidden { transform: translateY(-100%); }
#site-nav.nav-faded { opacity: 0; pointer-events: none; }

.nav-shell {
  max-width: var(--shell); margin-inline: auto; padding: 0 var(--edge);
  display: flex; align-items: center; justify-content: space-between; gap: 1.5rem;
  min-height: 58px;
}
.nav-mark {
  font-family: var(--ui); font-size: var(--ap-md); font-weight: 600;
  letter-spacing: .2em; text-transform: uppercase;
  color: var(--ink);
}
/* was 47x19 — under the 44px target minimum */
.nav-mark { display: inline-flex; align-items: center; min-height: 44px; }
.nav-chapters { display: flex; gap: .1rem; }
/* SET IN LITERATA, 2026-08-28. Ten roman numerals tracked out in the interface sans
   read as an app toolbar; the same ten in the book's own face read as a spine. Lining
   tabular figures so I, II, III and VIII occupy the same rhythm, and no letter-spacing
   — a roman numeral is one word, not an acronym. */
/* 🛑 44px ON BOTH AXES — 2026-08-30 (D21-D), the author's "44px rule follow it".
   These were `min-width: 34px` and measured **34 × 44** at 1280px. They clear WCAG
   2.2 SC 2.5.8 (24px) and fail the 44px floor this site holds itself to, and the
   round-sixteen audit missed them for one reason: **it measured 402px only**, where
   `.nav-chapters` is `display: none`. A check at one width is half a check — the
   same lesson the lexicon filter row taught at 1280 and this one taught at the other
   end. ⚠️ The strip grows ~100px. It has the room at 1280 and is not rendered at all
   below 900, so nothing else moves. */
.nav-chapters a {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px;
  font-family: var(--serif); font-variation-settings: var(--lit-normal);
  font-size: .8rem; font-weight: 500; letter-spacing: 0;
  font-variant-numeric: lining-nums tabular-nums;
  color: var(--ink-faint);
  transition: color var(--dur-fast) var(--ease);
}
.nav-chapters a:hover, .nav-chapters a:focus-visible { color: var(--ink); }
.nav-chapters a.is-active { color: var(--accent); font-weight: 600; }
.nav-actions { display: flex; gap: .3rem; align-items: center; }
/* 38px failed the 44px minimum on three buttons (measured 52x38 at 402px wide). */
/* ⚠️ THE NAV KEEPS --ap-md WHILE .btn MOVED UP TO --ap-lg, 2026-09-05. That is not an
   oversight: four 44px targets, a wordmark and the contents toggle already do not fit
   320px, and the bar was measured overflowing by 61px before the rule below. Nav
   labels are chrome consulted at a glance, which is exactly what --ap-md is for. */
.nav-actions .btn { padding: .6em 1em; min-height: 44px; font-size: var(--ap-md); }

/* 🔴 THE BAR WAS RUNNING OFF THE SCREEN ON A SMALL PHONE, 2026-09-05.
   Measured in a real browser: the shell's scrollWidth beat its clientWidth by 61px at
   320, 41px at 340, 21px at 360 — the night-mode button was the fourth action and the
   one that pushed it over, but it was already 17px over without it. Nothing here is
   allowed to shrink a target below 44×44, so the space comes out of the padding
   between the labels and their borders, and then out of the wordmark, which is the one
   item in the bar that navigates nowhere the page does not already offer: the jacket
   title sits a hundred pixels under it and the contents toggle is its neighbour.
   ✅ Re-measured after: 0px of overflow at 320 / 340 / 360 / 380 / 381 / 402 / 430 /
   480, and no control under 44px at any of them. */
@media (max-width: 420px) {
  .nav-actions { gap: .15rem; }
  .nav-actions .btn { padding: .6em .5em; }
}
@media (max-width: 380px) {
  .nav-mark { display: none; }
}
/* 🔴 THE FIFTH ACTION, 2026-09-16: #nav-lang (the globe, first ES / EN) beside the moon. Measured with it
   and the scrollbar off, the bar ran over by 21px at 320, 28 at 390, 16 at 402, 4 at 414
   and 24 at 430 — and by 0 everywhere without it. The room comes out of the 1.5rem gaps
   between the bar's groups, which on a phone were air nobody reads, and at the smallest
   width out of the contents toggle's side padding. No target goes under 44×44.
   ✅ Re-measured: see docs/FRONT-DOOR.md §0.13. */
@media (max-width: 480px) {
  .nav-shell { gap: .5rem; }
}
@media (max-width: 340px) {
  .nav-toc-toggle { padding: 0 .4em; }
  .nav-actions { gap: 0; }
}

/* 🔴 940, NOT 900, AND IT IS MEASURED, 2026-09-05. The ten chapter numerals used to
   come back at 901px and the bar overflowed by 32px the moment they did — the night
   toggle made the actions group wide enough that 901 no longer had room for both.
   Swept in 10px steps: +32 at 901, +22 at 911, +13 at 921, +3 at 931, and 0 from 941
   up. The numerals are a convenience the running head and the contents both duplicate;
   the bar running off the screen is not. */
@media (max-width: 940px) {
  .nav-chapters { display: none; }
  .nav-shell { min-height: 52px; }
}

/* ============================================================================
   HERO — the plate gets the page, the title lands on paper underneath
   The photograph carries NO type. This is the whole idea of the direction.
   ============================================================================ */
/* padding-BLOCK only. `padding: 0` also cleared the grid's padding-inline, so the
   plate's negative inline margin pushed the page 2×--edge wider than the viewport
   — horizontal scroll on every phone. */
#hero { padding-block: 0; }

/* Plates break the page margin but not the shell: they run edge-to-edge inside
   the 1440px measure of the book rather than to the viewport edge. On a laptop
   that reads as full-bleed; on a 27" monitor the page keeps its margins, which
   is what a book does and what a 100vw breakout cannot do without fighting the
   scrollbar. */
.hero-plate {
  grid-column: 1 / -1;
  margin-inline: calc(var(--edge) * -1);
  position: relative; overflow: hidden;
}
.hero-plate img {
  width: 100%;
  /* 🔴 SHORTER, 2026-09-05. The author: "can we raise up the The Invitation Hidden
     on Every Page it needs better seign its too long top to bottom."
     clamp(380px, 72vh, 760px) → this. The title cannot be raised by moving the
     title: it already sits directly under the photograph with 49px between them,
     and the only thing above it is the picture. 72vh is very nearly a full screen,
     so on a phone the jacket's first line was ALWAYS below the fold no matter how
     tight the type block underneath it was. At 64vh a 402x740 phone gets 474px of
     photograph instead of 533 and the mark clears the fold; the picture is still
     the largest thing on the page by a wide margin.
     ⚠️ THE PRELOAD IN index.html DOES NOT CHANGE WITH THIS. It names three AVIF
     derivatives by width and `sizes` is width-based; the height is a crop. */
  height: clamp(340px, 64vh, 700px);
  object-fit: cover; object-position: 50% 45%;
  transform: scale(1.07);
}
/* no scrim, no vignette, no gradient — the picture is simply the picture */

.hero-block {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
  align-items: start;
  /* 🔴 TIGHTENED 2026-09-05. The author: "i feel like theres too much space in
     between sections at the beggining make sure theyre clean not bloated."
     Measured at 1440 before: 74px of air between the bottom of the photograph and
     the mark, and 54px between the last line of the jacket and the ribbon below —
     which then adds 45px of its own. Three separate gaps stacking at the one place
     on the page where a stranger is deciding whether to stay. After: 50px and 37px.
     🛑 THE PHONE END BARELY MOVES (38px → 30px). The bloat was a desktop fault: the
     old clamp's 5vw upper arm grew with the window while the type did not. */
  /* 🔴 TIGHTER AGAIN, 2026-09-05 (round six), same note as the photograph's height
     above — the jacket is being raised, not just squeezed. 50px → 36px over the
     mark at 1440, and the phone end goes 30 → 24. */
  padding: clamp(1.5rem, 2.5vw, 2.3rem) 0 clamp(1.2rem, 2.1vw, 1.9rem);
}
@supports not (grid-template-columns: subgrid) {
  .hero-block { grid-template-columns: repeat(12, 1fr); column-gap: var(--step); }
}
/* ⭐ ITS OWN ROW, CENTRED, AND ROUGHLY TWICE THE SIZE. 2026-09-05, the author:
   "make the ebrew symbol the first one bigger its kinda small… why dont you
   center it? any reason not too?"
   🔴 THE MARGIN COLUMN IS WHAT WAS HOLDING IT SMALL. It sat at grid-column 1/3 —
   two of twelve tracks, 208px at 1440 — beside the title, so the clamp had to top
   out at 2.6rem or the word would have run out of its own column. That was a
   consequence of where it was parked, never a decision about how big the word the
   book is named after ought to be. Measured at 1440: 41.6px becomes 76.8px.
   ⭐ AND THERE WAS NO REASON NOT TO CENTRE IT. A device centred above a
   ranged-left title is the title page, not a mistake — it is the one centred
   element on the jacket, which is exactly what stops it reading as a heading and
   makes it read as a mark. The title, the standfirst and the pull line all keep
   the left grid they had.
   🛑 IT IS STILL A LINK to #lex-panim, still not underlined (css/site.css sets
   `a { color: inherit }` and every component turns the UA underline off for
   itself), and still writes itself in right-to-left once (.heb-g, below). */
/* 🔴 IT CAME OFF THE PAGE'S CENTRE LINE AND ONTO THE TITLE'S, 2026-09-05. The
   author: "hebrew word should be on top of title it sits on top of not in dead
   center."
   Centring it across all twelve tracks (the 2026-09-05 morning change) put the mark
   356px to the right of the title's left edge at 1440 — measured — so the word and
   the line it names shared no edge at all and read as two unrelated objects stacked
   in the same column of air. It now spans the SAME tracks as .hero-title and ranges
   left with it: the mark sits directly on top of the words it belongs to, which is
   what the author asked for and what the earlier "why dont you center it" was
   reaching for. Still full display size — the size was the real complaint that
   morning and none of it is given back.
   🛑 THE TRACKS MUST TRACK .hero-title. If the title's 3 / 10 ever changes, this
   changes with it or the mark silently starts floating again.
   ⚠️ margin-inline-start UNDOES THE TOUCH-TARGET PADDING BELOW, optically. The .12em
   of padding is what keeps the word's box over 44px wide; ranged left it would also
   push the glyph ~6px right of the title's stem, which is the one misalignment the
   eye actually catches on a title page. */
.hero-hebrew {
  grid-column: 3 / 10;
  justify-self: start;
  /* 🔴 margin-LEFT, NOT margin-inline-start, AND THAT WAS A REAL BUG UNTIL
     2026-09-06. This element carries dir="rtl" — it must, Hebrew is a
     right-to-left script and the nikkud depend on it — and in an RTL box the
     INLINE-START EDGE IS THE RIGHT ONE. So the negative margin that was supposed to
     cancel the touch-target padding on the left was quietly cancelling it on the
     right, and the word had been sitting 6px (phone) to 10px (desktop) right of the
     title's stem the whole time. Measured before: box left 20 / title left 20, ink
     left 26. After: ink and stem on the same vertical at every width.
     🛑 A LOGICAL PROPERTY IS THE WRONG TOOL ON AN RTL ISLAND INSIDE AN LTR PAGE.
     The page's reading direction decides the grid; the element's decides its own
     inline axis, and here the two disagree on purpose. Use physical properties for
     anything that has to line up with the page. */
  margin-left: -.12em;
  font-family: var(--heb); font-weight: 400;
  /* ⚠️ IT MUST NOT LAND WITHIN A FEW PIXELS OF THE TITLE. The first pass was
     clamp(2.9rem, 6.4vw, 4.8rem) → 76.8px at 1440 against a 70.4px .hero-title,
     and two display sizes six pixels apart do not read as a hierarchy, they read
     as a mistake. This clears the title by about a quarter at both ends: 49.6px
     over 35.2px on a 402px phone, 86.4px over 70.4px at 1440. */
  font-size: clamp(3.1rem, 6.6vw, 5.4rem); line-height: 1;
  color: var(--accent);
  padding-top: 0;
  /* ⚠️ HALVED WITH THE MOVE, 2026-09-05, and it is the same decision as the move.
     32px of air was right under a centred mark floating free of the title; over a
     title it now shares a left edge with, it read as a gap rather than as a pair.
     Measured at 1440: 32px → 17px. The two are one object now. */
  /* ⚠️ HALVED AGAIN 2026-09-05 (round six): 17px → 9px at 1440. The mark and the
     title are one object and share a left edge; the smaller the gap, the more they
     read as a mark ON a title rather than as two lines of a stack. */
  margin-bottom: clamp(.25rem, .6vw, .55rem);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur-fast) var(--ease);
}
.hero-hebrew:hover, .hero-hebrew:focus-visible {
  border-bottom-color: color-mix(in srgb, var(--accent) 45%, transparent);
}
.hero-hebrew:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
/* 🛑 44px ON BOTH AXES SURVIVES THE RESIZE, AND IT IS STILL DECLARED RATHER THAN
   ASSUMED. The floor was found on 2026-08-30 (D15-G) when this measured 42 x 27 at
   402px. At the new clamp the phone end is 2.9rem, so the word's own box now
   measures about 118 x 46 and clears the floor on its own — but the padding stays,
   because a floor that is only met by accident is a floor that breaks the next
   time somebody touches the clamp. The padding stays SYMMETRIC even though the
   word is ranged left again — the negative margin-inline-start above cancels the
   left half optically, which is cleaner than an asymmetric pad that would have to
   be re-reasoned every time the clamp moves. */
.hero-hebrew {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; min-width: 44px;
  padding-inline: .12em;
}
/* Retuned for Literata, 2026-08-27. Literata sets wider than Fraunces did, so the
   old clamp(…, 5.1rem) pushed the title from two lines to three and the jacket
   started to shout. Smaller, and a notch lighter than the 400 the shared display
   rule uses: at 70px the light end reads as air rather than as thin, which is not
   true at chapter-title size. */
.hero-title {
  grid-column: 3 / 10;
  font-size: clamp(2.2rem, 5vw, 4.4rem);
  font-weight: 350;
  margin: 0;
}
.hero-subtitle {
  grid-column: 3 / 9;
  color: var(--ink-soft);
  font-size: clamp(1.05rem, 1.5vw, 1.28rem);
  margin: 1.1rem 0 0; max-width: 48ch;
}
/* THE PULL LINE. It came off the onboarding modal on 2026-08-29 (decision D2-C),
   where it was the best sentence on the jacket and was sitting behind a dismiss
   button. It is set a step up from the subtitle and a step down from the title:
   the subtitle states the thesis, this one makes the promise. Rule above, because
   it is a change of voice and the book marks those with a rule everywhere else. */
.hero-pull {
  grid-column: 3 / 9;
  font-variation-settings: var(--lit-mid);
  font-weight: 350;
  font-size: clamp(1.14rem, 1.75vw, 1.46rem);
  line-height: 1.44; letter-spacing: -.01em;
  color: var(--ink);
  max-width: 40ch;
  margin: 1.5rem 0 0; padding-top: 1.3rem;
  border-top: 1px solid var(--rule);
}
.hero-meta {
  grid-column: 10 / 13;
  font-family: var(--ui); font-size: var(--ap-md); letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-soft); line-height: 2.1;
  padding-top: .5em;
}
.hero-meta b { display: block; color: var(--ink); font-weight: 600; }
.hero-actions { grid-column: 3 / 9; display: flex; gap: .8rem; align-items: center; margin-top: 1.8rem; flex-wrap: wrap; }
/* ⭐ THE OTHER LANGUAGE, 2026-09-16. Type, not a box — see the note in index.html.
   Sans at --ap-xl in --ink-soft, the voice .hero-sample-note already speaks on this
   jacket, with a hairline under it that turns to the accent: the underline is the
   affordance, the 44px is the target. Hover lifts the ink; nothing fills. */
.hero-lang {
  display: inline-flex; align-items: center;
  min-height: 44px; padding: 0 .2em;
  font-family: var(--ui); font-size: var(--ap-xl); font-weight: 400;
  color: var(--ink-soft);
  background: none; border: 0;
  text-decoration: underline;
  text-decoration-color: var(--rule-strong);
  text-decoration-thickness: 1px;
  text-underline-offset: .35em;
  transition: color var(--dur-fast) var(--ease),
              text-decoration-color var(--dur-fast) var(--ease);
}
.hero-lang:hover, .hero-lang:focus-visible {
  color: var(--ink);
  text-decoration-color: var(--accent);
}
/* 🛑 .hero-hint IS GONE, 2026-09-05, WITH ITS ONLY MARKUP. It styled the
   "Contents ↓" link beside the Begin button; the author asked for that link to go
   ("i told you i dont want the clicker that shows content near the button") and a
   rule with no markup is a trap for whoever reads this file next. See index.html
   for the argument. Nothing else on the site used the class — checked. */

@media (max-width: 900px) {
  .hero-block { grid-column: 1 / -1; }
  @supports not (grid-template-columns: subgrid) {
    .hero-block { grid-template-columns: repeat(6, 1fr); }
  }
  .hero-title, .hero-subtitle, .hero-pull, .hero-meta, .hero-actions { grid-column: 1 / 7; }
  /* 🔴 RANGED LEFT ON A PHONE TOO — REVERSED 2026-09-06, ON THE AUTHOR'S WORD.
     "the panim symbol needs to move left cause it looks weird fully centered on
     mobile."
     🛑 THIS IS THE THIRD POSITION THIS MARK HAS HAD AND IT IS THE LAST ONE, so the
     reasoning that lost is written down rather than deleted — the argument for
     centring was good and it was still wrong.
     THE ARGUMENT WAS: on a phone the title fills the whole measure, so "centred on
     the page" and "centred on the title" are the same place, and a centred device
     over a ranged-left title is the title-page move, available for free. The
     geometry is correct. What it misses is that the title is TWO RAGGED LINES, not
     a block — "The Invitation / Hidden on Every Page" — so the mark was not being
     centred over a shape, it was being centred over an average. It read as floating
     because it WAS floating: it shared no edge with anything on the page.
     ⭐ ONE LEFT EDGE, EVERYWHERE, AT EVERY WIDTH. The mark, the title, the
     standfirst, the pull line and the door now all start on the same vertical, on a
     phone exactly as on a desktop, and the mark reads as a mark ON the title
     instead of as a fourth thing stacked above it.
     ⚠️ SO THE NEGATIVE MARGIN COMES BACK. The .12em of padding is what keeps the
     word's box over the 44px touch minimum; ranged left, that padding also pushes
     the first glyph ~6px right of the title's stem, and a mark six pixels off the
     line it belongs to is the one misalignment the eye actually catches. The
     negative margin cancels it optically without touching the target. */
  .hero-hebrew {
    grid-column: 1 / 7;
    justify-self: start;
    margin-left: -.12em;   /* physical — see the note on the desktop rule */
    margin-bottom: .45rem;
  }
  .hero-meta { margin-top: 1.4rem; }
}

/* ============================================================================
   CHAPTER OPENING
   Number in the margin column, title in the wide range, hook beneath.
   ============================================================================ */
/* the header spans the whole grid and re-inherits its tracks with subgrid, so the
   numeral and the title sit on the SAME columns as the prose below them. Without
   subgrid the header would be one grid item and its children would lay out in
   their own private box, drifting a few pixels off the text column. */
.chapter-header {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
  padding-bottom: clamp(1.4rem, 2.6vw, 2.4rem);
}
.chapter-header .chapter-margin { grid-column: 1 / 3; }
.chapter-header .chapter-titleblock { grid-column: 3 / 11; }
/* Two actions at the chapter head. "Listen from here" is the control; "Share this
   chapter" is quieter, because sending a chapter is a thing a reader chooses to do
   and not the thing the page is for. .btn-quiet drops the control surface — no
   edge, no wash — and keeps the 44px target, so it reads as a link that happens to
   be a button, which is what it is. */
.chapter-actions {
  margin-top: 1.5rem;
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem;
}
.listen-from-here { margin-top: 0; }
.btn-quiet {
  border-color: transparent; background: none;
  color: var(--ink-faint);
  padding-inline: .2em;
}
.btn-quiet:hover, .btn-quiet:focus-visible {
  background: none; color: var(--ink);
  border-color: transparent;
  text-decoration: underline; text-underline-offset: .3em;
}

/* subgrid fallback: lay the header out on its own 12-column grid at the same
   gutter. Alignment is then within a pixel rather than exact, which is fine. */
@supports not (grid-template-columns: subgrid) {
  .chapter-header { grid-template-columns: repeat(12, 1fr); column-gap: var(--step); }
}

@media (max-width: 900px) {
  .chapter-header { grid-column: 1 / -1; padding-bottom: 1rem; }
  .chapter-header .chapter-margin { grid-column: 1 / 7; }
  .chapter-header .chapter-titleblock { grid-column: 1 / 7; }
  @supports not (grid-template-columns: subgrid) {
    .chapter-header { grid-template-columns: repeat(6, 1fr); }
  }
  /* 🔴 ONE ROW ON A PHONE, MARK FIRST, 2026-09-05. The author, of a screenshot of
     this exact block: "on mobile the hebrew woed ends up on the left it would be
     better if it was left of the title not just randomly floating… it shouldnt take
     up so much space top to  bottom."
     🛑 IT WAS NOT "ENDING UP" ANYWHERE — IT WAS BEING PUSHED THERE BY dir="rtl".
     The mark is a block with dir="rtl" on it (js/render.js sets it, and it must:
     Hebrew is a right-to-left script and the nikkud depend on it). A block in a
     right-to-left direction aligns its inline content to the RIGHT edge of the box,
     and on a phone the margin column is the full six tracks — so the word sat alone
     against the right margin, 300px from the chapter label above it and the title
     below it, related to neither. On the desktop the same rule is invisible because
     the margin column is only 196px wide and the word fills it.
     ⭐ SO THE COLUMN BECOMES A ROW AND THE MARK GOES FIRST. The chapter's own word,
     then CHAPTER II beside it on the same baseline: two pieces of apparatus reading
     as one line instead of as two floating objects, ranged hard left with the title
     under them, and about 40px of the page's height given back.
     ⚠️ THE FLEX IS ON THE PARENT AND MUST NEVER BE ON .chapter-mark ITSELF — the
     long note at that rule explains why (every grapheme is a span, and flex would
     lay a five-letter word out as five independent boxes).
     ⚠️ order, NOT A DOM SWAP. The reading order is numeral-then-mark for a screen
     reader, which is the right order to hear; only the phone's PAINT order changes,
     and both are apparatus rather than controls, so 2.4.3 Focus Order is untouched
     (neither is in the tab ring here — the mark's tabindex makes it focusable and it
     is a single item either way). */
  .chapter-header .chapter-margin {
    display: flex; align-items: baseline; flex-wrap: wrap;
    column-gap: .8rem; row-gap: 0;
    margin-bottom: .45rem;
  }
  .chapter-header .chapter-num { order: 2; padding-top: 0; margin-bottom: 0; }
  .chapter-header .chapter-mark { order: 1; }
}

/* ---------- the Hebrew in the margin ----------
   The book is named after one Hebrew word and, before this, that word appeared
   exactly twice on the whole site. It now sits in the margin column of every
   chapter opening, large and pale, in the chapter's own accent — a standing
   watermark, the way a running head works in print. Correct vocalised Hebrew
   (פָּנִים), reused rather than invented per chapter, so nothing is guessed. */
/* Each chapter's own word (content/marks.js), not a watermark any more.
   It was opacity .17 with pointer-events:none — decoration you could not read and
   could not touch. It now carries meaning, so it has to be legible and hoverable:
   the word in plain view, and what it means on hover or keyboard focus, through the
   same #gloss-card the inline glossary terms use.
   Smaller than before because four of the ten marks are phrases, not single words
   (peh el peh, panim be-panim, haster astir, prosopon pros prosopon). */
/* ⭐ LARGER AND ACTUALLY TOUCHABLE, 2026-09-05. The author: "Should we make all
   greek hebrew words biiger that are not in the large lot at the bottom easier to
   interact with?"
   Yes, and the two halves of that are separate faults. SIZE: outside the lexicon
   wall there are only two places a Hebrew or Greek word appears on this site — the
   jacket's mark and these ten — and both were sized by the column they happened to
   sit in rather than by what they are. The jacket's is fixed above; this is the
   other one.
   TOUCH: this is a tabindex="0" role="note" that opens the #gloss-card, i.e. a
   control, and it had no declared target at all. Measured at 402px before this:
   ch. I "פָּנִים" was 96 x 30 — thirty pixels tall against WCAG 2.5.5's 44, and the
   four Greek marks were worse because the serif sets shorter. The line-height and
   the min-height below are the target; nothing moves on the page, because the
   margin column has the room and the mark is the only thing in it. */
.chapter-mark {
  font-family: var(--heb); font-weight: 300;
  /* clamp(1.85rem, 3.1vw, 2.9rem) → this, 2026-09-05. The author: "Maybe hebrew on
     the left of all chapter could be beigger but still keep on the left dont put on
     the top." The ceiling is the constraint and it is arithmetic, not taste: the
     margin column is two of twelve tracks — 196px at the 1440 shell — and the four
     PHRASE marks (peh el peh, panim be-panim, haster astir, prosopon pros prosopon)
     have to wrap inside it without touching the title. Measured at 3.15rem the
     longest of them still clears the column. Do not raise this without re-measuring
     chapter V and chapter X, which are the two that bind. */
  font-size: clamp(2.45rem, 3.4vw, 3.15rem);
  line-height: 1.24;
  /* 🛑 min-height ON A BLOCK, NOT display:flex. Flex was the obvious way to centre
     the word in a 44px box and it would have quietly wrecked the Hebrew: js/render
     .js grapheme-splits every Hebrew mark into .heb-g spans so the combining nikkud
     stay attached to their letters, and under flex each of those spans becomes a
     flex ITEM — ten letters laid out as ten independent boxes, in a word that is
     supposed to be one. The box just gets taller instead. */
  min-height: 44px;
  display: block;
  color: var(--accent);
  margin-top: .5em;
  user-select: none;
  cursor: help;
  transition: color var(--dur-fast) var(--ease);
}
/* 🛑 THE OPACITY IS GONE, 2026-08-29, AND IT WAS AN AA FAILURE IN ALL TEN CHAPTERS.
   This was `opacity: .62` (.7 on phones) over `color: var(--accent)`, and the round
   seven audit measured the accent — which passes everywhere — without compositing the
   opacity over the paper underneath it. Measured across all eleven paper stocks the
   composite lands between 2.58:1 and 2.95:1 against a 4.5:1 requirement, and this is
   TEXT: the comment above this block says so itself ("It is content now: the word each
   chapter turns on"). Nothing passed. The opacity that would have passed every stock is
   .92, which is not a fade, so the fade goes and the accent stands at full strength —
   5.19:1 at the worst stock (ch VIII) and 7.07:1 at the best.
   The hover affordance survives and is now the site's own idiom rather than a private
   one: quiet in the accent, ink on hover, exactly like .btn-icon and .nav-chapters a. */
.chapter-mark:hover, .chapter-mark:focus-visible { color: var(--ink); }
.chapter-mark:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
/* Greek is left-to-right and is set in the book serif, not the Hebrew face */
/* Greek is left-to-right and is set in the book serif, not the Hebrew face. It is
   set a step down from the Hebrew because Frank Ruhl's Hebrew has no ascenders or
   descenders to speak of and Literata italic has both — matching the two by
   nominal size makes the Greek look larger than the Hebrew, not equal to it. */
.chapter-mark.is-greek {
  font-family: var(--serif);
  font-size: clamp(1.42rem, 2.3vw, 2.05rem);
  font-style: italic;
}
.chapter-mark .heb-g {
  display: inline-block;
  opacity: 0; transform: translateY(6px);
  transition: opacity 700ms var(--ease), transform 700ms var(--ease);
}
.chapter-mark.is-visible .heb-g { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .chapter-mark .heb-g { opacity: 1; transform: none; transition: none; }
}
/* It used to be hidden on phones because it was ornament. It is content now:
   the word each chapter turns on. Kept, smaller, above the title. */
@media (max-width: 900px) {
  /* ⚠️ THE PHONE HAD ITS OWN, SMALLER, FLAT SIZES — 1.3rem and 1.05rem — which
     undercut the clamp above and were the actual reason the marks were 30px tall
     where it mattered most. The clamp's own floor (1.85rem / 1.42rem) is what a
     phone gets now; the margin column on a phone is the full width (1/7), so
     there is nothing for a larger word to collide with.
     🔴 LARGER AGAIN 2026-09-05 — the author: "Maybe hebrew on the left of all
     chapter could be beigger but still keep on the left dont put on the top."
     It shares a line with CHAPTER II now (see the header rule above), which is
     where the room came from: the word is the size of a subhead instead of the
     size of a label, and the row is still shorter than the two stacked lines were.
     🛑 margin-top GOES TO ZERO WITH THE ROW. The .5em above was the gap under the
     numeral when the two were stacked; left in place it pushes the mark's baseline
     off the numeral's and the row stops being a row. */
  .chapter-mark { font-size: 2.45rem; margin-top: 0; }
  .chapter-mark.is-greek { font-size: 1.75rem; }
}

/* the hero's Hebrew writes itself in, right to left, once */
.hero-hebrew .heb-g {
  display: inline-block;
  opacity: 0; transform: translateY(8px);
  animation: heb-in 620ms var(--ease) forwards;
}
@keyframes heb-in { to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .hero-hebrew .heb-g { animation: none; opacity: 1; transform: none; }
}

/* ============================================================================
   VERSE — set, not boxed
   The old build put scripture in a bordered card with a watermark quote mark and
   a pulsing glow. Scripture in a real book is set apart by indentation, a change
   of size, and air. That is all this does.
   ============================================================================ */
.verse-box {
  grid-column: 3 / 9;
  margin: 2.1em 0 2.3em;
  padding-left: clamp(1rem, 1.6vw, 1.6rem);
  border-left: 2px solid var(--accent);
}
.verse-text {
  font-family: var(--serif);
  font-variation-settings: var(--lit-mid);
  font-weight: 350;
  font-size: clamp(1.25rem, 2.1vw, 1.62rem);
  line-height: 1.36;
  letter-spacing: -.012em;
  max-width: 32ch;
}
.verse-text .verse-line { display: block; }
/* HUNG IN THE MARGIN AND SET IN THE BOOK'S FACE, 2026-08-28.
   It was Archivo at .64rem — the size and voice of a player button, sitting under a
   verse. A set Bible puts its citation in the same family as the text and hangs it
   beside the quotation, which is what the empty outer column is for. The one place
   that cannot work is chapter X's prayer, which cites after almost every line; there
   the margin would place a reference beside whichever paragraph happened to leave
   room, so .prayer-zone .verse-ref drops back underneath. */
.verse-box { position: relative; }
.verse-ref {
  font-family: var(--serif);
  font-variation-settings: var(--lit-normal);
  font-size: var(--ap-md); font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent);
  position: absolute; top: .25em; right: calc(100% + var(--step));
  width: calc(var(--step) * 6);
  text-align: right;
  margin-top: 0;
}
/* THE CITATION SET AS A CITATION, 2026-08-29.
   It was one tracked-uppercase run, name and number together, and the tracking
   that makes SONG OF SONGS read as a running head put a gap between the 2 and the
   colon in 2:14. It is two spans now, in two voices, on two lines: the book name
   tracked and in the accent, the location under it in tabular lining figures with
   no tracking at all, and a hairline between them that sits on the verse's own
   left rule so the apparatus and the quotation share one horizon.
   The rule is 1px of --rule-strong, which is the only rule weight in the book. */
.verse-ref-text { display: block; }
.vr-book {
  display: block;
  font-size: var(--ap-sm);
  letter-spacing: .13em;
  line-height: 1.3;
}
.vr-loc {
  display: block;
  margin-top: .42em; padding-top: .42em;
  border-top: 1px solid var(--rule-strong);
  font-size: var(--ap-md);
  font-variant-numeric: lining-nums tabular-nums;
  font-weight: 400; letter-spacing: 0; text-transform: none;
  color: var(--ink-soft);
}
.verse-ref .verse-translation { display: block; margin-top: .5em; margin-left: 0; color: var(--ink-faint); font-weight: 400; font-size: var(--ap-sm); }
/* the prayer zone, and every viewport too narrow for a margin, keep it underneath —
   and underneath, a stacked two-line citation with a rule through it is apparatus
   where a single line is wanted, so it collapses back to one run. */
.prayer-zone .verse-ref,
.section[data-ch="10"] .prayer-zone .verse-ref {
  position: static; width: auto; text-align: left; margin-top: 1.1em;
}
.prayer-zone .verse-ref .vr-book,
.prayer-zone .verse-ref .vr-loc { display: inline; }
.prayer-zone .verse-ref .vr-loc {
  margin: 0 0 0 .5em; padding: 0; border-top: 0; font-size: var(--ap-sm);
}
/* 🛑 1099.98, NOT 1100. The apparatus block below opens at min-width:1100 and this
   one used to close at max-width:1100, so at EXACTLY 1100px both applied: the box
   was a grid and the citation was static, which dropped a static .verse-ref into
   the same grid cell as the quotation it names and set the two on top of each
   other. Caught at 1100px in the browser, not by reading. Any pair of blocks that
   share a breakpoint needs the sub-pixel on the max-width side. */
@media (max-width: 1099.98px) {
  .verse-ref { position: static; width: auto; text-align: left; margin-top: 1.1em; }
  .verse-ref .verse-translation { display: inline; margin-top: 0; margin-left: .6em; }
  .verse-ref .vr-book, .verse-ref .vr-loc { display: inline; }
  .verse-ref .vr-loc {
    margin: 0 0 0 .5em; padding: 0; border-top: 0; font-size: var(--ap-sm);
  }
}
.verse-quotemark { display: none; }   /* removed: decoration pretending to be typography */

/* ---------------------------------------------------------------------------
   THE VERSE NOTE — two lines of apparatus under a quotation
   Shape approved in round two: WHERE YOU ARE, then WORTH KNOWING. It sits in the
   reading column under the verse it belongs to, NOT in the margin the citation
   hangs in — that column is six steps wide and a sixty-word note set in it is a
   ribbon. One apparatus system, two jobs: the margin names the source, the note
   under the verse says what the source is doing.

   It is set in the book's own face at apparatus scale rather than in Archivo. The
   rule on this site is that the serif is the book and the sans is the machine, and
   a note about Hebrew grammar written by the author is not machinery. What
   separates it from the verse above is SIZE, MEASURE and INK, which is how a set
   book separates a footnote from its text.

   🛑 role="note" ON A DIV, NOT <aside>. It WAS an <aside>, and the reasoning written
   here was that a screen reader gets a complementary landmark it can skip — which
   matters when there are a hundred and thirteen of them. That is backwards, and axe
   caught it (landmark-complementary-is-top-level, 2026-08-29): a landmark is not
   skipped one at a time, it is listed, so 113 of them turn the rotor into a wall of
   identical "complementary" entries and bury the six landmarks that mean something.
   role="note" is the ancillary-content role, announces the apparatus as a note, and
   adds no landmark at all. Do not put <aside> back.
   --------------------------------------------------------------------------- */
/* English-only apparatus while the page reads in Spanish — the chapter standfirst,
   the contents hooks and the verse notes. Set and cleared by js/spanish-text.js. */
[data-es-hidden] { display: none !important; }

.verse-note {
  margin: 1.15em 0 0;
  padding-top: .85em;
  border-top: 1px solid var(--rule);
  max-width: 46ch;
  color: var(--ink-soft);
  font-family: var(--serif);
  font-variation-settings: var(--lit-normal);
  font-size: var(--ap-lg);
  line-height: 1.62;
  letter-spacing: .002em;
}
.verse-note .vn-line { margin: 0; }
.verse-note .vn-line + .vn-line { margin-top: .7em; }
.verse-note em { font-style: italic; color: var(--ink); }
/* The two labels are the only thing here that is signage, so they are the only
   thing set in the apparatus sans — tracked, small, and in the chapter's accent so
   the eye can find the second line without reading the first. */
.vn-label {
  font-family: var(--ui);
  font-size: .82em; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent);
  margin-right: .7em;
  white-space: nowrap;
}
/* The full stop is set OUTSIDE the label. Inside it, the uppercase tracking put a
   letter-space between the last letter and the period and it read as a gap. */
.vn-label::after {
  content: '.';
  letter-spacing: 0;
}
/* Chapter X's prayer cites after almost every line; a hundred-word note between
   two lines of a prayer would break it into paragraphs. The prayer keeps its
   citations and takes no notes — content/verse-notes.js has none for those
   blocks, and this is the belt to that file's braces. */
.prayer-zone .verse-note { display: none; }

@media (max-width: 640px) {
  .verse-note { max-width: none; }
  .vn-label { display: block; margin-bottom: .2em; }
}


/* A citation standing alone under the line it belongs to, in the apparatus
   voice. It stays in the text column rather than hanging in the margin: the
   prayer in ch. X cites a source after almost every line, and in the margin
   the grid places each one against whichever paragraph happens to leave room
   beside it, which is not always the paragraph it belongs to. Under the line,
   tight, it is unambiguous. */
.block-ref {
  grid-column: 3 / 9;
  margin: -1.1em 0 1.5em;
  font-family: var(--ui);
  font-size: var(--ap-sm); font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-faint);
}
/* 🔴 A CITATION LINE CAN CARRY A VERSE NOTE NOW, 2026-09-09, AND IT NEEDS THIS RULE
   TO SURVIVE. A .verse-note under a `verse` block is nested INSIDE the verse's own
   container, so it inherits that container's column and never asked for one. Hung off
   a `ref` block it is a direct child of the chapter grid instead, and a grid child
   with no column lands in a single auto track.
   🛑 MEASURED, NOT GUESSED: 132px wide and 468px tall at 1440 — a sixty-word note set
   six steps deep in the margin, which is exactly the ribbon js/render.js's own comment
   says not to make. It takes the citation's column because it is the citation's note.
   ⚠️ margin-top: 0 IS DELIBERATE. .block-ref already carries 1.5em beneath it (≈15px
   at --ap-sm); the note's own 1.15em is measured at --ap-lg (≈17px) and would stack
   with it. Zero here leaves the same gap the note has under a verse. */
.block-ref + .verse-note { grid-column: 3 / 9; margin-top: 0; }
@media (max-width: 900px) {
  .block-ref,
  .block-ref + .verse-note { grid-column: 1 / 7; }
}

/* ============================================================================
   NARRATION-AWARE STATE — the read-along mark
   🔴 `.block-p.is-live { color: var(--ink) }` HAD BEEN DOING NOTHING SINCE THE
   REBUILD, AND IT COVERED 94% OF THE CUES. Measured 2026-09-09: a .block-p's base
   colour computes to var(--ink) already, so setting it again on .is-live changed
   nothing at all. 1,725 of the 1,841 cue-carrying blocks on the page are .block-p;
   only the 116 .verse-boxes had a visible state, because theirs moves a BORDER.
   The v1 rule this replaced was a real effect — `text-shadow: 0 0 22px accent@45%`
   — and it was deleted on purpose ("a weight shift on the rule, no glow"). The
   deletion was right and the replacement was half-written: it lit the live
   paragraph without ever darkening the others, so there was nothing to stand out
   FROM. The follow ENGINE was never broken — cues resolve, .is-live lands on the
   right element, the page scrolls. Only the mark was invisible.

   ⭐ SO THE UNREAD TEXT RECEDES; THE READ LINE IS NOT LIT. That is the whole idea,
   and it is why this is not a highlight. A highlighter adds a coloured object on
   top of type — the thing this site removes everywhere else (the v54 "tabs", the
   card's box, the glow above). Here nothing is added: the paragraph being read
   stays exactly the colour it always was, and its neighbours step back half a tone.
   It is the oldest trick in a printed book — a reader's own thumb over the page —
   and it costs one property.

   🛑 GATED ON html.is-narrating, WHICH MEANS ALL THREE OF: playing, Follow on, and
   not scrolled away (js/sync.js reflectNarrating). Without the gate a paused page
   would sit with nine tenths of the chapter dimmed and nothing reading it.
   ⚠️ CONTRAST MEASURED BEFORE SHIPPING, NOT ASSUMED — this dims BODY COPY, so it is
   the one place a soft ink could fail WCAG outright. --ink-soft against all twelve
   day stocks: worst 6.01:1. Against all six night stocks: worst 8.34:1. AA for body
   text is 4.5. Both clear it with room; the live line stays at 14.65:1 / 15.29:1.
   🛑 IF A NEW CHAPTER STOCK IS ADDED, RE-RUN THAT. A darker paper moves the worst case.
   ============================================================================ */
.verse-box.is-live { border-left-color: var(--ink); }
html.is-narrating .block-p { color: var(--ink-soft); }
/* the live line holds full ink — and it needs the same specificity as the recede
   above (0,2,1) or the recede wins the cascade and dims the paragraph being read */
html.is-narrating .block-p.is-live { color: var(--ink); }
.block-p { transition: color var(--dur-med) var(--ease); }
/* the recede is a state, not motion, so it survives reduced-motion — but it should
   not FADE in and out for someone who asked for no animation */
@media (prefers-reduced-motion: reduce) { .block-p { transition: none; } }

/* ⭐ THE READING LINE, 2026-09-16. The author, of option A on the sheet: "do it".
   A hairline in the margin beside the paragraph being read, filling top to bottom as
   the voice goes through it. It needs no word timings: js/sync.js writes --read, the
   fraction of this paragraph's cue span already heard, about four times a second.
   🛑 NOTHING TOUCHES THE TYPE. Weight, size and spacing all re-wrap the line; this is
   a drawn mark in the margin, in the same place the hover mark uses (css/polish.css),
   which stands down on the live paragraph so the two never show at once.
   The track is --rule, the fill is the chapter's --accent; it is decorative, and the
   receding colour above is still what carries the state. */
@property --read { syntax: '<number>'; inherits: true; initial-value: 0; }
.block-p.is-live { position: relative; transition: color var(--dur-med) var(--ease), --read 300ms linear; }
html.is-narrating .block-p.is-live::after {
  content: '';
  position: absolute; left: -.85em; top: .35em; bottom: .35em;
  width: 2px;
  background: linear-gradient(to bottom, var(--accent) calc(var(--read, 0) * 100%), var(--rule) 0);
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) { .block-p.is-live { transition: none; } }

@media (max-width: 900px) { .verse-box { grid-column: 1 / 7; } }

/* ---------------------------------------------------------------------------
   THE APPARATUS COLUMN — the note moves beside the verse, ≥1100px
   DECISION D1-B, 2026-08-29. The note was a block BETWEEN the quotation and the
   sentence that answers it: eight lines and ~210px of philology standing in the
   path of the prose. Measured at 1440px, columns 10–12 were empty for the entire
   book — 507px of every screen, all 1911 text elements set in columns 3–9 — while
   the citation was already hung out in the LEFT margin. The page was half doing
   this already.
   It reads as a printed critical edition now: citation in the left margin,
   quotation in the measure, apparatus in the right margin, and the prose resumes
   immediately instead of over the top of a footnote. The editorial call underneath
   it is that a verse note is AVAILABLE reading, not REQUIRED reading — the author's
   answer on the sheet. Put it back under the verse only if that answer changes.

   🛑 THE RULE MOVED OFF THE BOX AND ONTO THE QUOTATION. The row is as tall as the
   taller of its two columns, so with border-left on .verse-box a one-line verse
   with a full note got a 100px accent rule with nothing beside it — "Jesus wept."
   is the worst case in the book. On .verse-text the rule measures the quote, which
   is the thing it is marking. Do not move it back to the box.

   Phones and tablets below 1100px keep exactly what they had: note under verse,
   citation under note. There is no margin down there to move anything into, and
   1100px is the same breakpoint the citation already uses to leave the margin.
   --------------------------------------------------------------------------- */
@media (min-width: 1100px) {
  .verse-box {
    /* 3 / 13 spans the reading measure AND the aside column (.col-aside is 10/13),
       and subgrid re-inherits the page's own tracks so nothing is re-derived. */
    grid-column: 3 / 13;
    display: grid;
    grid-template-columns: subgrid;
    column-gap: var(--step);
    align-items: start;
    border-left: 0;
    padding-left: 0;
  }
  /* subgrid line 1 is page line 3, so 1/7 is the reading measure and 8/11 is
     page columns 10–12 — the aside column, unchanged. */
  .verse-box > .verse-text {
    grid-column: 1 / 7;
    grid-row: 1;
    padding-left: clamp(1rem, 1.6vw, 1.6rem);
    border-left: 2px solid var(--accent);
    transition: border-color var(--dur-med) var(--ease);
  }
  /* absolutely positioned, so its grid placement only sets the box it hangs off:
     one step left of the reading measure, which is where it already hung. */
  .verse-box > .verse-ref { grid-column: 1 / 7; grid-row: 1; }
  .verse-box > .verse-note {
    grid-column: 8 / 11;
    grid-row: 1;
    margin: 0;
    padding-top: .3em;
    border-top: 0;
    max-width: none;
    /* --ap-lg, NOT --ap-md. css/site.css's own scale says --ap-md is for captions
       and running times and that "anything carrying a sentence takes --ap-md or
       --ap-lg"; a verse note is two sentences of the author's prose. It went to
       --ap-md on the first pass of this block purely because the column is narrow,
       which is the wrong reason to drop a step. The column is 313px at 1440px and
       carries 13px at 1.66 comfortably. */
    font-size: var(--ap-lg);
    line-height: 1.66;
  }
  /* the narration state follows the rule wherever the rule is */
  .verse-box.is-live { border-left-color: transparent; }
  .verse-box.is-live > .verse-text { border-left-color: var(--ink); }
  /* the prayer takes no notes and wants none of this: chapter X cites after almost
     every line, and a ten-track row per citation would set the prayer as a table. */
  .prayer-zone .verse-box {
    grid-column: 3 / 9;
    display: block;
    padding-left: clamp(1rem, 1.6vw, 1.6rem);
    border-left: 2px solid var(--accent);
  }
  .prayer-zone .verse-box > .verse-text { padding-left: 0; border-left: 0; }
  .prayer-zone .verse-box.is-live { border-left-color: var(--ink); }
}
@supports not (grid-template-columns: subgrid) {
  @media (min-width: 1100px) {
    .verse-box { grid-template-columns: repeat(10, 1fr); }
  }
}

/* ============================================================================
   PLATES AND FIGURES
   Two kinds. A full-bleed plate breaks the grid entirely and carries its caption
   underneath, on paper. An inline figure sits in the text column with the caption
   beneath it. Neither ever has type laid over it.
   ============================================================================ */
.plate {
  grid-column: 1 / -1;
  margin: clamp(2.2rem, 4vw, 3.6rem) calc(var(--edge) * -1);
  display: block;   /* not subgrid: the negative margin would misalign the tracks */
}
.plate-frame {
  height: clamp(320px, 62vh, 680px);
  overflow: hidden;   /* crops the parallax scale; without it the image paints over the caption */
}
/* On a phone `62vh` made the frame TALLER than it is wide (390 × 523), so a 3:2
   photograph was being centre-cropped to a portrait sliver and most of the
   composition was thrown away. Below the grid breakpoint the plate is driven by
   aspect-ratio instead, and stays landscape. */
@media (max-width: 900px) {
  .plate-frame { height: auto; aspect-ratio: 4 / 3; }
  .hero-plate img { height: auto; aspect-ratio: 4 / 3; }
}
/* descendant, not child: the image sits inside a <picture> since 2026-08-29 (AVIF
   srcset), so `.plate-frame > img` stopped matching and the plates lost their crop. */
.plate-frame img {
  width: 100%; height: 100%; object-fit: cover;
  /* the base scale lives here, not only in js/motion.js. Without it a plate that
     has not yet been on screen sits at scale(1) and visibly snaps to 1.07 the
     moment the parallax first touches it. */
  transform: scale(1.07);
}
/* the caption comes back inside the page margin, on the grid, under the picture */
.plate-caption {
  grid-column: 1 / -1;
  padding: .85rem var(--edge) 0;
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  column-gap: var(--step);
}
@media (max-width: 900px) {
  .plate-caption { grid-template-columns: repeat(6, 1fr); }
}
.plate-caption .plate-text {
  grid-column: 3 / 9;
  font-style: italic; color: var(--ink-soft); font-size: var(--ap-xl); line-height: 1.5;
  max-width: 54ch;
}
@media (max-width: 900px) {
  .plate-caption .plate-text { grid-column: 1 / 7; }
}

/* inline figure inside the reading flow */
.img-slot { grid-column: 3 / 9; margin: 2rem 0; position: relative; }
/* the control is the picture, and it is a real <button> — it must not look like
   one. Everything a UA gives a button is stripped; the focus ring is the site's. */
.slot-open {
  display: block; width: 100%;
  padding: 0; margin: 0; border: 0; background: none;
  font: inherit; color: inherit; text-align: inherit;
  cursor: zoom-in;
}
.slot-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.img-slot img { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; }
.img-slot figcaption {
  font-family: var(--ui); font-size: var(--ap-md); line-height: 1.6;
  color: var(--ink-soft); padding-top: .6rem;
  letter-spacing: .01em;
}
/* Empty slots render nothing at all now (js/render.js) — these two rules are gone
   with them. An unfilled slot used to leave a rule and the words "Image forthcoming"
   on a published page. If a placeholder is ever wanted again, put it back here. */
@media (max-width: 900px) { .img-slot { grid-column: 1 / 7; } }

/* ---------- the margin apparatus ----------
   🛑 .aside-note IS GONE, 2026-09-07 — three rules for a margin note that was never
   written into any renderer. The apparatus that actually ships in the aside column is
   the thread note (.ntr) and the plate caption; both are below. A new margin note is
   a new component, not this one revived. */

/* the back of the book, in both contents surfaces. It is set quieter than a chapter
   because it is apparatus, not narrative — a reader scanning for chapter VII should
   not have to step over three rows that look like chapters. */
.toc-row.toc-back .toc-num { color: var(--ink-faint); font-size: .9em; }
.toc-row.toc-back .toc-title { font-style: italic; }
.toc-row.toc-back .toc-dur { font-variant-numeric: normal; }
.ntr-sep {
  display: block; height: 1px; margin: .6rem 0;
  background: var(--rule-strong);
}
.ntr.ntr-back .ntr-title { font-style: italic; color: var(--ink-soft); }
.ntr.ntr-back:hover .ntr-title, .ntr.ntr-back:focus-visible .ntr-title { color: var(--accent); }
.ntr.ntr-back .ntr-num { color: var(--ink-faint); }

/* ============================================================================
   THE INDEX OF SCRIPTURE
   Built at render time from content/chapters.js (js/render.js renderScripture).

   THIS IS THE ONE PLACE THE PAGE USES ITS OWN WIDTH. Measured 2026-08-29: at
   1440px the book set every one of its 1911 text elements in columns 3–9 and left
   columns 1–2 and 10–12 completely empty — 507px of the page, on every screen, for
   the whole book. That is right for prose, because a measure is a measure. It is
   wrong for an index: an index is a lookup, it is read by scanning and not by the
   line, and the one thing it wants is columns. So this section runs 1 / -1 and lets
   the entries pack into as many columns as the screen can hold.
   ============================================================================ */
.si-head {
  grid-column: 1 / -1;
  display: grid; grid-template-columns: subgrid;
  padding-bottom: clamp(1.6rem, 3vw, 2.6rem);
  border-bottom: 1px solid var(--rule-strong);
}
@supports not (grid-template-columns: subgrid) {
  .si-head { grid-template-columns: repeat(12, 1fr); column-gap: var(--step); }
}
.si-head .chapter-num { grid-column: 1 / 3; padding-top: .55em; }
/* ============================================================================
   THE BARE HEAD — 🛑 A NAME LEFT STANDING HAS TO BE A TITLE
   .is-bare began on 2026-08-30 (D18-B) as "the head with no standfirst": the Index
   of Scripture's title was deleted and its margin label became the <h2>. The Lexicon
   followed (D19-A) and Names and Places last (D20-A), so all four back-matter heads
   are one shape now.
   ⚠️ AND FOR TWO OF THOSE ROUNDS IT WAS WRONG, in a way that only the author caught:
   *"names and places probably needs a proper title after. bigger text etc."*
   `.chapter-num` is `--ap-sm`, **10.24px** — right for a label whispering in the
   margin BESIDE a 34px title, and absurd as the only title a section has. It is the
   identical mistake this file already records one screen down, where the Names
   index's letter headings inherited `.si-book-name` and set a single letter at 10px.
   **Reuse a component for what it does, not for what it is called** — twice now.
   So a bare head sets as what it is: the section's title, in the book's serif, at
   the size a chapter standfirst uses. It keeps the accent and the tracking, so it is
   still recognisably the same family as the labels that are still labels.
   Contrast: --accent on the back-matter paper, 5.23:1, measured.
   ============================================================================ */
.si-head.is-bare, .lex-head.is-bare, .thread-head.is-bare { margin-bottom: clamp(.6rem, 1.4vw, 1.1rem); }
/* ⚠️ .thread-head JOINED THIS RULE 2026-09-07 and was the last one outside it. It
   is the fifth back-matter section and it had kept the old shape — a 10px label for
   its name and a second 10px label counting its own rows. Same component now. */
.si-head.is-bare .chapter-num,
.lex-head.is-bare .chapter-num,
.thread-head.is-bare .chapter-num {
  padding-top: 0; margin: 0;
  grid-column: 1 / 8;
  font-family: var(--serif); font-variation-settings: var(--lit-mid);
  font-size: clamp(1.6rem, 3.4vw, 2.4rem);
  font-weight: 350; line-height: 1.08;
  letter-spacing: .01em; text-transform: none;
  color: var(--accent);
}
@media (max-width: 900px) {
  .si-head.is-bare .chapter-num,
  .lex-head.is-bare .chapter-num,
  .thread-head.is-bare .chapter-num { grid-column: 1 / 7; }
}
/* 🛑 .si-standfirst IS GONE, 2026-09-07 — four rules for a head that has been bare
   since the apparatus sections dropped their standfirsts. js/render.js emits
   `.si-head.is-bare` and `.lex-head.is-bare` only; nothing has ever emitted this. */
@media (max-width: 900px) {
  .si-head .chapter-num { grid-column: 1 / 7; }
}

/* columns rather than a breakpoint per case, the same reasoning as .lex-plates:
   the content decides how many columns fit. A book never breaks across columns. */
.si-books {
  grid-column: 1 / -1;
  margin-top: clamp(1.8rem, 3vw, 2.8rem);
  columns: 4 15rem;
  column-gap: clamp(1.6rem, 3vw, 3rem);
}
.si-book { break-inside: avoid; margin: 0 0 1.6rem; }
.si-book-name {
  font-family: var(--ui); font-size: var(--ap-sm); font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink);
  margin: 0 0 .5em; padding-bottom: .35em;
  border-bottom: 1px solid var(--rule-strong);
}
.si-list { list-style: none; margin: 0; padding: 0; }
.si-cite { margin: 0; }
/* the reference on the left, the chapter numeral hard right, and a rule between —
   which is what the page number does in the index of a printed book, and the reason
   the row is worth the full column width instead of hugging its text. */
.si-cite a {
  display: flex; align-items: baseline; gap: .5em;
  min-height: 32px; padding: .18em 0;
  color: var(--ink-soft); text-decoration: none;
  transition: color var(--dur-fast) var(--ease);
}
.si-ref {
  font-family: var(--serif); font-variation-settings: var(--lit-normal);
  font-size: .92rem; line-height: 1.35;
}
/* the leader: the dotted run a printed index uses to carry the eye to the number */
.si-cite a::after {
  content: ''; flex: 1 1 auto; align-self: center;
  height: 1px; min-width: .8em;
  background: repeating-linear-gradient(to right,
    var(--rule-strong) 0 1px, transparent 1px 4px);
  opacity: .7;
}
.si-ch {
  font-family: var(--serif); font-variation-settings: var(--lit-normal);
  font-variant-numeric: lining-nums tabular-nums;
  font-size: .78rem; letter-spacing: .04em;
  color: var(--ink-faint);
  flex: 0 0 auto;
  /* the leader is a ::after and is therefore the LAST flex item by default, which put
     the dots after the numeral instead of between it and the reference. One step of
     order puts the numeral last, where a page number goes. */
  order: 1;
}
.si-cite a:hover, .si-cite a:focus-visible { color: var(--ink); }
.si-cite a:hover .si-ch, .si-cite a:focus-visible .si-ch { color: var(--accent); }
.si-ref-bk { /* visible at every width except the phone rule below */ }

/* ============================================================================
   THE VERSE REFERENCES ON A PHONE — 🛑 STOP PRINTING THE BOOK 151 TIMES
   2026-08-30 (D20-C), the author: *"the verse references can you redesign those for
   mobile."* Measured at 402px: `.si-books` collapsed to one column, so each citation
   became a **362px-wide row** carrying "Genesis 3:7", a dotted leader stretched
   across most of the screen, and a roman numeral hard right — and Genesis alone ran
   **521px tall**. The leader is the right device on a 15rem column, where it carries
   the eye a short distance to a number. Across a whole phone screen it is a rule
   with two words tied to its ends.
   And the row was repeating its own heading: **"Genesis" prints under GENESIS**, 151
   times, using most of the width to do it.
   So on a phone the list stops being rows and becomes a **wrapped run of chips** —
   `3:7 IX`, `3:8 II`, `4:5 II` — which is the shape the Names index already uses for
   the same job one section down, and the shape a printed index uses when the entries
   are short. Genesis goes from 521px to about a fifth of that.
   ⚠️ THE BOOK NAME IS HIDDEN, NOT REMOVED. `.si-ref-bk` takes the visually-hidden
   clip-path, NOT `display:none` — a screen reader moving through this list out of
   context still hears "Genesis 3:7, chapter IX", which is the whole accessible name
   of the link. Removing it from the tree to save 60px would be trading a real user
   for a layout.
   ============================================================================ */
@media (max-width: 900px) {
  .si-books { columns: auto; column-gap: 0; }
  .si-book { margin-bottom: 1.4rem; }
  .si-list { display: flex; flex-wrap: wrap; gap: 0 .55rem; }
  .si-cite a {
    display: inline-flex; align-items: baseline; gap: .3em;
    min-height: 44px; padding: 0 .1em;   /* 32px was the dense-list exemption; a
                                            chip is not a dense row, so it clears */
  }
  /* the leader has nothing left to carry the eye across */
  .si-cite a::after { display: none; }
  .si-ref-bk {
    position: absolute; width: 1px; height: 1px;
    margin: -1px; padding: 0; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap;
  }
  .si-ref { font-size: 1rem; }
  .si-ch { font-size: .82rem; color: var(--accent); }
}
.si-cite a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* a citation is a 32px row, under the 44px target minimum, and it is deliberate:
   WCAG 2.2 SC 2.5.8 exempts targets in a sentence or a dense list of links where
   spacing them out would change the meaning of the list. An index IS that list.
   The offset spacing below keeps adjacent rows from being mis-tapped. */
.si-cite + .si-cite { border-top: 1px solid color-mix(in srgb, var(--rule) 60%, transparent); }
@media (max-width: 640px) {
  .si-books { columns: 1; }
  .si-cite a { min-height: 44px; }   /* one column, so there is room to be generous */
}

/* ============================================================================
   LIGHTBOX
   ============================================================================ */
#lightbox {
  position: fixed; inset: 0; z-index: 980;
  background: rgba(14, 12, 9, .94);
  display: flex; align-items: center; justify-content: center;
  padding: clamp(1rem, 4vw, 3rem);
}
#lightbox[hidden] { display: none; }
#lightbox img { max-width: min(94vw, 1280px); max-height: 88vh; object-fit: contain; }
#lightbox .sheet-close { position: fixed; top: 1.1rem; right: 1.1rem; color: #EDE9E0; }

/* ============================================================================
   SHEETS / MODALS / TOAST — paper objects, not glass
   ============================================================================ */
.sheet {
  position: fixed; z-index: 960;
  background: var(--paper);
  color: var(--ink);
  border-left: 1px solid var(--rule-strong);
  transition: transform var(--dur-med) var(--ease);
  box-shadow: -24px 0 60px -30px rgba(0,0,0,.35);
  /* NOTE: .sheet here is the slide-over panel (index.html <div class="sheet"
     role="dialog">). It was an <aside> until 2026-08-29: ARIA does not allow
     role="dialog" on <aside>, whose complementary role is not overridable, so a
     screen reader could announce a modal as a complementary landmark instead.
     site.css uses .sheet for the page grid; these never appear on the same
     element, but keep that in mind before adding a bare .sheet rule anywhere. */
  display: block;
}
.sheet[hidden] { display: block; visibility: hidden; pointer-events: none; }
.sheet-inner { padding: 2rem 1.6rem 2.6rem; max-height: 100vh; overflow-y: auto; }
.sheet-close { position: absolute; top: .7rem; right: .7rem; font-size: 1.1rem; }

@media (max-width: 720px) {
  .sheet { left: 0; right: 0; bottom: 0; border-left: 0; border-top: 1px solid var(--rule-strong); transform: translateY(100%); }
  .sheet:not([hidden]) { transform: translateY(0); }
  .sheet-inner { max-height: 82vh; }
}
@media (min-width: 721px) {
  .sheet { top: 0; right: 0; bottom: 0; width: min(420px, 92vw); transform: translateX(100%); }
  .sheet:not([hidden]) { transform: translateX(0); }
}

.sheet h2 {
  font-variation-settings: var(--lit-mid);
  font-weight: 400; font-size: 1.75rem; line-height: 1.1; margin-bottom: .7em;
  letter-spacing: -.015em;
}
.sheet p { margin-bottom: 1em; line-height: 1.62; font-size: .96rem; color: var(--ink-soft); }

/* A second-level heading inside a sheet. The sheet's h2 is 1.75rem display; this is
   apparatus voice, so it takes the sans and the same caps treatment the chapter
   numbers and plate captions use, with a rule above it to break the panel in two. */
.sheet h3.about-sub {
  font-family: var(--ui); font-size: var(--ap-sm); font-weight: 600;
  letter-spacing: .18em; text-transform: uppercase; color: var(--ink-faint);
  margin: 1.9em 0 .8em; padding-top: 1.4em; border-top: 1px solid var(--rule);
}
/* The one line in the panel that is spoken TO the reader rather than about the book.
   Set in the book's italic and at full ink so it does not read as more apparatus. */
.about-thanks {
  font-style: italic; color: var(--ink) !important;
  border-left: 2px solid var(--accent); padding-left: .9em;
}
/* 🛑 44px on BOTH axes, from 2026-08-30 (D15-G). Measured at 402px: this link was
   351 x 31 — full width, and eight pixels short on height. It sits directly above
   the Share button inside a sheet, which is the worst place on the site for a
   thumb to land between two things. Round fourteen's lesson was that a height-only
   check is half a check; this one had neither axis checked. */
.about-links a {
  display: flex; align-items: center; min-height: 44px;
  margin: .25em 0; text-decoration: underline; text-underline-offset: 3px;
}

/* Fine print at the foot of the About panel. Apparatus voice and quiet, but the link
   still has to clear a 44px target and take a visible focus ring — an accessibility
   statement that is itself hard to reach would be a poor advertisement for one. */
.about-fineprint {
  margin: 1.9em 0 0; padding-top: 1.2em; border-top: 1px solid var(--rule);
  font-family: var(--ui); font-size: var(--ap-md); letter-spacing: .04em;
}
.about-fineprint a {
  display: inline-flex; align-items: center; min-height: 44px;
  color: var(--ink-faint); text-decoration: underline; text-underline-offset: 3px;
}
.about-fineprint a:hover, .about-fineprint a:focus-visible { color: var(--ink); }
/* The publishers' Scripture notices (2026-09-18). Required wording, so it is set as
   quiet apparatus rather than cut down; the colour is the sheet's own faint ink. */
.sheet p.about-notice {
  font-family: var(--ui); font-size: var(--ap-md); line-height: 1.55;
  color: var(--ink-faint); margin-bottom: .8em;
}

/* ============================================================================
   THE THREAD — a closing section, not a sheet
   Thirteen plants and their payoffs, laid out on the same 12-column grid the
   chapters use: the two chapter numerals hang in the margin column, the note sits
   in the reading measure. See content/thread.js for the data and the sourcing rule.
   ============================================================================ */
#thread { padding-block: clamp(3rem, 8vh, 6rem); }
.thread-head {
  grid-column: 1 / -1;
  display: grid; grid-template-columns: subgrid;
  padding-bottom: clamp(1.6rem, 3vw, 2.6rem);
  border-bottom: 1px solid var(--rule-strong);
}
@supports not (grid-template-columns: subgrid) {
  .thread-head { grid-template-columns: repeat(12, 1fr); column-gap: var(--step); }
}
/* 🛑 .thread-count IS GONE, 2026-09-07, WITH ITS MARKUP. It printed "13 threads"
   beside the section's own name, both at 10px tracked uppercase — a second label
   where there should have been a title, and a list counting itself out loud. The
   same ruling closed "11 works" on the Sources head (D20-A) and "Ten chapters" on
   the contents. The head now uses the shared `.is-bare` rule above. */

/* Each entry is a native <details>. Native rather than scripted because the
   keyboard behaviour, the ARIA and the find-in-page expansion are all free and
   all correct, and because a closing section is the last place to be shipping
   bespoke disclosure logic. */
.thread-item {
  grid-column: 1 / -1;
  display: grid; grid-template-columns: subgrid;
  border-bottom: 1px solid var(--rule);
}
@supports not (grid-template-columns: subgrid) {
  .thread-item { grid-template-columns: repeat(12, 1fr); column-gap: var(--step); }
}

/* display:grid takes the summary out of list-item display, which removes the
   default triangle in every engine; Safari needs its own pseudo killed too. The
   +/− lives on .thread-name::after instead, in the type rather than beside it. */
.thread-summary {
  grid-column: 1 / -1;
  display: grid; grid-template-columns: subgrid;
  align-items: start;
  padding: clamp(1.1rem, 2.4vw, 1.6rem) 0;
  min-height: 44px;
  cursor: pointer;
  list-style: none;
}
@supports not (grid-template-columns: subgrid) {
  .thread-summary { grid-template-columns: repeat(12, 1fr); column-gap: var(--step); }
}
.thread-summary::-webkit-details-marker { display: none; }
.thread-summary::marker { content: ''; }
.thread-summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------------------------------------------------------------------------
   THE SPAN RAIL — the margin column stops saying "IV → VI" and draws it
   Ten ticks on one scale, the same scale in every row, with the span between this
   entry's two chapters inked in. The section is about distance, and a rail is what
   distance looks like; the arrow made the reader subtract. Every tick is positioned
   from --n (1…10) so the geometry is one formula and cannot drift between the bar
   and the marks it connects.
   It is aria-hidden and a visually-hidden sentence carries the same fact in words —
   ten absolutely-positioned <i>s are not something to make a screen reader parse.
   --------------------------------------------------------------------------- */
.thread-span { grid-column: 1 / 3; padding-top: .5em; }
.thread-rail {
  position: relative; display: block;
  height: 13px;
  margin-right: .8em;
}
.thread-rail .thr-tick, .thread-rail .thr-bar { position: absolute; }
/* (n − 1) / 9 puts tick 1 at 0% and tick 10 at 100%, so the bar and the marks it
   connects are one formula and cannot drift apart. */
.thr-tick {
  top: 2px; left: calc((var(--n) - 1) / 9 * 100%);
  width: 1px; height: 7px; margin-left: -.5px;
  /* --rule-strong is .28 alpha and measures 1.8:1 on the darkest paper stock; the
     scale disappeared on chapters II and VIII. 48% is the same mix the player's
     chapter rail already uses for the same reason and clears 3:1 there. */
  background: color-mix(in srgb, var(--ink) 48%, transparent);
}
.thr-tick.is-under { background: color-mix(in srgb, var(--accent) 45%, transparent); }
.thr-tick.is-plant, .thr-tick.is-payoff {
  top: 0; height: 11px; width: 1.5px; background: var(--accent);
}
/* the payoff is the end the book arrives at, so it is the end that is filled in */
.thr-tick.is-payoff::after {
  content: ''; position: absolute; left: 50%; top: 3px;
  width: 5px; height: 5px; margin-left: -2.5px;
  border-radius: 50%; background: var(--accent);
}
.thr-bar {
  top: 5px; height: 1.5px;
  left: calc((var(--from) - 1) / 9 * 100%);
  width: calc((var(--to) - var(--from)) / 9 * 100%);
  background: var(--accent);
}
/* The numerals name what the rail shapes, and they name it from the FAR END of the
   row — decision D4-B, 2026-08-29. Hung from their own ticks they collided outright
   on neighbouring chapters (VII → VIII set as VIIVIII); stacked under the rail they
   left the row's other 900px empty. Hard right they cannot collide with ticks they
   are no longer under, and the row reads the way an index reads: name, leader, span.
   .si-cite in the Index of Scripture is the same three parts in the same order — the
   leader below is that leader, and the two sections are deliberately one language. */
.thr-legend {
  flex: 0 0 auto;
  font-family: var(--serif); font-variation-settings: var(--lit-normal);
  font-variant-numeric: lining-nums tabular-nums;
  font-size: var(--ap-md); font-weight: 600;
  letter-spacing: .12em; line-height: 1;
  color: var(--accent);
  white-space: nowrap;
}
.thr-legend .thr-to {
  font-style: normal; color: var(--ink-faint);
  margin: 0 .45em; letter-spacing: 0;
}
/* the dotted run that carries the eye from the thread's name to its span. Identical
   in build to .si-cite a::after; it is an element rather than a pseudo because the
   +/− already owns .thread-name::after. */
.thread-leader {
  flex: 1 1 auto; align-self: center;
  min-width: 1.2em; height: 1px;
  background: repeating-linear-gradient(to right,
    var(--rule-strong) 0 1px, transparent 1px 4px);
  opacity: .7;
}

/* the row's full remaining width, not a third of it: name left, leader, span right */
.thread-head-body {
  grid-column: 3 / 13;
  display: flex; align-items: baseline; gap: .75em;
}
.thread-name {
  font-variation-settings: var(--lit-mid);
  font-weight: 400; font-size: clamp(1.15rem, 2vw, 1.45rem);
  line-height: 1.16; letter-spacing: -.012em; margin: 0;
  transition: color var(--dur-fast) var(--ease);
}
/* 🛑 THE ALT TEXT IS NOT OPTIONAL, 2026-08-29. Chrome exposes generated content to
   the accessibility tree, so `content: '+'` put a stray plus on the END OF THE
   HEADING NAME: all thirteen threads announced as «"Lift up my face" plus», and
   «… minus» once open. It is doubly wrong because <summary> already announces its
   own expanded/collapsed state — the glyph is for eyes only. The `/ ''` is CSS
   generated-content alt text: it draws the same and names nothing.
   Verified in the AX tree (Accessibility.getFullAXTree), not by reading. */
.thread-name::after {
  content: '+' / '';
  margin-left: .5em;
  font-family: var(--ui); font-weight: 400; font-size: .8em;
  color: var(--ink-faint);
}
.thread-item[open] .thread-name::after { content: '\2212' / ''; }   /* − */
.thread-summary:hover .thread-name, .thread-summary:focus-visible .thread-name { color: var(--accent); }

/* 🛑 ::details-content, ADDED 2026-08-29. Chrome 131 gave <details> a UA wrapper
   pseudo around its open content, and Chrome is what this was measured in. That
   wrapper — not .thread-detail — is the grid item, so .thread-detail's
   `grid-column: 3 / 11` was being resolved inside a plain block and the thread
   notes were setting in an 88px ribbon, two words to a line, in the section the
   book closes with. Measured at 1440px: 88px where 745 was intended.
   The pseudo re-inherits the tracks and hands them down, so .thread-detail's own
   placement below is the rule that runs again. Nothing sets content-visibility
   here: the UA uses it to hide the closed state and overriding it would open every
   thread at once. Engines without the pseudo simply never match this rule and
   .thread-detail is the grid item directly, which is the old behaviour intact. */
.thread-item::details-content {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
}
@supports not (grid-template-columns: subgrid) {
  .thread-item::details-content {
    grid-template-columns: repeat(12, 1fr); column-gap: var(--step);
  }
}
.thread-detail {
  grid-column: 3 / 11;
  padding-bottom: clamp(1.2rem, 2.6vw, 1.8rem);
}
@supports not (grid-template-columns: subgrid) {
  .thread-detail { grid-column: 3 / 11; }
}
.thread-note {
  font-size: 1rem; line-height: 1.62; color: var(--ink-soft);
  max-width: 58ch; margin: 0;
}
.thread-note em { font-style: italic; color: var(--ink); }
.thread-refs {
  margin: .7em 0 0;
  font-family: var(--serif); font-variation-settings: var(--lit-normal);
  font-size: var(--ap-sm); font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase; color: var(--accent);
}
/* The two chapter links used to live in the numeral row at ~22px tall, under the
   24px WCAG 2.2 target-size floor, in a row whose shared baseline could not take
   padding. Down here they are ordinary links and can be a full 44px. */
.thread-jump {
  margin: .9em 0 0;
  display: flex; flex-wrap: wrap; gap: 1.4em;
  font-family: var(--serif); font-variation-settings: var(--lit-normal);
  font-size: var(--ap-sm); font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase;
}
.thread-jump a {
  color: var(--ink-faint);
  border-bottom: 1px solid currentColor;
  min-height: 44px; display: inline-flex; align-items: center;
}
.thread-jump a:hover, .thread-jump a:focus-visible { color: var(--ink); }

@media (max-width: 900px) {
  .thread-head { grid-template-columns: repeat(6, 1fr); }
  .thread-item, .thread-summary { grid-template-columns: repeat(6, 1fr); }
  .thread-span { grid-column: 1 / 7; padding-top: 0; margin-bottom: .7rem; }
  .thread-rail { max-width: 220px; }
  .thread-item::details-content { grid-template-columns: subgrid; }
  @supports not (grid-template-columns: subgrid) {
    .thread-item::details-content { grid-template-columns: repeat(6, 1fr); }
  }
  .thread-head-body, .thread-detail { grid-column: 1 / 7; }
  /* on a phone the row is 362px wide and a leader between a wrapping title and the
     span is two dots; the span drops to its own line under the name instead. */
  .thread-head-body { flex-wrap: wrap; gap: .5em .75em; }
  .thread-leader { display: none; }
  .thr-legend { flex-basis: 100%; }
}

.modal {
  position: fixed; inset: 0; z-index: 970;
  display: flex; align-items: center; justify-content: center;
  padding: 1.5rem;
  background: rgba(14, 12, 9, .55);
}
.modal[hidden] { display: none; }
.modal-card {
  max-width: 480px; width: 100%;
  background: var(--paper); border: 1px solid var(--rule-strong);
  color: var(--ink);
  padding: clamp(1.6rem, 3vw, 2.4rem);
}
.modal-card h2 {
  font-variation-settings: var(--lit-mid);
  font-weight: 400; font-size: 1.9rem; margin-bottom: .6em; letter-spacing: -.015em;
}
.legend { margin: 1.3em 0; }
.legend li {
  display: flex; gap: .9em; align-items: baseline; padding: .4em 0;
  font-size: .92rem; color: var(--ink-soft);
  border-top: 1px solid var(--rule);
}
.legend li:first-child { border-top: 0; }
.legend kbd {
  font-family: var(--ui); font-size: var(--ap-sm); font-weight: 600;
  border: 1px solid var(--rule-strong); color: var(--ink);
  padding: .25em .5em; min-width: 2.2em; text-align: center;
  letter-spacing: .06em;
}
.completion-actions { display: flex; gap: .6rem; flex-wrap: wrap; }

/* 🔴 THE LAST BLACK SLAB ON THE SITE, AND IT WENT 2026-09-06.
   The author: "the resume button is obnoxiously loud." The button he can see is
   .btn-begin on the jacket — but the thing that ACTUALLY shouts on a return visit is
   this: a near-black rectangle that slides in over the paper, carrying a second
   Resume button a few inches under the first one. Every other control on this page
   was taken off --ink and put onto the accent over the last two rounds; this one was
   missed because it only appears to a reader who has been here before.
   ⭐ It is paper now, with the accent rule down its left edge — the same object the
   five-minute card is, at a smaller size, which is the right relationship: both are
   the page offering to take you somewhere you have already been interested in.
   🛑 THE HARD-CODED WHITES HAD TO GO WITH IT. rgba(255,255,255,.35) on a --ink
   ground is invisible reasoning in night mode, where --ink IS near-white: the toast
   would have drawn white-on-white. Tokens only in here now.
   ⚠️ AND ITS BUTTONS WERE 34px — under the 44px minimum, on the one control a
   returning reader is most likely to reach for on a phone. They are .btn's full
   height now. */
/* 🔴 AND THE CONTROL INSIDE IT WENT 2026-09-07 — the box, the sentence and the ×.
   The author: "the resume thing it has tiny text a tiny x and a resume buton its kinda
   ugly cna we make it better?"
   The container above was already right — paper, hairline, accent rule. What sat in it
   was the old vocabulary: a run-on sans sentence with the chapter title in quotation
   marks, then a BORDERED BOX labelled Resume. That box is the shape this page never
   uses, and it is the exact thing that was taken off the jacket's own resume button
   two days earlier ("i meant loud as in ugly lol" → .btn-begin, above).
   ⭐ SO IT IS .btn-begin. Not a copy of it, not a smaller cousin of it — the class. The
   ring says "this plays", the verb sets on one line, the place sets under it, and the
   whole thing is one target instead of a sentence with a button after it.
   🛑 ONE ACCENT RULE, NOT TWO. The toast keeps its own left edge and the button drops
   its border and its left padding, so the mark the reader sees belongs to the object
   they can click. Padding moves onto the button for the same reason.
   ⚠️ THE × IS THE ONE THING THAT MUST STAY QUIET — it is the "no thanks", and a
   dismiss drawn at the weight of the action it dismisses is a coin toss. --ink-faint
   at 4.5:1, 44px both axes, and it sits top-aligned so it never competes with the
   two-line stack for the optical centre. */
.toast {
  position: fixed; left: var(--edge); bottom: calc(var(--player-h, 134px) + 10px); z-index: 940;
  background: var(--paper);
  color: var(--ink);
  border: 1px solid var(--control-edge);
  border-left: 2px solid var(--accent);
  box-shadow: 0 6px 18px rgba(0, 0, 0, .16);
  padding: 0; display: flex; gap: 0; align-items: stretch;
  font-family: var(--ui); font-size: var(--ap-lg); max-width: min(460px, 92vw);
  opacity: 0; transform: translateY(10px);
  transition: opacity var(--dur-med) var(--ease), transform var(--dur-med) var(--ease),
              bottom var(--dur-med) var(--ease);
}
.toast:not([hidden]).is-shown { opacity: 1; transform: none; }
.toast[hidden] { display: none; }
.toast .btn-begin {
  border-left: 0;
  flex: 1 1 auto; min-width: 0;
  padding: .85em .6em .85em 1.05em;
}
/* the title can run long — "The word he kept rehearsing" — and a bookmark that pushes
   the dismiss off the edge is worse than one that wraps */
.toast .bb-label { white-space: normal; }
.toast-dismiss {
  align-self: flex-start;
  min-height: 44px;
  color: var(--ink-faint);
  font-size: 1.1rem; line-height: 1;
}
.toast-dismiss:hover, .toast-dismiss:focus-visible { color: var(--ink); }

/* ---------------------------------------------------------------------------
   THE OFFLINE NOTICE — the same object as the toast, a different job
   Added 2026-09-09 with js/offline.js. It reuses .toast wholesale — the slot above
   the bar, the paper, the accent left edge, the fade-and-rise — and changes only
   what is inside, because a second card that looks nearly like the first is how an
   interface starts to read as assembled rather than made.
   🛑 THE × IS TAKEN OUT OF THE FLOW. .toast lays its children out in a row; this
   card's text block wraps its action button underneath on a narrow phone, and a
   dismiss participating in that wrap ends up alone on a second line, left-aligned,
   under a sentence. Absolute in the corner, 44×44 (.btn-icon), and the padding-right
   below is its lane.
   ⚠️ NOT --accent FOR THE TITLE. The accent is a rule colour and rides the chapter
   token; as type it is a contrast gamble that changes ten times down the page.
   --ink and --ink-soft are the measured pair (5.03:1 day, 8.52:1 night at worst).
   --------------------------------------------------------------------------- */
.offline-note {
  align-items: center;
  flex-wrap: wrap;
  gap: .55rem .8rem;
  padding: .85em 46px .85em 1.05em;
  max-width: min(520px, 92vw);
}
.offline-note .on-body {
  flex: 1 1 13rem; min-width: 0;
  display: flex; flex-direction: column; gap: .12em;
}
.offline-note .on-title {
  font-size: var(--ap-md); font-weight: 600;
  letter-spacing: .07em; text-transform: uppercase;
  color: var(--ink);
}
.offline-note .on-text {
  font-size: var(--ap-lg); line-height: 1.45;
  color: var(--ink-soft);
}
/* flex-basis auto, not 0: the label carries a size in it and must not be squeezed
   to one word per line beside a paragraph */
.offline-note .on-action { flex: 0 1 auto; font-size: var(--ap-md); padding: .6em .95em; }
/* 🛑 .btn SETS display:inline-flex, WHICH BEATS THE UA SHEET'S [hidden]{display:none}
   — same specificity, author sheet wins. So `noteAction.hidden = true` left an empty
   bordered box sitting in the middle of the notice, on every state that has no action.
   Any .btn given a hidden attribute anywhere on this site has the same problem. */
.offline-note .on-action[hidden] { display: none; }
.offline-note .toast-dismiss { position: absolute; top: 0; right: 0; }

#gloss-card {
  position: absolute; z-index: 950; max-width: 320px;
  padding: 1em 1.1em;
  background: var(--paper); border: 1px solid var(--rule-strong);
  color: var(--ink);
  /* This holds the MEANING OF A HEBREW WORD. That is the book talking, not the
     interface, and it was set in the interface sans. */
  font-family: var(--serif);
  font-variation-settings: var(--lit-normal);
  font-size: .92rem; line-height: 1.55;
  box-shadow: 0 16px 40px -22px rgba(0,0,0,.5);
  opacity: 0; transform: translateY(4px);
  transition: opacity var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
  pointer-events: none;
}
#gloss-card.is-open { opacity: 1; transform: none; pointer-events: auto; }

/* ============================================================================
   PRAYER MOVEMENT + FIVE WORDS
   ============================================================================ */
.prayer-zone { grid-column: 3 / 9; }
.prayer-zone .block-p { font-style: italic; color: var(--ink-soft); }
.prayer-zone .divider-beat { grid-column: auto; }
#hold-btn {
  position: fixed; left: var(--edge); z-index: 930;
  bottom: calc(var(--player-h, 134px) + 10px);
  transition: bottom var(--dur-med) var(--ease);
}
#hold-btn[hidden] { display: none; }
@media (max-width: 900px) { .prayer-zone { grid-column: 1 / 7; } }

#five-words { padding-block: clamp(6rem, 22vh, 14rem); }
/* 🛑 THE LINE-HEIGHT AND THE PADDING ARE BOTH LOAD-BEARING, 2026-08-30 (D20-B).
   The author, on a phone: *"i cant see the y well on they will see his face."*
   He was looking at a CLIPPED DESCENDER, and the cause is the light effect below,
   not the type. `.is-lit` paints the words with `background-clip: text` and
   `-webkit-text-fill-color: transparent`, so the glyphs are painted ONLY inside each
   word's background box. Inherited line-height put that box at 31.3px under a 30.4px
   font — measured at 402px — which is shorter than the glyph's descender, so the
   tail of the y in "They" fell outside the paint area and **was not drawn at all**.
   ⚠️ IT LOOKS LIKE A FONT PROBLEM AND IS A PAINT-BOX PROBLEM. overflow was already
   `visible` and nothing was clipping; there was simply no gradient down there to
   paint with. Raising line-height alone does not fix it either — the line box grows
   but the inline-block's own background box does not. The padding is what extends
   the paint area past the baseline; the line-height is what stops the next line
   sitting in it when the sentence wraps. Both, or the bug comes back on one width. */
.fivewords-text {
  grid-column: 3 / 11;
  font-size: clamp(1.9rem, 5.4vw, 4.1rem);
  line-height: 1.16;
}
.fivewords-text .fw-word {
  display: inline-block; opacity: 0; transform: translateY(8px);
  padding-bottom: .16em;
  transition: opacity var(--dur-slow) var(--ease), transform var(--dur-slow) var(--ease);
}
.fivewords-text .fw-word.is-visible { opacity: 1; transform: none; }

/* THE LIGHT CROSSES THE LINE.
   The book's last sentence is a face being lit, so the line is lit — once, slowly,
   left to right, after every word has landed. A band of the chapter accent travels
   across the letterforms and leaves.

   It is clipped to the TEXT, so nothing moves and no box glows: the ink itself
   changes colour under a passing light. `color` stays set underneath and is only
   given up inside @supports, so a browser without background-clip:text renders the
   line in plain ink and loses nothing but the effect. It runs ONCE — forwards, no
   iteration count — because a light that keeps arriving is a barber's pole, and
   this one is supposed to have arrived. */
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .fivewords-text.is-lit .fw-word {
    background-image: linear-gradient(100deg,
      var(--ink) 0 42%,
      color-mix(in srgb, var(--accent) 85%, #fff) 50%,
      var(--ink) 58% 100%);
    background-size: 320% 100%;
    background-position: 100% 0;
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent; color: transparent;
    animation: fw-light 4200ms cubic-bezier(.35, .1, .3, 1) 1 forwards;
  }
}
@keyframes fw-light {
  from { background-position: 100% 0; }
  to   { background-position: 0 0; }
}
.fivewords-ref {
  grid-column: 3 / 11;
  display: block; margin-top: 1.6em;
  font-family: var(--serif); font-variation-settings: var(--lit-normal);
  font-size: var(--ap-md); font-weight: 500;
  letter-spacing: .16em; text-transform: uppercase; color: var(--accent);
  opacity: 0; transition: opacity var(--dur-slow) var(--ease);
}
.fivewords-ref.is-visible { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .fivewords-text .fw-word { opacity: 1; transform: none; transition: none; }
  .fivewords-ref { opacity: 1; transition: none; }
  /* belt and braces: motion.js already declines to add .is-lit here */
  .fivewords-text.is-lit .fw-word {
    background-image: none; -webkit-text-fill-color: currentColor;
    color: var(--ink); animation: none;
  }
}
@media (max-width: 900px) { .fivewords-text, .fivewords-ref { grid-column: 1 / 7; } }

/* ch.8's tearing rule — kept, but as a rule in the margin rather than a glow */
.tear-line {
  position: absolute; top: 0; bottom: 0; left: calc(var(--step) * -1);
  width: 1px; background: var(--accent);
  transform: scaleY(0); transform-origin: top;
  transition: transform 800ms var(--ease);
}
.tear-line.is-torn { transform: scaleY(1); }
@media (prefers-reduced-motion: reduce) { .tear-line { transform: scaleY(1); transition: none; } }

/* ============================================================================
   THE PLATES — the ribbon, 2026-09-04 (v55, replacing the v54 contact sheet)

   🔴 WHY THE PROOF SHEET WAS REPLACED, AND IT IS ARITHMETIC RATHER THAN TASTE.
   The author: "the shape of the photos doesnt fit the photos that well". Every
   one of the ten plates is 1408x768 — 1.833:1, wide landscape — and measured in
   a real browser, this is what each layout was actually showing of them:

        the turn (lab/turn.html)  @402   ratio 0.506  ->  28% of the picture
        the turn                  @1440  ratio 0.637  ->  35%
        concept B, the 2:3 ribbon                     ->  36%
        concept D, the 3:4 wall                       ->  41%
        v54, this file, the 3:2 proof sheet           ->  82%
        concept C, the 16:9 list                      ->  97%

   The proof sheet was the second best of them and still cut a fifth off every
   plate; the turn was throwing away three quarters. THE FRAME IS NOW 11:6 —
   1408:768 reduced — so the strip shows the whole width of the negative. About
   9% comes off the height to buy the drift, and §the img rule says why.

   ⭐ WHAT SURVIVED FROM v54, BECAUSE IT WAS RIGHT: the numeral is captioned
   UNDER the frame and never over the picture, the plate is each chapter's own
   opening slot read from blocks[0], the entrance is the site's once-only rise,
   and this is still NOT the contents. Two rulings still hold — "rows are
   separated by rules, not cards" and the contents "ALWAYS STARTS CLOSED" — and
   this sits above #contents and touches neither.

   ⭐ WHAT IS NEW: the strip is one row that travels sideways instead of two that
   sit still, so a plate can be big; the row under each frame is the numeral,
   the title and the runtime, which is the typographic grammar of the list
   concept the author kept asking for; and one caption under the whole ribbon
   prints the hook of whichever chapter the ribbon is holding.

   🛑 AND IT DOES NOT PIN. The pinned version spent 340vh — about 2,700px — of
   page height before chapter I, four times the ~700px of index that was already
   objected to on 2026-08-28, and it scroll-jacks. The travel is the reader's own
   thumb on a native scroller instead.
   ============================================================================ */
/* ⚠️ THE BOTTOM IS NOT THE TOP ANY MORE, 2026-09-05. The author: "and less space
   from that to next station." The rail already carries 5rem of its own padding
   underneath the plates for the sway, and the caption sits below that, so the
   section's own bottom padding was stacking a third gap on top of two — measured
   at 1440 the ribbon's last line and the Table of Contents were 141px apart while
   the caption sat only 18px off the strip it belongs to. Arithmetic, both sides:
   #plates' old bottom clamp topped out at 3.4rem and #contents' old top at the
   same, so the seam was 54.4 + 54.4 = 108.8px of nothing between a caption and
   the line it leads into. Measured after: 48px. The block reads as one thing now
   and the seam to #contents is where the space went. */
/* 🔴 THE TOP CAME IN TOO, 2026-09-05, same note as .hero-block's padding above —
   "too much space in between sections at the beggining". The jacket's bottom padding
   and this section's top padding were two gaps doing one job across the page's most
   important seam: 54 + 45 = 99px of nothing between the last line of the jacket and
   the first plate. Measured after: 37 + 27 = 64px. The bottom is unchanged and stays
   tiny — see the 2026-09-05 note above about the caption belonging to the strip. */
#plates { padding-block: clamp(1.2rem, 2.6vh, 2rem) clamp(.6rem, 1.4vh, 1rem); }

/* ---------- the rail ----------
   🛑 EVERY PART OF THE TRAVEL IS THE BROWSER'S OWN. overflow-x plus scroll-snap
   has been Baseline since 2019 and brings momentum, rubber-banding, trackpad
   gestures, keyboard scrolling and screen-reader scroll-into-view with it. A
   transform-driven strip re-implements all of that badly, and differently on
   every OS. */
.pl-rail {
  grid-column: 1 / -1;
  /* ⚠️ IT BLEEDS TO THE WINDOW AT EVERY WIDTH, AND `100vw` ALONE CANNOT DO IT.
     100vw counts the classic scrollbar, so on Windows the usual full-bleed trick
     hands you a horizontally scrolling document — and the usual patch for THAT,
     overflow-x:hidden on body, would kill every position:sticky in this book. So
     js/ui.js measures the scrollbar once and writes --sbw, and the sum below is
     exact on every platform. It is safe to depend on script here and nowhere
     else would be: js/render.js builds this entire page, so a reader without
     JavaScript has no ribbon to mis-lay-out.

     🛑 AND PULLING BACK BY --edge ALONE WAS NOT ENOUGH, WHICH IS WHY THIS IS A
     SUM AND NOT A CONSTANT. .section is capped at --shell (1440px), so on a
     1920 monitor a rail pulled back by only the page margin stopped 240px short
     of the glass and left a plate sliced off beside a field of blank paper —
     which reads as a bug rather than as a strip continuing.

     The margin spans the box to the viewport; the padding puts the first plate
     back on the text grid. Below --shell the two collapse to exactly --edge. */
  --pl-bleed: calc((100vw - var(--sbw, 0px) - 100%) / 2);
  margin-inline: calc(-1 * var(--pl-bleed));
  padding-inline: var(--pl-bleed);
  /* 🔴 AND scroll-padding CANNOT REUSE THAT SUM, WHICH IS THE SUBTLEST THING ON
     THIS PAGE. A percentage in margin or padding resolves against the CONTAINING
     BLOCK; a percentage in scroll-padding resolves against the SCROLLPORT — and
     the scrollport is now the full window, so `100%` there is 100vw and the whole
     expression collapses to zero. Measured: scroll-padding-inline computed to
     `calc(-50% + 960px)` at 1920, i.e. 0, the first plate snapped flush to the
     glass, and the ribbon lost its alignment with the text grid at every width.
     No error, and it looks almost right, which is why it nearly shipped.
     So this is a real length: --edge is correct below --shell and js/ui.js
     replaces it with the exact resolved padding at every width. */
  scroll-padding-inline: var(--pl-snap, var(--edge));
  display: flex;
  gap: clamp(.7rem, 1.4vw, 1.1rem);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  /* 🔴 proximity, NOT mandatory, 2026-09-05. The author: "can you make the scrll
     bar any easier to scroll right to left on mobile… sometimes gets stuck."
     `mandatory` means the rail is FORBIDDEN to come to rest anywhere except on a
     snap point. On a phone the plate is min(78vw, 340px) — about 313px of a 402px
     screen — so a short flick that does not carry past the halfway mark is not
     slowed down, it is dragged backwards to where it started, and the ribbon
     reads as refusing to move. That is the stick. `proximity` snaps a flick that
     lands near a plate and otherwise leaves the reader's thumb alone, which is
     the whole difference between a rail that helps and one that argues.
     🛑 SNAPPING DOES NOT GO AWAY, AND IT MUST NOT. It is how the lit state and
     the caption below know which plate the ribbon is holding (js/ui.js). */
  scroll-snap-type: x proximity;
  /* the rail owns the horizontal gesture; the PAGE keeps the vertical one, so a
     thumb that starts on a plate and moves down still scrolls the document
     instead of being swallowed. Naming both axes is what says that out loud —
     `pan-x` alone would have taken the page scroll away from anyone who happened
     to start their swipe on the ribbon. */
  touch-action: pan-x pan-y;
  /* ⚠️ THE VERTICAL PADDING IS THE ROOM THE SWAY AND THE ARRIVAL NEED, and
     overflow-y is pinned because an overflow value on one axis makes the other a
     scroll container too — left at `auto` a plate riding 44px high would hand the
     ribbon its own vertical scrollbar. 2rem clears the sway (±14px) and the
     entrance rise (14px) at once, and it is also the room a focus ring needs. */
  /* ⚠️ 5rem, AND IT IS THE SWAY'S TRAVEL PLUS THE ARRIVAL'S — RECOMPUTED
     2026-09-05 WHEN THE SWAY GREW. The largest amplitude in js/render.js's
     PLATE_SWAY is now 66px (it was 44), and the entrance still rises 14px:
     66 + 14 = 80px = 5rem exactly. Anything less clips a plate at the top of its
     wave, and it clips only the plates whose phase is high at that moment, so it
     looks like a rendering fault rather than like a missing rule.
     🛑 IF PLATE_SWAY CHANGES, THIS CHANGES. The two numbers are one decision
     living in two files; the comment in js/render.js says the same thing from the
     other end.
     overflow-y is pinned because an overflow value on one axis makes the other a
     scroll container too — left at `auto` a plate riding 66px high would hand the
     ribbon its own vertical scrollbar. */
  /* 🛑 5rem, BOTH ENDS, AND TWO ATTEMPTS TO SHRINK THE BOTTOM WERE MEASURED AND
     REVERSED ON 2026-09-05. THE AUTHOR'S NOTE IS REAL AND THIS IS NOT THE ANSWER
     TO IT — the answer is in css/components.css `.pl-hook`'s margin and in the
     `#plates` rule below, both of which came in on the same round.
     The author: "maybe it doesnt have to be so far bwellow the pics so it looks
     more ohesive."
     ⚠️ 80px OF PAPER UNDER THE STRIP IS NOT A MARGIN. IT IS THE SWAY'S CLEARANCE:
     the largest magnitude in PLATE_SWAY is 66 and the arrival rises another 14, and
     overflow-y is `hidden` (it has to be — an overflow value on one axis makes the
     other a scroll container too), so anything less clips a plate at the bottom of
     its wave.
     🛑 ATTEMPT 1, biasing the wave upward, cost 23% of the visible travel and freed
     no clearance at all, because the amplitudes alternate sign — see the note at
     @keyframes pl-sway.
     🛑 ATTEMPT 2 assumed the full +66 only happens at `cover 0%`, below the fold.
     MEASURED, it does not: at 1440 the section is short enough relative to a 900px
     window that the rail's own bottom edge is already on screen when the wave is at
     full extension, and 3.4rem clipped 12px off plate I. (At 402 the same test
     comes back 47px — which is exactly why this had to be measured at both widths
     and not at the one that agreed.)
     ⭐ SO THE REAL TRADE IS CLEARANCE AGAINST TRAVEL, and it belongs to the author:
     every 10px off the largest amplitude is 10px off this gap, and 20px off the
     motion he asked for in the last round. It is written up in docs/DECISIONS.md. */
  /* ⭐ 5rem → 6.5rem, 2026-09-06. The author, asked to rule on the clearance-versus-
     travel trade this file spent two measured attempts on: "more motion some room is
     fine." So the motion went up 1.35× (js/render.js PLATE_SWAY) and the room went up
     with it. 104px of padding against a largest amplitude of 89 leaves 15px of slack,
     which is what the old pair had (80 against 66). Neither number moves alone. */
  /* 🔴 ASYMMETRIC, 2026-09-07, AND THIS IS THE THIRD ATTEMPT AT THE AUTHOR'S GAP —
     the first one that works, because it stopped treating the two ends as one number.
     He asked for both at once: "theres more seperation from the chapter description
     text and the pictures than i would rpobably want though i really want the motion
     and maybe even more motion lol."
     ⭐ THE TOP NEEDS THE FULL AMPLITUDE AND THE BOTTOM NEVER DOES. At `cover 0%` the
     wave is fully extended and the rail's top edge is already on screen, so a −110
     plate genuinely rides 110px up and 7.25rem (116px) is the floor there. But the
     rail's BOTTOM edge — the edge the author is looking at, the one the caption hangs
     under — is not on screen until the section has climbed far enough that the wave
     has decayed. Swept at every scroll position over six viewports, the largest
     downward travel while that edge is visible is 48–64px, +14px for the arrival's
     rise = 78px worst case (900×900).
     🛑 AND THEN 5rem WAS RAISED TO 5.5, BECAUSE THE OFF-SCREEN CLIP IS NOT GOOD
     ENOUGH. At 80px the +111 and −110 plates clip 31px and 30px at the FULL extreme —
     which the sweep says is never on screen, and "never on screen" is a claim about a
     scroll model, not about pixels. So it was measured the other way instead: how much
     EMPTY paper each plate carries below its own title row (the rail stretches all ten
     to the tallest, and Chapter IV's title wraps, so the one-line plates carry 26–30px
     of nothing at the bottom). At 88px the worst clip is 23px against a 26px tail at
     every width from 320 to 1920 — so nothing a reader could see is ever cut, at any
     scroll position, on screen or off, and the argument no longer depends on the wave
     decaying in time. 8px of the 24 given back for a guarantee.
     ⚠️ THE TAIL IS THE BUDGET AND IT IS NOT A CONSTANT. It exists because .pl-plate is
     a flex item stretched to the tallest title. If a chapter title ever stops wrapping
     — or another starts — the tail changes and this number has to be re-measured.
     Both numbers measured, not scaled; the sweep is written out at js/render.js
     PLATE_SWAY.
     🛑 SO THE MOTION WENT UP 1.25× AND THE GAP CAME DOWN 24px IN THE SAME EDIT, and
     the two notes above — which say this trade cannot be won — were right about a
     SYMMETRIC padding and wrong about the object. Do not re-symmetrise it.
     ⚠️ margin-bottom went with it: .2rem under a 104px pad was rounding, and under a
     tightened one it is 3px of the gap being complained about. */
  padding-block: 7.25rem 5.5rem;
  overflow-y: hidden;
  scrollbar-width: none;
  /* ⭐ THE STRIP FADES AT THE EDGE IT CONTINUES OVER, 2026-09-05. The author: "any
     way to encourage them to go right?"
     A row of plates that stops flush at the glass reads as a row that ENDS at the
     glass. A picture dissolving into the paper is the oldest way in print of saying
     "this goes on", and it is the honest one here because it goes on. js/ui.js adds
     .is-scrolled / .at-end, so the fade is only ever on the side that has something
     left — the reader who has reached chapter X is not told there is more.
     ⚠️ BOTH SPELLINGS. Safari only dropped the -webkit- prefix for mask-image in
     15.4, and this book still serves phones older than that. */
  /* 🛑 THE RESTING STATE FADES THE RIGHT EDGE ONLY, AND THAT IS WHAT MAKES IT SAFE
     WITHOUT SCRIPT. At scroll zero there is nothing off the left, so a left fade
     would be a lie; the two classes below are added by js/ui.js as the rail moves,
     and a reader whose script never ran keeps the correct default. */
  --pl-fade: 72px;
  --pl-fade-l: 0px;
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--pl-fade-l),
                      #000 calc(100% - var(--pl-fade-r, var(--pl-fade))), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 var(--pl-fade-l),
              #000 calc(100% - var(--pl-fade-r, var(--pl-fade))), transparent 100%);
}
.pl-rail.is-scrolled { --pl-fade-l: var(--pl-fade); }
.pl-rail.at-end { --pl-fade-r: 0px; }
.pl-rail::-webkit-scrollbar { display: none; }

/* ⚠️ THE SECTION GIVES ITS TOP PADDING TO THE RAIL, 2026-09-05. .section carries
   clamp(3rem, 7vh, 6rem) top and bottom, and the rail directly inside it carries
   5rem of its own — which is not decoration but the clearance a plate needs to
   rise into. The two stacked put ~132px of paper between the jacket and the first
   photograph, which is the seam the author has now asked about twice. The rail's
   padding cannot go, so the section's does. Measured at 402: 132px becomes 80.
   🛑 THE BOTTOM IS UNTOUCHED. Below the strip sit the caption and the five-minute
   card, and that padding is the space between this section and the contents. */
#plates { padding-block-start: 0; }

.pl-plate {
  flex: 0 0 min(78vw, 340px);
  scroll-snap-align: start;
  display: block;
  min-width: 0;
  /* ⚠️ css/site.css sets `a { color: inherit }` and NOT text-decoration, so every
     component in this file turns the UA underline off for itself. Without this the
     chapter titles and the runtimes come out underlined, which reads as ten links
     in a row rather than ten captioned plates — the whole plate is the link. */
  text-decoration: none;
  transition: opacity var(--dur-med) var(--ease);
  /* 🔴 position:relative IS LOAD-BEARING. Each plate carries a .visually-hidden
     span, which is position:absolute. With the plate static, that span's
     containing block is the INITIAL containing block rather than anything inside
     the scroller — and an absolutely positioned box is clipped by an ancestor's
     overflow only when that ancestor is in its containing-block chain. Measured:
     ten 1px labels escaped the rail and pushed the DOCUMENT's scrollable width to
     2,939px inside a 402px viewport. The whole page slid sideways on a phone,
     WCAG 1.4.10 Reflow, from a span with width:1px.
     ⚠️ overflow:hidden on the rail does NOT fix it, and that is the tell. */
  position: relative;
}
/* 🛑 clamp() AND NOT min(), BECAUSE THE FIRST VERSION MADE THE PLATES SMALLER AS
   THE WINDOW GOT BIGGER. `min(31vw, 400px)` is 279px at 901 while the rule it
   replaces is capped at 340 — so dragging a window one pixel past the breakpoint
   shrank every plate by 61px. Nothing looks cheaper than a layout that goes
   backwards. The floor is the mobile cap, so the two rules meet exactly at 900
   and the plate only ever grows from there. */
@media (min-width: 901px) { .pl-plate { flex-basis: clamp(340px, 31vw, 400px); } }

/* 🛑 11/6 IS THE NEGATIVE, NOT A CHOICE. */
.pl-frame {
  position: relative;
  display: block;
  overflow: hidden;
  aspect-ratio: 11 / 6;
  background: var(--rule);
  border: 1px solid var(--rule);
}
.pl-frame picture { display: block; height: 100%; }

.pl-frame img {
  /* 🛑 max-width:none, AND WITHOUT IT NONE OF THE BELOW HAPPENS. The site-wide
     img { max-width: 100% } silently clamps the 110% back to the frame's width,
     so the picture has no overhang at all and the drift drags it clear of its
     own right edge. Measured before the fix: a 398px image in a 400px frame,
     and an 18px band of bare frame down the right of every plate. */
  max-width: none;
  /* ⚠️ 112 / -6, WAS 110 / -5, 2026-09-05. The author: "i wouldnt mind ore movement
     with the riboon. it should show off."
     The overhang and the drift are one sum (see the note below), so widening the
     travel means widening the picture — and every percent of width costs picture.
     At 112% the strip shows 89.3% of the negative's width instead of 90.9%: one and
     a half per cent, for a quarter more travel. That trade is worth making once.
     🛑 IT IS NOT WORTH MAKING TWICE. The rest of the "show off" is in the lit
     plate's lift, the arrival nudge and the edge fade — all of which cost no
     picture at all. Do not reach for this number again. */
  position: absolute; top: 0; left: -6%;
  width: 112%; height: 100%;
  object-fit: cover;
  filter: saturate(.80) contrast(1.02);
  transition: filter var(--dur-slow) var(--ease);
  translate: var(--dx, 0) 0;
}
/* ⚠️ THE OVERHANG AND THE DRIFT ARE ONE SUM AND GETTING IT WRONG SHOWS. A
   translate percentage resolves against the element, not its box: 4.6% of a
   112%-wide picture is 5.15% of the FRAME, and the overhang is 6% a side, so at
   either extreme there is still 0.85% of picture past the edge — about 3px on a
   desktop plate, and it does not depend on the plate's size because every number
   in the sum is a percentage.
   🛑 AND IT COSTS ABOUT 9% OF THE PICTURE'S HEIGHT. object-fit:cover in a box
   110% as wide as a frame already cut to the photograph's own ratio has to lose
   the difference somewhere and it loses it top and bottom. Nine percent off the
   height of a landscape frame is a trim; the 72% of the WIDTH the turn was
   cutting was not. Say which one this is rather than claim neither. */

/* ---------- the row under the frame: the list's grammar ----------
   ⚠️ THE NUMERAL IS UNDER THE FRAME AND NEVER OVER THE PICTURE. That is the v54
   ruling and it is worth more here than it was there: with nothing printed on
   the plate there is no scrim, so no contrast debt and not one square inch of
   the photograph covered up. The type does the labelling. */
/* ⭐ CENTRED UNDER THE FRAME, AND LARGER, 2026-09-05. The author: "Titles should
   be larger and centerred."
   🔴 THE THREE-COLUMN GRID WAS THE THING STOPPING IT. It was
   `2.1rem 1fr auto` — numeral, title, runtime — a ledger row, and a ledger row
   cannot be centred: with the runtime gone the third track collapses and the
   remaining two sit off to the left of a frame that is 340-400px wide, which
   reads as a caption that has slipped. So the row is a centred flex line now, the
   numeral and the title on one baseline with a middot between them, and the whole
   thing sits under the middle of its own picture.
   ⚠️ inline-flex WITH wrap, NOT nowrap. Chapter IV is "The Word He Kept
   Rehearsing" — 25 characters — and at the 340px mobile plate that does not fit
   on one line beside a numeral. Wrapping centres the second line under the first,
   which is what a centred caption should do; nowrap would have squeezed the type
   or overflowed the frame. */
.pl-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  gap: .1rem .55rem;
  padding: .8rem .4rem 0;
  text-align: center;
}
.pl-n {
  font-family: var(--serif);
  /* was --ap-lg (.82rem), then 1.05rem. A numeral that is half the size of the
     title beside it reads as a footnote marker, so this moves whenever .pl-t does
     — the two are one caption and the ratio between them is the caption's voice. */
  font-size: 1.2rem; font-weight: 500;
  letter-spacing: .12em; color: var(--accent);
  font-variant-numeric: lining-nums tabular-nums;
  transition: color var(--dur-med) var(--ease);
}
/* the separator is drawn, not typed, so it never lands in a copy-paste or in an
   accessible name — the row is aria-hidden anyway, and the real label is the
   .visually-hidden span js/render.js writes above it */
.pl-n::after {
  content: '\00B7';
  margin-left: .55rem;
  color: var(--ink-faint);
  letter-spacing: 0;
}
.pl-t {
  /* 🔴 --lit-mid → --lit-display, 2026-09-09. The author, of the plate titles and the
     caption under them: "the text could probably be more iconic and 3d style or
     something", and then "that sounds good" to this.
     🛑 IT IS NOT A SIZE CHANGE AND NOT A NEW FONT. Literata carries an optical-size
     axis, 7 to 72, and the site already names three settings (css/site.css): 12 for
     reading, 28 for mid, 60 for display. This title sets at 27px at 1440 and was
     drawn at opsz 28 — a TEXT cut, whose thicker hairlines, blunter serifs and wider
     default fitting exist so that small type survives. Enlarged to 27px it is
     reading type made big, which is exactly the flatness he can see. opsz 60 is the
     same family drawn for display: finer hairlines, sharper serifs, more stroke
     contrast, tighter natural fitting.
     ⚠️ THE RIBBON'S RESERVED-HEIGHT LADDER WAS RE-SWEPT FOR THIS. Optical size changes
     glyph widths, so it changes where lines wrap — see .pl-hook below, and the note
     there about v66 shipping one line short at nine widths in ten from exactly this. */
  font-variation-settings: var(--lit-display); font-weight: 400;
  /* clamp(1.02rem, 1.5vw, 1.24rem) → clamp(1.16rem, 1.85vw, 1.46rem) → this.
     🔴 2026-09-05, the author: "titles could prob be bigger under pics."
     A caption under a 400px plate is competing with a photograph, and at 23px it
     was losing. 27px at 1440 sets it as the plate's title rather than as its label,
     and the numeral beside it moves with it (below) so the pair stays one caption. */
  font-size: clamp(1.34rem, 2.15vw, 1.7rem);
  line-height: 1.16; letter-spacing: -.018em;
  color: var(--ink); text-wrap: balance;
  transition: transform var(--dur-med) var(--ease);
}
/* 🛑 .pl-d WAS DELETED WITH ITS MARKUP, 2026-09-05 — the per-plate runtime. The
   author: "we dont need to list the minutes under chapter just the name and
   numeral would suffice in the same text." js/render.js still puts the runtime in
   each plate's hidden label, where it is the only sense of scale a screen reader
   user gets. Do not restore this rule without markup. */

/* a chapter whose first block is not a slot gets a typographic cell — no
   placeholder and no grey box, exactly as in v54 */
.pl-plate.is-bare .pl-frame { background: var(--control-wash); }

/* ---------- LIT: the plate the ribbon is holding ----------
   This is state, not decoration, so it survives prefers-reduced-motion. On a
   phone it is the only feedback there is: there is no hover to light it with. */
.pl-plate:not(.is-lit) .pl-frame img { filter: saturate(.62) contrast(1.02) brightness(.94); }
.pl-plate:not(.is-lit) .pl-t { color: var(--ink-soft); }
/* ⭐ THE LIT NUMERAL TAKES THE CHAPTER'S OWN ACCENT — 2026-09-07, and it is half of
   one idea. The caption below is arriving in the same colour in the same frame (see
   .plh-w), and that is what joins the two across the gap the sway clearance leaves.
   ⚠️ THAT GAP IS 80px NOW, NOT 104 — later the same day the rail's padding went
   asymmetric and the caption's own margin went to zero. The tie still earns its keep;
   there is simply less to tie across.
   🛑 ONE NUMERAL, NEVER TEN. v54 put a chip of every chapter's stock under every
   plate and the author called them "so ugly ... tabs". This is the current one and
   only the current one: it is the state that was already being drawn, painted in
   the light of the chapter it belongs to instead of in flat ink.
   ⚠️ --hook-lit IS WRITTEN BY js/ui.js setLit(); the fallback is --ink, so a
   browser that never runs it, or a first frame before the read lands, gets exactly
   what this rule drew before. */
.pl-plate.is-lit .pl-n {
  color: var(--hook-lit, var(--ink));
  transition: color 380ms var(--ease);
}
@media (prefers-reduced-motion: reduce) { .pl-plate.is-lit .pl-n { transition: none; } }
/* ⭐ THE LIT PLATE LIFTS, 2026-09-05. The author: "i wouldnt mind ore movement with
   the riboon. it should show off."
   This is the cheapest real motion on the strip and the only one tied to what the
   reader is actually doing: the plate the ribbon is holding rises 7px and grows
   1.5%, so scrolling the rail is a procession of pictures stepping forward one at a
   time rather than a filmstrip sliding past. It costs no picture and no paper.
   🛑 IT IS ON .pl-frame, NOT ON .pl-plate. The sway owns .pl-plate's transform —
   two rules writing transform on one element is a silent fight where whichever the
   cascade picks wins outright, and the sway is the one that would have lost.
   ⚠️ THE 1.5% SCALE FITS IN THE GAP. clamp(.7rem, 1.4vw, 1.1rem) is 11-18px between
   plates and 1.5% of a 400px frame is 6px, 3px a side. It never touches its
   neighbour. Under prefers-reduced-motion the lit state keeps its colour and loses
   the movement — the state is information, the lift is not. */
.pl-frame { transition: transform var(--dur-med) var(--ease); }
.pl-plate.is-lit .pl-frame { transform: translateY(-7px) scale(1.015); }
@media (prefers-reduced-motion: reduce) {
  .pl-frame { transition: none; }
  .pl-plate.is-lit .pl-frame { transform: none; }
}

/* ---------- the pointer, where there is one ----------
   the list's own gesture: point at one and the other nine step back */
@media (hover: hover) {
  .pl-rail:hover .pl-plate { opacity: .58; }
  .pl-rail .pl-plate:hover,
  .pl-rail .pl-plate:focus-visible { opacity: 1; }
  .pl-plate:hover .pl-frame img,
  .pl-plate:focus-visible .pl-frame img { filter: saturate(1) contrast(1); }
  /* ⚠️ IT WAS translateX(.28rem), AND THAT WAS RIGHT WHILE THE ROW WAS RANGED
     LEFT: a list nudges its titles sideways. A CENTRED caption that slides right
     on hover just looks off its axis, so the gesture became a 2px rise — the same
     amount, on the axis the caption is now symmetrical about. */
  .pl-plate:hover .pl-t, .pl-plate:focus-visible .pl-t { transform: translateY(-2px); }
  .pl-plate:hover .pl-n, .pl-plate:focus-visible .pl-n { color: var(--ink); }
  /* ⚠️ BUT NOT BOTH AT ONCE. The rail already drops the other nine to .58; the
     held-back filter on top of that dimmed a dark plate twice and chapter I went
     flat grey against the paper. Under a pointer, opacity carries the whole
     gesture and the filter stands down. */
  .pl-rail:hover .pl-plate:not(.is-lit) .pl-frame img { filter: saturate(.80) contrast(1.02); }
}
.pl-plate:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }

/* ---------- the drift ----------
   ⭐ TWO IMPLEMENTATIONS, AND THE CHEAP ONE IS THE FALLBACK RATHER THAN THE
   DEFAULT. A scroll-driven animation runs on the compositor, so the drift keeps
   its frame rate while the main thread is busy parsing 363KB of chapters.js —
   which is exactly when a rAF parallax stutters. Chrome 115+, Safari 26+, about
   84% of traffic; Firefox still has it behind a flag in stable as of mid-2026,
   so this cannot be the only path and js/ui.js carries the other one.

   🔴 THE TIMELINE IS DECLARED ON THE PLATE AND REFERRED TO BY NAME, AND THE
   OBVIOUS `animation-timeline: view(inline)` ON THE IMAGE IS A TRAP THAT COSTS
   THE WHOLE EFFECT SILENTLY. A view timeline is measured against the subject's
   nearest ancestor SCROLL CONTAINER, and .pl-frame has overflow:hidden, which
   makes it one — so every picture was being measured against the frame it lives
   in, which never scrolls. Measured in Chrome: all ten sat at translate
   -0.0003% at every scroll position on the rail. No error, no warning, no
   motion. .pl-plate is outside the frame and inside the rail, so a timeline
   declared there resolves against the thing that actually moves. */
@supports (animation-timeline: view(inline)) {
  html.pl-sda .pl-plate { view-timeline-name: --pl-plate; view-timeline-axis: inline; }
  html.pl-sda .pl-frame img {
    translate: none;                     /* the animation owns it now */
    animation: pl-drift linear both;
    animation-timeline: --pl-plate;
    animation-range: cover 0% cover 100%;
  }
  /* 🛑 5.6 AND THE 5.6 IN js/ui.js ARE ONE NUMBER IN TWO FILES. The fallback rAF
     path writes --dx; if the two ever disagree, Firefox drifts one distance and
     Chrome another and nothing anywhere reports it.
     ⭐ 4.6 → 5.6 on 2026-09-06, the author: "more motion some room is fine."
     ⚠️ THE CEILING IS 6, AND IT IS ARITHMETIC, NOT TASTE. .pl-frame img is laid out
     at left:-6%; width:112%, so the picture has exactly 6% of overhang on each side.
     Drift past 6 and the frame runs out of picture and shows its own background at
     the trailing edge on every plate. Widen the overhang first or do not widen this. */
  @keyframes pl-drift {
    from { translate: -5.6% 0 }
    to   { translate:  5.6% 0 }
  }
}

/* ---------- THE SWAY: the page's own scroll, not the rail's ----------
   The author, 2026-09-04: "can we have motion...... paralex something".

   ⭐ THE HORIZONTAL DRIFT ABOVE ONLY MOVES WHEN THE RIBBON DOES. Scrolling the
   PAGE past a strip that only answers to sideways swipes leaves it dead on
   arrival, which is what was missing. So the plates also ride a slow wave as the
   section crosses the window: each one rises and falls ±44px at its own phase,
   which is concept D's "five columns at five speeds" turned on its side and given
   to a row. It is what he pointed at when he said the wall looked better.

   🛑 IT MOVES THE FRAMES AND NOT THE PICTURES, AND THAT IS WHY IT IS FREE. A
   vertical drift INSIDE the frame would need vertical overhang, and the only
   place that overhang can come from is a second crop of the photograph — the one
   thing this whole rebuild exists to stop. Translating the plate costs nothing
   but paper, and paper is what the padding above is for.

   ⚠️ THE TIMELINE IS ON THE SECTION FOR THE SAME REASON THE DRIFT'S IS ON THE
   PLATE: .pl-rail has overflow on both axes, so it is a block-axis scroll
   container too, and `view(block)` on a plate would be measured against the rail
   — which never scrolls vertically. #plates is outside it and inside the page.

   ⚠️ NOTHING REFLOWS. transform only, ten elements, and the amplitudes are set
   once at render time. */
/* 🛑 THE WAVE IS SYMMETRIC AND STAYS SYMMETRIC — A BIASED VERSION WAS BUILT,
   MEASURED AND THROWN AWAY, 2026-09-05, AND THE MEASUREMENT IS WORTH KEEPING.
   The goal was to shrink the paper under the strip (the author: "maybe it doesnt
   have to be so far bwellow the pics so it looks more ohesive") by moving the
   wave's CENTRE up — +0.6·amp to −1.4·amp, same travel, less of it below the line.
   🛑 THE NUMBERS IN THIS NOTE ARE THE 2026-09-05 PAIR (±66). PLATE_SWAY is ±111 now
   and the rail's padding is asymmetric; the ARGUMENT below is unchanged and is why a
   biased wave is still the wrong answer, but do not read the figures as current.
   ⚠️ IT COSTS 23% OF THE VISIBLE MOTION AND GIVES BACK NOTHING. The amplitudes in
   js/render.js ALTERNATE SIGN — that is what makes adjacent plates oppose each
   other — so a bias that lifts the +66 plate pushes the −65 plate exactly as far
   down, and the clearance is unchanged. What does change is the part of the wave
   the reader actually sees: the rail is only on screen for `cover` progress 0 to
   about 0.65, so the symmetric wave shows 1.69 amplitudes of travel and the biased
   one shows 1.29. Measured, plate I at 402: 111px of travel becomes 85px.
   ⭐ THE PAPER CAME OFF A DIFFERENT WAY, AND THE ANSWER WAS IN THE SAME NUMBERS:
   the full ±66 only happens at `cover 0%`, when the rail's own bottom edge is still
   below the fold. From the moment that edge — and therefore the gap the author was
   looking at — is on screen, the largest downward translate is 35px. The padding
   below is sized for THAT, not for the extreme; see .pl-rail. */
@keyframes pl-sway {
  from { transform: translateY(var(--amp, 0px)) }
  to   { transform: translateY(calc(var(--amp, 0px) * -1)) }
}
@supports (animation-timeline: view(block)) {
  html.pl-sda #plates { view-timeline-name: --pl-section; view-timeline-axis: block; }
  html.pl-sda .pl-plate {
    animation: pl-sway linear both;
    animation-timeline: --pl-section;
    animation-range: cover 0% cover 100%;
  }
}

/* ---------- the caption ----------
   ⭐ THIS IS THE ONE THING WORTH SAVING OUT OF THE TURN. The back of each card
   carried its chapter's hook, and the hook is the best short copy in the book —
   it is what makes an index an invitation instead of a menu. The turn charged a
   3D flip and 72% of every photograph for it. A ribbon can just print it.

   🛑 THE HEIGHT IS RESERVED, NOT FLUID. The ten hooks run 90 to 136 characters,
   so a caption that sized itself would move the arc up and down under the
   reader's thumb on every snap. min-height holds the tallest of the ten. */
.pl-hook {
  /* ⚠️ COLUMN 1, NOT THE READING MEASURE, AND THAT IS DELIBERATE. Prose on this
     site lives in .col-text (3/9); this is not prose, it is the caption of a
     full-bleed strip, and a caption that starts two columns in from the plate it
     describes reads as a stray paragraph. It begins where the first plate begins
     and stops at a comfortable measure. */
  /* ⭐ 1/8 → 1/11, 2026-09-16. The author: "texdt under pics being wider should be
     fine." It was 763px of 1440 under a strip that bleeds the full width, with 47% of
     the row bare paper. Ten of twelve tracks keeps a measure a 34px display line can
     carry, and leaves the right-hand columns to the position and arrows above it.
     🛑 THE DESKTOP LADDER BELOW WAS RE-SWEPT FOR THIS WIDTH, NOT SCALED. */
  grid-column: 1 / 11;
  /* ⚠️ TIGHT ABOVE, ON PURPOSE, AND TIGHTER AGAIN 2026-09-05 — the author: "maybe
     it doesnt have to be so far bwellow the pics so it looks more ohesive."
     The rail's own bottom padding came down from 5rem to 3.4 in the same round (see
     the note there); this is the rest of it. The strip and its caption are ONE
     BLOCK and the space between them has to be smaller than the space under them,
     or the caption floats between the two and belongs to neither.
     🔴 .5rem → 0, 2026-09-07. The rail's bottom padding came down 24px in the same
     round (see .pl-rail) and this is the last 8px of the same complaint. The caption
     starts immediately under the strip's own edge now; everything still between them
     is the sway's clearance, which is the only space there doing a job. */
  margin: 0;
  /* ⭐ MUCH LARGER, 2026-09-05. The author: "Descriptions should be much larger
     too… that needs tonbe fixed."
     It was .92rem — smaller than the running text of the book — for the best short
     copy on the site. The hook is what turns an index into an invitation, and it
     was set at the size of a photo credit. It is now larger than body copy rather
     than smaller, which is the right relationship: this is the one line under a
     full-bleed strip and it has nothing to compete with.
     🛑 THE RESERVED HEIGHT MOVES WITH IT. min-height holds the tallest of the ten
     hooks (they run 90-136 characters) so the section below does not jump under
     the reader's thumb on every snap — recomputed at the new size, or the caption
     starts sizing itself again and the whole point of reserving it is lost. */
  /* 🔴 LARGER AGAIN, AND IN THE DISPLAY VOICE, 2026-09-05 (round two). The author,
     of the same line after the first enlargement: "maybe this text should be bigger
     its still tiny and nt captivating… should we use different font."
     The first pass took it from .92rem to 1.34rem, which fixed the size relative to
     body copy and left the VOICE alone — it was still set as prose, in --ink-soft,
     and prose at 21px under a full-bleed strip still reads as a caption. This is the
     answer to "different font" that the site can actually give: there is no third
     family loaded (Literata and Archivo are all of it) and adding one for one line
     is a webfont on the critical path for a caption. Literata is a VARIABLE face, so
     the voice changes without the family changing — --lit-mid raises the optical size
     axis to the setting the chapter titles use, weight drops to 350, and the colour
     comes off --ink-soft onto full --ink.
     Measured at 1440: 21.4px → 30.4px, and it now sets like a standfirst rather than
     like a photo credit. Author's pick, of three options offered. */
  font-family: var(--serif);
  /* 🔴 --lit-mid → --lit-display, 2026-09-09, AND IT IS THE THIRD AND LAST ANSWER TO
     "more iconic". The two before it were size (three times) and weight, and both had
     run out: the line is already 34px and already at the book's own roman. What was
     left was the OPTICAL SIZE, and it was wrong — this caption was drawn at opsz 28,
     the setting a chapter title uses at half this size, so at 34px it was reading type
     enlarged. opsz 60 is the display cut of the same face. Nothing else moves: same
     family, same weight, same clamp, same colour.
     🛑 AND THE LADDER BELOW WAS RE-SWEPT FOR IT, NOT SCALED — see the sweep note. */
  font-variation-settings: var(--lit-display);
  /* 🔴 350 → 400, 2026-09-07, AND IT IS THE OTHER HALF OF THE ITALIC FIX. 350 is a
     light weight, and a light weight at 34px is a display setting: it wants a big
     page and a lot of paper around it. Here it sits under ten photographs and above
     a card, and in night mode it is thin white strokes on near-black, which is the
     worst case a light serif has. 400 is Literata's roman — the weight the book
     itself is set in — so the caption now reads as the same voice as the prose it
     is advertising rather than as a lighter, fancier one.
     🛑 THIS IS THE WHOLE ANSWER TO "different font options", AND THERE IS NO FOURTH
     FAMILY COMING. Literata and Archivo are the entire type budget (fonts/fonts.css);
     the caption is now upright Literata at the book's own weight, which is the third
     of the three settings this line has had and the first one that is not a costume.
     The alternatives, and what each would cost, are written up in docs/DECISIONS.md. */
  font-weight: 400;
  /* 🔴 LARGER A THIRD TIME, 2026-09-05 (round five). The author: "the text under it
     should be much bigger explaining the chapter."
     .92rem → 1.34rem → 1.42rem floor → this. It is the one line of copy under a
     full-bleed strip of ten photographs and it has nothing on the page to compete
     with, so it is set at 34px at 1440 — larger than the chapter standfirsts and
     just under the plate titles' display voice.
     🛑 THE RESERVED-HEIGHT LADDER BELOW WAS RE-MEASURED AT THIS SIZE, not scaled.
     It is in `em`, so it tracks the clamp automatically — but the LINE COUNTS do
     not: bigger type wraps sooner, and two of the widths gained a line. */
  font-size: clamp(1.56rem, 2.42vw, 2.12rem);
  line-height: 1.3; letter-spacing: -.012em;
  color: var(--ink); text-wrap: pretty;
  /* 🔴 THE LADDER IS IN `em` NOW, AND RE-MEASURED FROM SCRATCH, 2026-09-05. Two
     things forced it: the type got much bigger (see above), and the old ladder was
     written in `rem` against the clamp's FLOOR, which silently under-reserved at
     every width where the clamp is not on its floor. At 1440 the tallest hook is
     119px tall and `calc(3 * 1.3 * 1.42rem)` reserves 89 — thirty pixels of the
     contents page jumping under the reader's thumb on one snap in ten, which is the
     exact fault this reservation exists to prevent.
     ⭐ `1.3em` IS ONE LINE BOX, EXACTLY, AT EVERY CLAMP VALUE, because line-height
     above is 1.3 and 1em is this element's own computed font-size. So the ladder now
     only has to carry the LINE COUNT and the clamp can move without breaking it.
     🛑 EVERY STEP IS MEASURED, not interpolated — the ten hooks run 90 to 136
     characters and these are the widths at which the tallest of them actually loses
     or gains a line, checked one by one:
        320 → 6 lines    340 → 5    480 → 4    560 → 3    760 → 2
        901 → 4          915 → 3    1440 → 3   1920 → 3
     ⚠️ 901 GOING BACK UP TO FOUR IS NOT A TYPO. That is the layout breakpoint: below
     it the caption spans all six phone tracks (828px at 900), above it seven of the
     desktop's twelve (477px at 901). The caption gets NARROWER as the window gets
     wider, once, and it costs two lines. 915 is where the desktop grid has grown
     enough to win them back. */
  /* 🔴 EVERY STEP OF THIS LADDER WAS ONE LINE SHORT — corrected 2026-09-07, by
     measurement, and it had been shipping. The reservation exists so the contents
     page does not jump under the reader's thumb when a snap lands on a long hook,
     and at nine of the ten widths below it was reserving one line LESS than the
     tallest hook actually needs. Measured, with the real ten hooks, at 28 widths
     from 320 to 1920:
         reserved 195 · actual 227   (320)
         reserved 162 · actual 195   (380–402)
         reserved 130 · actual 162   (440)
         reserved  97 · actual 130   (580)
         reserved  65 · actual  97   (840–880)   ← two lines short
         reserved  97 · actual 130   (1010)
         reserved 132 · actual 176   (1440–1920)
     Only 901–1009 was right. So one snap in ten moved the page by a full line,
     which is 32px on a phone and 44px on a desktop.
     ⚠️ IT IS NOT THE WORD SPANS. The same sweep run with plain textContent — the
     wrapping this had before the caption was rebuilt — returns the same numbers to
     the pixel. The ladder simply was not re-measured the last time the type grew.
     🛑 THE LINE COUNTS BELOW ARE WHERE THE TALLEST HOOK ACTUALLY LOSES A LINE, found
     by sweep, not interpolated. The current sweep is the 2026-09-09 one below.
     `1.3em` is exactly one line box at every clamp value, so the ladder only has to
     carry the count and the clamp can move without breaking it. */
  min-height: calc(8 * 1.3em);
}
/* ============================================================================
   🔴 THE CAPTION IS LIT BY THE CHAPTER IT NAMES — 2026-09-07
   The author: "on the ribbon now we have big text but its seperated from the very
   ribbon itself ... rather than just black or white like actually make that text
   legit make it fn to itnereact with."
   Two faults, one cause. The swap was 200ms of opacity down, a textContent write,
   200ms up — the cheapest transition there is, and a fade does not connect a
   caption to the thing that changed it. And the line was --ink at every position on
   a strip that is the ONE place a reader sees all ten chapters at once.
   ⭐ The words arrive one at a time, each carrying that chapter's own accent, and
   cool to ink over 700ms. Night blue through I–IV, the fire through V–VIII, morning
   gold for IX and X. The dawn arc, in type, with nothing drawn.
   🛑 NOTHING PERSISTS, AND THAT IS WHAT MAKES IT NOT THE ARC HE DELETED. v54's ten
   chips of paper stock were a permanent coloured object under the photographs and he
   called them "so ugly ... tabs" (FRONT-DOOR §2.8b). This is a colour the type passes
   THROUGH: 700ms after a snap there is no coloured element on the page.
   🛑 COMPOSITOR AND PAINT, NEVER LAYOUT. opacity and translate are composited;
   `color` is a paint on ~20 inline-blocks that never reflows because the words hold
   their boxes at every stage. The rejected alternative — the five-words gradient
   sweep — repaints a background-position on twenty background-clip:text spans every
   frame, and js/ui.js has the other reason not to reuse it.
   ⚠️ .32em, NOT A PIXEL VALUE. The caption clamps from 25px to 34px, and a fixed
   rise that reads as a lift at 34px reads as a jump at 25.
   ============================================================================ */
.pl-hook .plh-w {
  display: inline-block;
  /* 🛑 STATED, AND IT IS NOT DEFENSIVE PADDING. js/ui.js built these wrappers with
     createElement('i') until 2026-09-07, so the UA default made the whole caption
     italic and nothing in either file said so. The tag is a <span> now; this is the
     second lock, because the fault was invisible in the CSS and invisible in the JS
     and only showed up in a computed-style dump. */
  font-style: normal;
  opacity: 0;
  transform: translateY(.32em);
  color: var(--hook-lit, var(--accent));
  transition: opacity 400ms var(--ease), transform 400ms var(--ease),
              color 700ms var(--ease) 260ms;
}
.pl-hook.is-in .plh-w {
  opacity: 1;
  transform: none;
  color: var(--ink);
  /* the ladder is on the way IN only — going out, every word leaves together */
  transition-delay: calc(var(--w, 0) * 24ms), calc(var(--w, 0) * 24ms),
                    calc(260ms + var(--w, 0) * 24ms);
}
/* 🛑 RE-MEASURED FROM SCRATCH AT THE NEW SIZE, 2026-09-05, NOT SCALED. The `em`
   unit makes the ladder track the clamp automatically; it does not make the LINE
   COUNTS track it, and bigger type wraps sooner. Every width below is where the
   tallest of the ten hooks actually gains or loses a line, found by binary sweep
   over 37 widths from 320 to 1120 with one probe element per width:
       320  6 · 380  5 · 440  4 · 580  3 · 840  2 · 901  4 · 1010  3
   The old ladder (340 / 480 / 560 / 760 / 901 / 915) under-reserved at five of
   those widths once the type grew — up to a full line, which is 44px of the page
   jumping under the reader's thumb on one snap in ten.
   ⚠️ 901 GOING BACK UP TO FOUR IS STILL NOT A TYPO, and the reason is unchanged:
   that is the layout breakpoint, and above it the caption spans seven of the
   desktop's twelve tracks instead of all six of the phone's — so it gets NARROWER
   as the window gets wider, once. 1010 is where the desktop grid has grown enough
   to win the lines back. */
/* 🔴 RE-SWEPT 2026-09-07 AND EVERY STEP MOVED, because the caption stopped being
   italic. js/ui.js was wrapping each word in an <i> — see the note at .plh-w — so the
   ladder above had been measured against Literata ITALIC at weight 350, and the roman
   at 400 that replaced it is a wider face: the tallest hook now holds each line count
   for another 16-34px of width before it drops one.
   🛑 MEASURED, NOT SHIFTED. A probe cloned from .pl-hook itself, min-height off, all
   ten hooks through it at 38 widths from 320 to 1920, reading the real rendered
   height against the real line box:
       320-358  7 lines   ·  360-434  6  ·  436-488  5  ·  490-652  4
       654-900  3         ·  901+     4  (and 4 at every width to 1920)
   ⚠️ THE OLD 620px STEP WAS UNDER-RESERVING BY A FULL LINE FROM 620 TO 653, which is
   44px of the contents page jumping under a thumb on one snap in ten — the exact
   fault this reservation exists to prevent, reintroduced by a change three files away
   that had nothing to do with layout. If the caption's face, weight or size moves
   again, re-run the sweep; do not scale these numbers. */
/* 🔴 RE-SWEPT AGAIN 2026-09-09, AND EVERY STEP MOVED AGAIN — the caption went from
   --lit-mid (opsz 28) to --lit-display (opsz 60). Optical size is not a size: it is a
   different DRAWING of the face, and a display cut has finer hairlines and tighter
   natural fitting, so the words do not occupy the same widths. The note above said to
   re-run the sweep if the caption's face moves. This is that.
   🛑 MEASURED AT ONE-PIXEL RESOLUTION, 320 to 1010, then every 10px to 1920 — the same
   probe cloned from .pl-hook, min-height off, all ten hooks through it, real rendered
   height against the real line box:
       320-329  8 lines  ·  330-354  7  ·  355-428  6  ·  429-483  5
       484-642  4        ·  643-900  3  ·  901+     4  (and 4 at every width to 1920)
   ⚠️ 320 GAINED A LINE AND EVERY OTHER STEP CAME EARLIER, which looks contradictory
   and is not: the display cut fits tighter overall — so each count is given up 1-11px
   sooner — while at 320 the narrowest measure in the set the longest word in chapter
   X's hook no longer shares a line, and the whole caption takes an eighth. Both are
   the same sweep and both are real. The 1px resolution is what shows it; the previous
   sweep stepped in 2s and would have reported 330 as the base.
   🛑 THE BASE RULE ABOVE IS NOW 8, NOT 7. A base of 7 under-reserves by a full line
   from 320 to 329 — 44px of the contents page jumping under a thumb, on the narrowest
   phone there is. */
@media (min-width: 330px) { .pl-hook { min-height: calc(7 * 1.3em); } }
@media (min-width: 355px) { .pl-hook { min-height: calc(6 * 1.3em); } }
@media (min-width: 429px) { .pl-hook { min-height: calc(5 * 1.3em); } }
@media (min-width: 484px) { .pl-hook { min-height: calc(4 * 1.3em); } }
@media (min-width: 643px) { .pl-hook { min-height: calc(3 * 1.3em); } }
/* 🛑 .is-swapping IS GONE WITH THE CROSSFADE IT RAN, 2026-09-07 — nothing sets it
   any more. The caption's transition now lives on its words, above. */
@media (max-width: 900px) { .pl-hook { grid-column: 1 / 7; } }
/* ⚠️ THE CAPTION GETS NARROWER AS THE WINDOW GETS WIDER, ONCE, AND THAT IS NOT A
   TYPO. 900 is the layout breakpoint: below it the caption spans all six phone
   tracks (828px at 900), above it seven of the desktop's twelve (477px at 901). So
   it goes back up to four lines and stays there — measured at 901, 940, 980, 1010,
   1060, 1120, 1200, 1300, 1440, 1600 and 1920, every one of them four.
   🛑 THE OLD 1010px STEP BACK DOWN TO THREE WAS SIMPLY WRONG. At 1010 the tallest
   hook is chapter IV's and it is 130px — four lines — against 97 reserved. */
/* 🔴 RE-SWEPT 2026-09-16 FOR THE WIDER CAPTION (1/11): every width from 901 to 1960,
   one pixel apart, the same probe cloned from .pl-hook with all ten hooks — THREE
   lines at every one of them (the same sweep at the old 1/8 returns four, which is
   what proves the probe). The caption no longer narrows at the breakpoint, so the
   desktop reserves a line less than it did. */
@media (min-width: 901px) { .pl-hook { min-height: calc(3 * 1.3em); } }

/* ============================================================================
   THE POSITION AND THE ARROWS, UNDER THE STRIP — 2026-09-16
   The author, of the empty band between the pictures and the caption: "do it …
   should we have an arrow there???"
   🛑 IT SITS INSIDE THE RAIL'S BOTTOM PADDING, AND THE NUMBER IS MEASURED. That
   padding is the sway's clearance (see .pl-rail) and nothing may overlap a plate's
   title. While the rail's bottom edge is on screen a plate travels at most 78px down
   into it, and a one-line plate carries a 26px empty tail, so the lowest a title
   ever reaches is 36px above the rail's edge. -36px puts the row's top exactly
   there: no title touches it at any scroll position.
   Right-aligned, so it sits over the bare paper to the right of the caption rather
   than stacking on the caption's first word.
   ============================================================================ */
.pl-nav {
  grid-column: 1 / -1;
  display: flex;
  justify-content: flex-end;
  align-items: center;
  height: 44px;
  margin-top: -36px;
  position: relative;
}
.pl-step { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; }
.pl-step svg { width: 22px; height: 22px; }
.pl-step[aria-disabled="true"] { opacity: .3; cursor: default; }
.pl-pos {
  font-family: var(--serif);
  font-size: 1.05rem; font-weight: 500;
  letter-spacing: .12em;
  font-variant-numeric: lining-nums tabular-nums;
  color: var(--ink-soft);
  min-width: 4.6em;
  text-align: center;
}
/* the held chapter's numeral takes the same accent as its plate and its caption */
.pl-pos-n { color: var(--hook-lit, var(--accent)); transition: color 380ms var(--ease); }
@media (prefers-reduced-motion: reduce) { .pl-pos-n { transition: none; } }

/* ---------- the arrival ----------
   🛑 IT ANIMATES THE FRAME AND THE ROW, NOT THE PLATE, AND THAT IS LOAD-BEARING.
   The arrival and the hover-dim both want to own opacity on the same element,
   and a CSS animation's fill outranks every normal declaration — so an arrival
   that landed on .pl-plate would win the cascade forever and the dim would
   silently never work again. Animating the two children lets the two opacities
   multiply, which is what they mean anyway.
   ⚠️ AND .is-armed IS ADDED FROM SCRIPT, NEVER FROM HERE. A flat opacity:0 would
   hide all ten plates forever for anyone whose JavaScript did not run, because
   the animation that was going to bring them back is the same script.
   css/site.css already makes this call: `.reveal { opacity: 1 }` is its resting
   state. Nothing is hidden until something has proved it can unhide it. */
.pl-rail.is-armed .pl-frame,
.pl-rail.is-armed .pl-row { opacity: 0; }
.pl-rail.is-armed.is-in .pl-frame,
.pl-rail.is-armed.is-in .pl-row {
  animation: pl-arrive 700ms var(--ease) calc(var(--i) * 55ms) both;
}
@keyframes pl-arrive {
  from { opacity: 0; transform: translateY(14px) }
  to   { opacity: 1; transform: none }
}

/* 🛑 REDUCED MOTION IS A FULL STOP ON MOTION AND NOT ON NAVIGATION. The drift,
   the arrival and the type nudge are gone. Snapping stays, because snapping is
   how the thing is steered, and the lit state stays, because it is information.
   Turning off someone's animations must not cost them a control. */
@media (prefers-reduced-motion: reduce) {
  .pl-rail { scroll-behavior: auto; }
  .pl-frame, .pl-row { opacity: 1 !important; transform: none !important; animation: none !important; }
  .pl-plate { transform: none !important; animation: none !important; }
  .pl-frame img { translate: none !important; animation: none !important; }
  .pl-plate:hover .pl-t, .pl-plate:focus-visible .pl-t { transform: none; }   /* the 2px rise, off */
  /* the caption still CHANGES — it has to, it is how the rail says where you are —
     it simply does not arrive. js/ui.js paints it and adds .is-in in one step here,
     so these are the resting values and there is no transition to run. */
  .pl-hook .plh-w {
    opacity: 1; transform: none; color: var(--ink);
    transition: none;
  }
}

/* ============================================================================
   CONTENTS
   A real contents page, on the same grid as everything else: numeral in the
   margin column, title and standfirst in the reading measure, running time in
   the outer column. Rows are separated by rules, not cards — a card grid would
   be the exact generic pattern this rebuild exists to avoid.
   ============================================================================ */
/* the top is tightened to meet #plates above — see the note there. The bottom is
   unchanged: below this is chapter I, and a contents page needs air before a book
   starts. */
#contents { padding-block: clamp(1.2rem, 2.6vh, 2rem) clamp(2.6rem, 6vh, 5rem); }

.toc-head {
  grid-column: 1 / -1;
  display: grid; grid-template-columns: subgrid;
  align-items: baseline;
  padding-bottom: 1.1rem;
  border-bottom: 1px solid var(--rule-strong);
}
@supports not (grid-template-columns: subgrid) {
  .toc-head { grid-template-columns: repeat(12, 1fr); column-gap: var(--step); }
}
/* nothing sits in the margin column any more — the toggle IS the heading */
/* Apparatus, so it takes the apparatus vocabulary: a label and a rule, no chevron,
   no pill. It sits in the text range rather than the margin because it acts on the
   list below it, not on the page. */
.toc-expand {
  grid-column: 1 / 8;
  justify-self: start;
  display: inline-flex; align-items: center; min-height: 44px;
  padding: 0;
  font-family: var(--ui); font-size: var(--ap-md); font-weight: 500;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-faint);
  border-bottom: 1px solid transparent;
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.toc-expand::before { content: '+'; margin-right: .7em; font-size: 1.1em; line-height: 1; }
.toc-expand[aria-expanded="true"]::before { content: '\2212'; }   /* − */
.toc-expand:hover, .toc-expand:focus-visible { color: var(--ink); border-bottom-color: var(--ink); }
.toc-expand:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* 🛑 .toc-total WAS DELETED WITH ITS MARKUP, 2026-09-05 — it read
   "10 chapters · 5 hr 21 min". The author: "we dont need the time in length that
   can be in about not in table of contents nor do we need it saying 10 chapters
   they cna scroll to fidn out." The runtime lives on the About sheet. Do not
   restore this rule; js/render.js no longer emits the span. */

.toc { grid-column: 1 / -1; display: block; }

.toc-row {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  column-gap: var(--step);
  align-items: baseline;
  padding: 1.05rem 0;
  border-bottom: 1px solid var(--rule);
  text-decoration: none;
}
/* the hover target is the whole row, and it bleeds a little past the measure so
   the highlight reads as a band of paper rather than a button */
/* A tinted panel with a bleeding shadow is a web control. A printed index responds
   by inking its rule and nudging the numeral — that is all this does. */
.toc-row { transition: border-color var(--dur-fast) var(--ease); }
.toc-row:hover, .toc-row:focus-visible { border-bottom-color: var(--ink); }
.toc-row .toc-num { transition: transform var(--dur-fast) var(--ease); }
.toc-row:hover .toc-num, .toc-row:focus-visible .toc-num { transform: translateX(4px); }
.toc-row:hover .toc-title, .toc-row:focus-visible .toc-title { color: var(--accent); }
.toc-title { transition: color var(--dur-fast) var(--ease); }
.toc-row:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* THE CONTENTS NUMERALS ARE BOOK, NOT MACHINERY. They were Archivo at 600 with
   .16em tracking — a UI label sitting beside a Literata title, and the one thing in
   the contents that read as chrome. A chapter numeral is furniture of the book, the
   same call already made for the numerals in the running head. The running time
   beside it stays in Archivo, because a running time genuinely IS machinery. */
/* ⭐ THE NUMERALS GREW WITH THE COLUMN THAT FREED UP, 2026-09-05. The author, of
   the contents: "could be fancier". With the runtime gone there is a whole outer
   column doing nothing, and the cheapest honest way to spend it is to stop
   whispering the numerals: --ap-lg is .82rem, apparatus size, next to a title set
   as large as 1.75rem. A chapter numeral in a printed contents is furniture of the
   book and is set like it. No new ornament, no rule, no card — the same list, with
   its own proportions fixed. */
.toc-num {
  grid-column: 1 / 3;
  font-family: var(--serif); font-variation-settings: var(--lit-normal);
  font-size: clamp(1.2rem, 2vw, 1.6rem); font-weight: 500;
  font-variant-numeric: lining-nums tabular-nums;
  letter-spacing: .08em; color: var(--accent);
  line-height: 1.1;
}
/* the reading measure runs to the edge of the grid now that nothing sits beside
   it. Back-matter rows still carry a count, so they keep the old range — see
   .toc-row.toc-back below. */
.toc-body { grid-column: 3 / 12; }
.toc-row.toc-back .toc-body { grid-column: 3 / 10; }
.toc-title {
  display: block;
  font-variation-settings: var(--lit-mid);
  font-weight: 350; font-size: clamp(1.34rem, 2.3vw, 1.82rem);
  line-height: 1.14; letter-spacing: -.014em;
}
/* Option C, and now option C at the chapter openings too — see .chapter-hook in
   site.css. In a ten-row list the sans stops the rows reading as ten paragraphs;
   at a chapter opening it stops the standfirst reading as the chapter's real
   first paragraph. Same job both times. */
/* 🔴 ROMAN AT 450, NOT ITALIC — 2026-09-07, the same edit as .chapter-hook in
   css/site.css and for the author's note there. These two have always been one
   decision and they still are; only the mechanism changed. Weight and ink separate
   the line from the title above it now, which is how a printed contents page does
   it anyway. */
.toc-hook {
  display: block; margin-top: .45em;
  font-family: var(--serif); font-variation-settings: var(--lit-normal);
  font-style: normal; font-weight: 450;
  color: var(--ink-soft);
  /* .95rem was below body copy for the line the author said he likes best about
     this list — "i like the chapter descriptions". Same call as .pl-hook above. */
  font-size: 1.06rem; line-height: 1.55; max-width: 58ch;
}
/* The WHOLE list folds, not just the standfirsts. #contents.is-open is set by
   js/ui.js from the remembered choice; closed is the default on a first visit.
   The head line stays, so the section still reads as a contents page — it just
   does not spend a screen and a half proving it. */
#contents:not(.is-open) .toc { display: none; }
/* Closed, the section is the three words and nothing else — which is now true at
   every state, since the count and the running time that used to sit beside them
   are gone from the page entirely. */
#contents:not(.is-open) .toc-head { border-bottom-color: transparent; }
#contents:not(.is-open) .toc-head { border-bottom-color: var(--rule); padding-bottom: .9rem; }
.toc-dur {
  grid-column: 10 / 13;
  font-family: var(--ui); font-size: var(--ap-md); font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-faint);
  font-variant-numeric: lining-nums tabular-nums;
}

@media (max-width: 900px) {
  .toc-head { grid-template-columns: repeat(6, 1fr); }
  /* the toggle is the whole head now that the running time beside it is gone */
  .toc-expand { grid-column: 1 / 7; grid-row: 1; min-height: 44px; }
  .toc-row { grid-template-columns: repeat(6, 1fr); }
  .toc-num { grid-column: 1 / 2; }
  .toc-body, .toc-row.toc-back .toc-body { grid-column: 2 / 7; }
  .toc-dur { grid-column: 2 / 7; margin-top: .5em; }
}

/* ============================================================================
   THE INVITATION  (onboarding modal) — a doorway, not a second jacket
   REBUILT 2026-08-29, decision D2-C. It carried a Hebrew mark, a headline and a
   thesis paragraph, and the hero underneath carried the same three in the same
   order. Everything the hero already says is gone; what is left is the epigraph
   the book answers, one line saying what this thing is, the running time and the
   two doors. Four elements, one screen, on a phone as well as a laptop.
   🛑 .invite-mark, .modal-invite h2, .invite-pull and .invite-keys were deleted
   with the markup that used them. The pull line is .hero-pull now; the keys are
   the About sheet's .about-keys. Do not restore these rules without their markup.
   ============================================================================ */
.modal-invite {
  max-width: 560px;
  padding: clamp(1.8rem, 3.4vw, 3rem) clamp(1.6rem, 3vw, 2.8rem) clamp(1.4rem, 2.4vw, 2rem);
  max-height: 92vh; overflow-y: auto;
}
/* The epigraph leads, and with the mark and the headline gone it is now the first
   thing in the book a reader reads. Set one step larger than it was for that. */
.invite-epigraph {
  border-left: 2px solid var(--accent);
  padding-left: 1.1rem; margin: 0 0 1.5rem;
}
.invite-epigraph p {
  font-variation-settings: var(--lit-mid);
  font-weight: 300; font-size: clamp(1.32rem, 3vw, 1.56rem); line-height: 1.3;
  letter-spacing: -.014em; color: var(--ink); margin: 0 0 .55em;
}
.invite-epigraph cite {
  font-family: var(--serif); font-style: normal;
  font-variation-settings: var(--lit-normal);
  font-size: var(--ap-md); font-weight: 500; letter-spacing: .14em;
  text-transform: uppercase; color: var(--accent);
}
/* THE LEDE, three paragraphs since 2026-08-29 (D14-A). It was one sentence naming
   the format. It now opens with the discovery, pays the reader two reasons, and only
   then says what the thing is — so the paragraphs are graded rather than uniform:
   the hook is set in the reading ink at full size, the middle carries the argument,
   and the closing logistics line drops to the quiet ink the old single sentence used.
   🛑 The grade is the point. Flattening all three to one colour puts "ten chapters,
   start to finish" at the same weight as the sentence that has to earn the evening. */
.invite-lede {
  font-size: 1.06rem; line-height: 1.62; margin: 0 0 .85em;
  color: var(--ink);
}
.invite-lede:first-of-type {
  font-size: 1.16rem; line-height: 1.5; letter-spacing: -.008em;
}
.invite-lede-last {
  font-size: 1rem; color: var(--ink-soft); margin-bottom: 0;
}
.invite-lede em { font-style: italic; color: var(--accent); }
/* the Spanish line sits under the logistics line at its weight, one lede-gap below */
.invite-lede-es { margin-top: .85em; }
.invite-actions { display: flex; gap: .7rem; flex-wrap: wrap; margin: 1.6rem 0 1.2rem; }
.invite-meta {
  font-family: var(--ui);
  font-size: var(--ap-md); line-height: 1.9;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-faint);
  border-top: 1px solid var(--rule); padding-top: .9em; margin: 0;
  font-variant-numeric: lining-nums tabular-nums;
}
@media (max-width: 560px) {
  .invite-actions .btn { flex: 1 1 100%; }
}

/* The keys, on the About sheet. .legend does the work; this only stops a five-item
   list of shortcuts from reading as heavily as the two paragraphs above it. */
.about-keys { margin-bottom: 1.6em; }
.about-keys li { font-size: .88rem; }

/* the inline slot's reference — same idea as .plate-ref, one step quieter because
   an inline figure is not a plate and should not shout the way a frontispiece does. */
.img-slot figcaption .slot-ref {
  display: block; margin-top: .4em;
  font-family: var(--serif); font-style: normal;
  font-variation-settings: var(--lit-normal);
  font-size: var(--ap-sm); font-weight: 500; letter-spacing: .14em;
  text-transform: uppercase; color: var(--accent);
}

/* the plate's reference: where this is in the book, set below the caption */
.plate-ref {
  display: block; margin-top: .45em;
  font-family: var(--serif); font-style: normal;
  font-variation-settings: var(--lit-normal);
  font-size: var(--ap-sm); font-weight: 500; letter-spacing: .14em;
  text-transform: uppercase; color: var(--accent);
}

/* ============================================================================
   THE CONTENTS ROLLS UP INTO THE RUNNING HEAD
   ----------------------------------------------------------------------------
   Two problems solved by one object.
   (1) The contents page was ten static rows you scroll past once and never
       return to, and the running head numerals were a separate thing that knew
       nothing about it.
   (2) `.nav-chapters` is display:none under 900px, so on a PHONE there was no
       chapter navigation anywhere in the chrome — which is the only reason the
       player bar had to carry "Select a chapter to begin".
   So: as you scroll past #contents its rows lift and fade in sequence, and the
   running head takes over carrying them. The head is then tappable to bring the
   whole list back down over the page. Same list, two states.
   js/motion.js drives the roll; js/ui.js builds the panel.
   ============================================================================ */

.toc-row { will-change: transform, opacity; }
#nav-chapters { transition: opacity var(--dur-fast) linear; }

/* ---- the head's toggle. On desktop it reads "Contents"; under 900px, where the
        numerals are hidden, it becomes the current-chapter chip and is the only
        chapter affordance on the page. ---- */
.nav-toc-toggle {
  display: inline-flex; align-items: center; gap: .5em;
  min-height: 44px; padding: 0 .8em;
  background: none; border: 1px solid transparent; cursor: pointer;
  font-family: var(--ui); font-size: var(--ap-md); font-weight: 500;
  letter-spacing: .12em; text-transform: uppercase; color: var(--ink-faint);
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.nav-toc-toggle:hover, .nav-toc-toggle:focus-visible { color: var(--ink); }
.nav-toc-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.nav-toc-toggle[aria-expanded="true"] { color: var(--accent); border-color: var(--control-edge); }

/* the chip: the roman numeral you are inside, with the same filling hairline the
   numerals carry, so the head means the same thing at both sizes */
.ntt-here {
  position: relative; font-weight: 600; color: var(--accent);
  font-variant-numeric: lining-nums tabular-nums;
}
.ntt-here::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -5px; height: 1px;
  background: var(--accent);
  transform: scaleX(var(--ch-progress, 0)); transform-origin: left;
  transition: transform var(--dur-fast) linear;
}
.nav-toc-toggle .ntt-word { display: inline; }
@media (max-width: 900px) {
  /* the numerals are gone here, so the chip carries the chapter on its own */
  .nav-toc-toggle .ntt-word { display: none; }
  .nav-toc-toggle { font-size: var(--ap-md); }
  .nav-toc-toggle::before {
    content: 'Ch'; font-size: var(--ap-sm); font-weight: 500; color: var(--ink-faint);
    letter-spacing: .14em;
  }
}

/* ---- the panel that drops out of the head ---- */
/* Sibling of #site-nav and fixed on its own, so the nav's opacity fade and its
   scroll-down transform cannot take an open list with them. z-index sits one above
   the nav (800) and below the sheets (960). --nav-h matches .nav-shell min-height. */
:root { --nav-h: 59px; }
@media (max-width: 900px) { :root { --nav-h: 53px; } }
.nav-toc {
  position: fixed; left: 0; right: 0; top: var(--nav-h); z-index: 810;
  background: var(--paper);
  border-bottom: 1px solid var(--rule-strong);
  max-height: min(72vh, 640px); overflow-y: auto;
  overscroll-behavior: contain;
  transform-origin: top;
  transform: translateY(-8px); opacity: 0;
  transition: transform var(--dur-med) var(--ease), opacity var(--dur-med) var(--ease);
}
.nav-toc[hidden] { display: none; }
.nav-toc.is-open { transform: translateY(0); opacity: 1; }
/* while the list is down, the head that opened it stays put and stays legible —
   otherwise the only control that closes it can fade or slide away mid-use */
body.nav-toc-open #site-nav {
  opacity: 1 !important; transform: none !important; pointer-events: auto !important;
}

.nav-toc-inner {
  max-width: var(--shell); margin-inline: auto;
  padding: .4rem var(--edge) 1.1rem;
}
.ntr {
  display: flex; align-items: baseline; gap: 1rem;
  width: 100%; min-height: 48px; padding: .55rem .2rem;
  border-bottom: 1px solid var(--rule);
  text-align: left; color: var(--ink);
  text-decoration: none;
  transition: border-color var(--dur-fast) var(--ease);
}
.ntr:last-child { border-bottom: 0; }
.ntr:hover, .ntr:focus-visible { border-bottom-color: var(--ink); }
.ntr:hover .ntr-title { color: var(--accent); }
.ntr:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ntr-num {
  flex: 0 0 3.4em;
  font-family: var(--ui); font-size: var(--ap-md); font-weight: 600;
  letter-spacing: .16em; color: var(--accent);
  font-variant-numeric: lining-nums tabular-nums;
}
.ntr-title {
  flex: 1 1 auto; min-width: 0;
  font-variation-settings: var(--lit-mid);
  font-weight: 400; font-size: 1.02rem; line-height: 1.2;
  transition: color var(--dur-fast) var(--ease);
}
.ntr-dur {
  flex: 0 0 auto;
  font-family: var(--ui); font-size: var(--ap-sm); color: var(--ink-faint);
  font-variant-numeric: lining-nums tabular-nums;
}
.ntr.is-read .ntr-title { color: var(--ink-soft); }
.ntr.is-active .ntr-title { color: var(--accent); font-weight: 400; }
.ntr.is-active .ntr-num::before { content: '▸ '; }

@media (prefers-reduced-motion: reduce) {
  .nav-toc { transition: none; }
  .toc-row { transition: none; }
  .ntt-here::after { transition: none; }
}

/* ============================================================================
   THE LEXICON — the words, laid down in ink, then stripped back to their roots
   ============================================================================
   Every word here is LIVE TEXT in Frank Ruhl Libre, not a picture. A Hebrew
   letterform is already a drawing: set at this size it is sharper than any image
   of it, it weighs nothing on top of a font the site already ships, it can be
   selected and copied, a screen reader can read it, and it never needs a second
   file for a retina screen. An image of a Hebrew word is a worse Hebrew word.

   THE PLATE. This was one long column of rows divided by hairlines, and it read
   as a list — thirty-six paragraphs to scroll past. Every word now sits on its own
   plate, in its own frame, with room around it, and the plates flow into as many
   columns as the screen has room for. A word you can see the edges of is a word
   you look at; a word in a list is a row you skim.

   THE INK. @property makes a percentage animatable, so a mask can be swept across
   live type. The gradient has a soft leading edge (the 9% feather) rather than a
   hard one, which is the difference between a wipe and a nib laying ink down. It
   sweeps RIGHT TO LEFT, the direction Hebrew is written; .is-greek reverses it,
   because Greek is written the other way and a word that draws itself backwards
   is worse than one that just appears.

   2600ms, NOT THE 1500ms THIS SHIPPED AT. A sweep short enough to miss is a
   transition; the whole point is that you watch the word being written. The
   feather widened with it, because a fast nib and a slow nib do not lay the same
   edge.

   THE ROOT. 900ms after the word lands, every letter that is not a root consonant
   goes from the accent to --ink-faint, and the root is left burning. The dimmed
   letters stay at a colour that CLEARS AA on its own (4.7:1) rather than being
   faded toward the paper — this is real text, not chrome, and a reader must still
   be able to read the whole word after the effect has run. js/render.js decides
   which letters are root letters and fails safe; see the note over renderLexicon.

   Browsers without @property cannot interpolate the custom property, so --lex-ink
   would jump rather than sweep. The @supports block below catches that and gives
   them a plain fade. Nothing here is required to read the page.
   ============================================================================ */
@property --lex-ink {
  syntax: '<percentage>';
  inherits: false;
  initial-value: 0%;
}

#lexicon { padding-block: clamp(3rem, 8vh, 6rem); }

.lex-head {
  grid-column: 1 / -1;
  display: grid; grid-template-columns: subgrid;
  padding-bottom: clamp(1.6rem, 3vw, 2.6rem);
  border-bottom: 1px solid var(--rule-strong);
}
@supports not (grid-template-columns: subgrid) {
  .lex-head { grid-template-columns: repeat(12, 1fr); column-gap: var(--step); }
}
.lex-head .chapter-num { grid-column: 1 / 3; padding-top: .55em; }
.lex-standfirst { grid-column: 3 / 10; }
/* 🛑 .is-bare, shared with .si-head — 2026-08-30 (D19-A). The Lexicon's head lost
   its count-title and its provenance paragraph, so the margin label is the <h2> and
   one italic line stands beside it. Two things follow from that and neither is
   cosmetic: the label is a HEADING now, so it stops taking the baseline nudge that
   aligned it to a title that no longer exists; and the legend is the first line in
   the block, so it loses the top margin it carried as a second paragraph. */
/* the Lexicon's bare head keeps ONE thing of its own: it still has a line beside
   the title, so the legend stops being a second paragraph and the head closes up.
   Everything about the title itself is the shared rule above — D20-A. */
.lex-head.is-bare .lex-legend { margin-top: .45em; }
.lex-head.is-bare { padding-bottom: clamp(1.1rem, 2.2vw, 1.8rem); }
/* the title takes the margin columns AND the standfirst's, so the legend under it
   starts at the section's left edge rather than in the aside column */
.lex-head.is-bare .lex-standfirst { grid-column: 1 / 9; }
@media (max-width: 900px) { .lex-head.is-bare .lex-standfirst { grid-column: 1 / 7; } }
.lex-standfirst h2 {
  font-variation-settings: var(--lit-mid);
  font-weight: 350; font-size: clamp(1.5rem, 3vw, 2.1rem);
  line-height: 1.12; letter-spacing: -.016em; margin: 0 0 .6em;
}
.lex-standfirst p {
  font-style: italic; color: var(--ink-soft);
  font-size: 1rem; line-height: 1.55; max-width: 52ch; margin: 0;
}
.lex-legend { margin-top: .8em; }

/* THE GRID. auto-fit rather than a fixed count, so the plates decide the columns:
   one on a phone, two on a tablet, three on a wide desk, without a breakpoint per
   case. min(100%, 21rem) keeps a single plate from overflowing a narrow screen. */
/* ---------------------------------------------------------------------------
   THE WALL AND THE ARTICLE, 2026-08-29
   The lexicon was fifty cards in a three-column grid: 8,439px, eight and a half
   screens, to look up one word. A lexicon is a LOOKUP. It is now the shape a
   printed dictionary has — the headwords, and the article — and the article sits
   in columns 10–12, the apparatus column the verse notes moved into on the same
   day. One system, used twice.
   The wall keeps the reading measure so the words set as a block of type rather
   than as a table; the article takes the aside column and sticks, so scrolling the
   wall never scrolls the entry off.
   --------------------------------------------------------------------------- */
.lex-body {
  grid-column: 1 / -1;
  display: grid; grid-template-columns: subgrid;
  margin-top: clamp(1.8rem, 4vw, 3rem);
}
@supports not (grid-template-columns: subgrid) {
  .lex-body { grid-template-columns: repeat(12, 1fr); column-gap: var(--step); }
}
/* 🛑 JUSTIFIED TYPE, NOT A FLEX ROW — 2026-08-30 (D15-A). It was `display: flex;
   flex-wrap: wrap` with an even gap, which is a LIST that happens to wrap: every
   line ends ragged wherever the last chip fell, and the block reads as controls.
   A dictionary page or a poem index sets its headwords FLUSH TO BOTH MARGINS so
   the block reads as one field of type, and that is the difference between "a page
   of words" as a description and as a fact.
   The mechanism is `text-align: justify` over an inline run, so:
     · the chips are inline-flex, not flex children
     · js/render.js joins them on a NEWLINE — justify stretches white space, and
       with no white space on the line there is nothing to stretch
     · text-align-last keeps the FINAL line flush left. Without it the last line
       — often two or three words — is stretched across the full measure and the
       block ends in a row of holes.
   The line gap is margin-bottom on the chip plus the wall's line-height; there is
   no `gap` any more because there is no flex container. */
.lex-wall {
  grid-column: 3 / 10;
  display: block;
  text-align: justify;
  text-align-last: left;
  -webkit-text-align-last: left;
  line-height: 1;
  /* the base size of a headword. Every weight below is a multiple of it, so the
     wall rescales from one number. */
  --lex-w: 1.62rem;
}
/* Every word is a control and none of them may look like a button — a row of
   fifty buttons is a toolbar, and this is a page of words. The only thing that
   marks one is a hairline under it, which is what a dictionary uses for a
   headword, and the chosen one takes the accent.
   🛑 vertical-align: baseline is load-bearing now that the words come in three
   sizes. An inline-flex box takes the baseline of its first flex item, which is
   the word — so panim at weight 3 and tefillin at weight 1 SIT ON ONE LINE
   together and the transliterations hang below it. On `top` they would hang from
   a common ceiling and the size difference would read as misalignment. */
.lex-chip {
  display: inline-flex; flex-direction: column; align-items: flex-start; gap: .12em;
  vertical-align: baseline;
  padding: .35em .5em .3em;
  margin: 0 0 .5rem;
  min-height: 44px;
  border: 0; border-bottom: 1px solid var(--rule);
  background: none; color: var(--ink);
  cursor: pointer; text-align: left;
  -webkit-tap-highlight-color: transparent;
  transition: border-color var(--dur-fast) var(--ease),
              background-color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
/* the same 1px drop the filter tabs take (D17-F), so the two halves of the
   section answer a finger the same way */
.lex-chip:active { transform: translateY(1px); }
@media (prefers-reduced-motion: reduce) { .lex-chip:active { transform: none; } }
/* THE WALL CARRIES THE BOOK'S OWN WEIGHTING — 2026-08-30 (D15-B), the author's
   "do it". Three steps, not fifty: js/render.js explains WHY it is three and why
   it is not the raw mention count. What the reader gets is a wall where the eye
   lands on panim first, finds the ten chapter words next, and reads the rest as
   the field they stand in — with no label, no legend and no colour saying so.
   ⚠️ The steps only ever scale UP from the base, so the 44px min-height on the
   chip stays the floor for the smallest weight and every other weight clears it. */
.lex-chip { --lex-mul: 1; }
.lex-chip[data-weight="2"] { --lex-mul: 1.16; }
.lex-chip[data-weight="3"] { --lex-mul: 1.52; }
.lex-chip-word {
  font-family: var(--heb); font-weight: 300;
  font-size: calc(var(--lex-w) * var(--lex-mul)); line-height: 1.25;
  color: var(--accent);
}
/* the Latin and Greek faces run larger than Frank Ruhl at the same em, so they
   take .85 of the Hebrew step to sit at the same optical size */
.lex-chip-word.is-greek {
  font-family: var(--serif); font-variation-settings: var(--lit-normal);
  font-weight: 400; font-size: calc(var(--lex-w) * .85 * var(--lex-mul));
}
.lex-chip-word.is-roman {
  font-family: var(--serif); font-variation-settings: var(--lit-normal);
  font-style: italic; font-weight: 400;
  font-size: calc(var(--lex-w) * .85 * var(--lex-mul));
}
.lex-chip-t {
  font-family: var(--ui); font-size: var(--ap-sm); font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-faint);
  white-space: nowrap;
}
.lex-chip:hover { border-bottom-color: var(--ink); background: var(--control-wash); }
.lex-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.lex-chip.is-on {
  border-bottom-color: var(--accent); border-bottom-width: 2px;
  background: var(--control-wash);
}
.lex-chip.is-on .lex-chip-t { color: var(--ink); }

/* THE PAGE WRITES ITSELF, ONCE — 2026-08-30 (D15-C), the author's "yes can we see
   it???". The argument against it is still the argument the design sheet made: one
   word drawing when you CHOOSE it is an answer arriving, and fifty drawing at once
   is a page assembling itself, which happens to the page rather than to you. So it
   happens exactly once, on the reader's first arrival at the section, and after that
   every draw in this section is an answer again.
   🛑 THE MASK IS REMOVED WHEN IT FINISHES. js/ui.js drops .is-inking, so the wall
   spends the rest of the session as plain type — no live mask on fifty elements, and
   nothing for the print stylesheet or a filter reflow to fight.
   Each word draws in the direction its own language is written: to left for Hebrew,
   to right for Greek and the Latin transliterations. Same rule as the plates. */
.lex-wall.is-inking .lex-chip-word {
  --lex-ink: 0%;
  -webkit-mask-image: linear-gradient(to left,
    #000 0, #000 calc(var(--lex-ink) - 9%), transparent var(--lex-ink), transparent 100%);
  mask-image: linear-gradient(to left,
    #000 0, #000 calc(var(--lex-ink) - 9%), transparent var(--lex-ink), transparent 100%);
  transition: --lex-ink var(--lex-draw-ms, 2600ms) cubic-bezier(.22, .61, .25, 1);
  transition-delay: var(--lex-delay, 0ms);
}
.lex-wall.is-inking .lex-chip-word.is-greek,
.lex-wall.is-inking .lex-chip-word.is-roman {
  -webkit-mask-image: linear-gradient(to right,
    #000 0, #000 calc(var(--lex-ink) - 9%), transparent var(--lex-ink), transparent 100%);
  mask-image: linear-gradient(to right,
    #000 0, #000 calc(var(--lex-ink) - 9%), transparent var(--lex-ink), transparent 100%);
}
/* the transliteration is a caption on the word, so it arrives after its word has —
   otherwise the wall reads as fifty labels with the words catching up */
.lex-wall.is-inking .lex-chip-t {
  opacity: 0;
  transition: opacity 700ms var(--ease);
  transition-delay: calc(var(--lex-delay, 0ms) + 900ms);
}
.lex-wall.is-inking .lex-chip { border-bottom-color: transparent; transition: border-color 900ms var(--ease); transition-delay: var(--lex-delay, 0ms); }
.lex-wall.is-inking.is-drawn .lex-chip-word { --lex-ink: 112%; }
.lex-wall.is-inking.is-drawn .lex-chip-t { opacity: 1; }
.lex-wall.is-inking.is-drawn .lex-chip { border-bottom-color: var(--rule); }
.lex-wall.is-inking.is-drawn .lex-chip.is-on { border-bottom-color: var(--accent); }

/* 🛑 Written at the specificity of the rules above, not as a bare .lex-chip-word —
   this project's own invariant. A reader on reduced motion gets the finished wall,
   which is the wall everybody ends up looking at anyway. */
@media (prefers-reduced-motion: reduce) {
  .lex-wall.is-inking .lex-chip-word {
    -webkit-mask-image: none; mask-image: none;
    transition: none; --lex-ink: 112%;
  }
  .lex-wall.is-inking .lex-chip-t { opacity: 1; transition: none; }
  .lex-wall.is-inking .lex-chip { border-bottom-color: var(--rule); transition: none; }
}

/* 🛑 grid-row: 1, and it is REQUIRED as of D19-B. The controls moved under the wall
   and now sit between the wall and the article in DOM order. Grid auto-placement is
   sparse: the cursor had already advanced past row 1, so the article — which names
   its column but not its row — was placed in ROW 2 and dropped a full wall's height
   below the words it describes. Naming the row pins it beside the wall where it
   belongs, and `align-self: start` + sticky do the rest. */
.lex-article {
  grid-column: 10 / 13; grid-row: 1;
  position: sticky; top: clamp(4.5rem, 9vh, 6.5rem);
  align-self: start;
}
/* THE ARTICLE IS NOT A CARD. It was one when there were fifty of them in a grid and
   the box was what separated one word from the next. There is one now, standing in
   the apparatus column beside a wall of bare type, and a tinted bordered box there
   is decoration pretending to be material — which this book does not do. It takes
   the same treatment the verse note in that column takes: paper, a rule on the side
   the reading is on, and nothing else. */
.lex-article .lex-plate {
  background: none;
  border: 0;
  border-left: 1px solid var(--rule);
  padding: .1rem 0 .2rem clamp(1rem, 1.4vw, 1.4rem);
}
.lex-article .lex-plate[data-kind="mark"] { border-left: 2px solid var(--accent); }
/* 🛑 JUSTIFY NEEDS A MEASURE WIDE ENOUGH TO ABSORB THE STRETCH. Measured 2026-08-30:
   at 402px the wall is a 362px column, most lines hold two or three words, and the
   justification has to open 75–125px of white to reach the right margin — a third of
   the line, which reads as a broken layout rather than as set type. At 1440px the
   same wall is 763px, holds five to nine words a line, and the gaps land at 4–34px.
   So the justification is a DESKTOP treatment and the phone sets ragged-right, which
   is what a narrow measure wants anyway. 700px is where the wall stops being able to
   hold three words on a line. */
@media (max-width: 820px) {
  .lex-wall { text-align: left; }
}
@media (max-width: 900px) {
  /* under the wall on a phone the rule reads as a left margin, so it goes on top */
  .lex-article .lex-plate {
    border-left: 0; border-top: 1px solid var(--rule);
    padding: 1.2rem 0 0;
  }
  .lex-article .lex-plate[data-kind="mark"] { border-left: 0; border-top: 2px solid var(--accent); }
}

@media (max-width: 900px) {
  .lex-body { grid-template-columns: repeat(6, 1fr); }
  /* On a phone the aside column does not exist, so the article goes UNDER the wall.
     🛑 The wall is NOT a nested scroller. It was capped at 46vh with overflow-y so
     the article stayed on screen, and a scrolling box inside a scrolling page is one
     of the few things that is worse on touch than a long page: the wrong thing
     scrolls, momentum fights the page, and words go off-screen with nothing to say
     so. js/ui.js scrolls the article into view on selection instead, which is what
     the reader wanted to happen anyway. */
  .lex-wall { grid-column: 1 / 7; }
  /* one column below 900px, so the rows are DOM order: wall, controls, article.
     grid-row is cleared or the article would try to sit on the wall. */
  /* ==========================================================================
     🛑 THE ENTRY IS SET INTO THE WALL, NOT FLOATED OVER IT — D22-B
     2026-08-30, the author: *"i really like when all the hebrew and greek words load
     up and i see them all written when we have the thing moving at the top it
     actually hides that ... is there an alternative design that will allow us to see
     them load up and will also not have to scroll up and down to get back to it. the
     box is also big and ugly."*

     Two rounds of panel are recorded in js/ui.js and both were the same shape: a box
     over the words, first at the top of the screen (v42), then centred with a 379px
     floor so it could not reflow the page (v43). The floor is what he is calling big
     and ugly, and he is right — it is a 379px shutter standing over a 1,312px wall
     from the moment the section arrives, which means the wall writing itself, the
     one thing this section is FOR, happened behind it.

     There is nowhere on a 402px screen to float a panel that is not on top of the
     thing it describes. So it stops floating. js/ui.js inserts `#lex-article` into
     the wall's own inline flow directly after the chosen word: the line breaks under
     that word, the entry sets in the gap, the wall closes over it. Nothing is
     covered because nothing is on top, and the answer is physically attached to the
     question — which is what a dictionary sub-entry is.

     WHAT THIS RULE HAS TO DO, and it is short because the layout is doing the work:
       · `display: block` in an inline formatting context is what BREAKS the line.
         The wall is inline boxes joined by newlines (see .lex-wall above); a block
         child splits it into two anonymous blocks and sets between them.
       · It is paper with a rule top and bottom. No shadow, no radius, no floor, no
         nested scroller — every one of those existed to manage a box that was over
         something, and it is not over anything now.
       · NO min-height. Entries run 295–378px and each one sets to its own height,
         because a short entry padded out to a tall one's floor is dead space inside
         a block of type. The page reflowing under the entry is fine: js/ui.js pins
         the tapped word to its pixel with a scrollBy, so the reflow happens BELOW
         the reader's eye and they never see it.
     ⚠️ Order and sticky are both gone. The article is a plain grid/flex child at its
     home position and a plain block when it is in the wall. If it is ever floated
     again, this comment is the argument against it.
     ========================================================================== */
  .lex-body { display: flex; flex-direction: column; }
  .lex-wall { order: 0; }
  .lex-controls { order: 1; }
  .lex-article { order: 2; margin: 0; }
  /* in the wall: a block set into the type */
  .lex-wall > .lex-article {
    display: block; width: 100%;
    margin: .1rem 0 .9rem;
    padding: 0;
    background: var(--paper);
    border-top: 1px solid var(--rule-strong);
    border-bottom: 1px solid var(--rule-strong);
  }
  /* the wrapper carries the rules in here, so the plate carries none. The accent
     that marks a chapter's own word moves up onto the wrapper's top rule — one line,
     not two stacked. :has() is the right selector and its absence costs a colour,
     not a layout. */
  .lex-wall > .lex-article .lex-plate,
  .lex-wall > .lex-article .lex-plate[data-kind="mark"] {
    padding: 1rem 0 1.1rem; border-top: 0; border-left: 0;
  }
  .lex-wall > .lex-article:has(.lex-plate[data-kind="mark"]:not([hidden])) {
    border-top: 2px solid var(--accent);
  }
}


.lex-plates {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 21rem), 1fr));
  gap: clamp(1rem, 2vw, 1.6rem);
  margin-top: clamp(1.8rem, 4vw, 3rem);
}

/* 🛑 [hidden] LOSES TO AN AUTHOR display RULE. `hidden` is `display:none` in the UA
   stylesheet and any author `display` on the same element beats it outright, so the
   forty-nine closed entries all painted and the section measured 22,564px — nearly
   three times the card grid it replaced. This one line is what makes the article an
   article. It is the same trap as .sheet[hidden] a few hundred lines up. */
.lex-plate[hidden] { display: none; }
.lex-plate {
  display: flex; flex-direction: column;
  padding: clamp(1.5rem, 3vw, 2.2rem);
  background: color-mix(in srgb, var(--ink) 2.5%, var(--paper));
  border: 1px solid var(--rule);
  transition: border-color var(--dur-med) var(--ease);
}
.lex-plate.is-drawn { border-color: var(--rule-strong); }
/* the chapter's own word is the plate the chapter is named for — it gets the
   accent edge, and nothing else, so the set still reads as one material */
.lex-plate[data-kind="mark"] { border-left: 2px solid var(--accent); }

.lex-tag {
  margin: 0 0 .9em;
  font-family: var(--ui); font-size: var(--ap-sm); font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-faint);
}

/* the word itself */
.lex-word {
  font-family: var(--heb); font-weight: 300;
  font-size: clamp(2.8rem, 6vw, 4.4rem);
  line-height: 1.18;
  color: var(--accent);
  overflow: visible;
  margin-bottom: .35em;
  min-height: 1.18em;
}
.lex-word.is-greek {
  font-family: var(--serif);
  font-variation-settings: var(--lit-display);
  font-style: italic; font-weight: 300;
  font-size: clamp(1.7rem, 3.4vw, 2.7rem);
}
/* the one Akkadian row: a Latin transliteration, and it says so by being set as one
   — roman, not italic, letterspaced, the way a transliteration is set in a lexicon */
.lex-word.is-roman {
  font-family: var(--serif);
  font-variation-settings: var(--lit-display);
  font-style: normal; font-weight: 300;
  font-size: clamp(1.6rem, 3vw, 2.4rem);
  letter-spacing: .04em;
}
.lex-word.is-roman .lex-ink {
  -webkit-mask-image: linear-gradient(to right,
    #000 0, #000 calc(var(--lex-ink) - 9%), transparent var(--lex-ink), transparent 100%);
  mask-image: linear-gradient(to right,
    #000 0, #000 calc(var(--lex-ink) - 9%), transparent var(--lex-ink), transparent 100%);
}

.lex-ink {
  display: inline-block;
  --lex-ink: 0%;
  -webkit-mask-image: linear-gradient(to left,
    #000 0, #000 calc(var(--lex-ink) - 9%), transparent var(--lex-ink), transparent 100%);
  mask-image: linear-gradient(to left,
    #000 0, #000 calc(var(--lex-ink) - 9%), transparent var(--lex-ink), transparent 100%);
  transition: --lex-ink 2600ms cubic-bezier(.22, .61, .25, 1);
}
.lex-word.is-greek .lex-ink {
  -webkit-mask-image: linear-gradient(to right,
    #000 0, #000 calc(var(--lex-ink) - 9%), transparent var(--lex-ink), transparent 100%);
  mask-image: linear-gradient(to right,
    #000 0, #000 calc(var(--lex-ink) - 9%), transparent var(--lex-ink), transparent 100%);
}
.lex-plate.is-drawn .lex-ink { --lex-ink: 112%; }

/* one written letter with its own points. Inline, so the word still shapes and
   line-breaks as a word; a screen reader reads the letters back in order. */
.lex-g { transition: color 1100ms var(--ease); }
.lex-plate.is-rooted .lex-g { color: var(--ink-faint); }
.lex-plate.is-rooted .lex-g.is-root { color: var(--accent); }

.lex-root-note {
  margin: 0 0 .8em;
  font-family: var(--ui); font-size: var(--ap-sm); font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-faint);
  opacity: 0; transition: opacity 900ms var(--ease);
}
.lex-plate.is-rooted .lex-root-note { opacity: 1; }
/* 🛑 THE ABSENT-ROOT LINE IS NOT ANIMATED, added 2026-08-29 (D14-F). The rule above
   holds .lex-root-note at opacity 0 until js/ui.js adds .is-rooted, and .is-rooted is
   only ever added to a plate that HAS a root to dim to. An entry with no root would
   therefore have printed its explanation at opacity 0 — a sentence written to close a
   gap, invisible, in the gap. It is shown outright, in the faint ink, because it is a
   statement of fact and not the payoff of an animation. */
/* 🛑 --ap-lg, NOT --ap-md, from 2026-08-30 (D15-E). Found by auditing every
   computed font-size on the page against this project's OWN rule, written at the
   top of css/site.css: "--ap-md: captions, running times, anything with a few
   words / --ap-lg: apparatus that carries a sentence." These 31 notes are
   sentences — the longest is 89 characters — and they shipped at the caption
   size on 2026-08-29.
   It is the same fault as the player bar in round fourteen §6, one round later
   and in the opposite direction: there, furniture was set at apparatus size;
   here, a sentence was set at caption size. The rule was already written down
   both times. A token scale only holds if something measures against it. */
.lex-root-note.is-absent {
  opacity: 1; transition: none;
  letter-spacing: .1em; text-transform: none;
  font-family: var(--serif); font-variation-settings: var(--lit-normal);
  font-weight: 400; font-style: italic; font-size: var(--ap-lg);
  color: var(--ink-faint);
}
.lex-root-word {
  font-family: var(--heb); font-weight: 400;
  font-size: 1.5em; letter-spacing: .08em; text-transform: none;
  color: var(--accent); vertical-align: -.12em; margin-inline-start: .5em;
}

/* No @property: the percentage cannot interpolate, so the mask would snap. Drop the
   mask entirely there and fade instead — the word still arrives, just plainly. */
@supports not (background: paint(x)) {
  @supports not (--lex-probe: 1%) {
    .lex-ink { -webkit-mask-image: none; mask-image: none; opacity: 0; transition: opacity 900ms var(--ease); }
    .lex-plate.is-drawn .lex-ink { opacity: 1; }
  }
}

/* the reading side */
.lex-translit {
  font-family: var(--serif); font-variation-settings: var(--lit-mid);
  font-style: italic; font-weight: 400;
  font-size: clamp(1.1rem, 1.6vw, 1.35rem); line-height: 1.2;
  margin: 0 0 .55em; color: var(--ink);
}
.lex-gloss {
  font-size: .98rem; line-height: 1.62; color: var(--ink-soft);
  max-width: 46ch; margin: 0 0 auto;
}
.lex-where {
  margin: 1.2em 0 0;
  display: flex; flex-wrap: wrap; align-items: center; gap: .9em;
  font-family: var(--serif); font-variation-settings: var(--lit-normal);
  font-size: var(--ap-sm); font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase; color: var(--accent);
}
.lex-where a {
  color: var(--ink-faint);
  border-bottom: 1px solid currentColor;
  min-height: 44px; display: inline-flex; align-items: center;
}
.lex-where a:hover, .lex-where a:focus-visible { color: var(--ink); }

@media (max-width: 900px) {
  .lex-head { grid-template-columns: repeat(6, 1fr); }
  .lex-head .chapter-num { grid-column: 1 / 7; padding-top: 0; margin-bottom: .6rem; }
  .lex-standfirst { grid-column: 1 / 7; }
}

/* The sweep and the dimming are decoration on top of text that is already legible.
   Turned off, the word is simply there — and its root is lit from the start, because
   the root highlight is INFORMATION, not motion, and it is the one thing in this
   section a reader would actually lose. */
@media (prefers-reduced-motion: reduce) {
  .lex-ink {
    -webkit-mask-image: none; mask-image: none;
    transition: none; --lex-ink: 112%;
  }
  .lex-g, .lex-root-note { transition: none; }
  .lex-plate .lex-g { color: var(--ink-faint); }
  .lex-plate .lex-g.is-root { color: var(--accent); }
  .lex-plate .lex-root-note { opacity: 1; }
}

/* ============================================================================
   THE LEXICON FILTER
   ============================================================================
   Fifty plates is a lot to scroll if you came looking for one word. One attribute
   does the work: .lex-plates[data-filter="ch03"] hides every plate that is not
   chapter III's. No per-element display juggling, and the grid reflows itself.

   The buttons are numerals in the book's typeface with a small count beside them,
   because "III · 5" answers a question the bare numeral does not. They are real
   <button>s carrying aria-pressed — the state is "which one of these is on", which
   is exactly what aria-pressed says, and it is why this is not a row of links.
   ============================================================================ */
/* TWO GROUPS, ONE ROW, since 2026-08-29 (D14-E). "Show" selects a chapter or a
   language; "Order" selects chapter order or A–Z. They are different questions, so
   they are different groups with their own labels rather than one undifferentiated
   run of fourteen buttons the reader has to parse by reading all of them.
   The row wraps to two lines below ~900px on its own; nothing is hidden. */
/* 🛑 THE WALL'S COLUMNS, NOT THE SECTION'S — 2026-08-30 (D19-B). The row moved
   from above .lex-body to inside it, under the wall, and `grid-column: 1 / -1`
   came with it. Inside .lex-body that span reaches under the ARTICLE as well, so
   sixteen controls ran the full width and the entry beside them sat on top of
   their own right-hand end. It takes the wall's own track (3 / 10) instead, which
   is what "below the words" means: the same measure, directly under the block it
   filters. A rule above it separates the two without a heading saying so. */
.lex-controls {
  grid-column: 3 / 10;
  display: flex; flex-wrap: wrap; gap: .4rem 1.6rem;
  align-items: center;
  margin-top: clamp(1.2rem, 2.6vw, 1.9rem);
  padding-top: clamp(1rem, 2.2vw, 1.6rem);
  border-top: 1px solid var(--rule);
}
/* 🛑 THIS OVERRIDE HAS TO LIVE HERE, not up in the 900px block beside .lex-body.
   Both selectors are a single class, so the cascade is decided by source order and
   the .lex-body block is ~200 lines EARLIER in this file — the base 3 / 10 won and
   the row sat indented two columns under a full-width wall. Measured at 402px:
   controls at x=141 w=241 against a wall at x=20 w=362. Same-specificity media
   queries must come after the rule they override. */
@media (max-width: 900px) {
  .lex-controls { grid-column: 1 / 7; }
}
.lex-controls-label {
  font-family: var(--ui); font-size: var(--ap-sm); font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase; color: var(--ink-faint);
  margin-inline-end: .2rem;
}
.lex-filter, .lex-sort {
  display: flex; flex-wrap: wrap; gap: .4rem;
  align-items: center;
}
.lex-filter { flex: 1 1 24rem; }
/* the hairline between the ten chapter numerals and the three languages: one filter
   row, two kinds of thing in it, and no second border or heading to say so */
.lex-filter-sep {
  width: 1px; align-self: center; height: 1.5em; margin: 0 .55rem;
  background: var(--rule-strong);
}
/* ============================================================================
   THE CONTROLS SET AS TABS, NOT AS BOXES — 2026-08-30 (D17-F)
   Reader's words: "the all 1 2 3 4 hebrew buttons ... look super cheap right now
   ... its not pleasant and its cheap."
   They were right, and the reason is worth writing down because it was not the
   colours. Every button was a bordered rectangle with a wash inside it and a
   boxed count badge — a form widget — sitting six centimetres above a WALL OF
   FIFTY WORDS whose entire design principle is stated in this file already:
   "Every word is a control and none of them may look like a button ... the only
   thing that marks one is a hairline under it, which is what a dictionary uses
   for a headword, and the chosen one takes the accent."
   The section had two contradictory languages in it. The wall's is the right one
   — it is the one that comes from print — so the controls now speak it too.
   A tab is bare type standing on a rule. The rule under EVERY tab is always
   drawn, which is the affordance: this row is a row of controls, and you can see
   that without hovering it. The rule under the ONE THAT IS ON is twice as thick
   and takes the accent. That is the same two-state mark .lex-chip uses, so
   choosing a chapter up here and choosing a word down there now feel like the
   same gesture, because they are drawn with the same two marks.
   WHAT MAKES IT FEEL PRESSED, and it is three things, none of them a shadow:
     · hover grows the accent mark out from the centre to 40% — a real motion of
       one transform on one pseudo-element, not a colour swap;
     · :active drops the whole tab 1px and pushes the mark to 70%, so the press
       is a movement the finger caused and the release springs back;
     · the type goes from --ink-soft to --ink, so the tab gains weight under the
       cursor rather than gaining a border.
   ⚠️ CONTRAST AND TARGETS ARE UNCHANGED OR BETTER. The 44×44 floor stays — and
   min-WIDTH stays with it, which was the D14-E finding: Chapter I, V and X came
   out 34px wide against the height-only check, because one narrow roman letter
   plus a count is simply not as wide as "Chapter VIII". Three of sixteen failed
   and the row LOOKED even, which is why it survived a whole round. Both axes,
   still measured. WCAG 1.4.11 wants 3:1 on the visual boundary of a control:
   the always-drawn rule is --rule-strong and the on-state mark is --accent
   (#7E5A20 on #FDFAF3 = 5.98:1), so removing the box did not remove the
   boundary — it moved it to one edge and made it darker.
   ============================================================================ */
.lex-filter-btn {
  position: relative;
  display: inline-flex; align-items: baseline; justify-content: center; gap: .42em;
  min-height: 44px; min-width: 44px;
  padding: .6em .55em .75em;
  border: 0;
  background: none;
  color: var(--ink-soft);
  font-family: var(--serif); font-variation-settings: var(--lit-normal);
  font-size: 1rem; font-weight: 400;
  font-variant-numeric: lining-nums tabular-nums;
  letter-spacing: .06em;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
/* the rule the tab stands on. Always drawn — it is what says "control". */
.lex-filter-btn::before {
  content: ''; position: absolute; left: .18em; right: .18em; bottom: .42em;
  height: 1px; background: var(--rule-strong);
}
/* and the mark that answers. Scale, not width: a transform does not reflow the
   row, and fourteen of these animate at once on a phone. */
.lex-filter-btn::after {
  content: ''; position: absolute; left: .18em; right: .18em; bottom: .38em;
  height: 2px; background: var(--accent);
  transform: scaleX(0); transform-origin: 50% 50%;
  transition: transform var(--dur-med) var(--ease);
}
.lex-filter-btn:hover { color: var(--ink); }
.lex-filter-btn:hover::after { transform: scaleX(.4); }
.lex-filter-btn:active { transform: translateY(1px); }
.lex-filter-btn:active::after { transform: scaleX(.7); }
.lex-filter-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.lex-filter-btn.is-on { color: var(--ink); font-weight: 500; }
.lex-filter-btn.is-on::after,
.lex-filter-btn.is-on:hover::after,
.lex-filter-btn.is-on:active::after { transform: scaleX(1); }
/* the count sets as a superior figure — the way a dictionary prints a sense
   number — instead of as a badge with its own box inside another box */
.lex-filter-n {
  font-family: var(--ui); font-size: var(--ap-sm); font-weight: 600;
  color: var(--ink-faint);
  align-self: flex-start; transform: translateY(.18em);
}
.lex-filter-btn.is-on .lex-filter-n { color: var(--accent); }
/* 🛑 The press is the ONE motion here that a reader can trigger sixteen times in
   ten seconds, so it is the one that must go away entirely under reduce. */
@media (prefers-reduced-motion: reduce) {
  .lex-filter-btn,
  .lex-filter-btn::after { transition: none; }
  .lex-filter-btn:active { transform: none; }
}

/* the filter itself — it hides WORDS ON THE WALL now, not plates in a grid */
.lex-wall[data-filter="ch01"] .lex-chip:not([data-ch="ch01"]),
.lex-wall[data-filter="ch02"] .lex-chip:not([data-ch="ch02"]),
.lex-wall[data-filter="ch03"] .lex-chip:not([data-ch="ch03"]),
.lex-wall[data-filter="ch04"] .lex-chip:not([data-ch="ch04"]),
.lex-wall[data-filter="ch05"] .lex-chip:not([data-ch="ch05"]),
.lex-wall[data-filter="ch06"] .lex-chip:not([data-ch="ch06"]),
.lex-wall[data-filter="ch07"] .lex-chip:not([data-ch="ch07"]),
.lex-wall[data-filter="ch08"] .lex-chip:not([data-ch="ch08"]),
.lex-wall[data-filter="ch09"] .lex-chip:not([data-ch="ch09"]),
.lex-wall[data-filter="ch10"] .lex-chip:not([data-ch="ch10"]) { display: none; }
/* …and the same mechanism on the second axis. Three more rules rather than a
   JS class toggle, so both axes stay one attribute on one element — which is the
   reason the wall reflows without a single per-element write. */
.lex-wall[data-filter="lang:he"]  .lex-chip:not([data-lang="he"]),
.lex-wall[data-filter="lang:grc"] .lex-chip:not([data-lang="grc"]),
.lex-wall[data-filter="lang:akk"] .lex-chip:not([data-lang="akk"]) { display: none; }

/* On paper there is no filter and no chosen word, so the wall goes and ALL FIFTY
   ENTRIES PRINT. A printed lexicon that showed one article and forty-nine hidden
   ones would be a lexicon with one word in it. */
@media print {
  /* .lex-controls, not .lex-filter — the row gained a second group (the sort) on
     2026-08-29 and hiding only the first would have printed a stray "Order /
     Chapter order / A–Z" above a lexicon that has neither. Paper has no controls.
     The printed entries stay in the book's order whatever the screen was sorted to,
     which is right: on paper this is the back of THIS book, not a dictionary. */
  .lex-controls, .lex-wall { display: none !important; }
  .lex-article .lex-plate { display: flex !important; }
  .lex-article .lex-plate[hidden] { display: flex !important; }
  .lex-article { display: block !important; }
  .lex-plate .lex-ink { opacity: 1 !important; -webkit-mask-image: none !important; mask-image: none !important; }
}


/* ============================================================================
   NAMES AND PLACES — js/render.js renderNames(), #names
   The index a reader has and cannot do, 2026-08-30 (D15-F). It reuses the Index of
   Scripture's head (.si-head.is-bare), its book heading (.si-book-name)
   and its citation link (.si-cite) — three components, already measured, already
   at 44px. What is new here is only the entry itself: a name, what it is, and a
   row of chapter numerals.
   ============================================================================ */
.ni-body {
  grid-column: 1 / -1;
  display: grid; grid-template-columns: subgrid;
  margin-top: clamp(1.8rem, 4vw, 3rem);
  row-gap: clamp(1.8rem, 3vw, 2.6rem);
}
@supports not (grid-template-columns: subgrid) {
  .ni-body { grid-template-columns: repeat(12, 1fr); column-gap: var(--step); }
}
.ni-letter { grid-column: 1 / -1; display: grid; grid-template-columns: subgrid; }
@supports not (grid-template-columns: subgrid) {
  .ni-letter { grid-template-columns: repeat(12, 1fr); column-gap: var(--step); }
}
/* the letter stands alone in the margin column, with the entries beside it rather
   than under it — so .si-book-name's underline has nothing to underline and is
   dropped.
   🛑 IT IS NO LONGER A .si-book-name IN ANYTHING BUT NAME, from 2026-08-30 (D17-E).
   It inherited --ap-sm tracked uppercase — 10.24px — because it started life as the
   Index of Scripture's book heading, where the label is a WORD ("Deuteronomy") and
   10px of tracked caps is the right size for it. Here the label is a SINGLE LETTER
   doing the job a thumb-index does in a printed book: it is the thing you aim at
   from across the page, and at 10px it was the smallest type in the section and the
   hardest thing on it to see. Reader's words: "the abc ... could be bigger its hard
   to see."
   So it sets as a printed index letter instead — serif, large, in the section's
   accent. Contrast measured, not assumed: #7E5A20 on #FDFAF3 is 5.98:1, which
   clears AA for body text and is nearly double the 3:1 large-text floor. */
.ni-letter .si-book-name {
  grid-column: 1 / 3; padding-top: .05em;
  border-bottom: 0; padding-bottom: 0;
  font-family: var(--serif);
  font-variation-settings: var(--lit-normal);
  font-size: clamp(2.1rem, 3.6vw, 3.2rem);
  font-weight: 400;
  line-height: .88;
  letter-spacing: 0;
  text-transform: none;
  color: var(--accent);
}
/* the entries set in columns, so a 60-name index is a page rather than a scroll —
   auto-fill against a real minimum, the same reasoning .lex-plates used */
.ni-entries {
  grid-column: 3 / 13;
  display: grid; gap: 1.4rem clamp(1.2rem, 2vw, 2.2rem);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
  align-content: start;
}
.ni-entry { break-inside: avoid; }
.ni-name {
  font-family: var(--serif); font-variation-settings: var(--lit-normal);
  font-weight: 500; font-size: 1rem; line-height: 1.3; color: var(--ink);
  margin: 0 0 .2em;
  display: flex; align-items: baseline; gap: .5em; flex-wrap: wrap;
}
/* .ni-kind — the PERSON / PLACE badge — was dropped with its markup (D17-B). */
.ni-note {
  font-family: var(--serif); font-variation-settings: var(--lit-normal);
  font-style: italic; font-size: var(--ap-lg); line-height: 1.5;
  color: var(--ink-soft); margin: 0 0 .35em; max-width: 34ch;
}
/* 🛑 .ni-cite, NOT .si-cite — and this is the one place the reuse STOPS. The
   Index of Scripture's row is "reference … dotted leader … chapter numeral", the
   shape a printed index uses to carry the eye across a wide column to a page
   number. It draws that leader with a ::after and separates its rows with a
   border-top. Here the numerals are a short inline run with no reference to lead
   FROM, so both decorations came through as stray marks — a dangling dash after
   every numeral and a rule above it. Measured on screen 2026-08-30, not guessed.
   Reuse a component for what it does, not for what it is called. */
.ni-list { display: flex; flex-wrap: wrap; gap: 0 .1rem; margin: 0; padding: 0; list-style: none; }
.ni-cite { margin: 0; }
/* 🛑 44px on the numeral itself, not on a parent. These are the smallest targets in
   the back matter — a roman numeral is one to four narrow letters — and the round
   fourteen lesson was that a height-only check is half a check. Both axes, measured. */
.ni-cite a {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px;
  color: var(--accent); text-decoration: none;
}
.ni-num {
  font-family: var(--ui); font-size: var(--ap-md); font-weight: 600;
  letter-spacing: .08em; font-variant-numeric: lining-nums tabular-nums;
}
.ni-cite a:hover .ni-num,
.ni-cite a:focus-visible .ni-num { box-shadow: inset 0 -1px 0 var(--accent); }
.ni-cite a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ============================================================================
   NAMES AND PLACES ON A PHONE — 🛑 A ONE-COLUMN GRID IS NOT A LIST
   2026-08-30 (D20-D), the author: *"on mobile the names and places are not as
   clean."* Measured at 402px before the change: `.ni-entries` collapsed to a single
   362px column of `auto-fill minmax(15rem, 1fr)`, so **58 entries × 112px** of
   free-floating blocks separated by 1.4rem of air and nothing else. Two things were
   wrong with it and neither was the type:
     · **The note was capped at 34ch = 255px inside a 362px column**, so every note
       broke early and ragged with 107px of empty measure beside it. That cap exists
       to keep a note readable next to its neighbours in a multi-column grid. There
       are no neighbours here.
     · **Air was doing the separating.** A gap says "these are different blocks"; a
       rule says "these are rows of one list", and an index is a list. Blocks that
       only differ by 1.4rem of white read as a heap.
   So on a phone the entry becomes a real row: full measure, a hairline under it, and
   the numerals pulled up beside the name instead of stacked on a third line. The
   name and its chapters sit on one baseline the way a printed index sets them, and
   the note sits under both.
   ============================================================================ */
@media (max-width: 900px) {
  .ni-body, .ni-letter { grid-template-columns: repeat(6, 1fr); }
  /* full width now, so the letter gets the rule back: with no entries beside it,
     a bare glyph on a line reads as a dropped capital rather than as a heading. */
  .ni-letter .si-book-name {
    grid-column: 1 / 7; padding-top: 0; margin-bottom: .6rem;
    padding-bottom: .12em; border-bottom: 1px solid var(--rule-strong);
  }
  .ni-entries { grid-column: 1 / 7; display: block; }

  .ni-entry {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: baseline;
    column-gap: .6rem;
    padding: .55rem 0 .7rem;
    border-bottom: 1px solid var(--rule);
  }
  .ni-letter:last-child .ni-entry:last-child { border-bottom: 0; }
  .ni-name { grid-column: 1; margin: 0; font-size: 1.06rem; }
  /* 🛑 the numerals ride in the second track on the SAME row as the name, so they
     land hard right where a page number goes. They keep their 44px targets — the
     row simply got taller than the type, which is what padding is for. */
  .ni-list { grid-column: 2; grid-row: 1; justify-content: flex-end; }
  .ni-note {
    grid-column: 1 / -1; grid-row: 2;
    max-width: none;          /* 34ch inside a 362px column broke every note early */
    margin: .1rem 0 0;
    font-size: .92rem; line-height: 1.45;
  }
}

@media print {
  .ni-entry { break-inside: avoid; }
}

/* ============================================================================
   WHERE THESE CAME FROM — js/render.js renderSources(), #sources
   The sources page, 2026-08-30 (D15-D). It reuses the Lexicon's head (.lex-head /
   .lex-standfirst) rather than declaring a second one: two apparatus sections that
   open the same way ARE the same component, and this project has been bitten twice
   by keeping a second hand-copy of something.
   Below the head it is one system: a group heading in the apparatus voice, and
   under it a set of entries set as type on paper. No cards, no boxes, no tint —
   the same argument the Lexicon article settled on the day it stopped being a card.
   ============================================================================ */
.src-body {
  grid-column: 1 / -1;
  display: grid; grid-template-columns: subgrid;
  margin-top: clamp(1.8rem, 4vw, 3rem);
  row-gap: clamp(2.2rem, 4vw, 3.4rem);
}
@supports not (grid-template-columns: subgrid) {
  .src-body { grid-template-columns: repeat(12, 1fr); column-gap: var(--step); }
}
.src-group { grid-column: 1 / -1; display: grid; grid-template-columns: subgrid; }
@supports not (grid-template-columns: subgrid) {
  .src-group { grid-template-columns: repeat(12, 1fr); column-gap: var(--step); }
}
/* 🔴 THE GROUP HEADING WAS THE SECOND LABEL IN THE MARGIN, AND IT LOST — fixed
   2026-09-07. The author: "on the left it has the tiny text … but we already have a
   title, do we need two titles?" He is right about what he saw and the answer is that
   the section needs both LEVELS and had been drawing them identically: "THE SOURCES"
   and "THE TEXT ITSELF" were the same 10.24px tracked uppercase in the same margin
   column, so the page appeared to label itself twice and neither label won.
   ⭐ THIS IS THE FIX NAMES AND PLACES ALREADY SHIPPED, ARRIVING LATE. Read the note
   above `.ni-letter .si-book-name`: the same 10px margin label was "the smallest type
   in the section and the hardest thing on it to see" ("the abc … could be bigger its
   hard to see"), and it became a printed index letter — serif, large, in the accent.
   A group name here is a phrase rather than a letter, so it sets smaller than that
   one and wraps; everything else about it is the same object.
   🛑 CONTRAST IS INHERITED, NOT ASSUMED: --accent is #7E5A20 on the section's stock,
   measured at 5.98:1 for the Names letter, and this is larger than body text. */
.src-group-head {
  grid-column: 1 / 3;
  font-family: var(--serif);
  font-variation-settings: var(--lit-mid);
  font-size: clamp(1.25rem, 1.9vw, 1.6rem);
  font-weight: 400; line-height: 1.1;
  letter-spacing: -.005em; text-transform: none;
  color: var(--accent);
  margin: 0; padding-top: .05em;
  text-wrap: balance;
}
/* 🛑 THE NOTE RUNS THE FULL ENTRY MEASURE, NOT A THIRD OF IT. It was `3 / 10` with a
   52ch cap inside a column that is narrower than 52ch, so the cap never applied and
   the sentence broke at about forty characters — three cramped ragged lines that read
   as a caption for something. It is a standfirst; it sets like one. */
.src-group-note {
  grid-column: 3 / -1;
  font-style: italic; color: var(--ink-soft);
  font-size: var(--ap-lg); line-height: 1.55; max-width: 62ch;
  margin: 0 0 1.5rem;
}
/* 🛑 THE ENTRIES FILL THE PAGE. `3 / 10` left columns 10–12 empty down the whole
   section — a quarter of the sheet blank beside eleven short entries, while the
   Index of Scripture and Names and Places both run to the edge. Same auto-fill as
   `.ni-entries`, at a wider minimum because these carry a sentence of prose and a
   name that runs to sixty characters. */
.src-items {
  grid-column: 3 / -1;
  display: grid; align-content: start;
  gap: clamp(1.6rem, 2.6vw, 2.4rem) clamp(1.4rem, 2.4vw, 2.6rem);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 22rem), 1fr));
}
/* 🛑 NO RULE BETWEEN ENTRIES, AND THIS IS THE REASON RATHER THAN A PREFERENCE.
   Each entry used to carry `border-top`, with the first one clearing it. In a single
   column that reads; in a grid the top of the SECOND column also clears the group
   above it, so the rule appeared over one entry and not its neighbour — an orphan
   mark, and no selector can say "first in each column" across every column count.
   The rest of the back matter had already settled this: Names and Places separates
   sixty entries with space alone, and the Index of Scripture rules its book headings,
   not its rows. Title, attribution and prose are three voices already; space is
   enough. The only rule left in an entry is the accent flag on an open citation. */
.src-item { break-inside: avoid; }
.src-title {
  font-family: var(--serif); font-variation-settings: var(--lit-normal);
  font-weight: 500; font-size: 1.05rem; line-height: 1.35;
  color: var(--ink); margin: 0 0 .25em;
}
/* 🛑 --ap-md, not --ap-sm. --ap-sm is the tracked-uppercase floor and it is for
   SHORT labels — "Genesis", "Chapter I". These attributions run to sixty
   characters ("Brown, Driver and Briggs · Augmented Strong edition, via Sefaria"),
   and tracked-out 10.24px uppercase at that length is a wall. It stays a label —
   uppercase, tracked, in the accent — it just stops being set at the floor. */
.src-who {
  font-family: var(--ui); font-size: var(--ap-md); font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent); margin: 0 0 .55em;
}
.src-what {
  font-family: var(--serif); font-variation-settings: var(--lit-normal);
  font-size: var(--ap-lg); line-height: 1.6; color: var(--ink-soft);
  max-width: 56ch; margin: 0;
}
.src-where {
  font-family: var(--ui); font-size: var(--ap-sm);
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-faint); margin: .5em 0 0;
}
/* 🛑 THE OPEN CITATION IS MARKED IN INK, NOT IN COLOUR ALONE. The accent rule on
   the left is the same mark .lex-plate[data-kind="mark"] uses, and the sentence
   underneath says the same thing in words — so the flag survives greyscale, print,
   and a reader who cannot separate the accent from the ink. */
.src-item.is-open { border-left: 2px solid var(--accent); padding-left: clamp(.9rem, 1.2vw, 1.2rem); }
.src-flag {
  font-family: var(--serif); font-variation-settings: var(--lit-normal);
  font-style: italic; font-size: var(--ap-lg); line-height: 1.55;
  color: var(--ink-soft); max-width: 56ch; margin: .6em 0 0;
}

@media (max-width: 900px) {
  .src-body, .src-group { grid-template-columns: repeat(6, 1fr); }
  .src-group-head { grid-column: 1 / 7; padding-top: 0; margin-bottom: .6rem; }
  .src-group-note, .src-items { grid-column: 1 / 7; }
  /* one column on a phone: two 22rem tracks would each be narrower than the minimum
     and collapse to one anyway — this says so rather than relying on it */
  .src-items { grid-template-columns: 1fr; }
}

/* On paper the whole list prints — it is the one section whose entire job is to be
   checkable, and a printed page with the sources folded away would be worse than
   no page at all. */
@media print {
  .src-item { break-inside: avoid; }
}

/* ============================================================================
   SEARCH — js/search.js, #search-sheet
   The sheet chrome is the shared .sheet; everything below is the field and the
   result list. A result is one link, not a card: a rule between rows, the kind
   and the place in the apparatus voice, and the matching line of the book in the
   book's own face. Nothing here is a new component — it is the same margin
   apparatus and the same reading measure the chapters use.
   ============================================================================ */
.sheet-search .sheet-inner { max-width: 46rem; }
.search-field { margin: 1.2rem 0 .4rem; }
#search-input {
  width: 100%;
  box-sizing: border-box;
  padding: .8em .9em;
  border: 1px solid var(--control-edge);
  background: var(--paper);
  color: var(--ink);
  font-family: var(--serif);
  font-variation-settings: var(--lit-normal);
  font-size: 1.05rem;
  min-height: 48px;
  border-radius: 0;
  -webkit-appearance: none;
}
#search-input::placeholder { color: var(--ink-faint); }
#search-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.search-status {
  margin: 0 0 .4rem;
  font-family: var(--ui); font-size: var(--ap-md);
  letter-spacing: .06em; color: var(--ink-faint);
  min-height: 1.2em;
}
/* The list is scrollable inside the sheet rather than growing it, so the field
   stays put while the reader reads down the results. */
.search-results { max-height: min(56vh, 32rem); overflow-y: auto; }
.sr-hit {
  display: block;
  padding: .85rem 0;
  border-top: 1px solid var(--rule);
  color: var(--ink-soft);
  text-decoration: none;
}
.sr-hit:hover, .sr-hit:focus-visible { color: var(--ink); }
.sr-hit:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sr-meta {
  display: flex; gap: .8em; align-items: baseline; flex-wrap: wrap;
  margin-bottom: .3em;
  font-family: var(--ui); font-size: var(--ap-sm);
  letter-spacing: .12em; text-transform: uppercase;
}
.sr-kind { color: var(--accent); font-weight: 600; }
.sr-where { color: var(--ink-faint); }
.sr-text {
  display: block;
  font-family: var(--serif); font-variation-settings: var(--lit-normal);
  font-size: var(--ap-lg); line-height: 1.55;
}
/* The match is marked by INK, not by a highlighter block. A yellow band is
   product chrome and it is unreadable on eight different paper stocks; weight and
   a hairline underneath say the same thing and survive the dawn arc. */
.sr-text mark {
  background: none;
  color: var(--ink);
  font-weight: 600;
  box-shadow: inset 0 -1px 0 var(--accent);
}
#search-btn svg { width: 17px; height: 17px; display: block; }
#search-btn { display: grid; place-items: center; }

@media (max-width: 640px) {
  .search-results { max-height: 50vh; }
}

/* ---------------------------------------------------------------------------
   THE BACK MATTER'S SIDEWAYS DOOR — 2026-08-30 (D22-C)
   One row at the foot of each apparatus section, linking to the other four and to
   the contents. js/render.js says why it exists and why it is generated.
   It is a ROW OF TYPE, not a row of buttons — the same rule the lexicon controls
   live under: nothing in the apparatus may look like a toolbar. A hairline above
   the row, a hairline under each link on hover, and nothing else.
   🛑 44px minimum, and the vertical padding is what makes it. A line-height would
   give the text room without giving the finger any.
   --------------------------------------------------------------------------- */
.bm-links {
  grid-column: 3 / 11;
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 0 clamp(1rem, 2.2vw, 2rem);
  margin-top: clamp(2.4rem, 5vw, 4rem);
  border-top: 1px solid var(--rule);
}
@media (max-width: 900px) { .bm-links { grid-column: 1 / 7; } }
.bm-link {
  display: inline-flex; align-items: center; min-height: 44px;
  padding: .2rem 0;
  font-family: var(--ui); font-size: var(--ap-sm); font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-faint); text-decoration: none;
  border-bottom: 1px solid transparent;
}
.bm-link:hover { color: var(--ink); border-bottom-color: var(--ink); }
.bm-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* the way back is the last thing on the row and it says so in the accent */
.bm-link.is-back { color: var(--accent); margin-left: auto; }
.bm-link.is-back:hover { border-bottom-color: var(--accent); }
@media (max-width: 640px) { .bm-link.is-back { margin-left: 0; } }

/* ============================================================================
   THE QUOTE PILL — the button that appears over a selection
   The author, 2026-09-06: "is it possible to just send a small snippet of the
   book to someone?? that links to the whole book? Thats needed"

   🛑 IT LOOKS LIKE THE BOOK, NOT LIKE A BROWSER EXTENSION. Every share affordance
   on the internet is a dark floating capsule with a shadow under it; this is the
   page's own control — the same hairline, the same wash, the same accent lift on
   hover as every .btn — raised off the paper by one soft shadow so it reads as
   sitting ON the page rather than as part of it.
   ⚠️ POSITION IS ABSOLUTE, NOT FIXED, and js/quote.js explains why: a fixed pill
   slides away from its own selection the moment the page moves.
   ⚠️ z-index sits under the sheets and the player bar. A share pill that paints
   over an open dialog is a bug in every design system that has ever shipped one.
   ========================================================================== */
.quote-pill {
  position: absolute;
  z-index: 60;
  display: inline-flex; align-items: center; gap: .5em;
  min-height: 44px;
  padding: .6em 1em;
  border: 1px solid var(--control-edge);
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--ui); font-size: var(--ap-lg); font-weight: 600;
  letter-spacing: .01em;
  white-space: nowrap;
  /* the only drop shadow on the site, and it is here because this element is the
     only one that genuinely floats above the text rather than sitting in the flow */
  box-shadow: 0 6px 18px rgba(0, 0, 0, .16);
  transition: border-color var(--dur-fast) var(--ease),
              background-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
}
.quote-pill svg { width: 18px; height: 18px; flex: none; color: var(--accent); }
.quote-pill:hover, .quote-pill:focus-visible {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, var(--paper));
}
.quote-pill:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
/* the confirmation is a state of the same button, not a toast: a toast for a
   two-word acknowledgement is a second surface for no reason */
.quote-pill.is-done { border-color: var(--accent); color: var(--accent); }
.quote-pill.is-done svg { display: none; }
@media (prefers-reduced-motion: reduce) {
  .quote-pill { transition: none; }
}
