/* shell.css — the site's own rules on top of the sheet: the mark in the masthead (and its forks), the
   function-key strip that lives on the monitor, the door's keys, the placeholder marks, and the screens'
   room for the strip. Everything the atlas already had (css/sheet.css, css/ui.css, css/layers.css) is
   untouched; this file only adds. Loaded after them. */

:root{
  /* THE BASELINE (Bill, 2026-09-07 — round 1 feedback, item 1; the URL he pasted is the receipt). The site's baked
     values for the atlas's knobs; css/layers.css keeps the study's own tuning and is untouched. Every one of these
     is still a URL switch and a bench slider. Layers: grid and reflect are OFF by default (body class), motion ON. */
  --dot-pitch:2px; --dot-floor:.7; --defocus:.25px;   /* ?pitch= ?dots= ?defocus= */
  --scan:rgba(30,42,56,.07);                          /* ?scan= */
  --glass:.14;                                        /* ?glass= */
  --baud:120; --cps:400;                              /* ?baud= ?cps= (?cps.<id>= per screen) */
  /* the small mono tier — kicker, table heads, captions, readouts, the [proposed] marks: darkened site-wide (item 3;
     "the filters stay, the ink of that tier moves"). ?dim= and the bench's "dim ink" select; #43505e is the atlas's. */
  --ink-2:#243040;
  --key-shadow: 2px 2px 0 var(--ink);   /* the kit's keycap shadow, needed outside .ui too (the door) */
  /* the mark's colours. Default: the page's own ink on every part — a stamp printed in the same ink as the form.
     ?ink=pack wears the pack's colours; ?ink=blue keeps the ink and adds the pack's blue on the machine side. */
  --m-hu: var(--ink);   /* the human face */
  --m-ma: var(--ink);   /* the machine face */
  --m-rl: var(--ink);   /* the wordmark's rule */
  --m-wd: var(--ink);   /* the wordmark's letters */
}
html[data-ink="pack"]{ --m-hu:#D89351; --m-ma:#376A7A; --m-rl:#376A7A; --m-wd:#172B30; }
html[data-ink="blue"]{ --m-ma:#376A7A; --m-rl:#376A7A; }

.sr{ position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0; }

/* ---- the masthead: the mark beside the name. Pixel art at integer scale: the square symbol is 64 cells wide
   and renders at 64 CSS px (1 px per cell); the wide symbol 96; the wordmark's grid is 2 px per unit. */
.mast .ident{ display:flex; align-items:center; gap:16px; min-width:0; }
.mark{ display:none; line-height:0; flex:none; }
.mark svg{ display:none; shape-rendering:crispEdges; }
.mark .hu{ fill:var(--m-hu); } .mark .ma{ fill:var(--m-ma); }
html[data-mark="square"] .mark{ display:block; }  html[data-mark="square"] .mark svg.square{ display:block; width:calc(64px * var(--hs)); height:calc(64px * var(--hs)); }
html[data-mark="dust"]   .mark{ display:block; }  html[data-mark="dust"]   .mark svg.dust{ display:block; width:calc(96px * var(--hs)); height:calc(64px * var(--hs)); }
.mast h1 .word{ display:none; width:calc(190px * var(--hs)); height:calc(58px * var(--hs)); shape-rendering:crispEdges; }
.mast h1 .word .wd{ fill:var(--m-wd); } .mast h1 .word .rl{ fill:var(--m-rl); }
html[data-name="mark"] .mast h1{ line-height:0; }
html[data-name="mark"] .mast h1 .word{ display:block; }
html[data-name="mark"] .mast h1 .txt{ position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; }
.mast .stamp .led{ font-variant-numeric:tabular-nums; }
@media (max-width:420px){ html[data-mark="dust"] .mark svg.dust{ width:64px; height:43px; } }   /* narrower than the wide symbol + wordmark: the symbol steps down to 2/3 */
/* the masthead after round 1 (item 7/7a): the sub-line is the tagline, in the page's ink — the one line that says what
   this is; the three-cell strip and the clock are gone ("for now"), so the band is the whole masthead and carries the box's
   own rule. The clock's slot holds a quiet read-out fork (?stamp=rate: the current screen's typing rate, read off the page's
   own setting — it claims nothing the page is not doing); default none. */
.mast .band{ border-bottom:0; }
.mast .sub{ color:var(--ink); font-size:13px; letter-spacing:.06em; }
/* the small mono tier at Plex Mono's 500 weight: at 11 px the 400's strokes are sub-pixel and never reach their own ink
   through the dots and the defocus (measured: the full page ink only takes the kicker to 3.4:1); the 500 is the same
   advance width, so nothing moves. */
.kicker, figcaption, .legend, footer, .scr-ind, .figtag small, mark.pend{ font-weight:500; }
.mast .stamp{ display:none; }
html[data-stamp="rate"] .mast .stamp{ display:block; }
.mast .stamp .led{ white-space:nowrap; }

/* ---- the hero: the first-visit animation (js/hero.js; ?hero=). While data-hero-run is on <html> the STAGE (svg.stage — the
   square's own box, round 4B) is the mark — shown from the first frame, so nothing flashes — and the name's letters wait;
   the script floats the dust, mixes it, reaches, steps the heads together and lands the letters, then clears the attribute
   and the resting square takes over, pixel for pixel. Without the attribute nothing here applies: the still page is the
   round-4A page; the ?mark=dust fork is untouched. */
html[data-hero-run] .mark{ display:block; }
html[data-hero-run] .mark svg.square{ display:none; }
html[data-hero-run] .mark svg.stage{ display:block; width:calc(64px * var(--hs)); height:calc(64px * var(--hs)); }   /* the heads start outside it: .mark svg{overflow:visible} */
html[data-hero-run] .mast h1 .word .wd path, html[data-hero-run] .mast h1 .word .rl, html[data-hero-run] .mast h1 .word .rl-dith{ opacity:0; }
/* H3: the tagline is the hero's (data-plain in the markup — the typing layer never splits it into character spans, which would
   render it differently from the plain line); hidden while the run is on, it burns in last (js/hero.js), then the CSS rests. */
html[data-hero-run] .mast .tagline{ opacity:0; }
/* H2: the rule's loose pixels exist only during the run; they wear the rule's ink, or its gradient at their own x */
.mast h1 .word .rl-dust{ display:none; }
html[data-hero-run] .mast h1 .word .rl-dust{ display:block; }
.mast h1 .word .rl-dust rect{ fill:var(--m-rl); opacity:0; }   /* the script shows them --h-rule-lead before the rule draws */
html[data-ink="pack"] .mast h1 .word .rl-dust rect{ fill:url(#rl-grad-u); }
/* ROUND 4B (Bill, 2026-09-08, H1): the choreography's knobs — every timing he may want to tune. Read by js/hero.js at run
   start; the bench carries them (?h-<name>= on the URL). Cells and milliseconds; each phase rounds to whole steps. */
:root{
  --h-speed:1;       /* the master knob: every duration below × this (2 = the whole thing twice as long) */
  --h-apart:14;      /* cells the two heads start apart beyond the square (4 CSS px per cell on the desk). Round 8A (Bill): 14, was 20; the
                        phone's block keeps its 10 — that number is the room the mark has beside it on a phone, and it is under his 14 already */
  --h-step:110;      /* ms per dust step — the clock everything else is rounded to */
  --h-float:600;     /* the dust floats, two clouds apart */
  --h-mix:400;       /* the clouds intermix */
  --h-reach:500;     /* the mixed cloud holds; pixels reach toward their heads */
  --h-lead:200;      /* the heads start closing this long before the reach ends */
  --h-close:400;     /* the close: the dust dashes home, the heads land with the last pixel (round 8A: 400, was 600) */
  --h-word-at:-1;    /* when the name starts landing; -1 = with the close */
  /* H2 — the header */
  --h-letter:80;     /* ms per letter of COLLABOT (2B: 40) */
  --h-glow:360;      /* ms each letter's phosphor decays from hot to the ink */
  --h-rule:380;      /* ms the rule combines out of its pixels while DEV travels */
  --h-rule-step:70;  /* ms per drift of a loose rule pixel */
  --h-rule-lead:160; /* ms before the rule draws that its loose pixels appear */
  --h-dev-from:30;   /* units right of its place DEV starts (30 = wholly outside the wordmark's box) */
  --h-tilt:7;        /* degrees DEV leans on landing (about its baseline, toward its travel) */
  --h-tilt-ms:140;   /* ms of the lean */
  /* H3 — the tagline */
  --h-burn-gap:60;   /* ms after the tilt before the burn-in */
  --h-burn:500;      /* ms the tagline burns in (round 8A: 500, was 220) */
  --h-tail:60;       /* round 7 (P1): ms every stepped animation that fills holds its END value past its last step — WebKit held the
                        penultimate step as the fill (the iPhone hitch, README round 6B); the steps' timing is unchanged (0 = round 6B) */
  /* round 5A — H3: the anchor; N3: the quick redraw; L2: the table's plot */
  --h-anchor:1;      /* 1 = the human head stays at its resting place and the machine head starts --h-apart cells right and is pulled to it;
                        0 = round 4B's symmetric start (each head --h-apart/2 out) */
  --h-redraw:420;    /* ms the masthead is redrawn on every draw of home after the first visit — the square rasters in, the name lands,
                        the tagline burns in; no float, mix, reach, combine or tilt (0 = shown at once) */
  --t-draw:320;      /* ms the plotter rules a table before its rows take their turns (0 = the frame shows at once) */
  /* the nav (js/nav.js, css/nav.css) — N3 · N6 · N7 */
  --n-back:30;       /* ms per character the prompt backs up to where the paths part */
  --n-fwd:45;        /* ms per character it types the new tail */
  --n-accept:160;    /* ms of the accept — the caret holds solid for the beat before the frame paints (0 = none); round 6B took the hot sweep out of it */
  --n-gate:1;        /* 1 = the frame paints after the prompt has typed and the line is accepted; 0 = at once, as before */
  --n-hover:140;     /* ms the rule under a hovered command draws (four steps) */
  --n-hint-after:6000;   /* ms with no flip before the hint shows beside the SCREEN count (0 = never); the session's first flip retires it */
  --n-hint-pulse:1400;   /* ms of the hint's pulse, each way */
  --n-menu-open:140;     /* round 8B (P1): ms a submenu rasters down when it opens, a step per row (0 = at once); css/nav.css */
}

/* ==== ROUND 5A (Bill, 2026-09-09): THE DRAW ORDER'S STATES (js/draw.js) and THE DITHERED RULE (H2) =========================
   hero-wait: the masthead before its turn — the mark, the name and the tagline held back until the order reaches them (N3: on a
   first visit the run's own CSS above is the hidden state; on every other draw this is). tw-wait: a table before its turn — no
   border, no heading's ground, until the plotter has ruled it (L2); .tw-plot is the overlay the beam draws on. */
.js.fx-screens .mast.hero-wait .mark svg.square, .js.fx-screens .mast.hero-wait h1 .word .wd path, .js.fx-screens .mast.hero-wait h1 .word .rl,
.js.fx-screens .mast.hero-wait h1 .word .rl-dith, .js.fx-screens .mast.hero-wait .tagline{ opacity:0; }
.tablewrap:has(> .tw-plot){ position:relative; }   /* positioned only while the plot is on it: a positioned wrapper paints the resting table
                                                       on its own layer under the defocus and every glyph edge re-rasters (13–36k px against main) */
.js.fx-screens .tablewrap.tw-wait, .js.fx-screens .tablewrap.tw-wait th, .js.fx-screens .tablewrap.tw-wait td, .js.fx-screens .tablewrap.tw-wait tr{ border-color:transparent; }
.js.fx-screens .tablewrap.tw-wait thead th{ background:transparent; }
.tw-plot{ position:absolute; inset:0; width:100%; height:100%; overflow:visible; pointer-events:none; fill:none; stroke:var(--ink); stroke-linecap:butt; shape-rendering:crispEdges; }
/* H2: the rule dithered — the pack's colours by the head each belongs to (orange left, blue right), whole cells on the wordmark's grid;
   html[data-dither] (?dither=; the bench) picks the smooth rect (0) or a Bayer group (2; 4 — the default since round 8A, 2 before). Only the pack's ink has a
   gradient to dither; the page-ink forks keep their solid rule. */
.mast h1 .word .rl-dith{ display:none; }
.mast h1 .word .rl-dith .a{ fill:var(--m-hu); } .mast h1 .word .rl-dith .b{ fill:var(--m-ma); }
html[data-ink="pack"][data-dither="2"] .mast h1 .word .rl, html[data-ink="pack"][data-dither="4"] .mast h1 .word .rl{ display:none; }
html[data-ink="pack"][data-dither="2"] .mast h1 .word .rl-dith.d2, html[data-ink="pack"][data-dither="4"] .mast h1 .word .rl-dith.d4{ display:block; }

/* ==== ROUND 6A (Bill, 2026-09-09, on the round-5B forks — "I like the `inv` heading style. It's simple and reads really well."):
   THE HEADING TIER. The format every content screen's heading shares (the two cases he named in L3 — Home's kicker and the Story
   heading — made one): the words in the machine's face (Plex Mono, the 500 the page loads) at THE BODY'S SIZE (16 px — a printout
   has one size and sets a heading apart by case, spacing and a device), uppercase, tracked, in the FULL ink; and the device he
   picked, INVERSE VIDEO — the terminal's own emphasis, the nav's current-command mark at the heading's size — hugging the words.
   The words are Envoi's, not a character changed. set / rule / num (the round-5B forks) are retired: no fork attribute, no switch.
   THE BOX COSTS NO LAYOUT: its 3 px above and 2 px below are padding the margins take back (-3 px above; below, --hd-gap is
   measured from the words' line box), so the line sits where the plain format's would and a screen that fits with the plain
   format fits with the box — --hd-gap 8 (10 in 5B) keeps *operation* at 360 × 640 and *thesis* at 844 × 390 on one screen
   (the edge tables in README round 6A).
   IN TURN (the draw order): a heading is a typed line; the ink ground must not show before the line's turn — it is transparent
   while the line is split and no character has landed ([data-type] with no .ch.on) and shows from the first character, the
   characters landing in paper (the type layer's hot and cool turned round on this ground). With the type layer off the heading
   is a painted block and the crt-line clip wipes the box in with the words. Reduced motion the page honours, and script off:
   the finished heading. Knobs (?hd-*=; the bench's section *the heading tier*). */
:root{
  --hd-size:16;     /* px — the heading's size; 16 is the body's */
  --hd-weight:500;  /* Plex Mono's weight (400 is the other face the page loads) */
  --hd-track:.1;    /* em of tracking (round 8A: .1, was .06) */
  --hd-gap:8;       /* px below the heading's line box (5B: 10; before the format, the h2 had 4 and the kicker 14) */
}
.screen .maincol > section > h2, .screen .maincol > section > .kicker{
  display:flex; align-items:baseline; gap:10px; width:max-content; max-width:100%;
  padding:3px 8px 2px; margin:-3px 0 calc(var(--hd-gap) * 1px - 2px) -8px;
  font:var(--hd-weight) calc(var(--hd-size) * 1px)/1.25 var(--mono); letter-spacing:calc(var(--hd-track) * 1em); text-transform:uppercase;
  background:var(--ink); color:var(--paper); }
/* the ground waits its turn: transparent while the line is split and nothing has landed, ink from the first character */
.screen .maincol > section > :is(h2, .kicker)[data-type]:not(:has(.ch.on)){ background:transparent; }
.screen .maincol > section > :is(h2, .kicker) .ch.hot{ background:var(--paper); color:var(--ink);
  text-shadow:0 0 6px rgba(221,227,232,.95), 0 0 14px rgba(221,227,232,.6); }
.screen .maincol > section > :is(h2, .kicker) .ch.cool{ color:var(--paper); text-shadow:0 0 6px rgba(221,227,232,.95), 0 0 14px rgba(221,227,232,.6); }

/* ---- the flow's furniture. The nav is the prompt at the head of the monitor (css/nav.css); the round-1 strip and the
   Screen N / M readout at the foot are gone (Bill, 2026-09-07: hard to see, wraps on a phone). Round 4A: the count is
   back as the SCREEN cell of the frame's title block (the frame block at the end of this file). */
@media (max-width:720px){
  .mast .stamp{ flex:1 1 100%; }                    /* the readout takes its own line, right-aligned, under the name */
}

/* ---- frames (js/fx-screens.js): a screen taller than the monitor at this viewport is shown one frame at a time (the title
   block counts these as SCREEN n / total — Bill's word for one monitor-full). The blocks of the other frames are display:none,
   so nothing scrolls within and there is no scrollbar; a frame after the first opens with the continuation head — the
   screen's title, "continued", which frame of how many — in the small mono tier, in the flow, exactly 26 px tall with its
   margin (the engine's CONT). A block taller than a whole frame is the one case a frame scrolls (.tall). ?off=frames restores the previous engine: a tall screen scrolls within and flips at its edge. */
.js.fx-screens.fx-frames .screen{ overflow-y:hidden; }
.js.fx-screens.fx-frames .screen.tall{ overflow-y:auto; }
/* ROUND 8C (Bill, 2026-09-11, his one tweak on round 8 — "When there are multiple pages, the header should change… remove the main
   subtitle, keep the 'What this is · continued', and the text size, but add the black text background like the main subtitle"):
   ONE HEAD PER CONTINUATION SCREEN, IN THE HEADING'S DRESS AT ITS OWN SIZE. His screenshot (home/story/2) showed two heads: this line,
   and under it the inverse heading repeated. The repeat was a defect since round 6A, not a design: the heading's format is display:flex
   at (0,3,1) and .outframe was a bare class (0,1,0), so the frames engine marked the heading out of the frame and it rendered anyway —
   round 3 never showed a heading on a continuation frame, and every continuation frame since 6A carried 27 px (28 on a phone) the
   engine had not budgeted. The state rule now carries the element's specificity for every block of the section (the round-5B rule,
   as .proof tr.outframe already did for the rows) — the comms panel's grid (0,1,0, later in this file) would have leaked the same
   way had contact ever split. THE HEAD: the heading's own device — 3 px of ink above the words and 2 below, 8 px either side, the
   words in paper, the box hugging the words (max-content) — at this line's own face, untouched: the small mono tier's 11 px on a
   15 px line, .08 em, uppercase (his "keep the text size"). THE BOX COSTS NO LAYOUT, as the heading's does: the margins take the
   padding back (-3 above, collapsing through the section as the heading's does on a first screen; 11 - 2 = 9 below), so the line box
   sits where the plain line's did and the engine's CONT (26 px = 15 of line + 11 below) is unchanged — the frames tables are round
   8A's, and every paragraph (margin-top 0) opens a continuation frame exactly where it did. The one exception, stated: a block with
   its OWN top margin above 9 px collapses with 9 where it collapsed with 11 and sits 2 px lower — .proof .after (margin-top 14)
   when the projects table ends a frame and the paragraph under it opens the next (844 × 390, screen 3 of 3; the one such frame in
   the matrix). Ink outside the box (a shadow, a pseudo-element) would keep the margin at 11, and the paint kind's forwards clip would
   cut it — so the heading's device stays, and the 2 px is named here rather than hidden. The type layer never types this line (it is
   not in its list); it is a paint item (round 6B) and the crt-line clip wipes the box in with the words, as it does the heading with
   the type layer off. */
.outframe, .proof tr.outframe, .screen .maincol > section > .outframe{ display:none; }   /* the state rule at the element's specificity: the rows' display:block and the heading's display:flex both outrank a bare class */
.cont{ display:none; width:max-content; max-width:100%; padding:3px 8px 2px; margin:-3px 0 9px -8px;
  font:500 11px/15px var(--mono); letter-spacing:.08em; text-transform:uppercase; background:var(--ink); color:var(--paper);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.screen.contd .cont{ display:block; }

/* ---- the screens' copy. ROUND 8A (Bill, N1 — "On desktop, the text content needs to fill the entire margin to the right"): a
   paragraph's measure is THE COLUMN'S, not a character count. The 64ch cap (sheet.css's bare p, and .prose p here) stopped the desk's
   copy at 587 px of a 782 px column — the lede, at 17 px, at 623 (his screenshot: the empty right margin boxed in red). Lifted for
   every paragraph in a screen's column. THE PHONE: nothing changes there, and what he saw was a side effect — the phone's column
   (344 at 390, 314 at 360) was always narrower than 64ch (587–623 px), so the cap never bound; it is the rule now. The desk's measure
   at 16 px Archivo: ~90 characters a line before, ~120 after (the comfortable range tops near 75, and N2's smaller size lengthens the
   line a little more) — his call, made on his own screenshot. */
.prose p{ font-size:16px; }
.screen .maincol p{ max-width:none; }
/* ROUND 8A (Bill, N2 — "On desktop only, can we bump the content text size down 1 unit. (I may reverse this, so maybe add a knob I
   can tweak)"): THE BODY'S SIZE ON THE DESK IS A KNOB — --body-size (px; ?body-size=; the bench's section *the copy*), default 15:
   one step of the sheet's own scale (11 · 12.5 · 13 · 14 · 15 · 16 · 17) below the 16 the phone keeps. The lede rides one step above
   the body (16 on the desk; 17 on the phone, as before), and THE HEADING TIER FOLLOWS THE BODY — its rule since round 6A is "the
   heading at the body's size" (--hd-size: 15 on the desk, 16 on the phone; its own knob still overrides). The phone reads none of
   this: its paragraphs stay 16, the lede 17, the heading 16 whatever the knob says (the knob is read only inside the desk's query).
   ?body-size=16&hd-size=16 is round 7's desk. Every ink is unchanged (#1E2A38 on the paper, 11.24:1 by the hex pair). */
:root{ --body-size:15; }
@media (min-width:721px){
  :root{ --hd-size:15; }
  .screen .maincol p{ font-size:calc(var(--body-size) * 1px); }
  .screen .maincol p.lede{ font-size:calc((var(--body-size) + 1) * 1px); }
}
.screen .figcol:empty{ padding:0; }
.proof th, .proof td{ text-align:left; white-space:normal; vertical-align:top; }   /* words, not figures */
.proof td:first-child{ font-weight:600; font-family:var(--sans); }
.proof td:last-child{ white-space:nowrap; }    /* the address is one token on a desk; the middle column takes the slack */
@media (max-width:720px){   /* the receipts must never hide in a side-scroll: each row becomes a block — what / it is / where */
  .proof .tablewrap{ overflow:visible; border:0; border-top:1px solid var(--ink); }
  .proof table, .proof tbody, .proof tr, .proof td{ display:block; width:100%; }
  .proof thead{ display:none; }
  .proof tr{ padding:10px 0 12px; border-bottom:1px solid var(--hair); }
  .proof td{ padding:0; border:0; white-space:normal; }
  .proof td a{ overflow-wrap:anywhere; }
  .proof td:first-child{ font-size:14px; margin-bottom:2px; }
  .proof td:nth-child(2){ font-family:var(--sans); font-size:14px; color:var(--ink-2); margin-bottom:4px; }
  .proof td:nth-child(3){ font-size:11px; font-weight:500; letter-spacing:.03em; color:var(--ink-2); margin-bottom:4px; }
}
/* (round 18: the table left the projects section — its rows are records now, css/oss.css; the table rules above stay for a table that returns, and
   the table's link dress (.proof td a — ink and an underline) is retired with it: the text link below is the site's one dress) */

/* ==== ROUND 18 (Bill, 2026-09-14, U2): THE TEXT LINK — one dress for every hyperlink in the copy on the CRT. His words: "Every hyperlink
   (non-button) needs to use the thematic hyperlink style seen elsewhere on the site. For an example see the X.com link on the print section…
   some links are buttons or other thematic controls, those stay. This is just 'text' links. Also, this is only for the CRT site, not the 'Paper'
   article/post UI." The X link's dress (rounds 9–14, print's status line — the address in the comms panel's register): the prompt's face (Plex Mono
   500 at --n-ind-px, 13 on the desk, 12 on a phone), the full ink, no underline, and INVERSE VIDEO under the pointer or the keyboard — the site's
   own "current" mark — on a plate that costs the line no room (the padding eaten by a negative margin; the plate's 3 px past a line's edge is under
   the window's clip). Where it reaches: every <a> in a section's copy — the in-copy links (in print · Go look. · Get in touch.), the ecosystem's
   addresses, the open-source records' PM bots — and not the keys (.ui-key), the comms' terminals (.ln), the nav, the title cell, the mark, or the
   paper (the printout is outside .screen by construction; css/print.css .post a is its own). A typed line's link keeps the dress on its characters. */
.screen .maincol a:not(.ui-key):not(.ln){ color:var(--ink); text-decoration:none; font:500 calc(var(--n-ind-px) * 1px)/1.3 var(--mono); letter-spacing:.02em; text-transform:none; padding:1px 3px; margin:-1px -3px; }
.screen .maincol a:not(.ui-key):not(.ln):hover, .screen .maincol a:not(.ui-key):not(.ln):focus-visible{ background:var(--ink); color:var(--paper); outline:none; }
.proof .after{ margin-top:14px; }

/* a value that is not on the page yet renders as a bracketed mark, never as a link */
mark.pend{ background:transparent; color:var(--ink-2); border:1px dashed var(--ink-2); padding:1px 4px 0;
  font:500 10px/1.4 var(--mono); letter-spacing:.06em; vertical-align:1px; white-space:nowrap; }
/* round 6B (B1's sweep): the mark's dashed box inside a typed line waits for the line to reach it — its characters are the type
   layer's and land in turn, but the box is the element's own and showed from the moment the screen did. Transparent while the
   line is split (.ch inside it) and none of its own characters has landed; a line never split (reduced motion, script off,
   ?off=type — then the paragraph is a painted block and the clip carries the box) has no .ch and is left alone. */
.js.fx-screens [data-type] mark.pend:has(.ch):not(:has(.ch.on)){ border-color:transparent; }

/* (round 4C's doors — the three ways in drawn as a plan draws doors — lived here through round 5B; retired in round 6A when Bill
   picked the transmissions. The record is README round 4C and the git history at 6664c29.) */

/* ==== ROUND 6A (Bill, 2026-09-09, on round 5B's transmissions — "I like this concept a lot. It needs little work. Right now,
   especially on desktop, there is a lot of 'space'… Give it some framing. Again, think sci-fi terminal. Make this more 'instrument'
   looking. Keep the oscilloscope effect, but give it some idle animation as well. Some random noise and occasional 'blips'. No hover
   effect, as it doesn't work on mobile. Make the urls look a little more like an 'address', but don't obscure it… The actual 'buttons'
   should still be something obvious to click on… when the user clicks on a location, before doing the redirect (new window), play a
   short animation where the oscilloscope suddenly starts transmitting a burst of 'data'. Maybe add an LED or effect of 'TRANSMITTING'"):
   THE COMMS PANEL. The sheet is a drawing and the monitor a terminal, so the three ways in are an instrument's FRONT PANEL as a drawing
   draws one, out of the control kit's own parts (css/ui.css: a keycap, a lamp, a legend) — nothing new invented:
     the panel     — a 1 px box the column's width with its legend cut into the top rule (COMMS), padded like the kit's notice
     the scope     — the box at the left, on the drawing's own grid (a graticule: a cell a minor, four cells a major, in the
                     hairline's ink); three channels, one trace each, 1 px ink on the mark's cell (--c-cell). A trace is a serial
                     line — idle high, a space bit one cell low (round 5B's frame, the same units). js/comms.js keeps them alive
                     with the line's noise and the occasional blip (a hit on the line); script off, the idle lines in the markup
     the terminals — one per way in, the whole row the link: the channel's designator (CH n), the route (the address's own scheme,
                     mailto: / https: — a fact, not a costume: his "more like an address, but don't obscure it"), the address
                     verbatim (Envoi's target, the keys' face, the full ink), and a SEND key — the kit's keycap, the one thing on
                     this sheet that already reads as a button. No hover effect anywhere (his ruling — a phone has no hover); a
                     press sits the key flush (the kit's :active), and while the channel sends the key stays down and the address
                     holds inverse
     the TX lamp   — under the scope: TX, the kit's lamp, the read-out (IDLE · TRANSMITTING · SENT)
   SEND (js/comms.js): the channel's trace bursts with the address's own bytes — 8N1, least-significant bit first, the whole address
   over --c-burst ms — the lamp lights and the read-out reads TRANSMITTING; then the address opens in a new tab (a mailto: opens the
   mail client in place); a second press during the burst does nothing. Reduced motion the page honours: no idle, no burst — the
   address opens at once. Script off: the plain links. The block is drawn in turn by js/comms.js: the plotter rules the panel and the
   scope with the beam, the panel shows under the fading beams, the traces are alive, then the legend, the terminals' labels, the
   addresses and the keys take their turns (typed with the type layer on, painted otherwise). Every number is a knob (?c-*=). */
:root{
  --c-cell:8;      /* px per cell of the scope — 2 of the mark's 4 px cells on the desk; the phone's block sets 6 */
  --c-rows:16;     /* cells the scope is tall: three channels of five and a cell of margin; the landscape phone's block sets 12 */
  --c-tick:60;     /* ms per idle sample — the scope's time base at rest */
  --c-noise:12;    /* % of idle samples that carry the line's noise (a fraction of a cell either way) */
  --c-blip:25;     /* blips per thousand idle samples, per channel — a space bit or two, a hit on the line (25 ≈ one every 2.4 s) */
  --c-burst:2000;  /* ms the address's bytes take to cross the scope on SEND (0 = no burst: the address opens at once). Round 7 (Bill, K3:
                      "the sending animation is fun, let's have the animation play out just a little bit longer"): 700 → 1000; round 8A: 2000 */
  --c-hold:900;    /* ms the SENT read-out holds after the address has opened */
  --c-draw:320;    /* ms the plotter takes to rule the panel and the scope when the block is drawn (0 = at once) */
  /* ROUND 7 (Bill, K2 — "a bit of color somewhere. not a lot, but just a little color somewhere to break up the monotone"): THE SCOPE IS
     LIT. The traces are drawn in the site's phosphor — the pack's blue the hero's letters land hot in (css/shell.css --m-ma; js/hero.js
     HOT) — with the phosphor's bloom, and the TX lamp lights in it while a channel sends. One colour, one meaning the page already
     carries (hot = live), on the one thing on the sheet that is a screen. Everything else stays the ink. var(--ink) and 0 = round 6A. */
  --c-trace:#376A7A;   /* the scope's phosphor: the traces, the lit lamp (4.63:1 on the paper; a line, not text) */
  --c-glow:6;          /* px of the phosphor's bloom around a trace (0 = none; round 8A: 6, was 2) */
  /* ROUND 8A (Bill, C1 — "Add a knob for the comm transmission line size and bump it up 2 units. They are too thin."): the traces'
     stroke is a knob, --c-stroke (px; ?c-stroke=), default 3 — his number; 1 is round 7. The stroke is in CSS px whatever the scope's
     size (the paths carry vector-effect:non-scaling-stroke), so a phone's scope draws the same 3 px line. A space bit is one cell low
     (8 px on the desk, 6 on a phone): at 3 px the step still shows the cell's full drop, the line's ±.3-cell noise is a shimmer on a
     heavier line, and the bloom (--c-glow, 6 now) rides the wider stroke. */
  --c-stroke:3;        /* px the traces are drawn at (round 7: 1) */
  /* ROUND 7 (Bill, K1 — "make actual link text a little more obvious… some 'sprite' icons of X, LinkedIn, and Email. And the actual
     text a little more prominent"): each terminal wears a SPRITE — the channel's kind as pixel art in the mark's register, twelve
     cells square at --c-sp px a cell, rastered in row by row at its turn (the mark's own redraw) — and the address is the row's
     headline: --c-addr px, the full ink. The designator and the route stack beside the sprite as the terminal's tag (two lines, the
     key's height, so the row is no taller); the scope gives up four cells (36 → --c-cols 32) so the longest address fits the desk. */
  --c-cols:32;     /* cells the scope is wide (round 6A: 36; the phone's scope is the panel's width regardless) */
  /* ROUND 8A (Bill, C2 — "For the X.com icon, it needs to look more like the actual icon which has a thicker line with a gap in one
     part… If Dana needs to bump up the minimum size of the icons to make this change, that's fine, they read a bit small anyway"):
     the sprites' grid is 16 CELLS (12 in round 7) — four majors of the scope's own grid — so the X can carry the mark's construction:
     the heavy stroke a HOLLOW bar four cells across at 45° (a one-cell outline with a two-cell slit down its length — his "thicker line
     with a gap in one part"), the light stroke two cells, passing behind the bar and touching both its walls; the envelope and the "in"
     tile move to the same grid so the row stays one register. At --c-sp 2 a sprite is 32 px square (24 before). */
  --c-sp:2;        /* px per cell of a channel's sprite (16 cells square; round 7: 12) */
  --c-addr:14;     /* px the address is set at (round 6A: 12); the smallest phones set 13 */
}
.comms{ position:relative; display:grid; grid-template-columns:calc(var(--c-cell) * var(--c-cols) * 1px) minmax(0, 1fr); grid-template-areas:"scope rows" "tx rows";
  column-gap:24px; row-gap:8px; align-items:start; margin-top:22px; padding:16px 12px 12px; border:1px solid var(--ink); }
.comms .cm-legend{ position:absolute; top:-8px; left:10px; padding:0 6px; background:var(--paper); font:500 10px/1.5 var(--mono); letter-spacing:.14em; text-transform:uppercase; color:var(--ink); }
/* the scope: the drawing's grid behind the traces; the channel numbers at the left of each band, in the small tier */
.cm-scope{ grid-area:scope; position:relative; height:calc(var(--c-rows) * var(--c-cell) * 1px); border:1px solid var(--ink); overflow:hidden;
  background:
    linear-gradient(var(--hair) 1px, transparent 1px) 0 0 / calc(var(--c-cell) * 4px) calc(var(--c-cell) * 4px),
    linear-gradient(90deg, var(--hair) 1px, transparent 1px) 0 0 / calc(var(--c-cell) * 4px) calc(var(--c-cell) * 4px),
    linear-gradient(rgba(30,42,56,.06) 1px, transparent 1px) 0 0 / calc(var(--c-cell) * 1px) calc(var(--c-cell) * 1px),
    linear-gradient(90deg, rgba(30,42,56,.06) 1px, transparent 1px) 0 0 / calc(var(--c-cell) * 1px) calc(var(--c-cell) * 1px); }
.cm-tr{ position:absolute; inset:0; width:100%; height:100%; display:block; fill:none; stroke:var(--c-trace); stroke-width:calc(var(--c-stroke) * 1px); stroke-linecap:butt; stroke-linejoin:miter; shape-rendering:crispEdges;
  filter:drop-shadow(0 0 calc(var(--c-glow) * 1px) color-mix(in srgb, var(--c-trace) 55%, transparent)); }   /* round 7 (K2): the phosphor and its bloom; a browser without color-mix() drops the filter and keeps the colour */
.cm-tr path{ vector-effect:non-scaling-stroke; }
.cm-scope .n{ position:absolute; left:5px; font:500 9px/1 var(--mono); color:var(--ink-2); }
.cm-scope .n:nth-of-type(1){ top:calc(var(--c-rows) * var(--c-cell) * .03125px + 2px); }   /* half a cell into each band of five (of sixteen) */
.cm-scope .n:nth-of-type(2){ top:calc(var(--c-rows) * var(--c-cell) * .34375px + 2px); }
.cm-scope .n:nth-of-type(3){ top:calc(var(--c-rows) * var(--c-cell) * .65625px + 2px); }
/* the TX lamp and its read-out */
.cm-tx{ grid-area:tx; display:flex; align-items:center; gap:8px; font:500 10px/1.4 var(--mono); letter-spacing:.12em; text-transform:uppercase; color:var(--ink-2); }
.cm-lamp{ display:inline-block; width:10px; height:10px; border-radius:50%; border:1px solid var(--ink); background:transparent; }   /* the kit's lamp */
.comms.sending .cm-lamp{ background:var(--c-trace); border-color:var(--c-trace); box-shadow:0 0 calc(var(--c-glow) * 3px) color-mix(in srgb, var(--c-trace) 60%, transparent); }   /* round 7 (K2): lit in the phosphor, blooming */
.cm-st{ color:var(--ink); }
.comms.sending .cm-st{ background:var(--ink); color:var(--paper); padding:1px 5px 0; }
/* the terminals: a row is the link; the four columns — the sprite, the tag (the designator over the route), the address, the key —
   are shared down the panel (round 7: the sprite column, the tag stacked) */
.cm-rows{ grid-area:rows; display:grid; grid-template-columns:max-content max-content minmax(0, 1fr) max-content; align-items:center; column-gap:10px; }
.comms .ln{ display:grid; grid-column:1 / -1; grid-template-columns:subgrid; grid-template-rows:auto auto; align-items:center; padding:7px 0; border-bottom:1px solid var(--hair);
  color:var(--ink); text-decoration:none; min-width:0; cursor:pointer; }
.comms .ln:last-child{ border-bottom:0; }
/* the sprite (round 7, K1): pixel art on its own cell, crisp; rastered in row by row at its turn — the mark's redraw device, twelve
   rows in --line-ms (the paint kind's own clock); the same forwards fill the paint kind gives every item, so the engine is untouched */
.comms .sp{ grid-column:1; grid-row:1 / 3; display:block; width:calc(var(--c-sp) * 16px); height:calc(var(--c-sp) * 16px); }   /* round 8A: 16 cells (12) */
.comms .sp svg{ display:block; width:100%; height:100%; fill:var(--ink); shape-rendering:crispEdges; }
.js.fx-screens .comms .sp.pt.pd{ animation:sp-raster var(--line-ms, 80ms) steps(16, end) forwards; }   /* a row per step: sixteen now */
@keyframes sp-raster{ from{ clip-path:inset(0 0 100% 0); } to{ clip-path:inset(0); } }
.comms .des{ grid-column:2; grid-row:1; align-self:end; font:500 10px/1.4 var(--mono); letter-spacing:.12em; text-transform:uppercase; color:var(--ink-2); white-space:nowrap; }
.comms .route{ grid-column:2; grid-row:2; align-self:start; font:400 11px/1.4 var(--mono); letter-spacing:.02em; color:var(--ink-2); white-space:nowrap; }
.comms .addr{ grid-column:3; grid-row:1 / 3; justify-self:start; font:500 calc(var(--c-addr) * 1px)/1.3 var(--mono); letter-spacing:.02em; white-space:nowrap; padding:1px 4px; margin:-1px -4px; }   /* the box hugs the words; .02em — 12 px wanted .04, 14 does not, and the longest address is 303 of 319 on the desk with it */
.comms .addr .t{ display:inline-block; padding:1px 4px; margin:-1px -4px; }   /* the inner span carries the inverse: the outer is the draw order's item */
.comms .ln.sending .addr .t{ background:var(--ink); color:var(--paper); }
/* the key: the kit's keycap (ui.css .ui-key) with no hover state; a press sits it flush; while the channel sends it stays down */
.comms .key{ grid-column:4; grid-row:1 / 3; display:inline-block; justify-self:end; font:500 11px/1 var(--mono); letter-spacing:.1em; text-transform:uppercase; padding:9px 13px 8px;
  border:1px solid var(--ink); background:var(--paper); color:var(--ink); box-shadow:var(--key-shadow); user-select:none; white-space:nowrap; }
.comms .ln:active .key, .comms .ln.sending .key{ transform:translate(2px, 2px); box-shadow:none; }
.comms .ln.sending .key{ background:rgba(30,42,56,.14); }
.comms .ln:focus-visible{ outline:none; }
.comms .ln:focus-visible .key{ outline:1px dashed var(--ink); outline-offset:3px; }
/* in turn (js/comms.js): until the block's turn the panel is not there; its labels and addresses wait as their own kinds' items;
   the plotter's overlay is visible over the hidden block */
.js.fx-screens .comms.cm-wait{ visibility:hidden; }
.cm-plot{ position:absolute; inset:-1px; width:calc(100% + 2px); height:calc(100% + 2px); overflow:visible; pointer-events:none; visibility:visible;
  fill:none; stroke:var(--ink); stroke-width:1; stroke-linecap:butt; shape-rendering:crispEdges; }
@media (max-width:720px){   /* the phone: the scope full width, the lamp under it, the terminals below — the sprite, the tag and the key on the
                               first line, the address on its own line under them, the panel's whole width */
  :root{ --c-cell:6; }
  .comms{ grid-template-columns:minmax(0, 1fr); grid-template-areas:"scope" "tx" "rows"; column-gap:0; row-gap:10px; padding:16px 10px 10px; }
  .comms .ln{ grid-template-rows:auto auto auto; row-gap:0; padding:8px 0; }
  .comms .addr{ grid-column:1 / -1; grid-row:3; margin-top:5px; }   /* the address on its own line (the longest is 305 px at 14 px, 283 at 13; the panel's inner width is 322 at 390, 292 at 360) */
}
@media (max-width:389px){ :root{ --c-addr:13; } }   /* the small phones: the longest address at 14 px is 305 wide; below 390 the panel's inner width is under 322 */
@media (min-width:721px) and (max-height:520px){   /* a phone on its side: the panel's height is the three terminals' (134 > the scope's); tightened so contact stays one screen at 844 × 390 */
  :root{ --c-rows:12; --c-addr:13; }   /* round 7: the rows column is 456 there and the longest address at 14 px would run under the key */
  .comms .ln{ padding:5px 0; }
}

/* an article page: one screen that scrolls; the body is painted, not typed */
.article .lede{ margin-bottom:22px; }
.article .prose p{ max-width:66ch; }
.article .prose h3{ font-family:var(--sans); font-size:14px; letter-spacing:.06em; text-transform:uppercase; margin:26px 0 8px; }

/* ==== ROUND 4A (Bill, 2026-09-08): THE FRAME, THE GRID, THE HERO, THE TITLE BLOCK ================================
   LU1 — "random horizontal and vertical lines… some kind of actual framing… to frame us and ground the theme." The frame
   is the drafting sheet's border: one rule around the sheet at the grid's width, from the nav's foot to a gutter above
   the monitor's edge. The nav sits above it and the frame's top edge is the rule the nav sits on. The masthead's own box
   dissolves into it (a box in a box does one job twice; ?mastbox=1 keeps it as a fork) and the empty figure column —
   a 132 px margin with a rule and nothing in it on every screen, the stray vertical line — dissolves until a screen
   carries a figure. The frame's foot is the TITLE BLOCK: a hairline and a strip with the name at the left (the nameplate
   every non-home screen lacked) and SCREEN n / total at the right (SHEET until round 5A, L1) — Bill's N5 in the drawing's own words; it inherits the
   job of the reel counter N4 removed. Not a bezel: a rule, a title block, nothing curved or glassy.
   LD1 — the desk is a fixed grid, --grid = 864 px = 18 × 48 (the atlas's engineering-grid major), derived from the hero:
   the mark and the wordmark at an integer 2× (pixel art scales by whole cells — the square 128 px, the wordmark 380 × 116,
   the gap 48) are 556 wide; the copy wants 64ch at the lede's 17 px (~600) inside 40 px paddings; the tagline, set as a
   display line at the size that spans the content width, ties the two: 784 + 80 = 864. Fixed above 864, fluid below it
   (a half-screen window must not side-scroll), the phone's rules under 720.
   LM1 — the phone is flexible: the frame inset --frame-x from the edges, the copy --gut inside it.
   H4 — the hero composed full on the desk: the ident at 2× on the left, the tagline under the WHOLE ident spanning the
   content width (Archivo 700, tracked, --tag-px), the rule under COLLABOT the pack's blue↔orange gradient (H2's static
   half). Room for 4B: the heads may start ±44 px outside the square's box (60 px of padding to the left, the 48 gap to
   the right) under overflow:visible; DEV travels inside the wordmark's own box; the tagline is its own block. */
:root{
  --grid:864px;        /* LD1: the desk's fixed width */
  --gut:40px;          /* the frame's inner padding, sides */
  --gut-y:36px;        /* … and top */
  --frame-x:0px;       /* the frame's inset from the viewport's sides (the phone only) */
  --frame-gap:16px;    /* paper between the frame's foot and the monitor's edge */
  --foot:28px;         /* the title block's height */
  --hs:2;              /* the hero's integer scale: 2 = 4 CSS px per cell of the mark */
  --hero-gap:48px;     /* mark → wordmark */
  /* ROUND 8A (Bill's dev note: "Can I get a knob in bench for the desktop fixed width value?"): --grid is a bench knob (?width=, the
     bench's section *the frame*; whole 48-px majors, 672–1152). What follows it: the sheet, the nav's row and every column (they read
     --grid); THE TAGLINE — round 4A set it at the size that spans the column (29.9 px → 782 of 782), a number that is only true at
     864: at any other width js/shell.js sets the size from the masthead's measured width (width / 26.154 px of line per px of size —
     Archivo 700, .14 em, 33 characters; 782 / 29.9) on the masthead, live as the slider moves; at 864 the literal below stands and the
     page is byte-identical to round 7 (a calc() here gave 29.8998 and re-rastered the line). The hero's composition is fixed (the
     mark, the gap, the wordmark: 556 wide at 2×) and needs a column of at least 556 — --grid 672 gives 590; below that the ident
     would overrun, so the range stops there. Above 864 the copy's measure grows with the column (N1) — at 1152 a line runs ~170
     characters. The phone reads none of it (its sheet is 100% − 2·--frame-x); a viewport narrower than --grid is fluid as before. */
  --tag-px:29.9px;     /* the tagline's size: the size at which its 33 characters span the 782 px column (measured: 29 px → 759 px); js/shell.js derives it at any other --grid */
}
/* the frame: the sheet's own box. With the screens layer on it is the monitor's fixed frame (height from the nav's foot
   to the gutter); with it off, or script off, it wraps the stacked page. */
.sheet{ max-width:none; width:min(var(--grid), calc(100% - 2 * var(--frame-x))); border:1px solid var(--ink); }
.js.fx-screens .sheet{ height:calc(100% - var(--nav-top) - var(--frame-gap)); }
.js.fx-screens .screen{ padding:var(--gut-y) var(--gut) calc(var(--foot) + 18px); }   /* the copy stays clear of the title block */
.mark svg{ overflow:visible; }   /* 4B's start positions may lie outside the square's box */
/* the title block */
.foot{ display:none; }
.js.fx-screens .foot{ display:flex; position:absolute; left:0; right:0; bottom:0; z-index:2; height:var(--foot); border-top:1px solid var(--hair);
  padding:0 var(--gut); align-items:center; justify-content:space-between; background:rgba(221,227,232,.85);
  font:500 11px/1 var(--mono); letter-spacing:.08em; text-transform:uppercase; color:var(--ink-2); white-space:nowrap; }
.js.fx-screens .foot .scr-ind{ display:inline; position:static; right:auto; bottom:auto; font:inherit; letter-spacing:inherit; color:inherit; font-variant-numeric:tabular-nums; }
.js.fx-screens .foot .scr-ind:empty{ display:none; }   /* round 8B (P2): a one-screen section shows no count — the engine leaves the cell empty */
.foot .cell{ display:inline-flex; align-items:center; gap:6px; }   /* the SCREEN count */
/* ==== ROUND 8B (Bill, 2026-09-11, P3): THE SCROLL INDICATOR — a line the terminal prints under what it has shown (js/fx-screens.js
   builds it, one per screen, and says when it shows: while the section has more than one screen). In the prompt's face (css/nav.css
   .pr: Plex Mono 500, .04 em, the full ink — --n-ind-px, 13 on the desk, 12 on a phone), at the lower left, in the screen's bottom
   padding above the title block — so it takes no room from the frame; the frames table is round 8A's. The words -- SCROLL -- typed in
   turn, last on the screen; the direction is N6's pixel chevron (7 × 4, the tier's glyph since round 4B), up and/or down, which waits
   for the last character to land. N6 itself lives on: after --n-hint-after ms with no flip the chevrons pulse (--n-hint-pulse; glow,
   never a strobe), static under a reduced-motion preference the page honours; the session's first flip retires the pulse. */
:root{ --n-ind-px:13; }   /* the indicator's size: the prompt's (13 desk / 12 phone) */
.ind{ display:none; }
.js.fx-screens.fx-frames .screen.more > .ind{ display:flex; align-items:center; gap:6px; position:absolute; left:var(--gut); bottom:calc(var(--foot) + 3px); z-index:1;
  font:500 calc(var(--n-ind-px) * 1px)/1 var(--mono); letter-spacing:.04em; color:var(--ink); white-space:nowrap; padding:1px 0; }
.js.fx-screens.fx-frames .screen.tall > .ind{ display:none; }   /* a frame that scrolls within (one block taller than the frame — the projects table at 844 × 390) has no "below the content" to sit at; the scroll is its own cue */
.ind .hint{ display:none; flex:none; width:7px; height:4px; fill:currentColor; shape-rendering:crispEdges; }
.ind.up .hint.up{ display:block; transform:scaleY(-1); }
.ind.dn .hint.dn{ display:block; }
.ind:has(.ch):not(:has(.ch:last-child.on)) .hint{ visibility:hidden; }   /* the chevrons land with the last character (the type layer's spans; a line never split has none) */
html.hinting .screen.cur .ind .hint{ animation:hint-pulse calc(var(--n-hint-pulse) * 1ms) ease-in-out infinite alternate; }
@keyframes hint-pulse{ from{ opacity:.3; } to{ opacity:1; } }
@media (prefers-reduced-motion:reduce){ body:not(.fx-motion) .ind .hint{ animation:none; } }
/* ==== ROUND 16 (Bill, S20 §5 U4 / E1 — "replace [smooth scrolling] with our thematic '-- SCROLL --' scheme. We will refer to these as inner
   scroll… One universal speed for all inner scrolling"; §6 — "a small X/N list/screen indicator anchored to the right side (opposite --SCROLL--).
   This then becomes the pattern to follow for this type of UI"): INNER SCROLL'S LINE. js/pager.js builds one under every window that pages
   (print's list and tree, the ecosystem's record; the widget's stylesheet gives it its row): -- SCROLL -- with N6's chevrons at the left — the
   line above, the pager's own device, at a window's foot — and at the right THE PAGE OVER THE COUNT, 1/3, in the same face; shown only while
   the window has more than one page (the frame foot's own rule, P2). Typed in turn, last in the window's draw; the chevrons land with the
   last character. THE SPEED — one knob for the whole site, --i-step: ms per item a window's page draws in (the baud step when that is slower;
   0 = the baud step); each item wipes in over --line-ms (60 at the default) as a content screen's blocks do, with no collapse before it. */
:root{ --i-step:20; }   /* ms per item a window's page draws in — a content screen's blocks go at the baud step (8 at 120) after a 250 ms collapse, its lines at --cps; a seven-row page is on in ~200 ms here */
.pg-ind{ display:flex; align-items:center; gap:6px; min-height:18px; margin-top:4px; padding:1px 0; font:500 calc(var(--n-ind-px) * 1px)/1 var(--mono); letter-spacing:.04em; color:var(--ink); white-space:nowrap; }
.pg-ind[hidden]{ display:none; }
.pg-ind .hint{ display:none; flex:none; width:7px; height:4px; fill:currentColor; shape-rendering:crispEdges; }
.pg-ind.up .hint.up{ display:block; transform:scaleY(-1); }
.pg-ind.dn .hint.dn{ display:block; }
.pg-ind .n{ margin-left:auto; padding-left:12px; font-variant-numeric:tabular-nums; letter-spacing:.06em; }   /* the page over the count, at the right (item 0) */
.pg-ind:has(.ch):not(:has(.ch:last-child.on)) .hint{ visibility:hidden; }   /* the chevrons land with the last character (the type layer's spans; a line never split has none) */
.foot .title{ color:inherit; text-decoration:none; }   /* the title cell is the way home from any screen (the mark is on the thesis screen only) */
.foot .title:hover{ color:var(--ink); }
.foot .title:focus-visible{ outline:1px dashed var(--ink); outline-offset:3px; }
/* the masthead: the box dissolves into the frame; the band loses the box's padding so the mark stands at the copy's edge */
html:not([data-mastbox="1"]) header.mast{ border:0; background:transparent; }
html:not([data-mastbox="1"]) .mast .band{ padding:0; }
.mast .ident{ gap:var(--hero-gap); }
/* the tagline: the hero's second voice, under the whole ident, spanning the column */
.mast .tagline{ font:700 var(--tag-px)/1.1 var(--sans); letter-spacing:.14em; text-transform:uppercase; color:var(--ink); margin:22px 0 0; max-width:none; white-space:nowrap; }
/* the rule under COLLABOT in the pack's two colours (H2, the static half); the page-ink fork keeps it in ink */
html[data-ink="pack"] .mast h1 .word .rl{ fill:url(#rl-grad); }
/* the empty figure column dissolves; it comes back on any screen that carries a figure */
.doc-body:has(> .figcol:empty){ grid-template-columns:1fr; }
.doc-body:has(> .figcol:empty) .figcol{ display:none; }
.doc-body:has(> .figcol:empty) .maincol{ padding-left:0; }

@media (max-width:720px){   /* LM1: the phone — flexible, the frame inset, the hero at 1× */
  :root{ --gut:14px; --gut-y:16px; --frame-x:8px; --frame-gap:8px; --foot:26px; --hs:1; --hero-gap:16px; --h-apart:10; --n-ind-px:12; }   /* 4B: 5 cells each way = 15 px, inside the 14/16 px the mark has on a phone; 8B: the indicator at the phone prompt's 12 */
  .sheet{ margin:0 auto; }
  /* ROUND 6B (Bill, 2026-09-09, C1: "on mobile, the header is predominantly pushed to the left, leaving a lot of empty space on
     the right… Nothing radical, maybe something with the sub-header"): at 390 the ident is 270 px with 74 px of paper to its right,
     and the tagline was a 283-px mono caption under it, 62 px short of the column. The desk's own rule for the second line answers
     it (H4 above): THE TAGLINE IS THE DISPLAY LINE SET AT THE SIZE THAT SPANS THE COLUMN — 29.9 px spans 782 on the desk. The phone's
     column is fluid, so its size is the column's: 100cqw (the masthead is a size container) over the line's width per px of size
     (Archivo 700, .14em, 33 characters: 26.16, measured on the desk — 782.3 / 29.9; the 26.3 here leaves ~2 px). 13.1 px at 390,
     11.9 at 360, 13.9 at 412 — the sheet's own figtag face and size. The line runs edge to edge under the whole ident and the
     masthead is anchored on both sides; the ident itself does not move (the mark stays at the copy's left edge everywhere), and the
     desk and the landscape block are untouched. */
  header.mast{ container-type:inline-size; }
  :root{ --tag-px:calc(100cqw / 26.3); }
  .mast .tagline{ margin-top:8px; }
}
@media (min-width:721px) and (max-height:520px){   /* a phone on its side: the desk's width, a short monitor — the hero at 1×, the vertical rhythm tightened */
  :root{ --gut-y:12px; --frame-gap:8px; --foot:26px; --hs:1; --hero-gap:24px; --tag-px:15px; }
  .mast .tagline{ margin-top:4px; }
  #thesis .doc-body{ margin-top:12px; }   /* measured at 844×390: the thesis needs 256 of 254 with 6/14 — these take it to one frame */
  .js.fx-screens .screen{ padding-bottom:calc(var(--foot) + 10px); }
  .js.fx-screens.fx-frames .screen.more > .ind{ bottom:calc(var(--foot) - 5px); }   /* round 8B (P3): the tighter padding here — the indicator's top meets the content's end on a full frame, never crosses it */
}
