/* glass-reading — the paper's reading layout: legible type, and the spread.
 *
 * Dan, 2026-09-06: "the text here is hard to read, and all in one column.
 * Let's go after a more magazine style here and improve the legibility."
 * Measured on the published glass at 1440px before this sheet: body copy at
 * 12.48px running 191 characters to the line, in one column the full width
 * of the paper. Nothing on a screen is read at 191 characters.
 *
 * The law it follows is already written:
 *   the magazine-rails note (docs/design/) — "the day's document at READABLE type
 *                                      sitting in a frame"; sentence-case
 *                                      editorial in the body; tracked
 *                                      uppercase stays on the rails only.
 *   docs/design/typography-decision.md — Inter body (optical sizing on),
 *                                      Archivo display, Iosevka data.
 *   subsystems/present/styles/gloss/present.css — the house's magazine
 *                                      grammar: kicker, folio, pull quote,
 *                                      selected by [data-presentation].
 *
 * DARK IS THE SURFACE'S LAW (lead ruling, 2026-09-07). The glass ships one
 * scheme: it is an instrument, and an instrument has a face. There is no
 * `prefers-color-scheme` branch anywhere in the kit's CSS — the palette is
 * one set of tokens in tokens.css — and that is a decision, not an
 * oversight. Measured on /moon at 1600x1100 against `.paper`'s own
 * oklch(0.22 0.02 260): body and title 13.64:1, the pull quote 10.34:1, the
 * locator kicker and wikilinks 8.29:1, captions and the dateline 5.37:1 —
 * and the same seven numbers under an emulated light preference, which is
 * the proof that one scheme is what ships. A light scheme is its own card.
 * The full table is in docs/design/glass-reading.md.
 *
 * Two things this sheet is NOT. It never generates: no pull quotes we chose,
 * no summaries, no reordering — the words are the pack's, in the pack's
 * order, and every rule here is about size, measure and colour. And it never
 * decides what a section IS: glass-md.js counts the words (`GlassMd.long`)
 * and glass.js marks the section `.long`; the sheet only reads that mark.
 *
 * Loaded LAST in <head> on purpose: several of the rules below restate a
 * property the page's own <style> block sets at the same specificity, and
 * source order is what settles those. Registered in publish.py KIT_BASE so
 * a published glass ships it.
 */

/* ══ 0. THE TYPE SCALE ══
 * Dan, 2026-09-06: "like a magazine we should make sure we try to include
 * visuals, varying font sizes, etc."
 *
 * A magazine has a SCALE and uses all of it: a display title, a deck set
 * larger than the body, heads that step down, body, then captions and
 * marginalia below that — and one face per job (Archivo display, Inter
 * body, Iosevka data; docs/design/typography-decision.md). Sizes that
 * repeat get a name here so the page has one scale rather than a dozen
 * local decisions, and so glass-kit.md can state it.
 *
 * Body stays at 16px, the low end of Dan's "16-17px": at 17px the
 * narrowest column in the flow (1200px, two columns of 415px) drops to 52
 * characters, under the 55 floor. The scale grows upward from the body
 * instead, which is what makes the steps visible.
 *
 * The title is fluid because a spread is: clamp() lets it grow with the
 * page rather than sit at one size on a phone and a 27-inch screen. */
.paper {
  --rd-kicker: var(--fs-1);      /* the locator, tracked uppercase */
  --rd-caption: 0.85rem;         /* captions, marginalia */
  --rd-body: var(--fs-5);        /* 16px */
  --rd-h4: 0.95rem;
  --rd-h3: 1.05rem;
  --rd-h2: 1.25rem;
  --rd-h5: 0.95rem;
  --rd-deck: 1.1rem;
  --rd-title: var(--fs-6);
}
/* Dan, 2026-09-07, on a screenshot of `3d-background-camera-system §6`: the
 * section headings are too small. Against a 16px body a 1.6rem head is one
 * step, and one step does not say "a new section starts here" across a
 * column. The scale steps up — h2 2.1rem, then 1.5 / 1.2 / 1.05 — so each
 * level is visibly a level, and the space above a head (1.6em) is what
 * actually separates two sections; the space below (0.5em) keeps the head
 * with the prose it introduces. */
.paper[data-presentation="gloss"] {
  --rd-title: clamp(2.2rem, 1.15rem + 2.7vw, 3.2rem);
  --rd-deck: 1.25rem;
  --rd-h2: 2.1rem;
  --rd-h3: 1.5rem;
  --rd-h4: 1.2rem;
  --rd-h5: 1.05rem;
}

/* ══ 1. Legibility — every pack, every mode. The manual gets this too. ══
 * This half is not a style, it is the floor: type you can read. */

.sec .body {
  font-size: var(--rd-body);       /* 16px — was --fs-3 (0.78rem) */
  line-height: 1.65;
  color: var(--fg);                /* 13.6:1 on --panel — was #c9d2e0 at 11.4:1 */
  font-optical-sizing: auto;       /* Inter's opsz axis, the point of picking Inter */
}

