:root{ --ground:#ECEDEE; --ink:#000; --head:#333; --quiet:#666; --rule:#DEDEDE;
       --soft:30px;   /* the cover's ground blur */
       --sharp:1px;   /* the cover's facet blur - the sharpness lives here */
       --field-alpha:.8; /* Peter, 2026-08-24: gradient density to 80% */
       --field-fill:.45; /* THE INPUT'S OPACITY - the dial being walked */ }
*{box-sizing:border-box}
body{margin:0;background:var(--ground);color:var(--ink);
     font:400 12.5px/1.5 Roboto,Helvetica,Arial,sans-serif}

/* THE FIELD - export_report._cover_field(), seed 631, verbatim. Pinned to the
   window, never travels; scroll turns it, time makes it shimmer. */
.field{position:fixed; inset:0; z-index:-1; overflow:hidden; pointer-events:none;
  opacity:var(--field-alpha);
  -webkit-mask-image:linear-gradient(to bottom,#000 0%,#000 52%,transparent 96%);
          mask-image:linear-gradient(to bottom,#000 0%,#000 52%,transparent 96%);
  animation:turn 1s linear both; animation-timeline:scroll(root block)}
@keyframes turn{ from{transform:rotate(-5deg) scale(1.22)}
                 to  {transform:rotate( 5deg) scale(1.40)} }

.bg{position:absolute; inset:-90px; will-change:transform}
/* the soft ground, deeply blurred - the airbrush the cover sits on */
.bg1{filter:blur(var(--soft)); animation:d1 61s ease-in-out infinite alternate}
/* the facets and seams, barely softened - the hard 0.5deg wedges and the
   0.3%-wide white hairlines. Counter-drifting against the ground: the shimmer
   is these edges sweeping across it, not either layer moving fast. */
.bg2{filter:blur(var(--sharp)); animation:d2 43s ease-in-out infinite alternate}
@keyframes d1{from{transform:translate3d(-1.5%,-1%,0) scale(1.02)}
              to  {transform:translate3d( 2%, 1.5%,0) scale(1.06)}}
@keyframes d2{from{transform:translate3d( 1.6%, 1%,0) rotate(-.8deg)}
              to  {transform:translate3d(-2.2%,-1.4%,0) rotate( .9deg)}}

.ray{position:absolute; will-change:opacity}
.ray:nth-child(1){animation:g 17s ease-in-out infinite alternate}
.ray:nth-child(2){animation:g 23s 1.4s ease-in-out infinite alternate}
.ray:nth-child(3){animation:g 29s 3.1s ease-in-out infinite alternate}
.ray:nth-child(4){animation:g 19s 2.2s ease-in-out infinite alternate}
.ray:nth-child(5){animation:g 31s 5.0s ease-in-out infinite alternate}
.ray:nth-child(6){animation:g 26s 0.7s ease-in-out infinite alternate}
@keyframes g{from{opacity:.30}to{opacity:1}}

@media (prefers-reduced-motion:reduce){ .field,.bg,.ray{animation:none!important} }
@supports not (animation-timeline:scroll()){ .field{transform:rotate(-2deg) scale(1.3)} }

/* ── THE DISCLOSURES ────────────────────────────────────────────────────────
   Native <details>. No JavaScript, keyboard-operable and screen-reader-correct
   for free, and the page keeps its promise of no script. The smooth open is a
   progressive enhancement: ::details-content + interpolate-size is Chrome-only
   today, so everywhere else opens instantly, which costs nothing. */
/* THE PRISMATIC RULE - lite_render's echo rule, verbatim: the six deep tints
   across 90deg at 2px. Peter's note in that file: 1px muddies a gradient to grey.
   It replaces the tinted panel; the sections stand on the field itself. */
details{border:0; margin:0 0 4px; background:transparent; border-radius:0}
/* THE FOLD RULES ARE BLACK (Peter, 2026-08-26). The spectrum now belongs to
   ONE place on the page - the foot - where lite_render puts it and where it
   closes the reading. Six of them stacked down a column spent the thing that
   makes it mean something. The height stays 2px: the note that set it says
   1px muddies a GRADIENT to grey, which is a reason black does not have, but
   he named a colour and nothing else.

   1px NOW (2026-08-26, on seeing it). Black at 2px was a bar rather than a
   rule; the gradient needed the second pixel and black never did. */
/* THE RULE BELONGS TO THE SUMMARY NOW (Peter, 2026-08-26): "you should be able
   to click on any part of that rule or text and have the accordion open."
   A pseudo-element cannot take a click, and on `details` the rule sat OUTSIDE
   the summary's box - so the only target was the words. Moved onto the summary
   itself, it is inside the one element the browser already treats as the
   control, and the whole band from rule to plus is live. */
summary::before{content:""; display:block; height:1px; margin:0 0 8px;
  background:#030303}
details + details{margin-top:0}
summary{list-style:none; cursor:pointer; display:block;
  justify-content:space-between; gap:16px; padding:0 0 4px;
  font:400 16.5px/1.35 Roboto,Helvetica,Arial,sans-serif; color:#030303}   /* Peter, 2026-09-10 10:48 AM: '400 for all' (superseding the 2:50 AM 500) */
summary::-webkit-details-marker{display:none}
summary:focus-visible{outline:2px solid #333; outline-offset:3px}
/* the mark: a hairline cross that loses its upright when the section opens */
/* the old two-bar mark - an 11px box with two absolutely positioned strokes -
   is superseded by the SVG cross inside a .pmark. It had to GO rather than be
   overridden: `summary .mk` at (0,1,1) was still beating `.pmark` at (0,1,0),
   so the circle was rendering at 11px with the glyph centred inside it. */
details > *:not(summary){padding:0 0 18px}
/* the panels read in the page's own paragraph voice - same size, same weight,
   same leading as the lede. Only the summary's 500 separates head from body. */
details p{font:300 16px/1.5 Roboto,Helvetica,Arial,sans-serif;color:#030303}
/* THE ANIMATED FOLD - restored and staying, 2026-08-30 11:05 PM.

   IT WORKS. Peter checked Chrome 151.0.7922.174 - the same build installed on
   this machine - and Chromium 139 before it. Both headed, both fine.

   I REMOVED IT TWICE ON A BAD MEASUREMENT, and the record is here so no one
   repeats it. Every "the fold opens to 57px and stops" I produced came from
   HEADLESS Chrome, which does not run these transitions the way a real window
   does: `content-visibility` with `transition-behavior:allow-discrete` needs a
   compositor that headless is not giving it. The numbers were real; the
   instrument was wrong.

   THE RULE THIS COST: headless is not a valid medium for testing transition or
   animation behaviour. Layout and geometry it measures faithfully - the ghost
   overrun, the grid, the shared axis were all found and proven there. Motion it
   cannot judge, and a headless green or red about motion means nothing.

   Safari is unaffected either way: it does not support interpolate-size, so this
   @supports guard skips the block and <details> opens natively. Correct as
   written from the beginning. */
@supports (interpolate-size: allow-keywords){
  :root{interpolate-size:allow-keywords}
  details::details-content{
    block-size:0; overflow:hidden;
    transition:block-size .34s ease, content-visibility .34s allow-discrete;
    transition-behavior:allow-discrete}
  details[open]::details-content{block-size:auto}
}

main{max-width:672px; margin:0 auto; padding:25px 24px 24px}
/* the site's .eyebrow: Roboto sans 13/400/1.35, letter-spacing 0.2em, uppercase -
   the same move the report cover makes with THE CREATIVE PRISM REPORT. */
.eyebrow{font:400 11px/1.35 Roboto,Helvetica,Arial,sans-serif; letter-spacing:.2em;
  text-transform:uppercase; color:#030303; margin:0 0 14px}
/* ONE SIZE (Peter, 2026-08-24): every display voice at 16. The hierarchy is
   weight and family alone - slab 700 for the headline, sans 300 for the lede,
   sans 500 for every section head. Nothing on this page is bigger than
   anything else. */
h1{font:400 32px/1.2 Roboto,Helvetica,Arial,sans-serif; margin:0 0 -2px; color:#030303}
h2{font:250 16.5px/1.35 Roboto,Helvetica,Arial,sans-serif; color:#030303; margin:44px 0 12px}
h1 + h2{margin-top:0}   /* the subhead is the headline's second line, not a section head */
/* THE MASTHEAD LOCKUP, RULED (Peter, 2026-09-12 12:08 → 12:39 AM, "Perfect. Apply to
   Lab door too… type applies across both, hence made simpler with a unified CSS"):
   EDITION on the masthead's line after the ring; the masthead 2px above the headline's
   ascenders; a line space after the headline; the subhead Roboto 400 at 18 with no space
   beneath it; 60px at the top; the folds 16. Six comps on his eyes (lite_lockup_01–06);
   the lock amended. Scoped to the door's own subhead - `h1 + h2` - so the wing's h2s on
   the full door are untouched. */
h1 + h2{font-weight:400; font-size:18px; margin-bottom:0}
/* 2px TO THE ASCENDER. Measured: the headline's ink begins 6.2px below its line box's
   top (Roboto 400 at 32/1.2 - a 30px font ascent against a 24px ink ascent, plus 3.2px
   of half-leading), so the box overlaps the masthead by 4.2px and the ink sits 2px
   beneath it. `main .pmasthead` outranks the masthead's own `margin:0 0 5px`. */
main .pmasthead{margin-bottom:-4.2px}
h1{margin-bottom:22px}
h1 + h2{margin-top:0}
/* THE TOP: 60px at every width ("a little bit more breathing room… the patterns behind
   the prism itself are quite pretty, and the transparent design lets it show"; the phone
   may give some back on his word). `body main` outranks the breakpoints' padding
   shorthands. THE FOLDS: 16 (was 16.5). */
body main{padding-top:60px}
main summary{font-size:16px}
.lede{font:300 14.5px/1.4 'Roboto Slab',Georgia,serif; margin:0 0 15px;
  color:#030303}
p{margin:0 0 12px; font:300 14px/1.5 Roboto,Helvetica,Arial,sans-serif}
/* one full line of the body's own leading, between the button and what follows */
/* the instruction lives INSIDE the highlight now (Peter's markup, 2026-08-24 -
   the arrow pointed from the line into the block, and I had only taken the
   half-line half of that note). It heads the block rather than floating above
   it, so the shape carries the whole thought: what to write, then what that
   looks like. */
/* SPECIFICITY: this is a <p> inside blockquote.examples, so `blockquote.examples p`
   (0,1,2) was beating `.exhead-line` (0,1,0) and setting it in Roboto Slab 300/16
   like an example. Qualified here so it wins on its own terms. */
/* the instruction sits with the BOX it instructs, not inside the examples -
   it tells you what to write, so it belongs where you write. */
.writenote{font:700 14px/1.5 Roboto,Helvetica,Arial,sans-serif; color:#030303;
  margin:0 0 10px}
/* EXAMPLES: heads the block in the page's eyebrow voice, black like the four
   voices beneath it, sitting on the block's one left edge. */
blockquote.examples p.exlabel{font:400 11px/1.35 Roboto,Helvetica,Arial,sans-serif;
  letter-spacing:.2em; text-transform:uppercase; color:#030303;
  margin:15px 0 -5px; padding-left:17px}
/* CENTRED IN THE SPAN, WITHOUT DEEPENING IT (Peter, 2026-08-24). Measured: the
   run from the highlight's top edge to the quote's ink is 82.2px, and the
   headline was sitting 14.6px above its midpoint. Moving it down 15 and taking
   the same 15 back off the bottom margin (10 -> -5) leaves the flow height
   EXACTLY as it was - the block does not grow by a pixel - while the line lands
   on the middle. The negative margin is the point, not an accident. */   /* the rule's own inset, so the block
     keeps ONE left edge: headline, quote mark, label and copy all at 17px */
/* the offer, as the second beat under the headline - the site's h3 voice */
.freeline{font:500 18px/1.35 Roboto,Helvetica,Arial,sans-serif; color:#030303;
  margin:0 0 18px}
/* the privacy line rides the end of the lede rather than standing as its own
   paragraph - a reassurance in passing, not a notice. Both claims are the
   privacy page's in substance: the writing is not kept after the reading is
   made, and the provider does not train on API data by default. */
.privnote{color:#3A3A3A}
.privnote a{color:#3A3A3A; text-decoration:none; border-bottom:1px solid rgba(3,3,3,.35)}
.privnote a:hover{border-bottom-color:#030303}
/* ── THE HIGHLIGHT SHAPE ─────────────────────────────────────────────────────
   The printed report's own prompt treatment, ported: the quoted matter sits on a
   white ramp with the two LEFT corners at 10px and the right square where the
   shape bleeds away - `export_report`'s `.phl`, whose numbers these are exactly.
   The ramp runs 70% white to ZERO over 465px, which is the report's RUN constant,
   "the ramp completes by ~3/4 of the measure". (Peter, 2026-08-24, late: the end
   stop was 10% and is now 0 - the highlight fades to nothing rather than to a
   faint standing tint. Changed in export_report.py at the same time.)

   THE RULE IS BACK. In the report it returned 2026-08-21 and retired again on
   08-22, so this is a deliberate divergence for the web, not a stale copy: 1px
   solid black, standing 16px inside the highlight's edge, white on both sides,
   never touching the rounded corner (the report's own description of it).

   The bleed is clipped at main's right edge by the foreground's `contain:paint`.
   That costs nothing here: the ramp has already reached ZERO by 465px and the
   block is 1040 wide, so everything past that point is empty anyway. Keeping the
   containment matters more - it is what stops the type flashing. */
/* OPTICALLY even, not numerically even (Peter, 2026-08-24). Measured on the
   rendered page: at 30/30 padding the space above the quote's INK was 65.2px and
   the space below the last baseline was 37.3px - 28px out. The mark is the
   reason: its box is 22px tall but sits 21.7px down on the baseline grid, so its
   ink begins ~35px below the block's edge and the padding above is paying for
   space nobody can see. Taking 14 off the top and adding 14 to the bottom lands
   both at ~51px. The numbers look lopsided BECAUSE the result is balanced. */
blockquote.examples{position:relative; margin:0 0 4px; padding:8px 0 16px}  /* folded above the box (Peter, 2026-08-29); the 3-lede-line seam belonged to its old seat in the foot */
blockquote.examples::before{content:""; position:absolute; z-index:-1;
  left:0; right:-200px; top:0; bottom:0; border-radius:10px 0 0 10px;
  background:linear-gradient(to right, rgba(255,255,255,.7) 0,
                                       rgba(255,255,255,0) 465px)}
/* THE RULE MEASURES THE COPY, NOT THE BLOCK (Peter, 2026-08-24). It used to hang
   63px above the first line because it spanned the head row as well. The rule now
   belongs to `.excopy`, which holds only the four examples, so its height IS the
   copy's height. The head keeps the same left edge by carrying the rule's own
   width in padding (1px + 16px), so the quote mark stays aligned with the text. */
.exinner{margin-left:16px}
.exhead{padding-left:17px}
.excopy{border-left:1px solid #000; padding-left:16px}
/* half a line BETWEEN each quote (Peter's markup, 2026-08-24): 10 -> 22px.
   The line here is the block's own 24px leading, so half of it is 12. */
/* each quote is a GROUP now: who is speaking, then what they said. The 22px
   sits between groups; the eyebrow sits tight to its own quote, so the pairing
   reads before the spacing does. */
blockquote.examples .ex{margin:0 0 21.2px}
blockquote.examples .ex:last-child{margin-bottom:0}
blockquote.examples .ex p{margin:0}
blockquote.examples p.exwho{font:400 11px/1.35 Roboto,Helvetica,Arial,sans-serif;
  letter-spacing:.2em; text-transform:uppercase; color:#030303; margin:0 0 6px}
  /* BLACK, not grey (Peter caught it, 2026-08-24). #8A8A8A was mine, for tonal
     separation, and it broke the manual's "type is black or white only". The
     separation is already carried by size, tracking and case - it did not need
     colour doing the same job a third time. */

/* the mark IS the opener - the report's law. Playfair Display Black, the one
   place that face appears, at the system's standing 3.58x type ratio (43px on
   12px type there, 50px on 14px here). Literal UTF-8, never an entity.
   IT SITS INSIDE THE RULE, ABOVE THE TEXT - export_report.py:3582 nests it in
   `.ptext` within `.pinner`, so the mark is inset with the words it opens, not
   standing above the line. Corrected 2026-08-24 on Peter's note. */
/* the mark and its label share a line (Peter, 2026-08-24). The mark keeps its
   cropped box - .44em with the ink overflowing - so the label can sit beside it
   without the row inheriting a 50px line. The label takes the page's own eyebrow
   voice at the report label's quieter #333. */
/* BASELINE, not centre (Peter, 2026-08-24). The label was optically nudged
   against the mark's box; it now sits on the mark's actual BASELINE, which
   the browser computes from the font rather than from my guess - and which
   drops it lower, where a caption beside a quote belongs. */
/* the mark's ink sits 22.4px below its box, so a 14px margin left the eyebrow's
   caps 7.1px ABOVE the quote's ink - they overlapped. 36px puts a real 16px of
   air between them, measured on the ink rather than on the boxes. */
.exhead{display:flex; align-items:baseline; gap:14px; margin:0 0 36px}
/* ON THE BASELINE GRID (Peter's markup, 2026-08-24). Measured, not nudged:
   Playfair Display 700's opening quote at 50px puts its ink between 36.3px and
   22.4px ABOVE the baseline - the glyph never touches it, which is why aligning
   baselines still left the mark floating. Dropping it 22.4px (.448em) sets the
   ink's bottom ON the baseline, where the label's caps already sit. The two now
   share a line rather than a metric.
   In em so it survives a size change: 22.4 / 50 = .448em.
   WEIGHT 700, not 900 (Peter, 2026-08-24): measured at 50px the two differ by
   0.88px of ink height and 2.36px of width - findable if you look, invisible
   otherwise - so the mark comes inside comitini.com's max-700 rule instead of
   standing as an exception to it. The offset moved WITH the weight: different
   ink, different baseline. */
.qmark{display:block; font:700 50px/1 'Playfair Display',Georgia,serif; color:#000;
  height:.44em; overflow:visible; margin:0; position:relative; top:.448em}
.exlabel{font:400 11px/1.35 Roboto,Helvetica,Arial,sans-serif; letter-spacing:.2em;
  text-transform:uppercase; color:#333}
/* ROBOTO SLAB, ROMAN (Peter, 2026-08-24, with his fallback). Verified against
   the Google Fonts API rather than assumed: `Roboto Slab:ital` returns "400:
   Font family not found" - the family has no italic at any weight. Asking for
   one anyway would make the browser SYNTHESISE an oblique by slanting the roman,
   which is a fake italic and reads as one, so `font-style:normal` is explicit
   and `font-synthesis:none` makes sure nothing is ever faked here. */
blockquote.examples p{font:300 16px/1.4 'Roboto Slab',Georgia,serif; font-style:normal;
  font-synthesis:none; color:#2A2A2A}
/* the field lightens with it: no box, a rule under the writing line and the
   faintest veil so the field still reads as a target over the prism. */
textarea{width:100%;max-width:100%;min-height:15.5rem;padding:14px 2px;
  font:300 16px/1.35 Roboto,Helvetica,Arial,sans-serif;color:#030303;
  border:0;border-bottom:1px solid rgba(3,3,3,.28);
  border-radius:0;resize:vertical;outline:0}
textarea:focus{background:rgba(255,255,255,calc(var(--field-fill) + .12));border-bottom-color:rgba(3,3,3,.62)}
textarea::placeholder{color:rgba(3,3,3,.42)}
/* the waitlist takes the paste box's treatment - a rule, not a box - so the two
   places a person types on this page behave the same way. */
.waitlist{margin:4px 0 22px; max-width:24rem}
.waitlist input{width:100%; padding:10px 2px; border:0;
  border-bottom:1px solid rgba(3,3,3,.28); background:rgba(255,255,255,.28);
  font:400 16px/1.35 Roboto,Helvetica,Arial,sans-serif; color:#030303; outline:0}
.waitlist input:focus{background:rgba(255,255,255,.46);
  border-bottom-color:rgba(3,3,3,.62)}
.waitlist input::placeholder{color:rgba(3,3,3,.42)}
.waitlist button{margin-top:14px}
/* comitini.com "Outline on light" - design-styles.md, verbatim. Transparent, a
   hairline at 50% black, inverting to solid on hover. Label 12/500, 0.25em, caps. */
button{margin-top:14px;padding:13px 26px;border:1px solid rgba(3,3,3,.5);border-radius:0;
  background:transparent;color:#030303;
  font:500 12px/1 Roboto,Helvetica,Arial,sans-serif;
  letter-spacing:.25em;text-transform:uppercase;cursor:pointer;
  transition:background .2s ease,color .2s ease,border-color .2s ease}
button:hover{background:#030303;color:#ffffff;border-color:#030303}
/* THE END OF THE PAGE, as a Lite report ends it - lite_render's own foot: the
   spectrum rule, the one line, then the disclaimer over its hairline. (Peter,
   2026-09-11 8:29 PM: "This line in the footer is redundant and should be removed:
   'The Creative Prism - Your light refracted'" - the `.chrome` line beneath the
   cta is struck, with its rule.) */
footer{max-width:672px;margin:70px auto 0;padding:0 24px 70px}
footer .footrule{height:2px;margin:0 0 12px;
  background:linear-gradient(90deg,rgb(238,214,90),rgb(242,184,132),rgb(238,160,202),rgb(182,158,232),rgb(154,168,230),rgb(140,204,238))}
footer .cta{margin:16px 0 28px;padding:0}
footer .links{font:400 9.5px/1.6 Roboto,Helvetica,Arial,sans-serif;color:#666;margin-bottom:12px}
footer .links a{color:#666;margin-right:18px}
footer .disclaimer{font:italic 400 9px/1.4 Roboto,Helvetica,Arial,sans-serif;color:#666;
  border-top:1px solid #DEDEDE;padding-top:8px;margin:0}
.cta{font:500 14px/1.5 Roboto,Helvetica,Arial,sans-serif;color:#030303;
  margin:16px 0 0; padding:0 0 18px}

/* ── THE FRAME (Peter's option 2, 2026-08-24) ──────────────────────────────
   The measure stays where the manual puts it - roughly two alphabets, ~50
   characters - and the FRAME widens around it. Reading columns hold at 25em
   (about 50 characters at any size, since Roboto averages half an em per
   character); the prismatic rules, the section heads and the footer rules run
   the full width of the frame. The page then reads as deliberately narrow
   rather than accidentally narrow, and the field is seen beside the column
   instead of only behind it. */
/* PIXELS, NOT REM (2026-08-24). The frame was in rem while every type size on
   this page is in px - so a reader who has raised their browser's default font
   size got a proportionally WIDER frame carrying the same 16px type. At a 20px
   default, 76rem is 1520px. That mismatch is the "wide, and the type doesn't
   look proportioned" defect, and it is the family of bug design_decisions.md
   already flags as owed before launch. While the type is px-locked, the frame
   must be too, so every reader sees the same proportion. */
main,footer{max-width:672px}
/* THE MEASURE OPENS UP (Peter, 2026-08-24, looking at the page): the text runs
   the full frame, the same width as the accordion rules, instead of stopping at
   the ~50-character column. This deliberately overrides the design manual's
   "roughly two alphabets" rule - his eye on the artifact, over the written rule. */
p,.lede,details p,blockquote.examples p{max-width:none}
textarea{max-width:100%}   /* the wrapper carries the writing width now */
@media (min-width:1100px){
  main,footer{max-width:736px}      /* 736 frame, 688 content */
  main{padding-top:25px}
}

/* ── RESPONSIVE ─────────────────────────────────────────────────────────────
   Explicit breakpoints, no clamp() - comitini.com's locked rule 4: the designer
   has to know exactly what they will see at each breakpoint. */
main,footer{width:100%}
@media (min-width:1100px){
  main{padding-top:25px}
}
@media (max-width:768px){
  main{padding:25px 20px 24px}
  footer{padding:0 20px 56px}
  .eyebrow{font-size:12px; letter-spacing:.18em}
  textarea{min-height:12rem; font-size:16px}   /* 16 keeps iOS from zooming the page */
  ul.examples{padding-left:14px}
  summary{gap:12px}
}
@media (max-width:400px){
  main{padding:25px 16px 20px}
  footer{padding:0 16px 48px}
}

/* ── THE BEAMS ───────────────────────────────────────────────────────────────
   Car headlights through a window. The source is off-stage; what crosses the
   frame is the beam, and what you watch is it COMING APART as it goes.

   Each pass is six wavelengths that enter together and fan out on the way
   across - yellow bending least, cyan most, in the order a prism actually
   splits them. That ordering is the whole effect: a single white bar sweeping
   reads as a searchlight, six bars separating reads as refraction.

   Cheap on purpose, and this is the difference from the field behind the page:
   these are NARROW elements (9-17% of the frame) moving on transform alone.
   Fill-rate is a fraction of a full-viewport layer, and nothing here repaints.
   NOTE: unmeasured. The pane is hidden, which stops requestAnimationFrame, so
   no frame timing taken after comp 03 is worth anything - including mine. */
.sweeps{position:absolute; inset:-10%; overflow:hidden; pointer-events:none;
  mix-blend-mode:screen; opacity:.9}
.sweep{position:absolute; top:-40%; left:0; width:var(--w); height:180%;
  animation:pass var(--dur) cubic-bezier(.42,0,.58,1) var(--delay) infinite}
.sweep i{position:absolute; inset:0; display:block; border-radius:50%/6%;
  background:linear-gradient(90deg,transparent 0%,var(--c) 42%,var(--c) 58%,transparent 100%);
  filter:blur(7px); opacity:0;
  animation:fan var(--dur) cubic-bezier(.42,0,.58,1) var(--delay) infinite}
@keyframes pass{
  0%  {transform:rotate(var(--ang)) translate3d(-190%,0,0) scaleY(1)}
  50% {transform:rotate(var(--ang)) translate3d(120%,0,0)   scaleY(1.14)}
  100%{transform:rotate(var(--ang)) translate3d(430%,0,0)  scaleY(1)}
}
/* the fan: together on entry, apart on exit, and never both ends at once */
@keyframes fan{
  0%  {transform:translateX(0) rotate(0deg); opacity:0}
  14% {opacity:var(--peak)}
  55% {transform:translateX(var(--sep)) rotate(var(--tilt))}
  78% {opacity:calc(var(--peak) * .85)}
  100%{transform:translateX(calc(var(--sep) * 2.4)) rotate(calc(var(--tilt) * 2.1));
       opacity:0}
}
@media (prefers-reduced-motion:reduce){ .sweeps{display:none} }

/* ── TWO DIRECTIONS AT ONCE (Peter, 2026-08-24) ──────────────────────────────
   Seven beams instead of three, on short mutually prime cycles, so several are
   crossing at any moment - and they run on OPPOSING diagonals. The `up` family
   climbs to the right; the `down` family falls to the right on the mirrored
   angle and travels back the other way. Where two cross, their fans overlap and
   the colours add: that crossing is the thing worth watching for. */
.sweep.down{animation-name:passback}
@keyframes passback{
  0%  {transform:rotate(var(--ang)) translate3d(430%,0,0) scaleY(1)}
  50% {transform:rotate(var(--ang)) translate3d(120%,0,0) scaleY(1.16)}
  100%{transform:rotate(var(--ang)) translate3d(-190%,0,0) scaleY(1)}
}
.sweep i{filter:blur(6px)}

/* AND THE FIELD ITSELF MOVES MORE. The turn roughly doubles its arc, and the
   diagonal stretch from comp 16 comes back over it - pulled along the light's
   own axis, alternating so it reaches its extreme and retracts without a jump.
   (A scale above 1 only pushes edges outward, so no stretch can crop an edge -
   no extra overscan is needed for it.) */
@keyframes swing{
  from{transform:rotate(-11deg) scale(1.26) translate3d(-7%,-5%,0)}
  to  {transform:rotate( 12deg) scale(1.62) translate3d( 8%, 6%,0)}
}
@keyframes turn{
  from{transform:rotate(-11deg) scale(1.26)}
  to  {transform:rotate( 12deg) scale(1.62)}
}
.field > .bg, .field > .rays, .field > .ray{will-change:transform,opacity}
.bg1{animation:d1 41s ease-in-out infinite alternate !important}
.bg2{animation:d2 29s ease-in-out infinite alternate !important}
@keyframes d1{from{transform:translate3d(-4%,-3%,0) scale(1.03) rotate(-1.2deg)}
              to  {transform:translate3d( 6%, 4%,0) scale(1.12) rotate( 1.4deg)}}
@keyframes d2{from{transform:translate3d( 5%, 3%,0) rotate( 1.8deg) scale(1.02)}
              to  {transform:translate3d(-7%,-5%,0) rotate(-2.2deg) scale(1.10)}}

/* ── THE SCRIM (Peter, 2026-08-24) ───────────────────────────────────────────
   5% white laid IN FRONT of the field and BEHIND the type, so the reading holds
   across the motion. Deliberately a separate layer rather than a change to the
   field: the background is locked, and legibility is bought in front of it.
   One dial, --scrim. It is also the honest place to solve the busyness problem -
   raise it and the seams quiet behind the words without the beams losing any of
   their travel. */
.scrim{position:fixed; inset:0; z-index:-1; pointer-events:none;
  background:#fff; opacity:var(--scrim,.05)}

/* ── THE FOREGROUND IS ITS OWN LAYER ─────────────────────────────────────────
   The flashing was real, not an illusion. The field and the scrim sit at
   z-index -1, but the type and the input were in the SAME stacking context and
   the input was 28% opaque - so every beam that crossed underneath repainted
   through it, which is exactly what "a light bar passed over the front" looks
   like. Two fixes, both needed: give the foreground its own stacking context
   ABOVE the background, and promote the input to its own compositing layer so
   it stops being repainted by anything moving behind it. */
main,footer{position:relative; z-index:1}
.writebox{position:relative; isolation:isolate; transform:translateZ(0);
/* FULL WIDTH (Peter, 2026-08-24). The principle that actually applies is that
   a field's width should signal the LENGTH OF ANSWER expected - and this page
   asks for a paragraph, so full width is the honest affordance rather than a
   violation of anything. The waitlist stays short for the same reason in
   reverse: it wants one address, and a 1040px box would be asking for an essay. */
  border-radius:10px; width:100%}
textarea{background:rgba(255,255,255,var(--field-fill,.62));
  border-radius:10px; border:1px solid rgba(3,3,3,.14); border-bottom-color:rgba(3,3,3,.28);
  padding:14px 16px; transform:translateZ(0); backface-visibility:hidden}



/* ── THE ACTIVE RULE, v2 ─────────────────────────────────────────────────────
   CLIPPED TO THE BOX. The runner lives in a track the exact width of the field
   with overflow hidden, so it enters and leaves at the box's own edges instead
   of running past them. The track takes the box's bottom corners so the light
   never squares them off.

   IDLE is a short comet ping-ponging quickly - the machine breathing, waiting
   for you. WORKING is the same light let off the leash in one direction at an
   even pace. Both are transform-only on a 2px-tall element: about as cheap as
   an animation on this page gets.

   COLOUR CYCLING - the modern parallel to palette animation - is `hue-rotate`.
   Add the `cycle` class to see it. One filter property on one small element,
   done on the GPU: the colours turn while the light travels, which is the old
   trick with the palette held still and the ramp rotating underneath. */
.track{position:absolute; left:0; right:0; top:0; bottom:auto; height:2px; overflow:hidden;   /* Peter, 2026-09-11 1:53 AM: on the door page the shuttle moves to the TOP of the input box */
  border-radius:0 0 10px 10px; pointer-events:none}
.runner{position:absolute; left:0; top:0; height:2px; width:34%; border-radius:2px;
  opacity:0; transition:opacity .35s ease; will-change:transform;
  background:linear-gradient(90deg,transparent,
    rgb(238,160,202), rgb(182,158,232), rgb(140,204,238), transparent)}
.writebox.on .runner{opacity:1}

.writebox.idle    .runner{width:18%; animation:bounce 1s ease-in-out infinite alternate}  /* 1.5s -> 1s, +50% (Peter, 2026-08-27 2:23 AM) */
/* working's one-way sweep. THE DURATION IS A DIAL, `--wk` (2026-09-10 10:35 AM:
   "working seems slower - it was fine earlier"), so the sweep can be tuned on his
   eyes without a rebuild: `?wk=<ms>`, the lock's 2.6s by default. The `--spin`
   fallback it used to read is left alone - it still belongs to `.shot` and to the
   retired cycle-rides-working rule below. */
.writebox.working .runner{width:34%; animation:cross var(--wk,2.6s) linear infinite}
.writebox.writing .runner{width:30%}
/* a modifier, not a state: it rides whatever the light is already doing */
.writebox.cycle .runner{animation-name:cross,hue; animation-duration:var(--spin,2.6s),5s;
  animation-timing-function:linear,linear; animation-iteration-count:infinite,infinite}
@keyframes bounce{from{transform:translateX(0)} to{transform:translateX(455%)}}
@keyframes cross {from{transform:translateX(-100%)} to{transform:translateX(294%)}}
@keyframes pulse {from{opacity:.45} to{opacity:1}}
@keyframes hue   {to{filter:hue-rotate(360deg)}}
@media (prefers-reduced-motion:reduce){ .runner{animation:none!important; opacity:.5} }

/* ── THE WAVEFORM (writing) ──────────────────────────────────────────────────
   While text is arriving the rule stops being a travelling light and becomes a
   voice. The wave is a sine drawn once as an SVG, used as a MASK over the prism
   gradient - so the colour is the page's own spectrum and the shape is the only
   thing the SVG contributes. The pattern repeats every 120px and the element is
   translated exactly 120px, so the scroll is seamless with no redraw: transform
   only, like everything else here.

   Amplitude rides a second, slower clock (scaleY 0.55 -> 1.15), which is what
   makes it read as a voice rather than a test pattern - a steady sine is a
   machine, a modulated one is something speaking. */
.wave{position:absolute; left:0; top:0; height:18px; width:200%; opacity:0;
  transition:opacity .35s ease; will-change:transform;
  background:linear-gradient(90deg,
    rgb(238,214,90), rgb(238,160,202), rgb(182,158,232), rgb(140,204,238),
    rgb(238,160,202), rgb(238,214,90));
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27120%27%20height%3D%2718%27%20viewBox%3D%270%200%20120%2018%27%3E%3Cpath%20d%3D%27M0%209%20Q%2015%201%2030%209%20T%2060%209%20T%2090%209%20T%20120%209%27%20fill%3D%27none%27%20stroke%3D%27%23000%27%20stroke-width%3D%272%27%20stroke-linecap%3D%27round%27%2F%3E%3C%2Fsvg%3E"); mask-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27120%27%20height%3D%2718%27%20viewBox%3D%270%200%20120%2018%27%3E%3Cpath%20d%3D%27M0%209%20Q%2015%201%2030%209%20T%2060%209%20T%2090%209%20T%20120%209%27%20fill%3D%27none%27%20stroke%3D%27%23000%27%20stroke-width%3D%272%27%20stroke-linecap%3D%27round%27%2F%3E%3C%2Fsvg%3E");
  -webkit-mask-repeat:repeat-x;  mask-repeat:repeat-x;
  -webkit-mask-size:120px 18px;  mask-size:120px 18px}
.writebox.writing .track{height:18px; bottom:-24px; border-radius:2px}
/* the comet stands down - and its animation has to go with it, or the
   pulse keyframes keep overriding the opacity declaration */
.writebox.writing .runner{opacity:0; animation:none}
.writebox.writing .wave{opacity:1;
  animation:scroll 2.4s linear infinite, amp 1.7s ease-in-out infinite alternate}
@keyframes scroll{from{transform:translateX(0)} to{transform:translateX(-120px)}}
@keyframes amp{from{transform:scaleY(.55)} to{transform:scaleY(1.15)}}
@media (prefers-reduced-motion:reduce){ .wave{animation:none!important} }

/* ── THE FLASHING FIX ────────────────────────────────────────────────────────
   `mix-blend-mode:screen` on .sweeps has to READ ITS BACKDROP every frame. With
   the field un-isolated that backdrop was the whole page beneath it, so the
   browser was rasterising the entire stack - ground, facets, rays - then
   blurring 42 bars, then blending, once per frame at DPR 2. That is a full
   repaint pretending to be a composite, and the flash is it failing to keep up.

   `isolation:isolate` closes the group at the field's own edge: the beams still
   screen against the field, which is the whole intent, but the backdrop stops
   being the document. The look does not change - the compositing boundary does.
   Nothing in BACKGROUND_LOCKED.md moves. */
.field{isolation:isolate; contain:paint}

/* ── THE UNDULATION (writing) — supersedes the single sine above ─────────────
   An oscillation is one sine on a conveyor: the pattern is fixed and only the
   belt moves. An undulation needs INTERFERENCE. So: three sine layers at
   mutually prime periods (47 / 71 / 101px), scrolling at different speeds and
   in OPPOSING directions, each breathing on its own clock. Their sum never
   repeats within any watching span and the crest wanders along the line instead
   of marching - a worm, not a wire. Same reasoning as the seven beams.

   TRANSLATE AND SCALE ARE SEPARATE PROPERTIES, deliberately. The first version
   drove both through `transform`, so the amplitude keyframes overwrote the
   scroll and the wave pulsed in place without ever travelling. `translate:` and
   `scale:` compose instead of colliding - that was a real bug, not a tuning.

   It sits centred ON the box's bottom edge, 7px either side, instead of
   floating 24px below where it crowded the button. */
.wave{height:14px !important; width:220% !important;
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27120%27%20height%3D%2714%27%20viewBox%3D%270%200%20120%2014%27%3E%3Cpath%20d%3D%27M0%207%20Q%2015%201%2030%207%20T%2060%207%20T%2090%207%20T%20120%207%27%20fill%3D%27none%27%20stroke%3D%27%23000%27%20stroke-width%3D%272.2%27%20stroke-linecap%3D%27round%27%2F%3E%3C%2Fsvg%3E") !important; mask-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27120%27%20height%3D%2714%27%20viewBox%3D%270%200%20120%2014%27%3E%3Cpath%20d%3D%27M0%207%20Q%2015%201%2030%207%20T%2060%207%20T%2090%207%20T%20120%207%27%20fill%3D%27none%27%20stroke%3D%27%23000%27%20stroke-width%3D%272.2%27%20stroke-linecap%3D%27round%27%2F%3E%3C%2Fsvg%3E") !important;
  -webkit-mask-size:var(--p) 14px !important; mask-size:var(--p) 14px !important;
  will-change:translate,scale}
.wave.w1{--p:47px}  .wave.w2{--p:71px}  .wave.w3{--p:101px}
.writebox.writing .track{height:14px !important; bottom:-7px !important;
  border-radius:2px; overflow:hidden}
.writebox.writing .wave{animation:none}
.writebox.writing .wave.w1{opacity:.95; animation:scr1 1.9s linear infinite,
                                                  amp2 1.3s ease-in-out infinite alternate}
.writebox.writing .wave.w2{opacity:.55; animation:scr2 2.9s linear infinite,
                                                  amp2 1.7s ease-in-out infinite alternate}
.writebox.writing .wave.w3{opacity:.38; animation:scr3 4.3s linear infinite,
                                                  amp2 2.3s ease-in-out infinite alternate}
@keyframes scr1{from{translate:0 0}     to{translate:-47px 0}}
@keyframes scr2{from{translate:-71px 0} to{translate:0 0}}
@keyframes scr3{from{translate:0 0}     to{translate:-101px 0}}
@keyframes amp2{from{scale:1 .5}        to{scale:1 1.25}}

/* ── THE FOREGROUND IS HARDENED (Peter, 2026-08-24) ──────────────────────────
   "They sometimes flash on and off in pieces, shift alpha." That is tile-level
   repaint, and it is exactly what it sounds like: the compositor rasterises the
   page in tiles, and any tile holding translucent foreground gets INVALIDATED
   every time the field moves underneath it. z-index alone does not prevent that
   - it orders paint, it does not decouple it.

   The fix is to give the foreground its OWN compositing layer, rasterised once
   and thereafter only composited. Then nothing behind it can invalidate it: the
   background can do whatever it likes and the type is untouched.

   `translateZ(0)` + `will-change:transform` forces the layer. `isolation` stops
   any blend group reaching into it. `contain:layout paint` tells the engine that
   nothing inside affects anything outside, so invalidation cannot cross the
   boundary in either direction. The card behind the reading is the belt to that
   pair of braces - opaque enough that the type never has to composite against
   moving light at all. Dial it with --card. */
main,footer{position:relative; z-index:2; isolation:isolate;
  transform:translateZ(0); will-change:transform; contain:layout paint;
  backface-visibility:hidden}
.writebox,textarea,.waitlist input,button{transform:translateZ(0);
  backface-visibility:hidden}
/* 10 points more transparent, Peter 2026-08-24: .72 -> .62. Affordable now
   only because the foreground sits on its own compositing layer - the fill
   was belt-and-braces against the tile repaint, and the promotion is the belt. */
textarea,.waitlist input{background:rgba(255,255,255,var(--field-fill))}

/* ── THE MOBILE BUDGET ───────────────────────────────────────────────────────
   Honest answer to "I don't know how this is going to work on mobile": as built,
   probably badly. 42 blurred bars inside a blend group at DPR 3 is a phone's
   whole frame budget. So the phone gets the same design at a smaller cost - the
   ground and the facets stay exactly as they are, four of the seven beams stand
   down, and the survivors lose their blur. The look holds; the fill-rate does
   not. Measure on a real device before trusting any of this. */
@media (max-width:768px){
  .sweep:nth-of-type(n+4){display:none}
  .sweep i{filter:none}
  .sweeps{opacity:.75}
}
@media (prefers-reduced-motion:reduce){
  .sweeps{display:none}
  .field{animation:none}
}

/* ── ONE AND A HALF (Peter, 2026-08-24) ─────────────────────────────────────
   Every indicator effect at 1.5x. It is a weight change, not a scale: each
   number below is the old one times 1.5, and the SVG is REDRAWN at the heavier
   stroke rather than stretched - a stretched 2.2px stroke goes soft, a redrawn
   3.3px one stays crisp. That is the difference between heavier and bigger.
     runner        2px -> 3px
     track (rest)  2px -> 3px
     wave height  14px -> 21px      wave stroke  2.2 -> 3.3
     track (write) 14px -> 21px     offset  -7px -> -10.5px, still centred */
.runner{height:3px !important}
.track{height:3px !important}
.wave{height:21px !important;
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27120%27%20height%3D%2721%27%20viewBox%3D%270%200%20120%2021%27%3E%3Cpath%20d%3D%27M0%2010.5%20Q%2015%201.5%2030%2010.5%20T%2060%2010.5%20T%2090%2010.5%20T%20120%2010.5%27%20fill%3D%27none%27%20stroke%3D%27%23000%27%20stroke-width%3D%273.3%27%20stroke-linecap%3D%27round%27%2F%3E%3C%2Fsvg%3E") !important; mask-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27120%27%20height%3D%2721%27%20viewBox%3D%270%200%20120%2021%27%3E%3Cpath%20d%3D%27M0%2010.5%20Q%2015%201.5%2030%2010.5%20T%2060%2010.5%20T%2090%2010.5%20T%20120%2010.5%27%20fill%3D%27none%27%20stroke%3D%27%23000%27%20stroke-width%3D%273.3%27%20stroke-linecap%3D%27round%27%2F%3E%3C%2Fsvg%3E") !important;
  -webkit-mask-size:var(--p) 21px !important; mask-size:var(--p) 21px !important}
.writebox.writing .track{height:21px !important; bottom:-10.5px !important}

/* ── THE STATUS LINE ─────────────────────────────────────────────────────────
   Peter, 2026-08-24: the label beside the buttons is the right place to tell a
   person what the system is doing, so it stops being scaffolding and becomes an
   element. It takes the eyebrow's voice - Roboto 11/400 at 0.2em, uppercase -
   so it reads as page furniture rather than as a message. The BUTTONS beside it
   remain temporary; in the product the state is set by the server, not clicked. */
.statusline{display:flex; align-items:baseline; justify-content:space-between;
  gap:16px; margin:16px 0 0; min-height:16px}
/* beside the button, in sentence case at normal tracking (Peter's markup): it is
   the system speaking to a person, not a label on a control - so it takes the
   body's own voice rather than the eyebrow's. */
.actionrow{display:flex; align-items:center; gap:20px; margin-top:8px; flex-wrap:wrap}
/* THE AIR BELOW THE INPUT GROUP - three lede lines, 72px (ruled 2026-08-29 for
   BOTH doors; his markup 2026-08-30 9:52 PM, "add space"). Lite never received
   it: measured, its action row's bottom and the first fold's top were the SAME
   pixel - 2909 and 2909, zero air - while the full door carried 72 through
   `.doorfoot{margin-top:72px}`. This is the parity gap of his 1:11 AM ruling,
   not a new value. The 8px above the row stays: that grouping is deliberate,
   the row belongs to the box. */
.actionrow{margin-bottom:72px}  /* grouped with the box (Peter, 2026-08-28) */
.actionrow button{margin-top:0}
.status{font:400 14px/1.35 Roboto,Helvetica,Arial,sans-serif; color:#333;
  display:inline-flex; align-items:center}

/* ── THE WAITLIST FORM ───────────────────────────────────────────────────────
   Name, email, and an optional phone. Each field is a rule rather than a box,
   the same treatment as the paste box, with the label above it so the optional
   one can say so without placeholder text (a placeholder disappears the moment
   someone types, which is exactly when they need to know what the field was).
   THE GOOGLE BUTTON IS INERT - it is here to judge the layout, nothing more.
   Wiring it is a real decision: see the note in chat. */
.waitlist{margin:6px 0 22px; max-width:26rem}
.gbtn{display:block; width:100%; margin:0 0 18px; text-align:center;
  opacity:.55; cursor:not-allowed}
.orline{display:flex; align-items:center; gap:12px; margin:0 0 18px;
  font:400 11px/1.35 Roboto,Helvetica,Arial,sans-serif; letter-spacing:.2em;
  text-transform:uppercase; color:#888}
.orline::before,.orline::after{content:""; flex:1; height:1px; background:var(--rule)}
.fld{display:block; margin:0 0 16px}
.fld .lbl{display:block; font:400 11px/1.35 Roboto,Helvetica,Arial,sans-serif;
  letter-spacing:.2em; text-transform:uppercase; color:#666; margin:0 0 4px}
.fld .lbl em{font-style:normal; letter-spacing:.12em; color:#999}
.waitlist input{width:100%; padding:8px 2px; border:0;
  border-bottom:1px solid rgba(3,3,3,.28);
  font:400 16px/1.35 Roboto,Helvetica,Arial,sans-serif; color:#030303; outline:0}
.waitlist input:focus{background:rgba(255,255,255,calc(var(--field-fill) + .12));
  border-bottom-color:rgba(3,3,3,.62)}
.waitlist button[type=submit]{margin-top:6px}

/* ── THE READOUT SPEAKS (Peter, 2026-08-24) ──────────────────────────────────
   The status line is where the system gets to be a person for a second, so it
   says what it is doing in the register the trouble register already uses in
   `runner.py` - percolating, ticking, moving. These are Lite's own, and they are
   chosen to mean something rather than to fill time: "separating the light" is
   what a prism does, and "looking for what holds it up" is literally what the
   reading returns. Nobody has to be told what that means.

   NO SCRIPT. The words are stacked and cross-faded by staggered CSS animations -
   each holds for one slot of the cycle with a short fade either side. Opacity
   only, so it composites like everything else that works well on this page. */
/* THE BOX HAS TO HOLD A LINE (Peter, 2026-08-24). Every word is absolutely
   positioned, so this container collapsed to ZERO height and the words painted
   from an anchor with no line box - except the idle word, which was `static` and
   therefore the only one that sat right. That inconsistency is why the readout
   jumped between modes. One explicit line box, every span absolute inside it,
   and all four modes land on the same baseline. */
.words{position:relative; display:inline-block; min-width:15rem;
  height:1.35em; line-height:1.35}
.words span{position:absolute; left:0; top:0; line-height:1.35;
  white-space:nowrap; opacity:0}
.words.id span{opacity:1}

@keyframes cyc{
  0%{opacity:0} 1.5%{opacity:1} 12.8%{opacity:1} 14.3%{opacity:0} 100%{opacity:0}
}
.wk span{animation:cyc 21s linear infinite;
  animation-delay:calc(var(--i) * 3.000s)}
.wr span{animation:cyc 9s linear infinite;
  animation-delay:calc(var(--i) * 3.000s)}

/* which set is speaking is a function of the state, and nothing else */
/* the status lives inside .actionrow, which is the writebox's SIBLING - so the
   state has to reach it through that, not directly. (It read as a sibling of the
   status at first and every set resolved to display:none.) */
.status .words{display:none}
/* TENTH PASS: the readout left the action row for the show's third row, so the
   sibling selectors that reached it through `~ .actionrow` are gone; the descendant
   rules live with the caption row below. */
@media (prefers-reduced-motion:reduce){
  .words span{animation:none; opacity:0}
  .words span[style*="--i:0"]{opacity:1}
}

/* ── WIDOWS ──────────────────────────────────────────────────────────────────
   Width is a blunt instrument for widows - it moves every break and creates as
   many as it fixes. `text-wrap:pretty` is the tool that actually addresses them:
   the engine looks ahead over the last few lines and avoids leaving a lone word.
   `balance` does the same job for short display lines, evening them instead.
   Both are already used in the printed report (`export_report` sets pretty on
   the pull quote and balance on the prompt), so this is the house technique, not
   a new one. Verified supported in this engine before use. */
p,.lede,blockquote.examples p,.exinner p,details p,footer .disclaimer{text-wrap:pretty}
h1,h1 + h2,summary,.exlabel{text-wrap:balance}

/* CYCLE RUNS HOT (Peter, 2026-08-24): four times working's pace - 2.6s -> 0.65s.
   Placed last on purpose: the button applies `working cycle` together and both
   selectors carry the same specificity, so SOURCE ORDER is what decides which
   --spin wins. The hue comes down with it (5s -> 2s) so the colour turn stays
   related to the travel instead of drifting free of it. */
.writebox.cycle{--spin:.65s}
.writebox.cycle .runner{animation-duration:var(--spin),2s}

/* ── THE FIFTH PHRASE: THE SHOT (Peter, 2026-08-24) ──────────────────────────
   One pass, left to right, at cycle's pace (.65s), fired ONCE and gone. The
   other four are states - they describe a condition the system is in and they
   loop. This one is an EVENT: it says a thing just happened and then it stops
   existing. That difference is the grammar's, not the CSS's, and it is why it
   gets `iteration-count:1` and `fill-mode:both` rather than `infinite`.

   Deliberately unassigned. It confirms something - an upload landing, a choice
   taken, a save - and which of those is a decision for when the moment turns up.
   Building the vocabulary before the sentence is the point.

   In the product it fires by adding a class and removing it on `animationend`;
   the comp's SHOT button does exactly that so it can be fired repeatedly. */
.writebox.shot .runner{width:34%; opacity:1;
  animation:cross var(--shot,.65s) linear 1 both}


/* ── THE BUTTON REFRACTS (Peter, 2026-08-24) ─────────────────────────────────
   NO FILL. A full gradient face was out of character - the brand is a black rule
   and a travelling light, not a coloured surface. So the button says the same
   thing the readout says, in the same words: the rule doubles in weight and a
   rainbow comet runs around it.

     REST      1px solid black. The original weight, nothing else.
     HOVER     the rule doubles to 2px and the comet circles, 1.4s a lap.
     CLICK     the comet takes ONE fast lap at the grammar's .65s and stops.

   The comet is a conic gradient turned by an @property angle and masked to a
   RING - `mask-composite:exclude` cuts the middle out, so what remains is a
   2px band on the edge. It is a pseudo-element, so the doubling costs no layout:
   the real border stays 1px and merely turns transparent underneath.

   It repaints while it turns, unlike the transform-only effects elsewhere. That
   is affordable HERE and nowhere else on the page: one small element, only
   during a hover or a click, never at rest. */
@property --a{syntax:"<angle>"; inherits:false; initial-value:0deg}
/* the corners ease to match the box above. The paste box is 10px on a ~330px
   height; the same RATIO on a 40px button would be a pixel and read as square,
   so it is matched by eye at 6px - the same family of curve, at the button's
   own scale. The ring inherits it, or it would cut square corners across a
   rounded edge. */
.actionrow button{position:relative; border:1px solid #030303; background:transparent;
  border-radius:6px;
  color:#030303; transition:border-color .18s ease, color .18s ease}
.actionrow button::before{content:""; position:absolute; inset:-1px; padding:2px;
  border-radius:inherit;
  pointer-events:none; opacity:0; transition:opacity .18s ease;
  background:conic-gradient(from var(--a), rgb(255,255,255) 0.0deg, rgb(238,214,90) 10.0deg, rgb(242,184,132) 30.0deg, rgb(238,160,202) 50.0deg, rgb(182,158,232) 70.0deg, rgb(154,168,230) 90.0deg, rgb(140,204,238) 110.0deg, rgb(255,255,255) 120.0deg, rgb(238,214,90) 130.0deg, rgb(242,184,132) 150.0deg, rgb(238,160,202) 170.0deg, rgb(182,158,232) 190.0deg, rgb(154,168,230) 210.0deg, rgb(140,204,238) 230.0deg, rgb(255,255,255) 240.0deg, rgb(238,214,90) 250.0deg, rgb(242,184,132) 270.0deg, rgb(238,160,202) 290.0deg, rgb(182,158,232) 310.0deg, rgb(154,168,230) 330.0deg, rgb(140,204,238) 350.0deg, rgb(255,255,255) 360deg);
  -webkit-mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite:xor; mask-composite:exclude}
.actionrow button:hover{border-color:transparent}
/* THREE comets, 120 apart, and the two tempos are the GRAMMAR's - hover runs at
   working's 2.6s, the click lap at cycle's 0.65s. Same 4x step the readout makes,
   so the button is not inventing a speed of its own. With three lights on the
   ring a comet passes any given corner every 0.87s at hover and every 0.22s on
   the click: the acceleration reads as three times more than the lap time says. */
.actionrow button:hover::before{opacity:1; animation:lap 3.6s linear infinite}
/* MOUSEDOWN SPEEDS IT UP (Peter, 2026-08-24). Held, the band runs at cycle's
   0.95s instead of 3.6s - the same ~4x step the readout makes.
   SLOWER THAN THE RUNNER'S OWN NUMBERS, on purpose: with THREE lights on the
   ring, a lap of 2.6s puts an event past any corner every 0.87s, which reads
   three times busier than the single runner at the same duration. The lap is
   what the CSS sets; the RATE is what the eye reads. 3.6 / 0.95 brings the felt
   tempo back toward working and cycle. It sits
   AFTER the hover rule on purpose: both selectors carry the same specificity, so
   source order is what decides which duration wins while the pointer is doing
   both things at once. */
.actionrow button:active{border-color:transparent}
.actionrow button:active::before{opacity:1; animation:lap .95s linear infinite}
.actionrow button:focus-visible{outline:none; border-color:transparent}
.actionrow button:focus-visible::before{opacity:1}
.actionrow button.fired{border-color:transparent}
.actionrow button.fired::before{opacity:1;
  animation:lap .85s linear 3, fadeout .85s linear 1 1.7s forwards}   /* the one answer (2026-08-27 3:25 PM); bloom retired with it */
/* the marks' variant (Peter, 2026-08-26: "goes to white, then off"): the pulse
   comes back to colour by design - right for the button's press, wrong for the
   answer's ending. This one rises and HOLDS, so the ringout fade happens entirely
   inside the white. */
@keyframes bloomhold{
  0%{filter:none}
  22%{filter:saturate(.15) brightness(2.6)}
  100%{filter:saturate(.15) brightness(2.6)}}
/* THE ACCELERANDO (Peter's question, 2026-08-26 midnight: "spins more quickly at
   the end and looks like it's blending into white before it fades"). Newton's
   disc - the recombination through SPEED: five turns over the same 4.2s on an
   ease-in curve, from a gentle start to ~0.4s a turn at the end, while bloomblend
   walks the band into held white (fully white at 80%, a breath before the fade)
   with a half-pixel blur smearing the stops at peak speed. An EXPERIMENT behind
   the rig's end chip; the locked steady form is untouched until he rules. */
/* TUNED ON PETER'S WORD (2026-08-27 12:36 AM: "go around twice before it spins
   to white; it starts out slow - it should go medium, to light speed"): two
   segments now. Segment one, 0-80% of the answer: exactly TWO turns at a steady
   medium cruise (linear - no creep at the start). Segment two, the last 20%:
   three more turns on a hard ease-in - the kick to light speed - while
   bloomblend holds the colours clean through the cruise, tints in the kick, and
   lands full white at 88%; the fade (ringout2) begins only after the white. */
/* THE PRISMATIC (Peter's name, 1:09 AM; its form re-ruled 1:12 AM: "not worth
   pursuing the spinning into whiteness - the colour on screen for longer,
   simply circling and fading out; it could fade to white"). Four laps of pure
   colour at the steady pace, then one breath of exit that whitens as it goes -
   no acceleration journey, no hold. Simpler, easy to understand. */
/* fadewhite retired with the one-answer ruling (2026-08-27 3:25 PM): pure
   colour fading to transparent is the exit, everywhere. */
@keyframes fadeout{
  0%{opacity:1}
  100%{opacity:0}}

/* SPENT: after a click the ring stands down and STAYS down while the pointer is
   still inside - a confirmation that keeps glowing is not a confirmation, it is
   a state. CSS cannot un-hover a hovered element, so the class is added on click
   and removed on `pointerleave`: the button re-arms only when the pointer leaves
   the shape and comes back. */
.actionrow button.spent{border-color:#030303}
.actionrow button.spent:hover{border-color:#030303}
.actionrow button.spent:hover::before,
.actionrow button.spent::before{opacity:0; animation:none}
@keyframes lap{to{--a:360deg}}
@media (prefers-reduced-motion:reduce){
  .actionrow button:hover::before,.actionrow button.fired::before{animation:none}
}
@media (prefers-reduced-motion:reduce){
  .actionrow button:hover{animation:none}
}

/* ══ THE PAGE TAKES THE FULL PRODUCT'S LANGUAGE (Peter, 2026-08-26) ═════════
   "Convert this into the look and feel of the full product page. We lose the
   refracted button. We add the circular up arrow for input, the notation about
   shift return." The two doors stop being cousins and become the same door.

   WHAT DOES NOT CHANGE: this page's own five phrases. "I actually want to move
   back to the set of patterns for this page and how they are working. I think I
   like them better than what we experimented with." So idle keeps its
   ping-ponging comet, working keeps its one-direction travel, writing keeps the
   waveform, and the session's re-ruling of 2026-08-25 does NOT come here.
   ────────────────────────────────────────────────────────────────────────── */

/* ── THE MARK — the full product's, verbatim ────────────────────────────────
   28px circle, 18px glyph, 2.3 stroke, a 3px ring present always and only
   transparent at rest. Three stages: the arrow alone, the grey ring on hover,
   the prismatic cycle on click. Placement is the box's lower right, which is
   where both references Peter cited put it. */
.entry{position:absolute; right:0; bottom:12px; z-index:4;
  width:28px; height:28px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  color:#666; cursor:pointer;   /* matched to the download's ink */
  transition:color .25s ease, border-color .2s ease}
.entry,.entry:hover,.entry:focus,.entry:active{
  /* the page styles every <button> with a border and a black hover fill; this
     is a mark, not a button, so both are named off explicitly */
  background:none !important; box-shadow:none !important;
  border:3px solid transparent !important; margin:0 !important; padding:0 !important}
.entry svg{display:block; width:18px; height:18px}
.entry svg *{stroke:currentColor; fill:none; stroke-width:2.3;
  stroke-linecap:round; stroke-linejoin:round}
.entry::before{content:""; position:absolute; inset:-3px; padding:3px;
  border-radius:inherit; pointer-events:none; opacity:0;
  transition:opacity .18s ease;
  background:conic-gradient(from var(--a), rgb(255,255,255) 0deg, rgb(238,214,90) 10deg, rgb(242,184,132) 30deg, rgb(238,160,202) 50deg, rgb(182,158,232) 70deg, rgb(154,168,230) 90deg, rgb(140,204,238) 110deg, rgb(255,255,255) 120deg, rgb(238,214,90) 130deg, rgb(242,184,132) 150deg, rgb(238,160,202) 170deg, rgb(182,158,232) 190deg, rgb(154,168,230) 210deg, rgb(140,204,238) 230deg, rgb(255,255,255) 240deg, rgb(238,214,90) 250deg, rgb(242,184,132) 270deg, rgb(238,160,202) 290deg, rgb(182,158,232) 310deg, rgb(154,168,230) 330deg, rgb(140,204,238) 350deg, rgb(255,255,255) 360deg);
  -webkit-mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite:xor; mask-composite:exclude}
.entry:hover{color:#030303; border-color:rgba(3,3,3,.38) !important}
.entry.answering{border-color:transparent !important}
/* THE ONE ANSWER, EVERYWHERE (Peter, 2026-08-27 3:25 PM, ruled on his video
   of the inconsistencies - superseding the 3:27 AM input-field-only scope):
   every mark answers as this one does - two laps of pure colour at .85s, the
   third lap is the fade, colour to transparent, gone at 2.55s. The
   download's busy circling (1.4s, steady) stays as the continuous mood; its
   completion fires this same answer. */
.entry.answering::before{opacity:1;
  animation:lap .85s linear 3, fadeout .85s linear 1 1.7s forwards}
textarea{padding-right:46px}

/* ── THE HINT ───────────────────────────────────────────────────────────────
   Only while someone is composing, and never on a phone - there is no Shift
   key to press. */
.hint{margin-left:auto; opacity:0; pointer-events:none;
  font:400 11px/1.35 Roboto,Helvetica,Arial,sans-serif; color:#666;
  transition:opacity .3s ease; white-space:nowrap}
.writebox.composing ~ .actionrow .hint{opacity:1}
@media (max-width:767px){ .hint{display:none} }

/* ── CYCLE IS THREE SHOTS AND A WELL (Peter, 2026-08-26) ────────────────────
   "Three rapid fire fast prism shots with a well for a second and then three
   more, so that it feels like a cycle - you feel the slightly off-weight
   cadence of the wheel as it turns."

   So cycle stops being fast-working and becomes what its name says: a wheel
   coming round. Three passes then a rest, on a 2.4s turn - and the numbers are
   deliberately UNEQUAL, which is the whole point. The passes quicken slightly
   (0.32s, 0.31s, 0.30s) and the gaps between them lengthen (0.06s, 0.10s), so
   the wheel catches and drags rather than ticking like a metronome. Even
   numbers would read as a machine; these read as a thing with weight in it.

   THE WELL WAS HALVED ON 2026-08-26 (1.31s -> 0.66s, the turn 2.4s -> 1.75s)
   and IS RULED BACK, 2026-09-10 2:43 AM ("yes, 2.4"). The turn is the ORIGINAL
   2.4s again: three passes of 0.32 / 0.31 / 0.30s with gaps of 0.06 and 0.10s,
   then the full well of 1.31s. The passes and the gaps are UNCHANGED IN REAL
   TIME through both moves - the off-weight cadence lives in those, not in the
   rest - so only the percentages travel with the turn, and the wheel keeps its
   limp and gets its dwell back. The halved 1.75s is retired on this page.

   THE PASSES GO SLOWER, 2026-09-10 10:35 AM ("Prismatic rule: still moving too
   fast on cycle - the first rapid move"). The pass duration becomes a DIAL,
   `--cyc`, and the whole cadence is read off it: the three passes and the two
   gaps scale together by cyc/320, the 1.31s well does NOT move, and the turn is
   whatever those add up to. So the wheel's off-weight limp is preserved exactly -
   every interval keeps its ratio to every other - while the shots themselves slow
   down and the dwell stays the dwell it was ruled to be.

   THE DEFAULT IS 480ms, 1.5x the 08-26 passes: 0.48 / 0.465 / 0.45s with gaps of
   0.09 and 0.15s, then the 1.31s well - THE TURN 2.945s. (The contract's estimate
   of "about 3.1s" is its arithmetic; 1090 x 1.5 + 1310 = 2945 is the mechanism's
   own number, and the report names the difference. It is well inside the 4s the
   greeting holds either way.) `?cyc=320` puts the thirteenth pass's 2.4s turn back
   exactly - the same three passes, the same two gaps, the same well.

   THE PERCENTAGES ARE THE REAL TIMES OVER THE TURN. At the 480 default over
   2.945s: 0.48 -> 16.2988%, 0.57 -> 19.3548%, 1.035 -> 35.1443%, 1.185 ->
   40.2377%, 1.635 -> 55.5178%, then the well to 100%. The rig rewrites these from
   `--cyc` when the address carries one; the block below IS the default and is the
   number of record.

   Each pass is `shot`'s own travel - the vocabulary was already there. */
:root{
  --cyc:480ms;    /* the cycle's pass duration - `?cyc=<ms>` */
  --turn:2945ms;  /* 1090 x (cyc/320) + the 1.31s well - derived, never typed */
  --wk:2600ms;    /* working's one-way sweep - `?wk=<ms>`, the lock's 2.6s */
}
@keyframes trio{
  0%       {transform:translateX(-100%)}
  16.2988% {transform:translateX(294%)}
  19.3548% {transform:translateX(294%)}
  19.3648% {transform:translateX(-100%)}
  35.1443% {transform:translateX(294%)}
  40.2377% {transform:translateX(294%)}
  40.2477% {transform:translateX(-100%)}
  55.5178% {transform:translateX(294%)}
  100%     {transform:translateX(294%)}
}
.writebox.cycle .runner{width:34% !important;
  animation:trio var(--turn,2945ms) linear infinite,
            hue  var(--turn,2945ms) linear infinite !important}

/* ── THE STATE BUTTONS HIDE, AND KEEP THEIR SPACE ───────────────────────────
   `visibility`, not `display` - "more or less keep the space that they occupy,
   just hide them", so the status line does not jump when they come and go.
   The switch is a 7px dot at the row's own left edge: findable if you know it
   is there, invisible if you do not. */
.statusline{align-items:center}

/* ══ THE TWO DOCUMENT ACTIONS (Peter, 2026-08-26) ═══════════════════════════
   The same pair the session carries, and the same rule: being unavailable is
   their NORMAL condition. On Lite the upload belongs to the opening and the
   download to the reading that comes back, so for most of a run neither does
   anything - and a control that is dead most of the time has to look dead. */
.acts{display:inline-flex; align-items:center; margin-left:26px}
.act{border:0 !important; background:none !important; padding:0 !important;
  margin:0 !important; box-shadow:none !important; cursor:pointer;
  display:inline-flex; align-items:center; gap:6px;
  text-transform:none !important; letter-spacing:normal !important;
  font:400 11px/1.35 Roboto,Helvetica,Arial,sans-serif; color:#666;
  transition:color .25s ease}
.act:hover{color:#030303 !important}
.pmark{position:relative; display:inline-flex; align-items:center;
  justify-content:center; width:28px; height:28px; border-radius:50%;
  border:3px solid transparent; transition:border-color .2s ease; flex:0 0 auto}
.act:hover .pmark{border-color:rgba(3,3,3,.38)}
.pmark svg{display:block; width:18px; height:18px}
.pmark svg *{stroke:currentColor; fill:none; stroke-width:2.3;
  stroke-linecap:round; stroke-linejoin:round}
.pmark::before{content:""; position:absolute; inset:-3px; padding:3px;
  border-radius:inherit; pointer-events:none; opacity:0;
  transition:opacity .18s ease;
  background:conic-gradient(from var(--a), rgb(255,255,255) 0deg, rgb(238,214,90) 10deg, rgb(242,184,132) 30deg, rgb(238,160,202) 50deg, rgb(182,158,232) 70deg, rgb(154,168,230) 90deg, rgb(140,204,238) 110deg, rgb(255,255,255) 120deg, rgb(238,214,90) 130deg, rgb(242,184,132) 150deg, rgb(238,160,202) 170deg, rgb(182,158,232) 190deg, rgb(154,168,230) 210deg, rgb(140,204,238) 230deg, rgb(255,255,255) 240deg, rgb(238,214,90) 250deg, rgb(242,184,132) 270deg, rgb(238,160,202) 290deg, rgb(182,158,232) 310deg, rgb(154,168,230) 330deg, rgb(140,204,238) 350deg, rgb(255,255,255) 360deg);
  -webkit-mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite:xor; mask-composite:exclude}
/* THE RING'S ARMS (Peter's questions on the video, 2026-08-26 night; rig chips
   behind the devtoggle, NOTHING locked until he rules). Why the ring flashes white:
   the band was drawn for the 40px refracted button - three spectrums around, white
   at every seam, "peaks in one band" - and the 28px mark inherited it. At ring
   scale each colour arc is ~5px, the pastel stops merge toward white, and a seam
   passes any point every ~316ms at .95s; the bloom then recombines to white by
   design. His three levers, as arms: SPREAD (one spectrum around, one seam),
   DEEPEN (the same six colours, saturation compensated for the 3px band), SLOW
   (the lap; the count stays three, ruled). The band arms reach the folds' plus
   too - one band everywhere; the lap arms leave the folds' locked timing alone. */
body[data-rb="one"] .entry::before, body[data-rb="one"] .pmark::before,
body[data-rb="deep"] .entry::before, body[data-rb="deep"] .pmark::before{
  background:conic-gradient(from var(--a), rgb(255,255,255) 0deg,
    rgb(238,214,90) 30deg, rgb(242,184,132) 90deg, rgb(238,160,202) 150deg,
    rgb(182,158,232) 210deg, rgb(154,168,230) 270deg, rgb(140,204,238) 330deg,
    rgb(255,255,255) 360deg)}
/* deep = deeper STOPS, not a filter: the bloom's fill:both owns `filter` for the
   whole answer (backwards fill through the delay), so a static saturate never
   paints - measured, not guessed. Same six hues, saturation doubled in HSL. */
body[data-rb="deep"] .entry::before, body[data-rb="deep"] .pmark::before{
  background:conic-gradient(from var(--a), rgb(255,255,255) 0deg,
    rgb(255,225,73) 30deg, rgb(255,183,119) 90deg, rgb(255,143,203) 150deg,
    rgb(174,135,255) 210deg, rgb(129,152,255) 270deg, rgb(123,209,255) 330deg,
    rgb(255,255,255) 360deg)}
body[data-rl="14"] .entry.answering::before,
body[data-rl="14"] .pmark.answering::before{
  animation:lap .85s linear 3, fadeout .85s linear 1 1.7s forwards}   /* the one answer (Peter, 2026-08-27 3:25 PM, on the video) */
body[data-rl="19"] .entry.answering::before,
body[data-rl="19"] .pmark.answering::before{
  animation:lap 1.9s linear 3, bloomhold 1.9s ease-out 1 3.8s both,
            ringout 5.7s linear 1 forwards}
body[data-rend="accel"] .entry.answering::before,
body[data-rend="accel"] .pmark.answering::before{
  animation:lap .85s linear 3, fadeout .85s linear 1 1.7s forwards}

/* THE DOWNLOAD IS A STATUS (Peter, 2026-08-27 1:00 AM: "the prism light
   circling constantly while the report is downloading - it will do its cycle
   at the end of the download; we need to listen for those"): while the
   download runs, the band circles at the steady pace with no bloom; on
   completion the mark fires the ruled answer (even build to persistent
   white). Here the download is SIMULATED (2.8s); in the product, 4b's fetch
   owns the real start/finish events - carried to that contract. */
.pmark.busy{border-color:transparent}
.pmark.busy::before{opacity:1; animation:lap 1.4s linear infinite}
.pmark.answering{border-color:transparent}
/* the one answer (Peter, 2026-08-27 3:25 PM, on the video): the download's
   completion fires the input-field mark's own form; the busy circling above
   stays as the continuous mood while the report comes. */
.pmark.answering::before{opacity:1;
  animation:lap .85s linear 3, fadeout .85s linear 1 1.7s forwards}
/* THE CONTAINER IS THE CIRCLE, FULL STOP (Peter, 2026-08-26). The page-shaped
   rectangle was his own idea and he ruled it out on sight: every mark the
   Person acts through is the same circle, and a second container shape would
   have been the only exception on either page. */
body[data-dlbar="off"] .bar{display:none}
/* dead by default; the reading has not come back yet. There is no upload on
   this page - Lite reads what you type and hands a reading back, so the only
   document that ever moves is the one going out. (Peter, 2026-08-26.) */
.dnl{opacity:.34; pointer-events:none}
body.delivered .dnl{opacity:1; pointer-events:auto}

/* ── THE ROW, WITH ONLY TWO THINGS ON IT (Peter, 2026-08-26) ────────────────
   "With only two elements on the line, I would put download report aligned
   right with the grid." So the readout holds the left edge and the download
   holds the right, both on the frame the rest of the page uses.

   The hint sits BETWEEN them rather than after, and the auto margins come off
   so it cannot push the download around. That matters more than it sounds: the
   hint appears and disappears as someone types, and a right-aligned control
   that shuffles every time they start a sentence is worse than one that is
   merely off-grid. `space-between` holds all three still. */
.actionrow{justify-content:space-between}
.hint{margin-left:0 !important; order:2}

/* ── THE TWO CIRCLES SHARE A CENTRE LINE (Peter, 2026-08-26) ────────────────
   "We want those circles lined up exactly - let the upper arrow be the guide."

   The send mark is absolutely placed inside the box at `right:10px`, so its
   centre sits 24px in from the field's right edge (10 + half of 28). The
   download therefore takes the SAME 10px inset rather than sitting flush to
   the grid, and the two centres land on one vertical.

   It also has to come LAST in the row. With `space-between` the final child
   takes the right edge, and that was the hint - which is why the download was
   measured 220px adrift of the mark above it. `order` puts it back without
   moving it in the markup, where it belongs beside the readout it labels. */
.acts{margin-left:0; order:3; margin-right:0}



/* ── FOCUS SHOWS ON THE MARK, NOT AS A BOX ─────────────────────────────────
   A keyboard or a click leaves focus on these, and the browser's default focus
   ring draws a rectangle around the whole control - label and all. That is the
   one box this row can still produce, and it is almost certainly what Peter
   saw after clicking Download report. Focus is not suppressed, it MOVES: the
   outline goes, and the mark's own grey ring carries it instead, which is the
   same signal hover already uses. */
/* THE BOX, FOUND (Peter, three times). `.actionrow button::before` is the
   refracted button's prismatic ring, and it matches any <button> in that row -
   at border-radius:0 it draws a rectangle around the whole control, ON HOVER
   ONLY, which is why every check I ran at rest came back clean. Killed on the
   action marks the way it was already killed on the upload link. */
.act::before,.act::after{display:none !important}
.act:focus{outline:none !important}
.act:focus-visible{outline:none !important}
.act:focus-visible .pmark{border-color:rgba(3,3,3,.38) !important}

/* ── THE PLUS IS THE SAME MARK AS THE OTHERS (Peter, 2026-08-26) ────────────
   "The circles are off centre from the marks. These marks are simple enough for
   you to draw the circle." Exactly - so the cross is now an SVG inside a
   `.pmark`, which means the circle is drawn WITH the glyph rather than
   positioned over it. There is nothing left to misalign: the ring is the
   element's own border and the conic is its own pseudo-element, centred by
   construction the way the send and download marks are.

   That also cost nothing to gain - it deletes the hand-placed `summary::after`,
   its right/top/margin arithmetic, and the two absolutely positioned bars. */
summary .row{display:flex; align-items:center; justify-content:space-between; gap:16px}
summary:hover .pmark{border-color:rgba(3,3,3,.38)}
.mk .vbar{transition:opacity .18s ease, transform .18s ease; transform-origin:12px 12px}
details[open] .mk .vbar{opacity:0; transform:scaleY(0)}

/* THE RING ANSWERS AND GOES (Peter): "it persisted after the accordion opened,
   which it should not. It can answer again when it's clicked, but it disappears
   once the motion is complete or sooner." The opacity was a static declaration
   sitting under the animation, so it held at 1 forever. It is part of the
   animation now - the ring carries itself out on the same clock it came in on,
   and the fold can be closed and reopened to hear it again. */
@keyframes ringout{ 0%,84%{opacity:1} 100%{opacity:0} }
details[open] .mk::before{opacity:1;  /* the base ring is opacity-0; the retired exits carried visibility in their own keyframes - fadeout is delayed and cannot (the 3:07 AM "hardly work" diagnosed, 3:35 PM) */
  animation:lap .85s linear 3, fadeout .85s linear 1 1.7s forwards}   /* the one answer (Peter, 2026-08-27 3:25 PM, on the video) */

/* ── THE STATE BAR (Peter, 2026-08-28: the rig buttons join a collapsible
   header the way the full door's stepper did; the dot is the way back).
   Dress and dot are comp_04's own. With the bar down, main keeps its real
   padding, so the page reads at its true spacing. */
/* THE BOX GROWS TO ITS GHOST (ported from the door, 2026-08-30). The ghost was
   positioned absolutely, so it could not contribute height and the box took its
   size from the textarea alone. MEASURED HERE BEFORE THE CURE: at 375 the same
   words wrapped to 276px inside a 197px box - 78px hanging out below. Desktop
   was never affected. One grid cell holds field and ghost, so the row is the
   taller of the two at any measure. The entry arrow and the track stay
   absolutely positioned and are untouched - out of flow, so the grid never
   sees them. */
.writebox{display:grid}
.writebox > textarea,
.writebox > .ghost{grid-area:1 / 1 / 2 / 2}
.writebox .ghost{position:static; width:auto;
  padding:14px 46px 14px 16px; box-sizing:border-box;
  font:300 16px/1.35 'Roboto Serif',Georgia,serif; color:rgba(3,3,3,.42);
  pointer-events:none}
.writebox .ghost i{font-style:italic}
.writebox.filled .ghost{display:none}
.exfold{margin:0 0 10px}

/* THE MARK IN THE MASTHEAD (Peter, 2026-08-29 4:08 PM). The lockup replaces the
   eyebrow on both doors; the ring turns at an AMBIENT pace - readable as moving,
   never restless. Because the hue anchor and the flash are both fixed to the
   ring, they orbit together, so the comet carries its own yellow exactly as the
   print mark holds it at nine o'clock. The word never turns. */
.pmasthead{display:flex;align-items:center;gap:3.878px;margin:0 0 5px;padding:0}
.pmasthead .eb{font:600 9.5px/1 Roboto,Helvetica,Arial,sans-serif;
  letter-spacing:.18em;text-transform:uppercase;color:#333}
.pring{display:block;flex:none;overflow:visible}

/* THE ORBIT (Peter, 2026-08-29 5:02 PM — three corrections in one).
   1. THE APEX WAS IN THE WRONG PLACE. The dwell was centred on the
      rotation's zero, and the head's home is NINE o'clock, so the slow
      moment landed at nine — a quarter turn counter-clockwise of where the
      eye expects it. The mark was working against gravity, not with it.
      The dwell now centres on a +90deg rotation, so the head crests at TOP.
   2. IT WAS BEING POWERED OVER, NOT COASTING. At 10% the comet read as
      stalling and being pushed off. A body coasting a loop slows but never
      stops, so the floor is now 45% at the apex, easing on long flanks
      (exponent 1.6) in and out of midnight — 58% by 12:30, 76% by 1:00,
      full by 3:00.
   3. Faster overall: one lap 4.0s. */
@keyframes pringspin{0%{transform:rotate(0.00deg)}1.042%{transform:rotate(4.32deg)}2.083%{transform:rotate(8.58deg)}3.125%{transform:rotate(12.84deg)}4.167%{transform:rotate(17.10deg)}5.208%{transform:rotate(21.36deg)}6.25%{transform:rotate(25.56deg)}7.292%{transform:rotate(29.70deg)}8.333%{transform:rotate(33.78deg)}9.375%{transform:rotate(37.86deg)}10.42%{transform:rotate(41.82deg)}11.46%{transform:rotate(45.66deg)}12.5%{transform:rotate(49.38deg)}13.54%{transform:rotate(53.04deg)}14.58%{transform:rotate(56.52deg)}15.62%{transform:rotate(59.88deg)}16.67%{transform:rotate(63.12deg)}17.71%{transform:rotate(66.12deg)}18.75%{transform:rotate(69.06deg)}19.79%{transform:rotate(71.82deg)}20.83%{transform:rotate(74.40deg)}21.88%{transform:rotate(76.86deg)}22.92%{transform:rotate(79.26deg)}23.96%{transform:rotate(81.48deg)}25%{transform:rotate(83.64deg)}26.04%{transform:rotate(85.68deg)}27.08%{transform:rotate(87.72deg)}28.12%{transform:rotate(89.64deg)}29.17%{transform:rotate(91.62deg)}30.21%{transform:rotate(93.60deg)}31.25%{transform:rotate(95.64deg)}32.29%{transform:rotate(97.74deg)}33.33%{transform:rotate(99.96deg)}34.38%{transform:rotate(102.30deg)}35.42%{transform:rotate(104.70deg)}36.46%{transform:rotate(107.28deg)}37.5%{transform:rotate(109.98deg)}38.54%{transform:rotate(112.80deg)}39.58%{transform:rotate(115.80deg)}40.62%{transform:rotate(118.98deg)}41.67%{transform:rotate(122.28deg)}42.71%{transform:rotate(125.70deg)}43.75%{transform:rotate(129.30deg)}44.79%{transform:rotate(133.02deg)}45.83%{transform:rotate(136.80deg)}46.88%{transform:rotate(140.76deg)}47.92%{transform:rotate(144.78deg)}48.96%{transform:rotate(148.86deg)}50%{transform:rotate(153.00deg)}51.04%{transform:rotate(157.14deg)}52.08%{transform:rotate(161.40deg)}53.12%{transform:rotate(165.60deg)}54.17%{transform:rotate(169.92deg)}55.21%{transform:rotate(174.18deg)}56.25%{transform:rotate(178.50deg)}57.29%{transform:rotate(182.76deg)}58.33%{transform:rotate(187.08deg)}59.38%{transform:rotate(191.40deg)}60.42%{transform:rotate(195.72deg)}61.46%{transform:rotate(200.04deg)}62.5%{transform:rotate(204.36deg)}63.54%{transform:rotate(208.68deg)}64.58%{transform:rotate(213.00deg)}65.62%{transform:rotate(217.32deg)}66.67%{transform:rotate(221.64deg)}67.71%{transform:rotate(225.96deg)}68.75%{transform:rotate(230.34deg)}69.79%{transform:rotate(234.66deg)}70.83%{transform:rotate(238.98deg)}71.88%{transform:rotate(243.30deg)}72.92%{transform:rotate(247.62deg)}73.96%{transform:rotate(251.94deg)}75%{transform:rotate(256.26deg)}76.04%{transform:rotate(260.58deg)}77.08%{transform:rotate(264.90deg)}78.12%{transform:rotate(269.22deg)}79.17%{transform:rotate(273.54deg)}80.21%{transform:rotate(277.86deg)}81.25%{transform:rotate(282.18deg)}82.29%{transform:rotate(286.56deg)}83.33%{transform:rotate(290.88deg)}84.38%{transform:rotate(295.20deg)}85.42%{transform:rotate(299.52deg)}86.46%{transform:rotate(303.84deg)}87.5%{transform:rotate(308.16deg)}88.54%{transform:rotate(312.48deg)}89.58%{transform:rotate(316.80deg)}90.62%{transform:rotate(321.12deg)}91.67%{transform:rotate(325.44deg)}92.71%{transform:rotate(329.76deg)}93.75%{transform:rotate(334.08deg)}94.79%{transform:rotate(338.40deg)}95.83%{transform:rotate(342.72deg)}96.88%{transform:rotate(347.04deg)}97.92%{transform:rotate(351.36deg)}98.96%{transform:rotate(355.68deg)}100%{transform:rotate(360.00deg)}}2.083%{transform:rotate(0.96deg)}3.125%{transform:rotate(1.44deg)}4.167%{transform:rotate(1.98deg)}5.208%{transform:rotate(2.64deg)}6.25%{transform:rotate(3.30deg)}7.292%{transform:rotate(4.20deg)}8.333%{transform:rotate(5.22deg)}9.375%{transform:rotate(6.60deg)}10.42%{transform:rotate(8.40deg)}11.46%{transform:rotate(10.80deg)}12.5%{transform:rotate(13.98deg)}13.54%{transform:rotate(17.94deg)}14.58%{transform:rotate(22.26deg)}15.62%{transform:rotate(26.88deg)}16.67%{transform:rotate(31.50deg)}17.71%{transform:rotate(36.12deg)}18.75%{transform:rotate(40.74deg)}19.79%{transform:rotate(45.42deg)}20.83%{transform:rotate(50.04deg)}21.88%{transform:rotate(54.72deg)}22.92%{transform:rotate(59.34deg)}23.96%{transform:rotate(63.96deg)}25%{transform:rotate(68.64deg)}26.04%{transform:rotate(73.26deg)}27.08%{transform:rotate(77.88deg)}28.12%{transform:rotate(82.56deg)}29.17%{transform:rotate(87.18deg)}30.21%{transform:rotate(91.80deg)}31.25%{transform:rotate(96.48deg)}32.29%{transform:rotate(101.10deg)}33.33%{transform:rotate(105.72deg)}34.38%{transform:rotate(110.40deg)}35.42%{transform:rotate(115.02deg)}36.46%{transform:rotate(119.64deg)}37.5%{transform:rotate(124.32deg)}38.54%{transform:rotate(128.94deg)}39.58%{transform:rotate(133.56deg)}40.62%{transform:rotate(138.24deg)}41.67%{transform:rotate(142.86deg)}42.71%{transform:rotate(147.54deg)}43.75%{transform:rotate(152.16deg)}44.79%{transform:rotate(156.78deg)}45.83%{transform:rotate(161.46deg)}46.88%{transform:rotate(166.08deg)}47.92%{transform:rotate(170.70deg)}48.96%{transform:rotate(175.38deg)}50%{transform:rotate(180.00deg)}51.04%{transform:rotate(184.62deg)}52.08%{transform:rotate(189.30deg)}53.12%{transform:rotate(193.92deg)}54.17%{transform:rotate(198.54deg)}55.21%{transform:rotate(203.22deg)}56.25%{transform:rotate(207.84deg)}57.29%{transform:rotate(212.46deg)}58.33%{transform:rotate(217.14deg)}59.38%{transform:rotate(221.76deg)}60.42%{transform:rotate(226.44deg)}61.46%{transform:rotate(231.06deg)}62.5%{transform:rotate(235.68deg)}63.54%{transform:rotate(240.36deg)}64.58%{transform:rotate(244.98deg)}65.62%{transform:rotate(249.60deg)}66.67%{transform:rotate(254.28deg)}67.71%{transform:rotate(258.90deg)}68.75%{transform:rotate(263.52deg)}69.79%{transform:rotate(268.20deg)}70.83%{transform:rotate(272.82deg)}71.88%{transform:rotate(277.44deg)}72.92%{transform:rotate(282.12deg)}73.96%{transform:rotate(286.74deg)}75%{transform:rotate(291.36deg)}76.04%{transform:rotate(296.04deg)}77.08%{transform:rotate(300.66deg)}78.12%{transform:rotate(305.28deg)}79.17%{transform:rotate(309.96deg)}80.21%{transform:rotate(314.58deg)}81.25%{transform:rotate(319.26deg)}82.29%{transform:rotate(323.88deg)}83.33%{transform:rotate(328.50deg)}84.38%{transform:rotate(333.12deg)}85.42%{transform:rotate(337.74deg)}86.46%{transform:rotate(342.06deg)}87.5%{transform:rotate(346.02deg)}88.54%{transform:rotate(349.20deg)}89.58%{transform:rotate(351.60deg)}90.62%{transform:rotate(353.40deg)}91.67%{transform:rotate(354.78deg)}92.71%{transform:rotate(355.80deg)}93.75%{transform:rotate(356.70deg)}94.79%{transform:rotate(357.36deg)}95.83%{transform:rotate(358.02deg)}96.88%{transform:rotate(358.56deg)}97.92%{transform:rotate(359.04deg)}98.96%{transform:rotate(359.52deg)}100%{transform:rotate(360.00deg)}}3.125%{transform:rotate(2.22deg)}4.688%{transform:rotate(3.42deg)}6.25%{transform:rotate(4.68deg)}7.812%{transform:rotate(6.06deg)}9.375%{transform:rotate(7.62deg)}10.94%{transform:rotate(9.42deg)}12.5%{transform:rotate(11.58deg)}14.06%{transform:rotate(14.10deg)}15.62%{transform:rotate(17.28deg)}17.19%{transform:rotate(21.24deg)}18.75%{transform:rotate(26.10deg)}20.31%{transform:rotate(32.04deg)}21.88%{transform:rotate(38.82deg)}23.44%{transform:rotate(46.26deg)}25%{transform:rotate(53.94deg)}26.56%{transform:rotate(61.80deg)}28.12%{transform:rotate(69.66deg)}29.69%{transform:rotate(77.52deg)}31.25%{transform:rotate(85.44deg)}32.81%{transform:rotate(93.30deg)}34.38%{transform:rotate(101.16deg)}35.94%{transform:rotate(109.08deg)}37.5%{transform:rotate(116.94deg)}39.06%{transform:rotate(124.80deg)}40.62%{transform:rotate(132.72deg)}42.19%{transform:rotate(140.58deg)}43.75%{transform:rotate(148.50deg)}45.31%{transform:rotate(156.36deg)}46.88%{transform:rotate(164.22deg)}48.44%{transform:rotate(172.14deg)}50%{transform:rotate(180.00deg)}51.56%{transform:rotate(187.86deg)}53.12%{transform:rotate(195.78deg)}54.69%{transform:rotate(203.64deg)}56.25%{transform:rotate(211.50deg)}57.81%{transform:rotate(219.42deg)}59.38%{transform:rotate(227.28deg)}60.94%{transform:rotate(235.20deg)}62.5%{transform:rotate(243.06deg)}64.06%{transform:rotate(250.92deg)}65.62%{transform:rotate(258.84deg)}67.19%{transform:rotate(266.70deg)}68.75%{transform:rotate(274.56deg)}70.31%{transform:rotate(282.48deg)}71.88%{transform:rotate(290.34deg)}73.44%{transform:rotate(298.20deg)}75%{transform:rotate(306.06deg)}76.56%{transform:rotate(313.74deg)}78.12%{transform:rotate(321.18deg)}79.69%{transform:rotate(327.96deg)}81.25%{transform:rotate(333.90deg)}82.81%{transform:rotate(338.76deg)}84.38%{transform:rotate(342.72deg)}85.94%{transform:rotate(345.90deg)}87.5%{transform:rotate(348.42deg)}89.06%{transform:rotate(350.58deg)}90.62%{transform:rotate(352.38deg)}92.19%{transform:rotate(353.94deg)}93.75%{transform:rotate(355.32deg)}95.31%{transform:rotate(356.58deg)}96.88%{transform:rotate(357.78deg)}98.44%{transform:rotate(358.92deg)}100%{transform:rotate(360.00deg)}}4.167%{transform:rotate(5.22deg)}6.25%{transform:rotate(7.92deg)}8.333%{transform:rotate(10.80deg)}10.42%{transform:rotate(13.86deg)}12.5%{transform:rotate(17.10deg)}14.58%{transform:rotate(20.70deg)}16.67%{transform:rotate(24.57deg)}18.75%{transform:rotate(28.89deg)}20.83%{transform:rotate(33.66deg)}22.92%{transform:rotate(38.97deg)}25%{transform:rotate(44.91deg)}27.08%{transform:rotate(51.48deg)}29.17%{transform:rotate(58.86deg)}31.25%{transform:rotate(67.05deg)}33.33%{transform:rotate(76.14deg)}35.42%{transform:rotate(86.22deg)}37.5%{transform:rotate(97.20deg)}39.58%{transform:rotate(109.17deg)}41.67%{transform:rotate(122.04deg)}43.75%{transform:rotate(135.72deg)}45.83%{transform:rotate(150.03deg)}47.92%{transform:rotate(164.88deg)}50%{transform:rotate(180.00deg)}52.08%{transform:rotate(195.12deg)}54.17%{transform:rotate(209.97deg)}56.25%{transform:rotate(224.28deg)}58.33%{transform:rotate(237.96deg)}60.42%{transform:rotate(250.83deg)}62.5%{transform:rotate(262.80deg)}64.58%{transform:rotate(273.78deg)}66.67%{transform:rotate(283.86deg)}68.75%{transform:rotate(292.95deg)}70.83%{transform:rotate(301.14deg)}72.92%{transform:rotate(308.52deg)}75%{transform:rotate(315.09deg)}77.08%{transform:rotate(321.03deg)}79.17%{transform:rotate(326.34deg)}81.25%{transform:rotate(331.11deg)}83.33%{transform:rotate(335.43deg)}85.42%{transform:rotate(339.30deg)}87.5%{transform:rotate(342.90deg)}89.58%{transform:rotate(346.14deg)}91.67%{transform:rotate(349.20deg)}93.75%{transform:rotate(352.08deg)}95.83%{transform:rotate(354.78deg)}97.92%{transform:rotate(357.39deg)}100%{transform:rotate(360.00deg)}}}
@media (prefers-reduced-motion:reduce){.pring g{animation:none !important}}

/* THE WRITE-NOTE MOVES INTO THE BOX (Peter, 2026-08-29 4:37 PM): "A paragraph is
   plenty. More is better." now leads the ghost as its headline, so the guidance
   sits where the writing happens rather than above the frame. It keeps its own
   weight against the ghost's lighter voice, and it disappears with the ghost the
   moment anything is typed. */
.writebox .ghost .ghead{display:block;margin:0 0 10px;
  font:400 16px/1.35 'Roboto Slab',Georgia,serif;color:rgba(3,3,3,.46)}

/* ══ THE CHOREOGRAPHY (Peter, 2026-09-08, the sitting on the live site) ═══════
   Three rulings, comped. Nothing here touches the frame, the four type sizes or
   the five phrases. What changes is WHERE the words go and WHEN the mark turns.

     1  THE WRITING RISES. On send the Person's paragraph leaves the box and
        stands directly above it, in their own voice. "Input box does not move
        after prompt... it's essential that the input move into the screen
        above." DIALOGUE_LOCKED's law read at the door: one direction is the
        Person's input, and this is that one direction, made visible.
     2  THE BOX GOES DARK AND SPEAKS. On send the box's ground becomes #333 and
        becomes the ground for a slideshow: one centered headline at a time, the
        five of them rotating, the facilitator tapping its foot. At delivery the
        show gives way to one line saying the reading is ready.
     3  THE MARK ORBITS while the report is made, then answers once.

   THE BLEND (Peter, 2026-09-08 7:01 PM, on seeing comps A and B). Comp A put the
   delivery message in the ghost's own grey: "it looks like functional copy that
   nobody reads." STRUCK. "When user hits return, input moves above the input
   box; input box itself turns dark gray #333. That becomes dark background for
   the slideshow... This should be a dialogue between the system and the output.
   It's the facilitator tapping its foot."

   ───────────────────────────────────────────────────────────────────────────
   1 - THE RISEN BLOCK.
   Motion borrows the page's existing vocabulary. There is no .42s duration
   anywhere on this page (the .42 that appears here is a bezier control point on
   the beams, not a time), so the rise takes 300ms ease-out and 8px, and under
   prefers-reduced-motion it simply appears.
   The eyebrow is the page's own `.eyebrow`, unmodified.
   The words keep the setting they had in the box - 300 / 16px / 1.35 - because
   the rise is a move, not a resize; the face turns to the Person's voice, which
   is what the box's own ghost sample already uses on this page. */
/* TENTH PASS (Peter, 2026-09-09 5:27 PM): "I want the actual space that the input
   field used to occupy to be the space that this slideshow runs in; the interface
   actually jumps because the type moves up in the second screen but does not clear
   the space. The input box starts at about 275 pixels from the top edge. After
   running the prompt, 275 pixels down is in the middle of the printed user prompt."

   THE DOOR'S COPY GIVES WAY TO THE WRITING - the full product's own law, read at
   `session_2026-08-30.html`: `body.session .invite{display:none}` and
   `body.session .exfold{display:none}`, the door's folds dropping at the session.
   Here the lede and the Sample prompts fold go, and the risen writing takes exactly
   the region they held: the block's height is set in script to the measured run
   from the lede's top to the box's top, so the box's top on the page does not move
   by a pixel and the seventh pass's scroll compensation is retired with it.

   IT SCROLLS INSIDE ITSELF when the writing is taller than that region, with the
   full product's own transcript mask at the edge it runs off. The eyebrow stays
   out of the scroller so it is never faded; the ramp at the head arrives only once
   the block has actually been scrolled, because at rest there is nothing above the
   first line to cut. */
/* A LINE SPACE ABOVE THE RISEN WRITING (Peter, 2026-09-12 2:34 AM, his markup on the
   served page: the subhead and YOUR WRITING touched). The line is the page's own 22;
   the block gives the same 22 back from its height, so its outer measure is still
   the region the lede left and the box does not move. */
.rose{margin:22px 0 0; display:flex; flex-direction:column; overflow:hidden}
body.reading .lede, body.reading .exfold{display:none}
.rose .rosetext{margin:0; color:#030303; flex:1 1 auto; min-height:0;
  font:300 16px/1.35 'Roboto Serif',Georgia,serif; font-style:italic;
  font-synthesis:none; white-space:pre-wrap;
  overflow-y:auto; overscroll-behavior:contain;
  scrollbar-width:none; -ms-overflow-style:none}
.rose .rosetext::-webkit-scrollbar{width:0; height:0; display:none}
.rose.scrolls .rosetext{
  -webkit-mask-image:linear-gradient(to bottom,#000 calc(100% - 16px),transparent 100%);
          mask-image:linear-gradient(to bottom,#000 calc(100% - 16px),transparent 100%)}
.rose.scrolls.dropped .rosetext{
  -webkit-mask-image:linear-gradient(to bottom,transparent 0,#000 18px,
     #000 calc(100% - 16px),transparent 100%);
          mask-image:linear-gradient(to bottom,transparent 0,#000 18px,
     #000 calc(100% - 16px),transparent 100%)}
@keyframes rise{from{opacity:0; transform:translateY(8px)}
                to  {opacity:1; transform:none}}
.rose.up{animation:rise .3s ease-out both}
@media (prefers-reduced-motion:reduce){ .rose.up{animation:none} }

/* 2 - THE BOX GOES DARK, AND THE SHOW RUNS IN IT.
   The transparency law belongs to the IDLE box - a field the page's light shows
   through while it is waiting for you. This is the working box, and it is opaque:
   #333, the page's own --head. The textarea goes with it (there is nothing to
   type into while a reading is being made) and so does the ghost, whose grey
   copy was the thing Peter struck.

   THE SEND ARROW GOES TOO. One decision the ruling did not settle, taken and
   named in the report: the arrow is the textarea's own affordance, it is inert
   while a reading runs, and at #666 on #333 it reads as a smudge on the show's
   ground rather than a control. Delete this one line and it comes back.
   (Peter, 7:33 PM, on that decision: it stands - nothing can be sent while the
   box is dark, and the arrow returns with the box.)

   85% (Peter, 8:50 PM: "Go to 85% on the Alpha", superseding the 65% of 8:06 PM,
   which superseded the 45% of 7:33 PM, which was the idle box's own
   `--field-fill:.45` read as a dark twin). The page's light still moves through
   the working box - the law is unchanged - but the ground under the show is
   darker again, and it is what the white type is read against. The contrast that
   buys is measured in the report. */
/* THE FIELD REVERSED (Peter, 2026-09-09 12:33 AM: "reverse the color scheme of
   the slideshow. It's getting there, but it feels heavy handed with the big black
   field on the very white page. Let's change it to a white background 70% alpha;
   text goes to #444"). The working field is now the page's own light at 70% and
   the type on it is #444 - the same reversal the idle box already is, and the
   same transparency law: the page's light still moves through the working box.
   The alphas above are the record of how it got here - .45 -> .65 -> .85 dark,
   then reversed to .70 white - and the class is still called `dark` because that
   is what every rule, every comment and the harness call the working field; it
   names the moment, not the color. */
/* THE FIELD GOES AWAY (Peter, 2026-09-09 9:53 AM, folding COMP D into comp C:
   "the background field simply goes away, and we are left with the moving
   background that's there. The space does not collapse. It remains open, and the
   type appears in dark gray over the background directly, exactly as it's doing
   now, in exactly the same position, but without a visible background to it").
   So the working box paints NOTHING: no ground, no border, no shadow. The space
   is still held - the pinned height, the pinned place and the scroll compensation
   all stand - and the type sits over the page's own moving light directly. The
   alphas above are the record of how it got here: .45 -> .65 -> .85 dark,
   reversed to .70 white, and now none at all. */
.writebox.dark{background:transparent; border:0; box-shadow:none}
.writebox.dark > textarea,
.writebox.dark > .ghost,
.writebox.dark > .entry{display:none}
/* THE WHOLE BOX IS THE DOWNLOAD AT THE FINISH (Peter, 8:50 PM: "I don't see any
   reason why the final message shouldn't be 'ready, download it here' and have the
   entire box be clickable - I found myself wanting to do that anyhow"). Once the
   reading has landed the dark box is ONE BUTTON: it takes the pointer, it takes
   focus and a tab stop, and its ground goes to .9 under either.
   THE TAP BACK TO AN EMPTY INPUT IS STRUCK - "right now if you click it, the
   messaging disappears and it goes back to being an input box with no
   functionality, which is not great" - so the box stays dark with the ready line
   on it, and the Person's risen writing stays above it. */
/* ELEVENTH PASS (Peter, 10:15 PM: "We're not 'ready' anymore; we're done"): the
   whole-box click is STRUCK. The two pairs are the controls, so the box stops
   being one - no pointer, no role, no tab stop, and a press on its own space does
   nothing (check 7 presses it on purpose and reads NOTHING happening). */
body.delivered .writebox.dark{cursor:auto}
/* NO HOVER ON THE GROUND (Peter, 9:53 AM): there is no ground left to lighten, so
   the box's hover and focus states go with the field. The links and the mark carry
   their own - the link underlines, the mark takes its grey ring. */
/* THE PAGE'S OWN LAW ABOUT FOCUS, applied here (Peter ruled the focus rectangle
   out three times on the download's own button: "Focus is not suppressed, it
   MOVES: the outline goes, and the mark's own grey ring carries it instead, which
   is the same signal hover already uses"). A click focuses an element with a
   tabindex, so without this the browser would draw a rectangle around the whole
   box the moment it is pressed - the exact defect he chased. With the field gone
   the box shows no focus state of its own; the two things a person can focus
   INSIDE it - the mark and the link - carry their own, which is where the ruling
   put the signal in the first place. */
body.delivered .writebox.dark:focus{outline:none}

/* THE SLIDESHOW. "A single line of center type against the darker background
   with the type standing out in white... more like a headline, probably in
   Roboto Slab 600 or 700." Slab 600 at 22px/1.35 over Roboto 200 at 22px/1.35
   (Peter, 11:15 PM: "update head, slab 600, 22px; subhead, roboto 200, 22px",
   superseding 400 at 22 over 400 at 16), #FAFAFA, balanced.

   ALL SIX LINES ARE IN THE MARKUP AND STACKED IN ONE GRID CELL, one of them lit
   at a time, which is what makes the change from one line to the next a true
   CROSSFADE rather than a dissolve through the ground.

   THE BOX NEVER CHANGES SIZE (Peter, 8:06 PM: "I don't want the size of the
   enclosing box to bounce around. I want to see a fixed size at the exact same
   dimensions as the input box throughout the entire slideshow, text centered in
   the middle as you've been doing."). The box's height is pinned in script to
   what the input box measured at the instant of send, and it holds that through
   every slide and through the ready line, to the end of the reading (the tap back
   to an empty box is struck, 8:50 PM).
   The first pass's growth to the tallest slide and the second pass's shrink to
   the ready line are BOTH STRUCK. Every line is centered in that fixed field -
   across by `text-align:center`, down by `align-self:center` in a row that
   stretches to the pinned height. Nothing moves sideways, because the box is
   width:100%. */
/* THE DOWNLOAD IS NOT PERSISTENT (Peter, 7:40 PM: "the download report link is
   not standing out enough. It should not be persistent. It should be [modal,]
   becoming visible when there's something to download, and at that point you
   would also see the circle spinning until somebody clicks on it to actually get
   the report."). So the row is ABSENT - `display:none`, which takes it out of
   the tab order as well - from page load and through the whole reading, and
   nothing below the box reserves its space. It arrives WITH the reading, fading
   up on the page's own 300ms; under no-motion it is simply there. A second send
   drops `body.delivered` and it is gone again. */
.acts{display:none}
body.delivered .acts{display:inline-flex; animation:dnlin .3s ease both}
@keyframes dnlin{from{opacity:0} to{opacity:1}}
@media (prefers-reduced-motion:reduce){
  body.delivered .acts{animation:none}   /* it appears in place */
}
/* THE MARK SPINS WHEN THE ROW ARRIVES (Peter, 7:40 PM) - "you would also see the
   circle spinning until somebody clicks on it" - at FULL ink, which is what this
   rule guarantees while `busy` is on. The orbit during the making is struck with
   it: the slideshow is what carries the wait now, and the circling is kept for
   the one moment it is asking to be pressed. */
.act.dnl:has(.pmark.busy){opacity:1}

.writebox > .show{grid-area:1 / 1 / 2 / 2; display:none}
/* ── THE FIT CURE (THIRTEENTH PASS, 2026-09-10 1:45 AM) ──────────────────────
   The twelfth pass set the whole show in Roboto 600 at 26px and the stop rule
   fired: at 390 the tallest slide plus its two rows overran the pinned box by
   54.81px, and at 768 - the tighter of the two - by 11.47px. 1280 fit with 44.53
   to spare. The cure ruled: THE PHONE AND THE TABLET READ AT 22px, THE DESK AT 26,
   weight 600 throughout. One breakpoint at 900, which is above the 768 that was
   over and below the 1280 that was not.

   THE WHOLE SHOW IS SET ON TWO NUMBERS and they are declared here, once: the type
   size and the line box it makes (size x 1.35). The paragraph, the indicator row,
   the caption row and `start another` all read them, so the three rows keep the one
   line spacing the tenth pass ruled at either size and there is no second place a
   number could drift. */
.show{--showsize:22px; --showline:29.7px}
@media (min-width:900px){ .show{--showsize:26px; --showline:35.1px} }
/* The field fills the pinned box and centres its one lit slide in it. There is
   nothing left to transition: the second pass's shrink-to-the-ready-line is
   struck, so the field is one size from send to the finish. */
/* THE SHOW IS ONE THREE-ROW GRID, AND THE LIGHT IS ITS MIDDLE ROW (Peter,
   2026-09-09 9:53 AM). Every slide's headline is placed in row 1, the comet's
   track is row 2, every slide's subhead is row 3 - so the seven slides SHARE the
   three rows instead of each carrying its own block. Two things follow from that,
   and both are ruled:

     THE LIGHT NEVER MOVES. Row 1 is as tall as the tallest headline at that width
     and row 3 as tall as the tallest subhead, so the track's top is one number for
     the whole reading - "It should always remain in the same position and
     continuous movement back and forth" (12:57 AM). A track placed inside each
     slide would have sat at a different height on every slide and crossfaded with
     it; this one is a single element that never restarts.

     THE TYPE GATHERS ON IT. The headline is bottom-aligned in row 1 and the
     subhead top-aligned in row 3, so a short slide closes up around the light
     rather than drifting away from it, and the three rows together stay centred in
     the pinned box (`align-content:center`).

   `.slide{display:contents}` is what lets the seven pairs share the rows: the
   wrapper stops generating a box and its two spans become grid items in their own
   right. The crossfade moves onto those two spans with it.

   THE PADDING GOES SIDEWAYS TO NOTHING (Peter, 9:53 AM: "Align text left with
   margin of other type"). The box is width:100% inside `main`, so its own left
   edge is already the page's type margin - the lede's left edge, 16px at 390,
   68px at 768, 296px at 1280 - and with no horizontal padding the show's type
   sits exactly on it. The 14px top and bottom stay. */
.writebox.dark > .show{display:grid; padding:14px 0;
  grid-template-columns:100%;
  grid-template-rows:auto auto auto;
  align-content:center;
  text-align:left}
/* A SLIDE IS NOW A PAIR (Peter, 8:50 PM, with the new copy): a headline and a
   subhead beneath it, and the PAIR is what sits centred in the fixed box - across
   by `text-align:center`, down by `align-self:center`. The ready line is a
   headline with no subhead under it. */
/* TENTH PASS (Peter, 5:27 PM): "Try a new version where the headline and
   subheadline run together as a paragraph - one long thought." So a slide stops
   being two rows and becomes ONE BLOCK in row 1: the head's words lead the
   paragraph inline in the slab, the rest runs on in the sans, and the whole thing
   crossfades as one element. All seven are stacked in the same grid cell, which is
   what makes the change from one slide to the next a true crossfade.
   `text-wrap:pretty` rather than `balance`: the page's own widow rule says balance
   is for short display lines and pretty is for paragraphs, and this is a paragraph
   now. */
.show .slide{display:block; margin:0; grid-area:1 / 1 / 2 / 2; align-self:end;
  text-wrap:pretty; text-align:left;
  font:400 var(--showsize)/1.35 Roboto,Helvetica,Arial,sans-serif; color:#333;   /* 10:48 AM: 400 for all */
  font-synthesis:none;
  opacity:0; transition:opacity .3s ease}
/* THE TYPE, LEFT (Peter, 9:53 AM: "go to slab 500, 28px + sans 200, 22px fonts.
   #333 font color. Align text left with margin of other type"), superseding the
   Roboto 600 / 22 the desk set at 12:24 AM and the #444 of 12:33 AM. Slab 500 is
   a real face and not a synthesized weight: it is added to the page's font
   request and `font-synthesis:none` refuses the fake, which check 10 proves.
   NINTH PASS (Peter, 2:55 PM: "fonts both go to Roboto Sans 300, 22px;
   headlines to Slab 26px"): the headline comes down 28px -> 26px and the
   subhead's weight goes 200 -> 300. The 200 is retired; 300 is a real face out
   of the same variable 100..900 request, which check 10 proves. */
/* TWELFTH PASS (Peter, 2026-09-09 10:21 PM: "actually the whole show at 600, 26px,
   no difference in the headline at all. Let's just call that text at this point.
   There's no separation. They work better as one thought - not everything's a
   headline."). THE HEAD SPAN IS GONE from the six tour slides: each is now ONE TEXT
   in the markup, with no run-in to set differently, so there is nothing left inside a
   paragraph that could carry a second size or weight. `.shl` and `.ssb` survive only
   in the FINISH, which is two lines by the eleventh pass's ruling - and line 1 takes
   the show's own setting, which is why the rule below declares no face of its own. */
/* THE FINISH'S FIRST LINE, and nothing else, is what this now sets. It declares no
   face: it INHERITS the show's one text - Roboto 600 at 26px/1.35 - so "the finish's
   first line the same" is true by construction rather than by a matching pair of
   numbers that could drift apart. The three rows are therefore 26 x 1.35 = 35.1px. */
.show .slide .shl{display:inline}
/* the subhead's setting is now ruled too (Peter, 11:15 PM: "update head, slab
   600, 22px; subhead, roboto 200, 22px"), superseding the desk's 400 at 16px/1.4:
   the page's own sans, the same 22px/1.35 as the headline, the same ink. The
   page's Roboto request is the variable 100..900, so the weight is a real face
   and not a synthesized light - which check 10 proves. NINTH PASS: 200 -> 300
   (Peter, 2:55 PM), which is the weight the finish's "view it here" takes too. */
.show .slide .ssb{display:inline; margin:0;
  font:300 22px/1.35 Roboto,Helvetica,Arial,sans-serif; color:#333;
  font-synthesis:none}
/* THE FINISH HAS TWO ACTIONS (Peter, 11:15 PM): "download it here" and "view it
   here", set in the subhead's own face and separated by a spaced vertical bar.
   White, and underlined on hover or focus ONLY - which is also where the page's
   focus law lands here: the outline goes and the underline carries the signal,
   the same one hover uses. */
.show .slide .ssb a{color:#333; text-decoration:none; cursor:pointer}
.show .slide .ssb a:hover,
.show .slide .ssb a:focus{text-decoration:underline; outline:none}
.show .slide .ssb .fbar{padding:0 .5em}
.show .slide.on{opacity:1}
/* THE UNLIT SLIDES MUST NOT CATCH THE POINTER. All seven are stacked in the one
   grid cell, so the last of them lies over the finish's two links even at zero
   opacity: without this, a press meant for "view it here" landed on slide 6 and
   bubbled to the box, which would have made the box's download fire on the one
   press the ruling says it must not. Measured before the fix: Playwright could
   not reach either link. */
.show .slide:not(.on){pointer-events:none}
/* asked for no motion: the lines cut from one to the next; everything else -
   the dark ground, the ready line, the download waking - still arrives. */
@media (prefers-reduced-motion:reduce){
  .show .slide{transition:none}
}

/* ── THE LIGHT MOVES UP, UNDER THE HEADLINE ──────────────────────────────────
   Peter, 2026-09-09 12:57 AM: "take the shuttling light at the bottom of the input
   box and move it right underneath the headline. It should always remain in the
   same position and continuous movement back and forth. Give it some room, 1em or
   so, between the headline and subhead, centred in the space vertically." And the
   1:02 AM correction: "I actually like the expressive quirkiness of the shuttle;
   that's why I'm moving it up to a very prominent spot... So no, not a monotonous
   metronome. Something dynamic that moves with the process."

   So this track carries THE WHOLE GRAMMAR, not a new one. Its light is the same
   `.runner` and the same three `.wave` layers the rule under the box has always
   carried, inside the same `.writebox`, so every phase rule already written drives
   it untouched: idle's short ping-pong, working's one-direction travel at 2.6s,
   cycle's three-shots-and-a-well with the hue turning, and writing, where the comet
   stands down and the three sine layers take over. Nothing about the articulation
   was rewritten; it was rehoused.

   THE ROOM: 1em above and 1em below, where the em is the subhead's 22px - 22px of
   air on each side of the light, which is the reading of "1em or so" that leaves
   the phone real room (§ the report). The visible light is 3px, the rule's own
   weight, centred in the row.

   WHY THE ROW IS 3px AND THE CLIP IS NOT. In the writing phrase the rule under the
   box grows from 3px to 21px to hold the sine layers. Here that would push the
   subhead down 18px in the middle of the reading, which is exactly the jump the
   seventh pass spent itself curing - so the ROW stays 3px in layout and the clip
   grows around its own centre, absolutely, out of flow. The track's top is one
   number from the first slide to the finish.

   REDUCED MOTION: `.runner{animation:none; opacity:.5}` above already leaves the
   light at rest at translateX(0) - the left third of the track at working's 34%
   width - which is what the ruling asks for, so nothing is added here. */
/* TENTH PASS (Peter, 5:27 PM): "The moving indicator runs right below it, and the
   status line right under that, so that it seems to be a caption for the moving
   indicator. Each of those elements should have the same line spacing." So the
   22px of air above and below the track is struck: the track's row is now exactly
   ONE LINE BOX of the paragraph's own setting - 22 x 1.35 = 29.7px - with the
   light centred in it, and the caption row beneath it is another. Paragraph,
   indicator, caption: three rows on the same line spacing, no gap between them.
   The 3px light still floats absolutely inside the row, so the writing phrase's
   21px clip grows around its own centre and the track's top never moves. */
.show .ctrack{grid-area:2 / 1 / 3 / 2; position:relative; height:var(--showline);
  margin:0; pointer-events:none; overflow:visible}
.show .ctrack .cclip{position:absolute; left:0; right:0; top:50%; height:3px;
  transform:translateY(-50%); overflow:hidden; border-radius:2px}
.writebox.writing .show .ctrack .cclip{height:21px}

/* AND THE RULE UNDER THE BOX GOES QUIET (Peter, 9:53 AM: "the rule stays, quiet").
   There is no drawn hairline under the working box on this comp - the textarea's
   border goes with the textarea, and the light IS the rule - so "quiet" means the
   track stays exactly where it is, at its resting 3px, carrying nothing. The
   specificity is deliberate: `.writebox.writing .track` would otherwise still grow
   and drop the empty track 10.5px below the box. */
.writebox.on.dark > .track{height:3px !important; bottom:0 !important}
.writebox.on.dark > .track .runner,
.writebox.on.dark > .track .wave{opacity:0 !important; animation:none !important;
  transition:none !important}   /* it goes dark AT the send, not over the next .35s */

/* ── THE FINISH: ONE ACTION, INLINE ──────────────────────────────────────────
   Peter, 12:37 AM: "Instead of the 'download it here' link, should we just put a
   download arrow-and-circle button next to 'Your report is ready!'? ... we can
   almost replace the other button completely. This is bimodal, unsimplified. We
   only need one, and this is far more apparent. Keep 'view it here' as a text link
   for the moment. I think I may wanna try the circle P as an icon next to it, and
   that would be for the online version of the report - that's just the gray mark;
   I don't want it to compete with the colorful [mark] above."

   The mark is the page's OWN download mark - the same `.act.dnl` and `.pmark`
   markup, moved out of the row beneath the box and into the headline's own line,
   so every rule and every line of script that already drives it (the busy
   circling, the one answer, the fetch) applies unchanged. It sits 12px after the
   exclamation point, centred on the headline's line. The row beneath the box is
   gone with it: "we only need one". The mark is named the way the page's send
   mark is named - `aria-label` on the button, no visible label - so the headline
   reads as the four words it is. */
/* ELEVENTH PASS: the mark leaves the headline for the pair line beneath it; the
   12px trail goes with it, now carried on the mark inside its own link. */
.show .slide.rdy .ssb .acts{margin-right:24px}   /* NO vertical-align here: the
   wrapper is an INLINE box on the pair line now, and an inline box set to middle
   is shifted bodily against the x-height - measured, it grew the pair line's line
   box from 29.69px to 31.03px and broke the one line spacing the three rows share.
   The circle inside it carries the middle alignment, which is all that needed it. */

/* THE GREY MONOGRAM, NOW AFTER THE WORDS. The full product's About mark,
   verbatim (`_resources/design/web_full/session_2026-08-30.html`, the `.pmono`
   drawing: the letter's own path inside one ring). It takes the link's own ink,
   so it is grey by construction and can never colour - it is the quiet twin of
   the spectrum ring at the top of the page, which is the whole point of it.

   NINTH PASS (Peter, 2:55 PM: "in both cases the button should trail the words;
   circles on the buttons should be the same size and about 150% larger than the
   current download circle; line on Circle-P should thicken to match the
   prismatic circle's proportions"). Three changes, all here:
     IT TRAILS. `margin-right:8px` before the words becomes `margin-left:12px`
     after them - the same 12px the download mark takes after its own words.
     IT IS THE DOWNLOAD MARK'S SIZE. 1.35em of the subhead (29.70px) becomes a
     flat 42px, which is 1.5x the page's own 28px mark, so the two circles at
     the finish measure the same.
     ITS RING THICKENS. "The prismatic circle" is the spectrum band the mark
     wears while it is circling - `.pmark::before`, inset -4.5px with a 4.5px
     padding around the 42px mark, so a 4.5px band on an outer 51px circle:
     stroke over outer diameter, 4.5/51 = 3/34 = 0.088235. (The mark's other
     ring, the grey hover border, is 3/28 = 0.10714; that is the other reading,
     and 3/34 is the one taken because the prismatic ring is the one on the
     page at the finish - the hover border is transparent while `busy`.)
     The monogram was drawn at stroke 1.6 on an outer 24.8 of its 26-unit
     viewBox = 0.0645. Solving sw/(2r + sw) = 3/34 at the drawing's own
     r = 11.6 gives sw = 2.2452, an outer of 25.4452: at 42px that renders as a
     3.63px stroke on a 41.10px circle - the same proportion the prismatic ring
     carries, at the mark's own box size. The letter's own path is untouched,
     and r does not move: only the line thickens. */
.show .slide .ssb .pmono{display:inline-block; vertical-align:middle;
  width:26px; height:26px; margin-left:0; flex:0 0 auto}   /* 5:34 PM: 26px; ELEVENTH PASS: the 12px trail moves to the .pmark that now carries the band; FIFTEENTH PASS: 18px */
/* FIFTEENTH PASS - AND THE LETTER SCALES ITSELF. The monogram is drawn on a 26-unit
   viewBox and the letter's own `scale()` lives inside it, so taking the box from 26
   to 18 takes the ring, the ring's 2.4 stroke and the letter's path through the same
   0.692308 in one number. The ring therefore keeps its ruled proportion exactly -
   2.4 / 25.6 = 3/32 before, 1.6615 / 17.7231 = 3/32 after - and the inner `scale()`
   is left alone, because touching it as well would scale the letter twice. */
.show .slide .ssb .pmono circle{fill:none; stroke:currentColor; stroke-width:2.4}   /* at 26px the prismatic ring is 3 on 32: sw/(2r+sw)=3/32 at r=11.6 -> 2.4 */
.show .slide .ssb .pmono path{fill:currentColor; stroke:none}


/* ── THE FINISH IS ONE LINE (Peter, 2026-09-09 2:55 PM, marking up the eighth
   pass's finish: "Move 'view it here' to new position next to the download
   button; in both cases the button should trail the words; circles on the
   buttons should be the same size and about 150% larger than the current
   download circle") ────────────────────────────────────────────────────────
   Up to the eighth pass the finish was a pair like every other slide: the
   headline in row 1, "view it here" in row 3, the light between them. Now the
   two word-and-button pairs read as ONE line:

       Your report is ready!  (download)   view it here  (P)

   HOW IT IS BUILT. `.slide{display:contents}` is what lets the six tour pairs
   share the show's three rows; the finish stops doing that and becomes a single
   grid item in ROW 1, bottom-aligned exactly where the headlines sit. Its two
   spans go inline inside it, so they flow as one line instead of stacking. The
   light keeps row 2 and row 3 stays empty at the finish - which is what holds
   the track's top to the one number the ruling asked for (check 20).

   THE PAIRS KEEP THEIR FACES. "Your report is ready!" stays in the headline
   face (Slab 500 at 26px) and "view it here" in the subhead's (Roboto 300 at
   22px), which is what "the headline stays in the headline face" asks for.

   EACH PAIR IS UNBREAKABLE, THE LINE IS NOT. `white-space:nowrap` on each span
   keeps a button with its words; the ordinary word space between the two spans
   is the only place the line may break, so at phone width the second pair drops
   whole to the next line rather than a circle being orphaned.

   THE GAP BETWEEN THE PAIRS is 24px - twice the 12px the ruling gives for words
   to button, so the two pairs read as two things and each button reads as
   belonging to the words in front of it. It is carried on the FIRST pair's
   right, not the second's left, so a wrapped second line still starts flush on
   the page's own margin. One number; the report names it. */
/* `margin:0` because the finish stops being `display:contents` and becomes a real
   block, which wakes the page's own `p{margin:0 0 12px}` underneath it - 12px that
   would grow row 1 by 12px and push the light and the whole block down with it. */
/* TENTH PASS (Peter, 5:27 PM): "I think I want the buttons smaller, and in a
   different matching font: 22px Roboto Sans 400 / 1.35." So the finish's words -
   BOTH pairs, the headline's included - leave the slab for the page's own sans at
   400, and the two circles come back to the page's own 28px mark. The composition
   is otherwise the ninth pass's: two word-and-button pairs on one line, each button
   trailing its own words by 12px. */
.show .slide.rdy:not(.on){pointer-events:none}   /* the unlit finish catches nothing */
.show .slide.rdy .pmark{width:26px; height:26px}   /* Peter, 2026-09-11 12:32 AM: circles 26 */
/* FIFTEENTH PASS (Peter, 2026-09-10 11:02 AM: "The download and view buttons are too
   large - reduce to 18px and make the icons smaller proportionally"). 26 -> 18 is
   18/26 = 0.692308, and EVERY number inside the mark is taken through it rather than
   the box alone: the glyph, the letter, the band and the two rings all scale together
   so the mark reads as the same drawing, smaller.

   THE BORDER IS THE ONE NUMBER THAT IS NOT THE SCALE, AND IT HAS TO BE. The mark's
   3px border is a SPACER at the finish (its colour is transparent here) - what it
   really does is set the padding box the prismatic band is positioned inside, and
   through that the band's own outer diameter. 3 x 0.692308 = 2.0769, and Chromium
   snaps a border's used width to whole pixels - the same snap the thirteenth pass
   met on the grey ring and had to paint around - so 2.0769 would compute as 2 and the
   band's box would land at 18.15 rather than 18. The border is therefore declared at
   a whole 2px, which needs no rounding, and the band's own inset is declared at 2 to
   match it: padding box 14 + 2 + 2 = an outer of EXACTLY 18, the mark's own box, which
   is the relationship the 26px mark had (20 + 3 + 3 = 26). Measured, not assumed. */
/* AND THE LIGHT STILL NEVER MOVES. The download mark is absent from the document
   until the reading lands (`.acts{display:none}` - Peter, 7:40 PM: "it should not
   be persistent"), and its 42px box is what sets the height of the finish's first
   line. Now that the finish is the tallest thing in row 1, that line arriving 7px
   taller would push row 1 down and take the light with it - measured at 390:
   the track's top moved 6.91px at delivery. So the line carries a STRUT: a
   zero-width inline box of the mark's own height and the mark's own alignment,
   present from page load, so the line box is the same before the mark and after
   it. Nothing is drawn; row 1, the track's top and the whole block hold one
   number from the first slide to the finish (check 20). */
/* ELEVENTH PASS: the strut goes with the mark, onto the PAIR LINE - it is that
   line the mark arrives on now, and line 1 carries only words. */
.show .slide.rdy .ssb::before{content:""; display:inline-block; width:0;
  height:26px; vertical-align:middle}

/* THE TWO CIRCLES COME BACK TO THE PAGE'S OWN MARK (Peter, 5:27 PM: "I think I
   want the buttons smaller"). The ninth pass's 1.5x scaling - 42px with a 4.5px
   ring and a 27px glyph - is struck, and with the override gone the finish's mark
   is simply `.pmark` itself: 28px, a 3px ring, an 18px glyph, and a 3px prismatic
   band on an outer 34px. The ring proportions are therefore kept exactly, 3/34,
   which is what the monogram is still matched to. "Smaller" reads either as the
   page's own 28px, which is what is built, or as a middle 34px; that is one number
   and it is named in the report. */

/* ── THE COMET'S TRACK, FULL-BLEED - NOW THE ONE BEHAVIOR (Peter, 2026-09-09
   10:39 PM: "I like full bleed, by the way. Ruled.") ─────────────────────────
   The ninth pass built this as `?edge`, a rig variant so the two readings were
   one query apart. It is ruled: the box-measure track is retired and the switch
   with it, so there is one track and it spans the page.

   THE GEOMETRY IS ALL THAT CHANGES. The track keeps its row, its height and its
   vertical position; only the clip it runs inside is the full viewport rather
   than the box's own measure, centred on the same middle. The comet is the
   same `.runner` under the same phase classes, and `cross` already carries it
   from translateX(-100%) to translateX(294%) - one full comet-width off the
   left and one off the right - so at this width it leaves the page at one edge
   and arrives at the other with nothing added to the grammar.

   WHAT IT COSTS, and the cost is now paid rather than offered: `main` carried
   `contain:layout paint`, which clips everything painted inside it to the
   column. The paint containment has to come off for the band to cross the
   gutters, so it goes (the layout containment, the isolation and the
   compositing layer all stay). At the port that has to be weighed against the
   repaint the containment was bought to stop - it is named, not hidden. */
main{contain:layout}
.show .ctrack .cclip{left:50%; right:auto; width:100vw;
  transform:translate(-50%,-50%)}

/* ── THE CAPTION ROW (Peter, 5:27 PM) ───────────────────────────────────────
   "the status line right under that, so that it seems to be a caption for the
   moving indicator." The readout LEAVES the action row under the box and becomes
   the show's third row, directly beneath the light. It keeps its own face and its
   own ink - Roboto 400 at 14px, #333 - and is set on the paragraph's 29.7px line
   box so the three rows share one line spacing. Left, on the page's own margin,
   like everything else in the show.
   Two consequences, both named in the report: the readout is inside the show, so
   it is not on the page at all until the box is dark - the door at rest no longer
   says "Ready when you are" - and the action row beneath the box keeps only the
   composing hint. */
.show .status{grid-area:3 / 1 / 4 / 2; display:block;
  height:var(--showline); line-height:var(--showline)}
.show .status .words{height:var(--showline); line-height:var(--showline); min-width:0}
.show .status .words span{line-height:var(--showline)}
/* which set is speaking is still a function of the box's state and nothing else -
   read as a DESCENDANT now that the readout lives inside the box. */
.writebox.idle    .show .words.id,
.writebox.working .show .words.wk,
.writebox.cycle   .show .words.wk,
.writebox.writing .show .words.wr{display:inline-block}
/* THE READOUT GOES SILENT AT DELIVERY (Peter, 7:33 PM). The box itself is saying
   the reading is ready; "Ready when you are" underneath it at the same moment was
   the double message comp A flagged. With the tap back to idle struck (8:50 PM)
   the caption stays empty for the rest of the reading. */
body.delivered .writebox.dark .show .words.id{display:none}


/* ══ ELEVENTH PASS - THE FINISH IS TWO LINES, AND WE ARE DONE ══════════════
   Peter, 2026-09-09 10:15 PM: "'Your report is ready!' comes above the buttons by
   a line. Its text is replaced by the word 'download' and then the arrow; both
   'download' and 'view' are live links, but text-decoration none. I want circling
   prismatic colors on both the circle-arrow and the circle-P. No shuttle light
   once the buttons appear. We're not 'ready' anymore; we're done."

   LINE 1 is the sentence alone, in the paragraph's own face at the head's 26px on
   the head's own 29.7px line box - so the finish's first line is set exactly like
   every headline that came before it.
   LINE 2 is the two pairs. Each pair is ONE LIVE LINK - the word and its circle
   together - so the whole of it is the control and there is nothing dead to press.
   The words carry no underline at rest, on hover or on focus; hover darkens the ink
   (the page's own `.act:hover`) and a keyboard focus draws a ring AROUND THE PAIR,
   which is where the ruling puts the signal now that the underline is gone. (The
   page's older law - "focus is not suppressed, it MOVES: the mark's grey ring
   carries it" - still fires on the download mark underneath; the two read as one
   focused control. That is the reading taken and the report names it.)
   Each pair is `nowrap`; the only break the line allows is BETWEEN them (a `<wbr>`),
   so at phone width the second pair drops whole rather than a circle being orphaned. */
/* FIFTEENTH PASS (Peter, 11:02 AM: "A line space between the head and the buttons").
   ONE EMPTY LINE BOX OF THE SHOW'S OWN LINE HEIGHT - 29.7 under 900, 35.1 above -
   carried as the head's bottom margin, so the gap is the show's own `--showline`
   token rather than a second number that could drift from it. It is a margin and not
   an empty element: the pair line's top minus the head's floor is then exactly one
   line, which is what the ruling asks and what check 6 measures.

   IT DOES NOT MOVE THE LIGHT. Every slide is stacked in row 1 of the show's grid and
   the row is as tall as the tallest of them, so a finish that grew past the tallest
   tour slide would take row 1 - and the comet's track under it - down with it, against
   the 12:57 AM ruling that the light never moves. Measured with the line in: the
   finish stands 89.06 at 390 and 768 against a tallest slide of 118.75 and 89.06, and
   99.88 at 1280 against 105.28 - inside the row at all three widths, by 29.69, 0.00
   and 5.40px. Three lines of 29.6875 (Chromium lays 29.7 out on its 1/64 grid) come
   to exactly what the three-line slide at 768 comes to, so the row does not move by a
   pixel there either - but there is NO margin left at 768, and the report names it:
   one more line at the finish would take the light down with it at that width. */
.show .slide.rdy .shl{display:block; white-space:nowrap;
  margin-bottom:calc(var(--showline) / 2)}   /* Peter, 2026-09-11 12:21 AM: the space between the headline and the buttons halved */
.show .slide.rdy .ssb{display:block; white-space:normal}
.show .slide.rdy .ssb .acts,
.show .slide.rdy .ssb .fview{white-space:nowrap}
.show .slide.rdy .ssb .fview{margin-right:24px}   /* the third pair follows it (Peter, 09-11 9:42 PM) */
/* TIGHTEN AT THE PHONE (Peter, 2026-09-11 10:07 PM, on the third pair wrapping at 390:
   "Tighten. At phone width the gaps between pairs go from 24 to about 16 pixels so
   three fit on one line."): under the show's own 900 breakpoint the two gaps are 16. */
@media (max-width:899px){
  .show .slide.rdy .ssb .acts,
  .show .slide.rdy .ssb .fview{margin-right:16px}
}
/* THIRTEENTH PASS (Peter, 2026-09-10 1:45 AM): "'Download' and 'view' seem light -
   I'd like those at least the same weight as the headlines, a little bit smaller."
   The show's text is Roboto 600; the pair words take that weight at 22px, which is
   "a little bit smaller" than the 26 the desk's show reads at. The 300 is struck.
   The size does not move with the show's own breakpoint: 22 is already the smaller
   of the two, and at 390 the pair line is the one line that must not grow. */
.show .slide.rdy .ssb a{font:400 14px/1.35 Roboto,Helvetica,Arial,sans-serif;   /* 1:20 AM: 14px, 400 */   /* 12:32 AM: wgt 500 / 18px */   /* 10:48 AM: 400 for all */
  color:#333; text-decoration:none; font-synthesis:none}
.show .slide.rdy .ssb a:hover,
.show .slide.rdy .ssb a:focus{text-decoration:none}
/* THE RING GOES AROUND THE PAIR, and it has to say `!important` to get there: the
   page's older focus law - written for the naked mark in the action row, where there
   were no words to ring - suppresses the outline on `.act` with an `!important` of
   its own ("the outline goes, and the mark's grey ring carries it instead"). The
   eleventh pass's own line puts the signal on the PAIR now that the underline is gone
   from the words, so the strike is lifted here and only here; the mark's grey ring
   still comes up underneath, the same one hover shows. Both pairs are rung alike. */
.show .slide.rdy .ssb a:focus-visible{outline:2px solid rgba(3,3,3,.38) !important;
  outline-offset:4px; border-radius:4px}
/* the download pair flows INLINE, like the view pair, so its word and its circle sit
   on the pair line as one run of text with the mark 12px after the words - the same
   12px the monogram takes, measured off where the words actually end. */
body.delivered .show .slide.rdy .ssb .acts{display:inline; animation:dnlin .3s ease both}
@media (prefers-reduced-motion:reduce){
  body.delivered .show .slide.rdy .ssb .acts{animation:none}
}
/* THE ARROW GLYPH AT 18/26 OF ITS CURRENT SIZE: 18 x 0.692308 = 12.4615. The glyph is
   an SVG on a 24-unit viewBox, so its 2.3 stroke scales with the box and the drawing
   keeps its weight. The rule is scoped to the DOWNLOAD pair alone: the page's own
   `.pmark svg` is 18px everywhere, and the monogram next to it is a `.pmono` whose
   size is set on its own rule, which this must not reach past. */

.show .slide.rdy .act.dnl{display:inline; gap:0}
.show .slide.rdy .act.dnl .pmark{margin-left:12px; vertical-align:middle}

/* ── BOTH CIRCLES CARRY THE BAND ─────────────────────────────────────────────
   "I want circling prismatic colors on both the circle-arrow and the circle-P."
   The monogram is wrapped in the page's own `.pmark`, so it inherits the band, the
   `busy` circling and the one answer without a second mechanism being written: the
   same `::before`, the same conic gradient, the same `lap`, started in the same
   frame as the arrow's - two lights, same pace, in step.

   THE BAND LANDS ON THE P'S OWN RING, which is what keeps it one circle and not two.
   The arrow's band is `inset:-3px; padding:3px` on a 26px mark whose 3px border leaves
   a 20px padding box, so the band's own box comes back out to 26 across and the ring
   is 3px wide on that - MEASURED IN PIXELS, not assumed (the ::before painted solid and
   read off a 4x capture: 26.0 x 26.0). The monogram draws its own ring INSIDE the same
   26px box: r = 11.6 of a 26-unit viewBox at 26px, stroke 2.4, so the painted annulus
   runs from radius 10.4 to 12.8. `inset:.2px; padding:2.4px` puts the band on exactly
   that annulus - 25.6 outer, 2.4 wide - so while it circles the spectrum sits precisely
   over the grey line and the letter stays grey inside; when it rests after its press the
   grey circle-P is simply there again, with nothing having moved. THE TWO BANDS
   therefore stand 26.0 and 25.6 across - the same circle to within half a pixel - and
   the arrow's line is 0.6px the thicker. THE NUMBER LEFT TO RULE, and a correction:
   the harness has been reporting the arrow's band as 3px on an outer 32 since the ninth
   pass, because it computed the outer as the mark plus twice the band rather than
   measuring it; that 3/32 is what the P's ring was matched to at 10:08 PM. The band is
   really 3 on 26. A true match would put the P's stroke at 3.03 rather than 2.4. The
   ruled 2.4 is left exactly as it stands; the report names the correction. */
.show .slide.rdy .pmark.pv{width:26px; height:26px; border:0;
  margin-left:12px; vertical-align:middle}
/* FIFTEENTH PASS - THE BAND STAYS ON THE P'S OWN RING at the new size. The .2 / 2.4
   that put the band exactly on the drawn annulus at 26px are the same two numbers
   through 18/26: the ring at 18px runs from radius 7.2 to 8.861, and inset .13846
   with a 1.66154 padding puts the band's annulus on it to the same fraction of a
   pixel it held before. Nothing about the relationship moves; the whole circle is
   0.692308 of the one Peter saw. */
.show .slide.rdy .pmark.pv::before{inset:.2px; padding:2.4px}

/* ── THE ARROW SITS IN A GREY RING TOO (Peter, 2026-09-10 1:45 AM: "I want the
   arrow to have a gray circle around it, like the circle-P") ────────────────
   Up to here the arrow's mark carried `border:3px solid transparent` - a ring that
   only appeared on hover - so at rest the arrow was a bare glyph beside a drawn
   circle-P. It gets the P's own ring: THE SAME INK and THE SAME STROKE-TO-DIAMETER
   PROPORTION, at rest.

   THE INK is `currentColor`, which is exactly how the P's ring takes its own
   (`.pmono circle{stroke:currentColor}`): both read the pair link's #333, and both
   darken together under the page's `.act:hover{color:#030303}`. Neither can acquire
   a hue.

   THE PROPORTION is the P's, measured off what the P paints: r = 11.6 of a 26-unit
   viewBox at 26px with a 2.4px stroke, so the annulus runs 10.4 to 12.8 and the
   outer diameter is 25.6 - 2.4 / 25.6 = 3/32 = 0.09375. The arrow's ring stands on
   an outer 26 (the mark's own box), so the line that carries the same proportion is
   26 x 3/32 = 2.4375px. The two rings therefore stand 26.0 and 25.6 across with
   2.4375 and 2.40 of line on them - the same circle and the same weight of line to
   within half a pixel.

   IT IS PAINTED, NOT BORDERED, AND THAT IS FORCED. The obvious way - the mark's own
   `border`, coloured instead of transparent - CANNOT hold the number: Chromium
   snaps a border's used width to whole pixels, so `2.4375px` computed as 2 and the
   ring came out at 2/26 = 0.077, a fifth thinner than the P's. Measured, not
   assumed. An inset box-shadow is painted with subpixel precision, so the ring is a
   `::after` carrying `inset 0 0 0 2.4375px`.

   AND IT SITS ON THE BAND'S OWN ANNULUS. The `::after` takes exactly the box the
   prismatic `::before` takes - `inset:-3px` off the mark's padding box, which is the
   mark's own 26 across - so when a press spends the band the spectrum is painted
   over the grey line rather than beside it. That is precisely the relationship the
   P already has between its drawn ring and its band, which is what keeps each of
   them ONE circle and not two.

   THE MARK'S BOX DOES NOT MOVE. The border stays 3px and only loses its hover
   colour here, so the padding box, the band's geometry and the 18px glyph centred
   in it are all exactly as they were: "the arrow glyph inside unchanged". Hover now
   does on the arrow what it does on the P - it moves the INK (`.act:hover` takes
   the pair to #030303 and both rings are `currentColor`) rather than lighting a
   second, differently-sized ring underneath. */
.show .slide.rdy .ssb .act.dnl .pmark{border-color:transparent}
/* FIFTEENTH PASS - THE RING KEEPS ITS PROPORTION, WHICH IS THE POINT OF SCALING IT.
   The ruled line is 3/32 of the circle it stands on. At 18px the band's box is 18
   across (14 of padding box + 2 + 2), so the line that carries 3/32 is 18 x 3/32 =
   1.6875px - 2.4375 x 0.692308 to the fourth place, which is the scale arriving at
   the same number from the other direction. The `::after` still takes the band's own
   box (inset -2 now, off a padding box the 2px border leaves), so a press still
   paints the spectrum OVER the grey line rather than beside it. */
.show .slide.rdy .ssb .act.dnl .pmark::after{content:""; position:absolute;
  inset:-3px; border-radius:50%; pointer-events:none;
  box-shadow:inset 0 0 0 2.4375px currentColor}

/* ── THE MARKS ANSWER THE HAND (Peter, 2026-09-10 10:35 AM: "On hover with prism,
   we should see the prism circle move; on click it should speed up and then fade
   to gray") ─────────────────────────────────────────────────────────────────────
   Three states, one band, no new mechanism:

     REST      both circles grey, their bands unspent - the thirteenth pass's law.
     HOVER     the band appears and TURNS at the busy pace (1.4s a lap, the same
     / FOCUS   pace the download's status circles at), so the mark says "this is
               live" the moment the hand is on it. Keyboard focus does the same
               thing, because a mark that only answers a pointer is a mark half
               the Person's keyboard cannot see.
     PRESS     the band SPEEDS UP and fades to grey. Read the block below for
               what that is now: the 1:20 AM build put the page's own answer here
               (three laps at .85s with the fade riding the third) and the
               SIXTEENTH PASS, REWRITTEN made it actually run.

   `spent` IS STRUCK (Peter, 2026-09-11 1:37 PM: "Download and view buttons can be
   re-invoked, actions repeated until a new session started"). The fourteenth pass
   held the mark grey under the hand that pressed it; the ruling asks for the
   opposite - the answer ends and the button is live again, so the hand still on it
   is a hover and the band comes back up. */
/* AND THE SPECTRUM HAS TO BE ON TOP OF THE GREY LINE, NOT UNDER IT. Both rings are
   pseudo-elements of the same mark and both are positioned, so they paint in tree
   order: the grey `::after` the thirteenth pass added for the arrow was painting OVER
   the prismatic `::before`, covering 2.4375 of the band's 3px and leaving a sliver.
   Nothing showed it until this pass asked the band to be seen - at rest the band is
   at opacity 0, and the pressed still is the first picture ever taken of it lit
   (`press_1280_pass14.png` before this line: a dark ring, not a spectrum). One
   `z-index` puts the band back on top, which is the relationship the thirteenth pass
   described and the P already had. The glyph is untouched: the band is masked to its
   own annulus and paints nothing inside it. */
/* AND THE BAND'S BOX SCALES WITH THEM, so hover and press paint the same
   relationship at the new size: inset -2 off the 14px padding box gives an outer of
   exactly 18, and the band's own width is 3 x 0.692308 = 2.0769 - the same 3/26 of
   the circle the 26px mark carried (0.115385 both). */
.show .slide.rdy .ssb .act.dnl .pmark::before{z-index:1;
  inset:-3px; padding:3px}

/* ── SIXTEENTH PASS, REWRITTEN (Peter, 2026-09-11 1:37 PM: "Still experiencing
   awkward sudden stop. Buttons should respond like this: Rest - grey button.
   Hover - prism colors and motion, (alpha) fade to resting off hover. On click -
   invoke action, prism colors and fast motion, cycle 3 times, no stopping of
   motion, fade to grey circle. Download and view buttons can be re-invoked,
   actions repeated until a new session started.") ────────────────────────────
   THE 1:20 AM BUILD HAD THREE FAULTS AND THE SUDDEN STOP IS ALL THREE, MEASURED:

   1. THE FADE NEVER RAN. `opacity:1 !important` sat in the same rule as the
      `fadeout` animation, and an !important author declaration outranks an
      animation in the cascade - so the fade animated nothing. The band held
      full color for the whole answer and was then switched off in one frame by
      `spent`'s `opacity:0 !important`. That is the blink.
   2. THE THREE LAPS WERE CUT SHORT. `lap` is the hover animation's name too, so
      changing the rule RETIMED the running animation instead of restarting it:
      it kept its current time (already ~1.1s in from the hover) and ran out
      1.4s after the press, not 2.55s. The band then stood STILL at 0deg, in
      full color, for the last 1.1s of the answer. That is the hard stop.
   3. THE ANGLE JUMPED AT THE PRESS. Retimed from a 1.4s lap to a .85s one, the
      band went 282.8deg -> 113.2deg in one frame.

   THE CURE IS ONE IDEA: THE BAND'S ANGLE IS CARRIED, NOT RESET. `--a0` holds
   where the band is; every turn runs FROM it, so a press picks the rotation up
   exactly where the hover left it. The hover turn and the press turn are two
   NAMES for one movement (`hoverturn`, `lapon`) precisely so a class change
   RESTARTS the animation at `--a0` instead of retiming the old one. The press
   turn fills `forwards`, so its three laps land back on `--a0` and the hover
   turn resumes on the same degree. And nothing carries `!important` on opacity
   any more: the fade is free to paint, riding the third lap, and the motion is
   still running underneath it when the color reaches zero. */
@property --a0{syntax:"<angle>"; inherits:true; initial-value:0deg}
@keyframes hoverturn{from{--a:var(--a0)} to{--a:calc(var(--a0) + 360deg)}}
@keyframes lapon{from{--a:var(--a0)} to{--a:calc(var(--a0) + 360deg)}}
/* the page's own 300ms, the ramp's timing: off hover the color FADES to rest,
   it does not cut. (The 180ms is the folds' and the entry button's; the finish's
   two marks are the only ones this pass rules on.) */
.show .slide.rdy .pmark::before{transition:opacity .3s ease}
/* `lit` is on the mark while the hand is on it AND for the length of that fade
   after it leaves, so the band is still TURNING while its color goes out - the
   ruling's "no stopping of motion" read at the leave as well as at the press.
   The class is the only thing CSS cannot do for itself here: `:hover` ends in the
   frame the pointer leaves, and an animation declared on it ends with it. */
.show .slide.rdy .pmark.lit::before{animation:hoverturn 1.4s linear infinite}
.show .slide.rdy .act.dnl:hover .pmark::before,
.show .slide.rdy .act.dnl:focus-visible .pmark::before,
.show .slide.rdy .fview:hover .pmark::before,
.show .slide.rdy .fview:focus-visible .pmark::before{opacity:1}
/* AND THE HAND STILL ON IT IS A HOVER AGAIN. The answer ends at opacity 0 and the
   button is live (`spent` is struck), so the band has to come back up - and come
   back up the way hover always lights it, over the page's 300ms, not in one frame.
   Removing the answer's animation does not hand the job to a transition: Chromium
   paints the cascade's `opacity:1` in the next frame and the fade Peter just watched
   is undone in a blink (measured; REPORT.md section 24). So the re-light is an animation of
   its own - `rampin`, the page's own fade-in - riding the hover turn, which stays at
   index 0 of the list so removing this class leaves it running and the angle whole.
   Written on the hover selectors so a mark the hand has LEFT cannot light itself. */
.show .slide.rdy .act.dnl:hover .pmark.rearm::before,
.show .slide.rdy .act.dnl:focus-visible .pmark.rearm::before,
.show .slide.rdy .fview:hover .pmark.rearm::before,
.show .slide.rdy .fview:focus-visible .pmark.rearm::before{
  animation:hoverturn 1.4s linear infinite, rampin .3s ease 1}
/* THE PRESS: three cycles of the band at the page's own answer pace (.85s), the
   fade riding the third. `fadeout` fills BOTH - backwards through the 1.7s delay,
   which is what holds the color up for laps one and two now that the rule no
   longer declares opacity at all - and forwards at the end, so opacity is 0 while
   the turn is still running and before any animation is taken away. */
.show .slide.rdy .pmark.answering::before{
  animation:lapon .85s linear 3 forwards,
            fadeout .85s linear 1.7s 1 both !important}
/* asked for no motion: hover shows the band, standing still; a press shows it and
   fades it out, with no lap under it. The ruling names both. */
@media (prefers-reduced-motion:reduce){
  .show .slide.rdy .pmark.lit::before,
  .show .slide.rdy .act.dnl:hover .pmark.rearm::before,
  .show .slide.rdy .act.dnl:focus-visible .pmark.rearm::before,
  .show .slide.rdy .fview:hover .pmark.rearm::before,
  .show .slide.rdy .fview:focus-visible .pmark.rearm::before{animation:none}
  .show .slide.rdy .pmark.answering::before{
    animation:fadeout var(--cyc,480ms) linear var(--cyc,480ms) 1 both !important}
}
/* THE PANEL'S MARKS CARRY THE SAME FOUR STATES (Peter, 2026-09-11 9:00 PM: "on
   first modal box, none of the buttons respond on hover"). The seventeenth pass
   read the band as ruled onto the finish alone; his hand says otherwise. Every
   rule above, said again for `.pdlg .act` - the same names, the same timings,
   nothing re-invented - and the marks are armed in the script with the finish's
   own arm(). */
.pdlg .act .pmark::before{transition:opacity .3s ease}
.pdlg .pmark.lit::before{animation:hoverturn 1.4s linear infinite}
.pdlg .act:hover .pmark::before,
.pdlg .act:focus-visible .pmark::before{opacity:1}
.pdlg .act:hover .pmark.rearm::before,
.pdlg .act:focus-visible .pmark.rearm::before{
  animation:hoverturn 1.4s linear infinite, rampin .3s ease 1}
.pdlg .pmark.answering::before{
  animation:lapon .85s linear 3 forwards,
            fadeout .85s linear 1.7s 1 both !important}
@media (prefers-reduced-motion:reduce){
  .pdlg .pmark.lit::before,
  .pdlg .act:hover .pmark.rearm::before,
  .pdlg .act:focus-visible .pmark.rearm::before{animation:none}
  .pdlg .pmark.answering::before{
    animation:fadeout var(--cyc,480ms) linear var(--cyc,480ms) 1 both !important}
}
/* THE DISMISS (Peter, 9:00 PM: "after an email is sent we need a dismiss X in the
   upper right corner to get rid of the modal and return to the previous page").
   Drawn on every page of the panel, not only the last: a panel with no visible
   way out is a trap on a phone, where there is no Escape key. The cross is the
   folds' own plus turned a quarter, in the quiet line's grey, black under the
   hand; a 44px target around a 14px mark. */
.pdlg .mx{position:absolute; top:6px; right:6px; width:44px; height:44px; margin:0;
  padding:0; border:0; background:transparent; color:#666; cursor:pointer;
  display:flex; align-items:center; justify-content:center; letter-spacing:0;
  transition:color .18s ease}
.pdlg .mx:hover{background:transparent; color:#030303}
.pdlg .mx svg{width:14px; height:14px; stroke:currentColor; stroke-width:2;
  stroke-linecap:round; fill:none}
.pdlg .mx:focus-visible{outline:2px solid rgba(3,3,3,.38); outline-offset:-6px;
  border-radius:50%}

/* ── THE PERSON'S VOICE, BEHIND THE FINISH (Peter, 2026-09-10 11:02 AM: "let's try
   fading in a conversation-style highlight gradient (the Person's voice) behind the
   buttons and the headline - not the status or the reset link") ──────────────────
   THE TREATMENT IS NOT NEW AND IS NOT INVENTED HERE. It is the full product's locked
   one, read out of `_resources/design/web_full/session_2026-08-30.html` at
   `.tv-ramp .turn.person::before` and named in DIALOGUE_LOCKED's type table ("the
   Person's turn | the same, on `export_report`'s white ramp - .7 to 0 over 465px"):
   a white ramp from `rgba(255,255,255,.7)` at the left to zero at 465px, the left
   corners rounded 10px and the right ones square, sitting behind a block padded
   12px 0 14px 16px. Every number below is that rule's, unchanged.

   WHAT IT SITS BEHIND. The finish is one element - `.slide.rdy` - holding line 1,
   the new empty line and the pair line, so the ramp is that element's own `::before`
   and the block the ruling describes needs no wrapper built for it. The caption row
   and "start another" are SIBLINGS in the show's other two grid rows and are not
   reached: measured at the finish, the ramp's floor stands 15.7px clear of the
   caption row's ceiling at 390 and 768 and 21.1px at 1280. It does lie over the top
   14px of the comet's own row, which is empty by then - the light goes out when the
   finish arrives ("we're done") - and that is the lock's own 14px of air beneath the
   last line, not a second object.

   THE PADDING IS ON THE RAMP, NOT ON THE BLOCK, AND THAT IS THE ONE DEPARTURE.
   Written the lock's way - 12px 0 14px 16px on the element itself - the finish would
   grow 26px taller than its own text, and because every slide shares row 1 of the
   show's grid and the row is as tall as the tallest of them, at 768 and 1280 the
   finish would become the tallest thing in it and take row 1, the comet's track and
   the caption down with it: measured, 26.0px at 768 and 20.6px at 1280, against the
   ruling of 12:57 AM that the light never moves and the room it runs in is held.
   So the 16px goes on the block (the type moves right by it, as the lock has it, and
   the ramp's own left edge holds the page's margin) and the 12 and the 14 go on the
   ramp as negative offsets - which paints the identical picture and costs the layout
   nothing. The alternative is one declaration: `padding:12px 0 14px 16px` on the
   block and the two offsets back to 0, with the light moving 26px at the finish.

   THE RAMP IS CUT TO THE BLOCK, as the lock's is cut to its column. The gradient is
   an absolute 465px, so at 1280 (688 of measure) and 768 (632) it has reached zero
   long before the right edge and there is nothing to cut; at 390 the box is 358 and
   the ramp is still at alpha 0.16 where it ends. That is the lock's own behaviour at
   a narrow measure and the number is in the report; extending the box past the
   measure is the other reading, and `main`'s containment (now `layout`, since the
   full-bleed track) would not clip it.

   IT FADES IN WITH THE FINISH, 300ms - the page's own `dnlin` timing, which the
   download pair already arrives on - and under reduced motion it is simply there. */
.show .slide.rdy{position:relative; z-index:0; padding-left:16px}
.show .slide.rdy::before{content:""; position:absolute; z-index:-1;
  left:0; right:0; top:-27px; bottom:-29px;   /* 1:20 AM: +15 top and bottom on the lock's 12/14 */
  border-radius:10px 0 0 10px;
  background:linear-gradient(to right, rgba(255,255,255,.7) 0,
                                       rgba(255,255,255,0) 465px);
  opacity:0}
body.delivered .show .slide.rdy::before{animation:rampin .3s ease both}
@keyframes rampin{from{opacity:0} to{opacity:1}}
@media (prefers-reduced-motion:reduce){
  body.delivered .show .slide.rdy::before{animation:none; opacity:1}
}

/* ── NO SHUTTLE LIGHT AT THE FINISH ("we're done") ───────────────────────────
   The comet and its three sine layers go dark the instant the finish arrives, and
   the caption goes silent with them - but the track's ROW keeps its 29.7px and the
   caption's row keeps its own, so nothing above or below moves. The light is out;
   the space it ran in is held. */
body.delivered .writebox.dark .show .ctrack .runner,
body.delivered .writebox.dark .show .ctrack .wave{opacity:0; animation:none}

/* ── START ANOTHER (Peter: "Do we need a reset button?") ─────────────────────
   Built for his eye, one word to strike. A quiet text link standing in the caption
   row's place - the row the readout has vacated by then - that puts the door back:
   the writing cleared, the lede and the Sample prompts fold back, the box empty and
   editable with the invitation returned, and the report's token dropped. It is one
   grid item in row 3 alongside the silent caption, so it costs the layout nothing.
   The alternative is a page reload, which does the same thing in one line of the
   Person's own browser chrome; the report weighs the two. */
.show .again{grid-area:3 / 1 / 4 / 2; justify-self:start; align-self:center;
  display:none; font:400 16px/var(--showline) Roboto,Helvetica,Arial,sans-serif;   /* Peter, 2026-09-11 8:13 PM: "'start another' becomes 'start a new session'; matches weight 400" */
  color:#666; text-decoration:none; cursor:pointer; font-synthesis:none}
body.delivered .writebox.dark .show .again{display:block}
.show .again:hover{color:#030303; text-decoration:none}
.show .again:focus-visible{outline:2px solid rgba(3,3,3,.38); outline-offset:4px;
  border-radius:4px; text-decoration:none}

/* ── THE MODAL: EVERY BUTTON ASKS BEFORE IT ACTS ─────────────────────────────
   (Peter, 2026-09-11 1:37 → 1:50 PM: "Explicit confirmation message/button on
   'Download', 'view it', or 'start another'." · "Every button opens it; otherwise
   we've missed the opportunity to capture a significant marketing resource if they
   just choose to download or view, and then leave." · "A third button that captures
   email addresses (send button). Second modal page for Full name / email / mobile
   (optional) fields… Sends an email link as a permanent record. No online account
   for Lite, but email marketing resource captured here.")

   IT IS A NATIVE <dialog> OPENED WITH showModal(), and that is the whole reason to
   use one: the browser itself makes the page behind inert, answers the Escape key
   and keeps Tab inside the panel — three behaviours that would otherwise be three
   pieces of script to write and to get wrong.

   ITS GROUND IS THE IDLE BOX'S OWN (ruled). `--field-fill` is the input's 45% white,
   read off the page's own dial rather than copied as a number, with the box's 10px
   corners and its hairline, so the panel reads as the input box lifted onto the page
   rather than as a second material. The type is #333, the show's ink.

   THE BACKDROP IS LEFT CLEAR — the desk's reading, Peter's to overrule. The page is
   very white and he has already struck one dark field over it ("it feels heavy
   handed with the big black field on the very white page", 09-09 12:33 AM), so the
   modality is carried by the panel and its own shadow, not by a veil.

   THE BUTTONS ARE THE PAGE'S OWN: `.act` with a `.pmark`, the same 26px circles the
   finish carries, the same grey rings, the same one answer on a press. They do NOT
   carry the finish's four-state band (the hover turn, the carried angle): that
   mechanism is ruled onto the two marks AT THE FINISH and nothing here asks for it
   to travel. */
.pdlg{width:min(34rem, calc(100vw - 32px)); box-sizing:border-box;
  padding:20px 20px 22px; border:1px solid rgba(3,3,3,.14);
  border-bottom-color:rgba(3,3,3,.28); border-radius:10px;
  background:rgba(255,255,255,.8); color:#333;   /* Peter, 09-11 9:08 PM: "set box to 80%" (was the input's 45%; 90% for a minute) */
  box-shadow:0 18px 48px rgba(3,3,3,.18)}
/* THE VEIL BEHIND IT IS THE SAME 45% WHITE, and it is there for one measured
   reason: at 45% alone the finish's own line reads straight through the panel and
   collides with its buttons (the still at 390 before this line: "Your report is
   ready!" crossing "Email me a link"). The backdrop takes the page's own dial, so
   what stands behind the panel is the door quieted by exactly the material the
   panel is made of - one number, `--field-fill`, in both places - rather than a
   grey scrim the page has never carried. Peter's to move. */
.pdlg::backdrop{background:rgba(255,255,255,.68)}   /* Peter, 09-11 9:00 PM: "a little bit less opaque - reduce alpha by 20%"; .85 less a fifth; the panel itself keeps --field-fill */
.pdlg [hidden]{display:none !important}
/* the door's own paragraph voice, in the show's ink */
.pdlg .mmsg{margin:0 44px 18px 0; color:#333;   /* the right margin clears the dismiss X (Peter, 9:08 PM) */
  font:300 14px/1.5 Roboto,Helvetica,Arial,sans-serif}
.pdlg .msent{margin:0 44px 18px 0; color:#333;
  font:400 22px/1.35 Roboto,Helvetica,Arial,sans-serif}
/* the three buttons sit on one line and wrap as a line of type does; each pair —
   its word and its circle — is kept whole, the way the finish keeps its own. */
.pdlg .mrow{display:flex; flex-wrap:wrap; align-items:center; gap:12px 24px}
.pdlg .act{white-space:nowrap; color:#333; text-decoration:none; gap:0;
  font:400 14px/1.35 Roboto,Helvetica,Arial,sans-serif; font-synthesis:none}
.pdlg .act .pmark{width:26px; height:26px; margin-left:12px; vertical-align:middle;
  border-color:transparent}
/* the arrow's grey ring, painted not bordered — the thirteenth pass's line and its
   reason (Chromium snaps a border's used width to whole pixels and 2.4375 computes
   as 2), at the finish's own 3/32 of the circle it stands on. */
.pdlg .act .pmark::after{content:""; position:absolute; inset:-3px;
  border-radius:50%; pointer-events:none;
  box-shadow:inset 0 0 0 2.4375px currentColor}
.pdlg .act .pmark::before{z-index:1}
/* the monogram draws its own ring, so it takes none from the rule above */
.pdlg .act .pmark.pv{width:26px; height:26px; border:0}
.pdlg .act .pmark.pv::after{display:none}
.pdlg .act .pmark.pv::before{inset:.2px; padding:2.4px}
.pdlg .act .pmono{display:inline-block; vertical-align:middle;
  width:26px; height:26px; flex:0 0 auto}
.pdlg .act .pmono circle{fill:none; stroke:currentColor; stroke-width:2.4}
.pdlg .act .pmono path{fill:currentColor; stroke:none}
/* the ring goes around the PAIR for the keyboard, as it does at the finish, and it
   has to say `!important` there too: the page's older focus law strikes the outline
   on every `.act`. */
.pdlg .act:focus-visible{outline:2px solid rgba(3,3,3,.38) !important;
  outline-offset:4px; border-radius:4px}
/* the quiet way out, in the type "start a new session" wears on this page
   (Peter, 8:13 PM: the link's own weight goes to 400 with its new words) */
.pdlg .mquiet{display:inline-block; margin-top:18px; color:#666;
  font:400 16px/1.35 Roboto,Helvetica,Arial,sans-serif;
  text-decoration:none; cursor:pointer}
.pdlg .mquiet:hover{color:#030303}
.pdlg .mquiet:focus-visible{outline:2px solid rgba(3,3,3,.38); outline-offset:4px;
  border-radius:4px}
.pdlg .mform{margin:0 0 4px; max-width:26rem}
.pdlg .mrow .mquiet{margin-top:0}

.writebox .ghost .ginvite{display:block}
.writebox.sent .ghost .ginvite{display:none}   /* the invitation does not return mid-run */

/* ── GLOBAL UNIFY (Peter, 2026-09-12 3:09 PM: "Global unify") ──────────────────
   Every 28px mark answers a hover the way the finish's three do (the sixteenth
   pass, 09-11): rest gray · hover the prismatic band turning at 1.4s · off over
   .3s with the band still turning · press the answer. The entry (send) and the
   folds' plus join here; the modal's marks already carry it. The grey ring the
   08-27 ruling gave these two on hover is superseded: the band replaces it.
   The script arms them with the finish's own arm(), so `lit` outlives the
   pointer by the fade exactly as it does on the finish. */
.entry::before{transition:opacity .3s ease}
.entry.lit:not(.answering)::before{animation:hoverturn 1.4s linear infinite}
.entry:hover::before,.entry:focus-visible::before{opacity:1}
.entry:hover,.entry:focus-visible,.entry.lit{border-color:transparent !important}
summary .pmark::before{transition:opacity .3s ease}
summary .pmark.lit::before{animation:hoverturn 1.4s linear infinite}
summary:hover .pmark::before,summary:focus-visible .pmark::before{opacity:1}
summary:hover .pmark,summary:focus-visible .pmark,summary .pmark.lit{border-color:transparent}
@media (prefers-reduced-motion:reduce){
  .entry.lit:not(.answering)::before,summary .pmark.lit::before{animation:none}
}

/* ── READ BUT NOT SEEN (Contract 2b, 2026-09-12) ─────────────────────────────
   The show is a picture of words: a reader who cannot see the crossfade hears
   nothing of it at all, and a link that opens a new window gives no warning that
   it will. `.vh` is this page's one way to put a phrase in the document for a
   screen reader and nowhere on the screen - taken out of flow and clipped to a
   pixel, so it moves no mark on the page at rest (the suite's check 9 reads
   exactly that). */
.vh{position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap;
  border:0}
/* ── THE PORT'S TWO ADDITIONS (Contract 1y §4) ────────────────────────────────
   The consent line and the sentence the panel says when a send does not go. Both
   sit on page two of the panel; nothing above this line is touched. */
.pdlg .mconsent{display:flex; align-items:flex-start; gap:9px; margin:0 0 18px;
  font:400 13px/1.4 Roboto,Helvetica,Arial,sans-serif; color:#333; cursor:pointer}
.pdlg .mconsent input{flex:0 0 auto; width:15px; height:15px; margin:1px 0 0;
  accent-color:#333}
.pdlg .mconsent input:focus-visible{outline:2px solid rgba(3,3,3,.38);
  outline-offset:2px}
/* the readout's ink, beneath the Send button */
.pdlg .merr{margin:14px 0 0; color:#333;
  font:400 13px/1.4 Roboto,Helvetica,Arial,sans-serif}