/* THE MEASURE — 55ch, which is ~72 characters.
 * `ch` is the advance of the digit 0, and in Inter that is about 30% wider
 * than an average prose character: the brief's `68ch` measured 89 real
 * characters, at the far end of the comfortable band and only half the fix
 * Dan asked for. 55ch measures ~72 (verified in the browser, and the ruling
 * is written up in docs/design/glass-kit.md). The things that are NOT prose
 * — a table, a fence, a canvas, a figure — still run to the column, because
 * cutting a diagram to the measure does not make it more readable.
 *
 * `.fold-body` is in the list because a FOLD is not a different kind of
 * prose: `<div class="body fold"><button…><div class="fold-body">` put every
 * word inside it out of reach of a `>`-scoped rule, so a folded section had
 * no measure at all — on the manual, which §1 explicitly covers. */
.sec .body > p,
.sec .body > ul,
.sec .body > ol,
.sec .body > blockquote,
.sec .body > h4.mdh,
.sec .body > h5.mdh,
.sec .body > hr.mdhr,
.sec .body > .fold-body > p,
.sec .body > .fold-body > ul,
.sec .body > .fold-body > ol,
.sec .body > .fold-body > blockquote,
.sec .body > .fold-body > h4.mdh,
.sec .body > .fold-body > h5.mdh,
.sec .body > .fold-body > hr.mdhr { max-width: 55ch; }

.sec .body p { margin: 0 0 0.8em; }
.sec .body > :last-child { margin-bottom: 0; }

/* Lists hang: the marker sits outside, so a wrapped line aligns under the
 * words rather than under the bullet. */
.sec .body ul.mdlist,
.sec .body ol.steps { padding-left: 1.35em; margin: 0.5em 0 0.9em; }
.sec .body ul.mdlist > li,
.sec .body ol.steps > li { margin-bottom: 0.35em; line-height: 1.6; }
.sec .body ul.ckl > li { margin-bottom: 0.35em; }

/* Code is data type, sized against the prose it sits in. */
.sec .body code { font-family: var(--font-data); font-size: 0.9em; }
.sec .body pre.code,
.sec .body pre.diagram { font-size: 0.85em; line-height: 1.5; }

/* A quote is the present grammar's pull: amber rule, cyan words (10.3:1). */
.sec .body blockquote {
  border-left: 3px solid var(--amber);
  color: var(--cyan);
  padding: 0.1rem 0 0.1rem 1.1rem;
  margin: 0.9em 0;
}
/* …but a NAMED callout is a note, not a quote: it keeps its box and reads
 * in body colour under its amber title. */
.sec .body blockquote.callout { color: var(--fg); padding: 0.7rem 1rem; }

/* Headings in the body are editorial: sentence case, display face, no
 * tracking, no uppercase. Tracked uppercase stays on the rails — which is
 * where the locator kicker (.loc) lives, and it keeps its small tracked
 * lavender form (8.3:1) unchanged. */
.paper h1 {
  text-transform: none;
  letter-spacing: -0.005em;
  line-height: 1.2;
}
.sec h2 {
  font-size: var(--rd-h2);
  letter-spacing: normal;
  text-transform: none;
  line-height: 1.3;
  margin: 0 0 0.5rem;
}
h4.mdh {
  font-size: var(--rd-h3);
  letter-spacing: normal;
  text-transform: none;
  color: var(--lavender);
  margin: 1.1em 0 0.4em;
}
h5.mdh {
  font-size: var(--rd-h5);
  letter-spacing: normal;
  text-transform: none;
  color: var(--muted);          /* 5.4:1 — a caption-weight heading */
  margin: 1em 0 0.35em;
}

/* A caption is Inter at caption size, sentence case — optical sizing does
 * the small-text work the tracked-uppercase label used to fake. */
.sec .shot figcaption,
.sec .body figure > figcaption {
  font-family: var(--font-body);
  font-optical-sizing: auto;
  font-size: var(--rd-caption);
  letter-spacing: normal;
  text-transform: none;
  color: var(--muted);
  line-height: 1.45;
}

/* ══ 2. Gloss — the spread. Site packs open here; the manual opens plain. ══
 * Selected by [data-presentation] exactly as the present subsystem selects
 * its print styles, so one word switches the whole page. */

.paper[data-presentation="gloss"] { padding: 1.6rem 2.6rem 3rem; }

.paper[data-presentation="gloss"] h1 {
  font-size: var(--rd-title);
  line-height: 1.05;
  letter-spacing: -0.015em;
  margin: 0 0 0.9rem;
  max-width: 22ch;               /* a headline breaks, it does not run on */
}
.paper[data-presentation="gloss"] .dateline { margin: -0.5rem 0 1.6rem; }
/* THE KICKER — a lead heading that had nothing under it (§15). The bake lifts
   it out of the flow rather than deleting it, and it is set under the title in
   the small tracked face: the author's own words, in the one place a magazine
   puts a line that is not the headline and not the deck. */
.mast > .kicker {
  font-family: var(--font-data); font-size: var(--rd-kicker); letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--muted); margin: -0.4rem 0 0.9rem;
}

/* The section rule and its indent are the instrument's, not the page's: on
 * a spread the kicker line IS the address, so the rule steps back — except
 * on a search hit, where it is the one thing pointing at the answer. */
.paper[data-presentation="gloss"] .sec {
  border-left-color: transparent;
  padding-left: 0;
  margin-bottom: 2rem;
}
.paper[data-presentation="gloss"] .sec.hit {
  border-left-color: var(--amber);
  padding-left: 0.9rem;
}

/* ── THE LOCATOR FLOATS RIGHT ─────────────────────────────────────────
 * Dan, 2026-09-07, on a screenshot of `learning §4`: "The section headings
 * like learning §2 don't look good since they cause the main content to be
 * offset from the left a bunch. The section numbers should float to the
 * right of the section and be dim unless hover."
 *
 * He is describing a measurable defect: with the locator inline and first,
 * every heading on the page started 6–9em right of the paragraph under it,
 * so the left edge of the column was the prose's on one line and the
 * kicker's on the next. The heading row is a flex line now — the head at the
 * prose's own left edge with nothing before it, the locator pushed to the
 * right end by `margin-inline-start: auto`.
 *
 * It stays FIRST IN THE DOM (`order`), because a screen reader and the
 * ASR's section vocabulary both want the address with its heading, and
 * because `order` moves pixels without moving meaning.
 *
 * Dim, because it is an address and not a title: 35% until the row is
 * hovered or the button focused, and always full on the section you are in
 * (`.sec.hit`), which is the one an address would be about. */
.paper[data-presentation="gloss"] .sec h2 {
  font-size: var(--rd-h2); margin: 1.6em 0 0.5em; line-height: 1.15;
  display: flex; align-items: baseline; gap: 0.9rem; position: relative;
}
.paper[data-presentation="gloss"] .sec.lead > h2 { margin-top: 0; }
.sec h2 > .loc {
  order: 2; margin-inline-start: auto; flex: 0 0 auto; align-self: baseline;
  font-family: var(--font-data); font-size: var(--rd-kicker); letter-spacing: 0.1em;
  color: var(--lavender); background: none; border: 0; padding: 0.2rem 0.1rem;
  cursor: pointer; opacity: 0.35; transition: opacity 120ms linear;
}
.sec h2:hover > .loc, .sec h2 > .loc:hover,
.sec h2 > .loc:focus-visible, .sec.hit > h2 > .loc { opacity: 1; }
.sec h2 > .loc:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .sec h2 > .loc { transition: none; } }

/* ── the address, when you ask for it ─────────────────────────────────
 * Anchored under the locator it belongs to. Everything in it is the
 * author's words or a fact about them — the address, the count, the
 * heading, and the section's own opening sentence. The glass does not
 * write a summary (glass-locator.js says why at length). */
.loc-pop {
  position: absolute; top: 100%; right: 0; z-index: 60; width: min(30rem, 82vw);
  margin-top: 0.35rem; padding: 0.8rem 0.9rem; text-align: left;
  background: var(--raised); border: 1px solid var(--line); border-radius: 6px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
  font-family: var(--font-body); font-size: var(--rd-body); font-weight: 400;
  letter-spacing: normal; line-height: 1.5; color: var(--fg);
}
.loc-pop .lp-head { display: flex; align-items: baseline; gap: 0.5rem; margin-bottom: 0.5rem; }
.loc-pop .lp-n { font-family: var(--font-data); font-size: var(--rd-kicker);
  letter-spacing: 0.1em; color: var(--lavender); }
.loc-pop .lp-h { font-family: var(--font-display); font-size: 1rem; color: var(--fg); }
.loc-pop .lp-addr { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.loc-pop code { font-family: var(--font-data); font-size: 0.85rem; color: var(--cyan);
  background: none; word-break: break-all; }
.loc-pop .lp-act { font-family: var(--font-display); font-size: 0.78rem; letter-spacing: 0.06em;
  color: var(--amber); background: none; border: 1px solid var(--line); border-radius: 4px;
  padding: 0.25rem 0.55rem; cursor: pointer; }
.loc-pop .lp-act:hover { border-color: var(--amber); background: var(--bg); }
.loc-pop .lp-read { display: block; margin: 0.6rem 0 0; color: var(--cyan); }
.loc-pop .lp-lede { margin: 0.7rem 0 0; padding: 0.5rem 0 0; border: 0; border-top: 1px solid var(--line);
  font-style: normal; color: var(--fg); }
.loc-pop .lp-words { display: block; margin-top: 0.35rem;
  font-family: var(--font-data); font-size: var(--rd-caption); color: var(--muted); }

/* The deck: the first section is the lead, set larger and narrower under
 * the title, with no locator kicker over it — you are already there. */
/* THE DECK IS THE PAGE'S LEAD SECTION, and `.lead` is how it says so.
 * `:first-of-type` was the same fact expressed as a position, and the
 * position stopped being true the moment glass-spread.js packed the flow
 * into screens: the page's first section is no longer the first section of
 * its parent, and every spread's opening section matched instead. Measured
 * on /cassini at 1600px with the spreads in: SIX drop caps down one page and
 * three bodies set in 20px deck type (s0, s4, s10). glass.js marks the first
 * section it actually renders — the first non-hidden one, which is what
 * `:first-of-type` meant. */
.paper[data-presentation="gloss"] .sec.lead .body { font-size: var(--rd-deck); line-height: 1.5; }
.paper[data-presentation="gloss"] .sec.lead .body > p { max-width: 50ch; }
.paper[data-presentation="gloss"] .sec.lead > h2 > .loc { display: none; }

/* Paragraph separation on a spread is the CLASSIC INDENT, not a gap: the
 * first paragraph after a heading is flush, every one after it is indented
 * and follows without a blank line. (The brief offered indent or gap; the
 * indent is the one that reads as a magazine rather than as a blog, and the
 * gap stays the rule in plain mode, one line up.) */
.paper[data-presentation="gloss"] .sec .body > p { margin-bottom: 0; text-indent: 1.15em; }
/* A paragraph is a continuation only of another PARAGRAPH. After anything
 * else — a heading, a list, a fence, a figure, a quote, a table, display
 * math, a rule — it starts a new run and sits flush. */
.paper[data-presentation="gloss"] .sec .body > p:first-child,
.paper[data-presentation="gloss"] .sec .body > :not(p) + p,
.paper[data-presentation="gloss"] .sec.lead .body > p { text-indent: 0; }
.paper[data-presentation="gloss"] .sec.lead .body > p { margin-bottom: 0.7em; }

/* An authored `> [!quote]` is a pull quote: the author's own words, in the
 * author's own place in the page, given the width of the whole measure.
 * Nothing here selects WHICH words — the note already did. */
.paper[data-presentation="gloss"] .sec .body blockquote.callout[data-kind="quote"] {
  column-span: all;
  border-left: 0;
  border-top: 2px solid var(--amber);
  border-bottom: 2px solid var(--amber);
  border-radius: 0;
  background: none;
  color: var(--cyan);
  font-family: var(--font-display);
  font-size: var(--fs-6);
  line-height: 1.35;
  max-width: 42ch;
  margin: 1.4rem auto;
  padding: 0.9rem 0 0.8rem;
  text-align: center;
}
.paper[data-presentation="gloss"] .sec .body blockquote.callout[data-kind="quote"] > b {
  color: var(--amber);
}


/* ── the pull quote ──────────────────────────────────────────────────
 * One sentence per long section, chosen at bake by a stated rule from the
 * author's own words (site_pull.py) and reported. It sits INSIDE a column
 * — a pull quote is meant to interrupt the reading, not to become a second
 * headline — and it says where it came from, which is a fact, not a claim.
 * Plain mode does not repeat a sentence: the quote is a magazine device.
 *
 * D-19 — AND IT IS A DOOR. A sentence lifted out of a paragraph and set in
 * display type raises one question, where was this, and `.pull` answered it
 * with `cursor: auto` and no children. It is a <button> now (glass.js), so
 * the base rules take the browser's button chrome off and leave the quote
 * looking exactly as it did; glass-locator.js's `pulls` takes the reader to
 * the paragraph the sentence left. */
.sec .pull {
  -webkit-appearance: none; appearance: none; background: none; border: 0;
  width: 100%; text-align: left; cursor: pointer;
}
.sec .pull:focus-visible { outline: 2px solid var(--amber); outline-offset: 3px; border-radius: 2px; }
.paper[data-presentation="gloss"] .sec .pull:hover { color: var(--fg); }
.paper[data-presentation="gloss"] .sec .pull:hover .pull-src { color: var(--cyan); }
/* WHERE IT CAME FROM, for a moment. Not a highlight that stays: the reader
   asked one question and the answer is a place, not a state. The amber is
   the surface's own "here", and prefers-reduced-motion keeps the mark and
   drops the fade — the information is the mark. */
@keyframes pull-from-fade {
  from { background: color-mix(in srgb, var(--amber) 28%, transparent); }
  to { background: transparent; }
}
.pull-from { animation: pull-from-fade 1.4s ease-out 1; border-radius: 3px; }
@media (prefers-reduced-motion: reduce) {
  .pull-from { animation: none; background: color-mix(in srgb, var(--amber) 22%, transparent); }
}
.paper[data-presentation="gloss"] .sec .pull {
  display: block;
  margin: 1.1rem 0;
  padding: 0.7rem 0 0.6rem;
  border-top: 2px solid var(--amber);
  border-bottom: 1px solid var(--line);
  font-family: var(--font-display);
  font-size: var(--rd-h3);
  line-height: 1.3;
  color: var(--cyan);
  text-indent: 0;
  break-inside: avoid;
}
.paper[data-presentation="gloss"] .sec .pull .pull-src {
  display: block;
  margin-top: 0.4rem;
  font-family: var(--font-body);
  font-size: var(--rd-caption);
  letter-spacing: normal;
  color: var(--muted);
}
/* PLAIN MUST NOT LOSE THE SENTENCE. Hiding the quote was right while it was
 * a COPY of a sentence still in the paragraph. It is a cut now: the pull is
 * the author's only copy, and `display: none` would delete a sentence they
 * wrote from every plain reader's page. Plain shows it as what it is — a
 * sentence of that section — with none of the magazine dressing, and
 * without the attribution, which is a device rather than a fact plain
 * needs. */
.paper:not([data-presentation="gloss"]) .sec .pull {
  display: block; margin: 0 0 0.8em; padding: 0; border: 0;
  font-family: inherit; font-size: inherit; line-height: inherit; color: inherit;
}
/* plain keeps the door: the sentence still came from somewhere */
.paper:not([data-presentation="gloss"]) .sec .pull:hover { color: var(--cyan); }
.paper:not([data-presentation="gloss"]) .sec .pull .pull-src { display: none; }

/* ── a generated page is not the author's page ───────────────────────
 * Lead ruling, 2026-09-07: the Offers page — and anything else the bake
 * WRITES rather than quotes (`pack.docs[i].generated`) — renders Plain
 * whatever the viewer picked. A drop cap, a spread and an opening still are
 * how this surface sets somebody's writing, and there is nobody whose
 * writing that page is; setting it like an essay would be the glass
 * dressing up its own output as the author's.
 *
 * `glass-rail.js applyReading` already writes `data-presentation="plain"`
 * for such a page, so every gloss rule in this sheet stops matching on its
 * own. These rules are the second line: if any lane ever puts `gloss` back
 * on a generated paper, the spread still does not appear. */
.paper[data-generated][data-presentation="gloss"] #doc { columns: auto; column-count: 1; column-rule: none; max-width: 46rem; }
.paper[data-generated] .hero { display: none; }
.paper[data-generated] #doc .sec.lead .body > p:first-of-type::first-letter {
  float: none; font-size: inherit; line-height: inherit; padding: 0; color: inherit; font-family: inherit;
}

/* ── the drop cap ────────────────────────────────────────────────────
 * The opening letter of the page, set in display type. `::first-letter` is
 * a rendering of a character that is already there — no DOM changes, no
 * split word, so selection, find and the voice reader all see the same
 * sentence they always did. That is the whole reason the brief asked for
 * CSS here rather than markup.
 *
 * `:first-of-type`, not `:first-child`: three pages of 153 open on a figure
 * or a list and got no cap at all, because the first paragraph was not the
 * first element.
 *
 * `.no-cap` is put there by glass-flow.js when the paragraph's first NODE is
 * a link: `::first-letter` lifts the letter out of the link's colour and
 * underline, so /spin-glasses rendered an amber floated S beside a cyan
 * underlined "pin glasses". CSS cannot ask that question — `a:first-child`
 * is about element children and matches 46 paragraphs here that merely
 * CONTAIN a link after some text, which would take the cap off most of the
 * site. One page of 153 needs it; one class is the honest instrument. */
.paper[data-presentation="gloss"] #doc .sec.lead .body > p:first-of-type:not(.no-cap)::first-letter {
  float: left;
  font-family: var(--font-display);
  font-size: 3.2em;
  line-height: 0.78;
  padding: 0.06em 0.09em 0 0;
  color: var(--amber);
}
/* …and never on a phone, where a three-line cap eats a third of the column */
@media (max-width: 900px) {
  .paper[data-presentation="gloss"] #doc .sec.lead .body > p:first-of-type::first-letter {
    float: none; font-size: inherit; line-height: inherit; padding: 0; color: inherit;
    font-family: inherit;
  }
}

/* ── the opening visual ──────────────────────────────────────────────
 * Full-bleed across the flow, above the title, with its caption under it.
 * `.hero` is a <figure>, so the spanner rule above gives it the whole
 * spread whatever the column count is.
 *
 * The links still is the page's own neighbourhood, drawn once at bake
 * (site_hero.py). It does not move, it is not a control, and it does not
 * compete with the words: the lines are --line, the dots amber, the labels
 * small and quiet, and only the page you are ON is drawn at full strength.
 * A dot is a link — glass.js's delegated wikilink handler answers to it. */
/* ── the mast: the picture and the headline, composed ────────────────
 * Dan asked for the opening visual "full-bleed across the columns, with the
 * title set over or beside it, magazine-style". What shipped was a centred
 * pillarboxed picture with the title 707px below it: measured on #titan at
 * 1600x1100, a 1160x505 frame holding a 675px picture, ~240px of dark
 * ground on each side, and the h1 under the whole thing.
 *
 * `.mast` is the one block glass.js now wraps the hero, the title and the
 * dateline in — the same line, no line added — so CSS can lay them out
 * together. BESIDE, not over: the picture fills its own column of the mast
 * at its own aspect, so nothing is letterboxed and nothing is cropped, and
 * the headline sits in the column next to it. Cropping the author's picture
 * to fit our layout is still not this surface's decision to make.
 *
 * Only a PICTURE goes beside the title. The links still is a 1000x340 frame
 * whose labels are set in its own viewBox units: at 660px of a 1160px mast
 * they render at ~10px and the caption the reviewer called readable stops
 * being readable. It keeps the full width, with the title under it. */
.paper[data-presentation="gloss"] .mast { margin: 0 0 1.6rem; }
@media (min-width: 901px) {
  .paper[data-presentation="gloss"] .mast:has(> .hero-img) {
    display: grid;
    grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr);
    column-gap: 2.4rem;
    align-items: center;
  }
  .paper[data-presentation="gloss"] .mast:has(> .hero-img) > .hero-img {
    grid-column: 1; grid-row: 1 / span 2; margin: 0; align-self: center;
  }
  .paper[data-presentation="gloss"] .mast:has(> .hero-img) > h1 {
    grid-column: 2; grid-row: 1; align-self: end; max-width: none; margin-bottom: 0.5rem;
  }
  .paper[data-presentation="gloss"] .mast:has(> .hero-img) > .dateline {
    grid-column: 2; grid-row: 2; align-self: start; margin: 0;
  }
  /* the composed mast places every child by hand, so the strip has to be
     placed too or it auto-flows into the picture's column — and so does the
     kicker, which sits under the dateline in the headline's own column. Row 3
     is empty on a page with no kicker, and an empty implicit row is 0px. */
  .paper[data-presentation="gloss"] .mast:has(> .hero-img) > .kicker,
  .paper[data-presentation="gloss"] .mast:has(> .hero-links) > .kicker {
    grid-column: 2; grid-row: 3; align-self: start; margin: 0.4rem 0 0;
  }
  .paper[data-presentation="gloss"] .mast:has(> .hero-img) > .nearby {
    grid-column: 1 / -1; grid-row: 4;
  }
  /* In its own column the picture takes the column's width at its own
   * aspect and the BOX IS THE PICTURE — `width: auto` rather than 100%, so
   * there is no frame left over to letterbox into. When the height cap
   * bites, the width follows the height and the box stays the picture.
   *
   * THE CAP IS ABSOLUTE AS WELL AS RELATIVE. At 62vh a PORTRAIT hero sets
   * the mast's height from its own long side: measured on /moire-materials
   * at 1600x1100, a 784x1168 photo rendered 458x682 and the headline — 73px
   * of text — sat in the middle of a 682px column with 304px of empty
   * ground above it and 305px below. `min(42vh, 460px)` keeps a tall
   * picture to the same band a landscape one takes (/titan measured 478px
   * before this rule and was judged right), and bounds it on a tall window
   * too, where 62vh of a 2600px frame is most of a page. */
  .paper[data-presentation="gloss"] .mast:has(> .hero-img) > .hero-img > img {
    width: auto; max-width: 100%; height: auto; max-height: min(42vh, 460px);
    /* …and it sits against the headline, not in the middle of its cell: a
     * tall picture leaves room in its own column, and that room belongs on
     * the outside of the spread with the page margin, not as a gap between
     * the picture and the words it belongs to. */
    margin-inline: auto 0; background: none;
  }
}
/* THE STILL GOES BESIDE THE HEADLINE ONLY WHERE IT CAN BE READ.
 * Lead ruling, 2026-09-07: compose the links still in the mast where its
 * labels reach 12px, and leave it above the title full-width below that.
 * The labels are set in the still's own viewBox units (15 of 1000), so
 * their rendered size IS the frame's width divided by 1000 and multiplied
 * by 15: at 1600px the mast's picture cell is 664px, which draws them at
 * 10.0px, and the readability the constellation round bought is gone. The
 * flow's own max-width steps to 1740px at 1800px viewports, which makes
 * that cell 992px and the labels 14.9px — the first width where the
 * composition costs nothing. Measured both, in the report. */
@media (min-width: 1800px) {
  .paper[data-presentation="gloss"] .mast:has(> .hero-links) {
    display: grid;
    grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr);
    column-gap: 2.4rem;
    align-items: center;
  }
  .paper[data-presentation="gloss"] .mast:has(> .hero-links) > .hero-links {
    grid-column: 1; grid-row: 1 / span 2; margin: 0;
  }
  .paper[data-presentation="gloss"] .mast:has(> .hero-links) > h1 {
    grid-column: 2; grid-row: 1; align-self: end; max-width: none; margin-bottom: 0.5rem;
  }
  .paper[data-presentation="gloss"] .mast:has(> .hero-links) > .dateline {
    grid-column: 2; grid-row: 2; align-self: start; margin: 0;
  }
  .paper[data-presentation="gloss"] .mast:has(> .hero-links) > .nearby {
    grid-column: 1 / -1; grid-row: 4;
  }
}


.paper[data-presentation="gloss"] .hero { margin: 0 0 1.4rem; }
/* The opening picture is capped so the TITLE is still on the first screen —
 * measured: a 1600x1067 photo took 667px of a 1000px viewport and pushed the
 * headline below the fold. `contain`, not `cover`: letterboxing on the dark
 * ground costs nothing, and cropping the author's picture to fit our layout
 * is a decision about their material that this surface does not get to make. */
.paper[data-presentation="gloss"] .hero > img {
  display: block; width: 100%; max-height: 46vh; object-fit: contain;
  object-position: center; border-radius: 4px;
  background: color-mix(in srgb, var(--raised) 55%, transparent);
}
.paper[data-presentation="gloss"] .hero > svg {
  display: block; width: 100%; height: auto;
  background: color-mix(in srgb, var(--raised) 55%, transparent);
  border-radius: 4px;
}
.paper[data-presentation="gloss"] .hero > figcaption {
  font-family: var(--font-body); font-optical-sizing: auto;
  font-size: var(--rd-caption); color: var(--muted); line-height: 1.45;
  margin-top: 0.45rem; letter-spacing: normal; text-transform: none;
}
.hero-links .he { stroke: var(--line); stroke-width: 1.4; }
.hero-links .hn { fill: var(--amber-idle); }
.hero-links .hn-ego { fill: var(--amber); }
/* a name too small to read is not a name (reading review 7, I-3): under the
   11px floor glass-hero.js marks the frame and the dots carry it, with the
   caption saying in words what the picture is. */
.hero-links svg.hl-mute .hl, .hero-links svg.hl-mute .hl-ego { display: none; }
.hero-links .hl {
  fill: var(--muted); font-family: var(--font-body); font-size: 15px;
  text-anchor: middle; font-optical-sizing: auto;
}
.hero-links .hl-ego { fill: var(--fg); font-family: var(--font-display); font-size: 17px; }
/* the frame is the door to the live sky (glass-hero.js wire), the dots are
   addresses — two gestures, two cursors, so neither is a surprise */
.hero-links svg { cursor: zoom-in; }
.hero-links .hg { cursor: pointer; }
.hero-links .hg:hover .hn, .hero-links .hg:hover .hl { fill: var(--cyan); }
/* plain keeps the page it always had: no hoisting, no opening figure */
.paper:not([data-presentation="gloss"]) .hero { display: none; }

/* ── figures, in the flow ────────────────────────────────────────────
 * A magazine figure is the picture and its caption, not a picture in a
 * box: the border goes, the caption sits under it in caption type, and a
 * figure never gets cut in half by a column break (the break rules are in
 * the flow block above). */
.paper[data-presentation="gloss"] .sec .shot { margin: 0.4rem 0 1.4rem; }
.paper[data-presentation="gloss"] .sec .shot img {
  border: 0; border-radius: 3px; max-width: 100%;
}
.paper[data-presentation="gloss"] .sec .shot figcaption { margin-top: 0.5rem; max-width: 62ch; }

/* ── numbers and tables ──────────────────────────────────────────────
 * The data face, and figures that do not shimmer as they change
 * (docs/design/typography-decision.md §2). A table wider than a column
 * spans the flow — that rule is with the other spanners above. */
.sec .body table.tbl { font-family: var(--font-data); font-variant-numeric: tabular-nums; }
.sec .body table.tbl th { font-family: var(--font-display); }
.sec .body table.tbl td { font-size: 0.9em; }

/* ══ 3. The author's own figures ══
 * An inline <svg> the note carries, sanitised by glass-svg.js and drawn
 * where the author put it. It may run to the column; it never overflows. */
.sec .body figure.fig-svg { margin: 1rem 0 1.2rem; max-width: 100%; }
.sec .body figure.fig-svg > svg { display: block; max-width: 100%; height: auto; }

/* The rail's header carries three chips now: the two axes of the vault
 * (Topics · Time) and then the page's setting (Gloss · Plain). The setting
 * gets its own row — a 260px rail should never have to squeeze six tracked
 * uppercase words onto one line. */
.toc-modes.toc-reading { margin-top: -0.3rem; }

/* ══ 3b. The contents rail: shut by default on a site pack ══
 * Dan, 2026-09-06, in substance: the side menu did not look like ours — at
 * least we should default the thing to collapsed."
 *
 * Shut means GONE, not hidden: `display: none` on the aside and a 0 column
 * on the stage grid, so `#toc .toc-row` is empty and every voice consumer
 * that reads that list as "what a visitor can see" stays honest — the same
 * rule a shut GROUP already follows (docs/design/glass-kit.md). The rail
 * comes back drawn from scratch, so expandTo/renderToc are unaffected.
 *
 * The tab is the left-hand twin of #rail-tab (ⓘ GLASS): same vertical
 * writing mode, same rounded outer edge, the console grammar rather than a hamburger
 * floating over the page. */
/* AND IT IS AN INSTRUMENT, NOT A SLIVER. Dan, 2026-09-07, on a screenshot of
 * /3d-background-camera-system: "The table of contents and menu to the left
 * is now entirely gone ... How do I navigate from here easily?" It was 12px
 * of amber at the edge with one word set at 0.6rem. Collapsed stays;
 * invisible does not. The tab now states what is behind it — the word, the
 * rail's own page count, and a chevron — at a 44px hit target, which is the
 * floor for a control anyone taps. glass-nearby.js writes the count, so the
 * tab and the contents can never disagree about how big the site is. */

/* ONE column, not a zero-width first one: with the aside displayed away the
 * paper becomes the grid's first item, and a `0 1fr` track would put it in
 * that zero track and squeeze the page to 72px. */
body.toc-shut .stage { grid-template-columns: 1fr; }
body.toc-shut #toc-side { display: none; }
/* THE GRAPH SEAT, with no rail to sit in.
 * Open, the rail absorbs the float: glass.html:211 pads `.side` by the
 * measured --g3d-seat so the first row starts below it. Shut, there is no
 * rail, and a fixed float parked left would sit ON the page. The rule
 * picked here is a LEFT MARGIN, not a top band: the graph is a corner
 * companion (docs/design — the corner-companion law), so the page reads
 * BESIDE it and the title is still the first thing on screen. A 13rem
 * empty band at the top would have been the alternative, and it costs the
 * reader the one thing the collapse was for. --g3d-seat-w is a CONSTANT (the
 * seat's designed width, constellation.css) — it used to be the float's
 * measured width, republished on every resize, which is exactly how dragging
 * the graph reflowed the paper. Nothing the graph does at runtime lands here.
 * One rule covers both left corners: bottom-left covers paper text too now
 * that the rail is gone. */
/* …and ONLY on a desk. On a phone constellation.css:103-105 makes the float
 * full-bleed (`left: 0.4rem; right: 0.4rem; width: auto`), so --g3d-seat-w is
 * ~362px on a 375px screen and this padding pushed the whole article off the
 * side of the world. Its (0,3,1) beat both mobile resets, and a media query
 * adds no specificity — so the rule itself has to be inside one. */
@media (min-width: 901px) {
  body.g3d-left.toc-shut .paper { padding-left: calc(var(--g3d-seat-w, 10rem) + 1.5rem); }
}

/* No cap on the flow: Dan's rule is that the paper takes the width it is
 * given and the prose answers with more columns, not with a wider line.
 * (A 70rem cap was the first shape and it is exactly the empty space he
 * pointed at.) The results tray is not the flow, so it keeps a measure. */
.paper[data-presentation="gloss"] #hits { max-width: 70rem; }

/* the ◂ that shuts it, riding the rail's own CONTENTS header */
.side h3 #toc-shut { float: right; cursor: pointer; color: var(--amber);
  font-size: 1.1em; line-height: 1; padding: 0 0.2rem; border-radius: 2px; }
.side h3 #toc-shut:hover { color: var(--fg); }
.side h3 #toc-shut:focus-visible { outline: 2px solid var(--amber); outline-offset: 1px; }

/* ══ 4. ≤ 900px — one column, the same legible type ══ */
@media (max-width: 900px) {
  .paper[data-presentation="gloss"] { padding: 1rem 1.1rem 2rem; }
  .paper[data-presentation="gloss"] h1 { max-width: none; }
  .paper[data-presentation="gloss"] { --rd-deck: 1.1rem; --rd-h2: 1.3rem; --rd-h3: 1.1rem; }
  .paper[data-presentation="gloss"] #doc { column-width: auto; column-count: 1; column-rule: none; }
  /* the phone already shows the contents as a chip shelf across the top —
   * that shelf IS the rail, so it is never collapsed and needs no tab */
  body.toc-shut .stage { grid-template-columns: none; }
  body.toc-shut #toc-side { display: block; }

  /* THE RESERVE BELONGS TO WHATEVER IS FIRST IN THE STACK.
   *
   * On a phone the constellation is a fixed full-bleed card under the header
   * (constellation.css: `left/right: 0.4rem`, no corner to park in), and the
   * page reserves room for it with `body.g3d-left .paper { padding-top:
   * calc(22vh + 5.4rem) }`. But `#toc-side` — the contents chip shelf — comes
   * BEFORE `.paper` in the stage, and it had no reserve at all: measured at
   * 375x812 on /moon, the shelf is 375x80 at y=107 and the float is 362x213
   * at y=116, so the float covered it completely. `elementFromPoint` at the
   * first chip's own centre returned the float, and a 375x1900 frame showed
   * no chip anywhere. The rows were in the DOM — which is what the renderer
   * fix bought — and invisible to anyone using their eyes and a finger.
   *
   * So the reserve moves to the shelf and the paper keeps only its own gap.
   * The float is a corner companion; on a phone the corner is the top band,
   * and what sits under it has to start below it. */
  body.g3d-left #toc-side,
  body.g3d-right #toc-side { padding-top: calc(22vh + 5.4rem); }
  body.g3d-railed.g3d-left #toc-side,
  body.g3d-railed.g3d-right #toc-side { padding-top: 3.4rem; }
  body.g3d-left #toc-side ~ .paper,
  body.g3d-right #toc-side ~ .paper,
  body.g3d-railed.g3d-left #toc-side ~ .paper,
  body.g3d-railed.g3d-right #toc-side ~ .paper { padding-top: 1rem; }
  #toc-tab, body:not(.toc-shut) #toc-tab { display: none; }
  .side h3 #toc-shut { display: none; }
}

/* ── where you left off ───────────────────────────────────────────────
 * Home's one extra line, under the site's own, when the reader asked to be
 * remembered and there is somewhere to go back to (glass-memory.js). A link
 * to a real address — the page and the section — set as an aside, not as a
 * banner: it is an offer, and the site's own opening is still the page. */
.mem-resume {
  margin: -0.4rem 0 1.4rem; max-width: 44rem;
  font-family: var(--font-display); font-size: var(--rd-caption); line-height: 1.45;
}
.mem-resume-a { color: var(--cyan); text-decoration: none; border-bottom: 1px solid transparent; }
.mem-resume-a:hover { border-bottom-color: var(--cyan); }

/* ── DRAFT, above the title it belongs to ─────────────────────────────
 * The kicker of a page the owner has not published. It sits where a
 * magazine's kicker sits — over the headline, in tracked caps — because
 * that is the one place a reader looks before the title, and a draft they
 * mistake for a live page is the whole failure this round is preventing. */
.draft-kicker {
  font-family: var(--font-display); font-size: var(--fs-1); font-weight: 600;
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--amber); margin: 0 0 0.35rem; }
.paper[data-presentation="gloss"] .draft-kicker { margin-bottom: 0.5rem; }

/* A date only the author needs: a note revised and never dated. The reader
   view says nothing about it (a page with no publication date has no
   dateline — never "unknown"), and owner mode says when it last moved. */
.dateline.for-owner { display: none; }
body.is-owner .dateline.for-owner { display: block; color: var(--amber); }
