/* =====================================================================
   brand.css: the look of the brand pages, ruled 7 to 11 Oct 2026.

   Who loads it: / (index.html), /why, /pricing, /sample (the list) and the papers under it, and the pages
   that keep their old body but wear the new nav and footer (/privacy,
   /terms, /dmca from tools/legal_from_vault.py, /feature-request, 404).
   Those last five load styles.css first for their sheet, then this file.
   The alpha page (/alpha) does NOT load this file: it keeps styles.css
   alone so it stays exactly as it was.

   Sections, in order: fonts, colours (light, then dark), base, buttons,
   site nav, site footer, landing page (index.html: the hero, then the document
   under the hero), hero figure (B7), /why, /pricing, /sample.

   Rules from the brand (vault webapp/Design_Language/latently-brand.json):
   paper, ink, one blue; STIX Two Text for display and body; IBM Plex Mono
   for labels, ids, buttons and the wordmark; no Caveat on these pages.
   The font subsets carry no arrows, No. sign or square root, so arrows are
   inline SVG in the markup.
   ===================================================================== */

/* ---------- fonts (self-hosted; STIX is a variable font, 400 to 700) ---------- */
/* fonts:start (written by tools/build_fonts.py; edit the script, not this block) */
@font-face{font-family:'STIX Two Text';font-style:normal;font-weight:400 700;font-display:swap;src:url("/fonts/stix-two-text.55459aaa.woff2") format('woff2')}
@font-face{font-family:'STIX Two Text';font-style:italic;font-weight:400 700;font-display:swap;src:url("/fonts/stix-two-text-italic.d8746899.woff2") format('woff2')}
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/ibm-plex-mono-400.69860749.woff2") format('woff2')}
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:500;font-display:swap;src:url("/fonts/ibm-plex-mono-500.e89188f3.woff2") format('woff2')}
@font-face{font-family:'Caveat';font-style:normal;font-weight:400 700;font-display:swap;src:url("/fonts/caveat.a1d7d1be.woff2") format('woff2')}
@font-face{font-family:'STIX Two Text Fallback';font-style:normal;font-weight:400;src:local('Georgia');size-adjust:96.02%;ascent-override:79.35%;descent-override:24.79%;line-gap-override:26.03%}
@font-face{font-family:'STIX Two Text Fallback';font-style:normal;font-weight:700;src:local('Georgia Bold'),local('Georgia-Bold');size-adjust:92.00%;ascent-override:82.83%;descent-override:25.87%;line-gap-override:27.17%}
@font-face{font-family:'STIX Two Text Fallback';font-style:italic;font-weight:400;src:local('Georgia Italic'),local('Georgia-Italic');size-adjust:90.44%;ascent-override:84.26%;descent-override:26.32%;line-gap-override:27.64%}
@font-face{font-family:'STIX Two Text Fallback';font-style:italic;font-weight:700;src:local('Georgia Bold Italic'),local('Georgia-BoldItalic');size-adjust:85.77%;ascent-override:88.84%;descent-override:27.75%;line-gap-override:29.15%}
/* fonts:end */
/* ---------- colours: paper, ink, one blue ---------- */
:root{
  --blue-100:#E7EFFE; --blue-200:#C7DBFD; --blue-400:#60A0F8; --blue-500:#3B82F6;
  --blue-600:#2563EB; --blue-700:#1E3A8A; --blue-900:#152348;
  --desk:#E7E4DB; --page:#FFFFFD; --line:#DDD9CC; --mut:#696557; --ink2:#3D3B33; --ink:#16150F;
  --err:#B3361C;            /* a form's error: 6.1:1 on the page, 5.6:1 on the desk */
  --link:var(--blue-600); --link-hover:var(--blue-700);
  /* type in the link blue: blue-600 a shade darker (same hue and chroma, OKLCH lightness .546 to .519), because blue-600
     measures 4.06:1 on the desk and AA asks 4.5:1 of type under 24px; this is 4.57:1 there and 5.81:1 on the page. Fills,
     edges and the focus ring keep --link. --mut was #6E6A5C (4.25:1 on the desk) for the same reason; it is now 4.59:1.
     The PDF drawn on the site keeps the old grey (--sheet-mut): on paper it measures 5.4:1 (review of 28 Oct 2026). */
  --link-text:#1D5AE2;
  --on-solid:#FFFFFD;            /* text on a solid blue button */
  --solid-hover:var(--blue-700);
  --line-hover-bg:var(--blue-100);
  --landed:var(--blue-200);      /* the flash on a place an in-page link lands on */
  --track:var(--blue-100);       /* the empty part of a progress bar */
  --pdf:#FAF8F1;                 /* the PDF mock stays paper in both themes */
  /* a PDF page drawn on the site stays paper in both themes: its page, ink and rule colours */
  --sheet:#FFFFFD; --sheet-ink:#16150F; --sheet-ink2:#3D3B33; --sheet-mut:#6E6A5C; --sheet-line:#DDD9CC;
  /* the motion palette (Ming, 22 Oct 2026): a duration or curve that more than one rule uses is named here once, so
     the next change is one edit. brand.js reads --t-glide and --t-fade (GLIDE_MS, PAPER_FADE_MS). Left as plain numbers
     on purpose, each used once and tuned by eye: .25s (the turn's cross-fade), .35s (the button wipe), .4s (the hero's
     fade in the opening) and the re-typeset's own timings. The re-typeset's lift keeps its own curve, --ease-lift:
     a hair quicker off the mark than --ease-out (at most 4.4% of the distance, 17ms), and the hero is the one moment
     Ming ruled final, so it is not folded into --ease-out. */
  --t-feedback:.15s;    /* a colour swap that answers a hover */
  --t-fade:.2s;         /* something fades in or out */
  --t-state:.3s;        /* a control appears or goes */
  --t-reveal:.6s;       /* something rises into the page */
  --t-glide:.9s;        /* something large moves to a new place */
  --t-landed:1.2s;      /* the flash on a place an in-page link lands on */
  --ease-out:cubic-bezier(.2,.7,.2,1);    /* quick off the mark, a long soft landing */
  --ease-lift:cubic-bezier(.2,.8,.2,1);   /* the re-typeset's lift only */
  --fs:'STIX Two Text','STIX Two Text Fallback',Georgia,serif;   /* the fallback is Georgia set to STIX's metrics (tools/build_fonts.py) */
  --fm:'IBM Plex Mono',ui-monospace,monospace;
  /* Greek letters are not in the STIX subset, so equations fall back to a math face */
  --fmath:'STIX Two Text','STIX Two Math','Cambria Math','Times New Roman',serif;
}
/* dark: same roles. Two blocks because a media query cannot see data-theme
   (the toggle in theme.js); the values must match in both. */
:root[data-theme="dark"]{
  --desk:#0B1322; --page:#13203B; --line:#2A3A5C; --mut:#8A92A6; --ink2:#C6C3B8; --ink:#EAE8E0;
  --err:#FF9A7B;            /* the same red, light enough for the dark desk: 9:1 */
  --link:#6FA6FF; --link-text:var(--link); --link-hover:#A9C8FF; --on-solid:#0B1322; --solid-hover:var(--blue-400);
  --line-hover-bg:#1B2B4D; --track:#22345A; --landed:#2F4876;
}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){
    --desk:#0B1322; --page:#13203B; --line:#2A3A5C; --mut:#8A92A6; --ink2:#C6C3B8; --ink:#EAE8E0;
    --err:#FF9A7B;            /* the same red, light enough for the dark desk: 9:1 */
    --link:#6FA6FF; --link-text:var(--link); --link-hover:#A9C8FF; --on-solid:#0B1322; --solid-hover:var(--blue-400);
    --line-hover-bg:#1B2B4D; --track:#22345A; --landed:#2F4876;
  }
}

/* ---------- base (scoped to .brand-page so the styles.css pages are not disturbed) ---------- */
.brand-page *,.brand-page *::before,.brand-page *::after{box-sizing:border-box}
.brand-page{margin:0;background:var(--desk);color:var(--ink);font-family:var(--fs);
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;overflow-x:clip}
/* :where() keeps these resets at zero specificity, so any class rule below wins */
:where(.brand-page) :where(h1,h2,h3,p,figure,ul,ol,dl,dd){margin:0}
:where(.brand-page) a{color:var(--link-text)}
:where(.brand-page) a:hover{color:var(--link-hover)}
.brand-page :focus-visible{outline:2px solid var(--link);outline-offset:3px}
.wrap{max-width:1100px;margin:0 auto;padding:0 20px}
.mono{font-family:var(--fm);font-size:11px;letter-spacing:.05em;line-height:1.6;color:var(--mut)}
.label{display:block;font-family:var(--fm);font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--mut)}
.visually-hidden{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}
.arrow{display:inline-block;vertical-align:middle;flex:0 0 auto}

/* ---------- buttons: mono, the brackets are in the text, square corners ---------- */
/* every button carries a 1px edge, seen or not, so a solid and a line button side by side (the 404 page) are the same
   size: a line button's edge is the one you see, a solid button's is its own blue (they were 2px apart, review of 28 Oct 2026) */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:44px;padding:12px 20px;border:1px solid transparent;
  font-family:var(--fm);font-size:12.5px;letter-spacing:.04em;line-height:20px;text-decoration:none;
  transition:background var(--t-feedback) ease,color var(--t-feedback) ease}
.brand-page a.btn-solid{background:var(--link);border-color:var(--link);color:var(--on-solid)}
.brand-page a.btn-solid:hover{background:var(--solid-hover);color:var(--on-solid)}
.brand-page a.btn-line{border:1px solid var(--link);background:var(--page);color:var(--link)}
.brand-page a.btn-line:hover{background:var(--line-hover-bg);color:var(--link-hover)}
/* the hover wipe (Ming, 13 Oct): on hover, or keyboard focus, a button fills from the left with its
   opposite (a solid one turns page-white with blue type and a blue edge, an outlined one turns blue),
   and leaves off the right edge when the pointer goes (brand.js job 7, below). The fill is a copy of the label in
   ::after (brand.js keeps data-label in step with the text), shown through a mask that slides right: a repeating
   train of bands, each 1.1 button widths long with as much gap, so the type changes colour exactly where a band has
   reached. One axis, one property, painted on the main thread: a clip-path did the same until 29 Oct 2026, and
   Chrome's compositor painted a retargeted one (the pointer gone while the wipe was still coming in) collapsing in
   the middle. Without script there is no data-label, and the plain colour change above stays. Touch screens never
   show it (brand.js). */
.btn[data-label]{position:relative;isolation:isolate}
.btn[data-label]::after{content:attr(data-label);content:attr(data-label) / "";position:absolute;inset:0;
  display:flex;align-items:center;justify-content:center;white-space:nowrap;pointer-events:none;
  background:var(--wipe-bg);color:var(--wipe-ink);box-shadow:inset 0 0 0 1px var(--link);
  -webkit-mask:linear-gradient(90deg,#000 50%,transparent 50%) 0 0/220% 100% repeat-x;
  mask:linear-gradient(90deg,#000 50%,transparent 50%) 0 0/220% 100% repeat-x;
  -webkit-mask-position:var(--wipe-x,91.6667%) 0;mask-position:var(--wipe-x,91.6667%) 0;
  transition:mask-position .35s var(--ease-out),-webkit-mask-position .35s var(--ease-out)}
@media (prefers-reduced-motion:reduce){.btn[data-label]::after{transition:none}}
/* the ways in and out (Ming, 29 Oct 2026): the train only ever moves right, by one step at a time (brand.js job 7 sets
   --wipe-x). The mask is 2.2 widths long, a band of 1.1 and a gap of 1.1, so a step of 1.1 widths is 91.6667% of
   mask-position (a percentage moves it by the 1.2 widths it is longer than the button). At 91.6667% a gap covers the
   button (at rest, nothing shows); at 0 a band does (shown); at -91.6667% that band has gone off the right and the
   next gap is over the button; at -183.333% the next band is in. So a pointer gone early sends the slide on and the
   band passes through and off; a pointer back while a band is still clearing sends it on again and the next band's
   front enters on the left the instant the last one's back leaves the right. The 0.1 width past each edge keeps the
   ease-out's slow tail from lingering as a sliver inside the border. :hover stops the moment the pointer goes, so the
   script drives it; .is-reset for one frame, once a slide has ended, puts the train back to its first position of the
   same kind with no motion, so the number never grows */
.btn[data-label].is-reset::after{transition:none}
.btn-solid,.doc-form button.btn{--wipe-bg:var(--page);--wipe-ink:var(--link)}
/* a solid button's edge is its border now, so the wipe draws none of its own: the wiped-in page-white sits in a 1px blue
   frame, exactly a line button at rest, not a 2px one */
.btn-solid[data-label]::after,.doc-form button.btn[data-label]::after{box-shadow:none}
.btn-line,button.turn-toggle{--wipe-bg:var(--link);--wipe-ink:var(--on-solid)}
/* under a wipe the plain hover colours stay put, so only the wipe moves */
.brand-page a.btn-solid[data-label]:hover,.doc-form button.btn[data-label]:hover{background:var(--link);color:var(--on-solid)}
.brand-page a.btn-line[data-label]:hover,.brand-page button.turn-toggle[data-label]:hover,.brand-page button.paper-next[data-label]:hover{background:var(--page);color:var(--link)}
.text-link{display:inline-flex;align-items:center;gap:8px;min-height:44px;padding:12px 6px;
  font-family:var(--fm);font-size:12.5px;letter-spacing:.04em;line-height:20px;text-decoration:none}
.brand-page a.text-link{text-decoration:none}

/* ---------- site nav ---------- */
.site-nav{position:relative;z-index:20;max-width:1100px;margin:0 auto;padding:26px 20px 22px;
  display:flex;align-items:center;gap:18px;font-family:var(--fs)}
.site-nav-brand{display:flex;align-items:center;margin-right:auto;transition:opacity var(--t-fade) ease}
.site-nav-brand:hover{opacity:.62}
.lockup{display:block;height:30px;width:auto}
:root[data-theme="dark"] .lockup{content:url(/assets/logo-lockup-dark.svg)}
@media (prefers-color-scheme: dark){:root:not([data-theme="light"]) .lockup{content:url(/assets/logo-lockup-dark.svg)}}
.site-nav-links{display:flex;align-items:center;gap:26px}
.site-nav-links a{display:inline-flex;align-items:center;min-height:44px;font-family:var(--fm);font-size:12px;letter-spacing:.05em;
  color:var(--ink2);text-decoration:none}   /* 44px tall to tap, and the bar keeps the height the burger gave it */
/* aria-current is "page" on the page itself and "true" on a page inside it (a sample under /sample) */
.site-nav-links a:hover,.site-nav-links a[aria-current],.site-nav-menu a:hover,.site-nav-menu a[aria-current]{color:var(--link-text)}
/* and underlined, so colour is not the only mark (the menu's [ alpha programme ] is blue whether current or not) */
.site-nav-links a[aria-current]:not(.site-nav-cta),.site-nav-menu a[aria-current]{text-decoration:underline;
  text-decoration-thickness:1px;text-underline-offset:5px}
/* the way into the alpha ends the bar as the site's call to action: a line button (see "buttons"), at the theme toggle's
   height, with the same wipe. The rules are the nav's own, not .brand-page's, so /alpha (no .brand-page) draws it alike.
   On /alpha it is filled, as a pressed button: you are here. In the phone's menu it is a row like the others, in blue */
.site-nav-links .site-nav-cta{min-height:34px;padding:6px 12px;border:1px solid var(--link);background:var(--page);color:var(--link);
  font-size:12px;letter-spacing:.05em;white-space:nowrap}
.site-nav-links .site-nav-cta[aria-current]{background:var(--link);color:var(--on-solid)}
.site-nav .theme-toggle{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;padding:0;
  border:1px solid var(--line);border-radius:8px;background:var(--page);color:var(--ink2);cursor:pointer;
  transition:color var(--t-feedback),border-color var(--t-feedback),opacity var(--t-reveal) ease .3s}
.site-nav .theme-toggle:hover{color:var(--ink);border-color:var(--mut)}
.site-nav .theme-toggle svg{display:block}
.site-nav .theme-toggle .i-sun{display:none}
:root[data-theme="dark"] .site-nav .theme-toggle .i-moon{display:none}
:root[data-theme="dark"] .site-nav .theme-toggle .i-sun{display:block}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .site-nav .theme-toggle .i-moon{display:none}
  :root:not([data-theme="light"]) .site-nav .theme-toggle .i-sun{display:block}
}
.site-nav-burger{display:none;flex-direction:column;justify-content:center;align-items:center;gap:5px;
  width:44px;height:44px;margin-right:-8px;padding:0;background:none;border:0;cursor:pointer}
.site-nav-burger span{display:block;width:22px;height:2px;background:var(--ink);transition:transform var(--t-fade),opacity var(--t-fade)}
.site-nav-burger[aria-expanded="true"] span:nth-child(1){transform:translateY(7px) rotate(45deg)}
.site-nav-burger[aria-expanded="true"] span:nth-child(2){opacity:0}
.site-nav-burger[aria-expanded="true"] span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}
@media (prefers-reduced-motion:reduce){.site-nav-burger span{transition:none}}   /* the bars swap to an X at once: turning is movement */
/* the menu: on a desk every link is in the bar, so the burger and its menu are a phone's (820px and under), where the
   menu is a sheet under the bar with every link, in the bar's order */
.site-nav-menu{display:none}
@media (max-width:820px){
  .site-nav{padding:18px 20px 16px;gap:10px}
  .site-nav .lockup{height:26px}
  .site-nav-links{display:none}
  .site-nav-burger{display:flex}
  .site-nav-menu{position:absolute;top:100%;left:0;right:0;flex-direction:column;padding:4px 20px 18px;background:var(--desk);
    border-top:1px solid var(--line);border-bottom:1px solid var(--line);box-shadow:0 10px 20px rgba(22,21,15,.10)}
  .site-nav.is-open .site-nav-menu{display:flex}
  .site-nav-menu a{font-family:var(--fm);font-size:13px;letter-spacing:.05em;color:var(--ink2);text-decoration:none;padding:14px 0;
    border-bottom:1px solid var(--line)}
  .site-nav-menu .site-nav-cta{color:var(--link-text)}
}

/* the nav comes back when the reader scrolls up (brand.js job 12; Ming, 23 Oct 2026, sample papers included the same
   evening). On every page it sticks to the top of the window: brand.js sends it away (.is-away) once the reader has
   scrolled down past it, and brings it back after a deliberate scroll up, over --t-state; a jump sends it away at once.
   Only the transform moves, so nothing in the layout shifts. Once the page has moved (.is-floating) it is over the page, so it gets its own background, reaching both
   edges of the window, and a hairline under it; at the top it stays clear, as before (the desk opening draws the hero
   large right under it). With reduced motion it comes and goes without sliding.
   The background is a border image drawn outside the nav's box: 100vw to each side and 1px below, the desk colour with
   the hairline's colour in the last pixel. What a border image draws outside the box is paint, not layout, so the page
   never grows wider: a box 100vw wide did, because 100vw counts the scrollbar, and on a desk whose scrollbar takes room
   (Windows) every page scrolled 8px sideways (check_nav, red first). */
.site-nav.can-reveal{position:sticky;top:0;transition:transform var(--t-state) var(--ease-out)}
.site-nav.can-reveal.is-floating{border-image:linear-gradient(var(--desk) calc(100% - 1px),var(--line) 0) fill 0//0 100vw 1px}
.site-nav.can-reveal.is-away{transform:translateY(calc(-100% - 1px))}
@media (prefers-reduced-motion:reduce){.site-nav.can-reveal{transition:none}}

/* ---------- site footer: a colophon at the foot of the desk ---------- */
.site-footer{border-top:1px solid var(--line);font-family:var(--fs);background:var(--desk);color:var(--ink)}
.site-footer-inner{max-width:1100px;margin:0 auto;padding:44px 20px 0;display:flex;flex-wrap:wrap;gap:32px 48px}
.site-footer-brand{flex:2 1 240px}
.site-footer-brand a{display:inline-block}
.site-footer-brand .lockup{height:36px}
.site-footer-tagline{margin:14px 0 0;font-family:var(--fm);font-weight:400;font-size:13px;letter-spacing:.01em;line-height:1.4;color:var(--ink)}
.site-footer-col{flex:1 1 150px;display:flex;flex-direction:column;align-items:flex-start;gap:2px}
.site-footer-col .label{margin-bottom:6px}
.site-footer-col a{font-family:var(--fm);font-size:13px;color:var(--ink2);text-decoration:none;padding:6px 0}
.site-footer-col a:hover{color:var(--link-text)}
/* the legal links stand one under the other, as Site and Contact do beside
   them; the dots in the markup only separate a row, so a column hides them */
.site-footer-legal{display:flex;flex-direction:column;align-items:flex-start;gap:2px;font-family:var(--fm);font-size:13px;color:var(--mut)}
.site-footer-legal span[aria-hidden="true"]{display:none}
.site-footer-col-legal{flex-basis:190px}
@media (max-width:640px){.site-footer-inner{column-gap:32px}}
.site-footer-copy{max-width:1100px;margin:0 auto;padding:28px 20px 34px;font-family:var(--fm);font-size:11px;
  letter-spacing:.06em;line-height:1.6;color:var(--mut)}
/* Blockworks: the one link in the line, so it is underlined to read as a link among muted text */
.site-footer-copy a{color:inherit;text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:3px}
.site-footer-copy a:hover{color:var(--link-text)}

/* hero, text column (the figure has its own block further down) */
/* as much air above the hero as under it, at every width (Ming, 13 Oct) */
.hero{max-width:1100px;margin:0 auto;padding:64px 20px;display:flex;align-items:center;gap:36px}
.hero-text{flex:1 1 0;min-width:0;display:flex;flex-direction:column;gap:20px}
.hero h1{font-size:52px;font-weight:700;letter-spacing:-.015em;line-height:1.08;max-width:480px}
.hero-sub{font-size:18px;font-style:italic;line-height:1.5;color:var(--ink2);max-width:440px}
.hero-actions{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px;margin-top:4px}
.hero-figure-col{flex:0 0 590px;min-width:0}
@media (max-width:1059px){
  .hero{display:block;padding-top:56px;padding-bottom:56px}
  .hero-text{max-width:620px}
  .hero-figure-col{margin-top:44px}
}
/* phones: the figure comes straight after the buttons, so the PDF page is on the
   first screen. display:contents lets the text column's children and the figure
   share one flex column, so one gap spaces them all. */
@media (max-width:640px){
  .hero{display:flex;flex-direction:column;align-items:stretch;gap:18px;padding-top:56px}
  .hero-text{display:contents}
  .hero h1{font-size:clamp(34px,10vw,44px);line-height:1.06}
  .hero-sub{font-size:17px}
  .hero-actions{margin-top:0}
  .hero-figure-col{flex:none;margin-top:14px}
}
/* the hero's longest label, "look at our alpha programme", is 288px wide and a 320px screen leaves 280 */
@media (max-width:340px){.hero-actions .btn{padding-left:14px;padding-right:14px}}

/* ---------- the document under the hero ----------
   Under the hero the landing page reads as one piece of writing, the way the
   app sets a paper: running prose in STIX, no labels, no rules between
   passages, no bands, and a heading only where the content needs one.
   A passage shares the hero's box (1100px, 20px gutter), so its text starts
   on the hero's left edge on purpose: one document, one edge, and the pace
   changes inside it. The space under a passage is all that separates it
   from the next, as between the sections of an essay.
   Order: the maze, with Latently, why Latently exists, the call. */
.passage{max-width:1100px;margin:0 auto;padding:0 20px 96px}
/* on a desk the last two passages stand side by side, the position on the left and the call on the right, in the
   hero's box (Ming, 29 Oct 2026: one under the other left the right half of a wide window empty). Each keeps its own
   type and air below; the call drops the air above it so the two start level. Under 900px they stack as before */
@media (min-width:900px){
  .passages{max-width:1100px;margin:0 auto;padding:0 20px;display:grid;grid-template-columns:1fr 1fr;column-gap:64px;align-items:start}
  .passages .passage{max-width:none;margin:0;padding-left:0;padding-right:0}
  .passages .call{padding-top:0}
}
/* -6px is the link's own padding, so its text starts on the column's edge */
.section-link{margin-top:14px;margin-left:-6px}
@media (max-width:640px){
  .passage{padding-bottom:72px}
}

/* the position, the first paragraph under "Why Latently exists".
   The measure is in em so the lines break in the same places at every
   width from 375px up: 16em sits between the longest line it must hold
   ("Latently sets the paper so you can", 15.4em) and the first line it must
   not ("Assistants read it for you. Latently", 16.9em), so it always reads
   two short sentences, then the long one. The three subjects are set in
   small caps (the font's own smcp and c2sc), the typesetter's lead-in. */
.opening{max-width:16em;font-size:30px;line-height:1.4;color:var(--ink)}
/* the three lead words are the passage's three parties: small caps, bold and a little more tracked, so the
   difference is seen at a glance (Ming, 29 Oct 2026: the small caps alone were not obvious enough) */
.opening-lead{font-variant-caps:all-small-caps;font-weight:700;letter-spacing:.06em}
/* the third lead is the name, in the mono the wordmark sets it in (Ming, 29 Oct 2026; the lockup itself was tried and
   not kept): as written, not small caps, at .85em so its x-height matches the serif's beside it, and the mono's medium */
.opening-brand{font-family:var(--fm);font-variant-caps:normal;font-weight:500;font-size:.85em;letter-spacing:.02em}
@media (max-width:640px){
  .opening{font-size:21px;line-height:1.45}  /* 21px, so the lines hold on a 375px phone too */
}

/* a section header: a name, one step above the lead sentences under it */
.section-head{margin:0 0 36px;font-size:36px;font-weight:600;letter-spacing:-.012em;line-height:1.15;color:var(--ink)}
@media (max-width:640px){.section-head{margin-bottom:28px;font-size:28px}}

/* ---------- a beat: a lead sentence on the left and its picture on the right ----------
   The maze is the one beat; "with Latently" below reuses its text styles.
   The lead sits on the hero's left edge (110px at 1280). No rules, no
   labels, no bands. Below 900px a beat is one column, text then picture.
   Each .beat-figure is a size container, so a picture can scale to the
   room it is given (cqw). */
.beat{max-width:1100px;margin:0 auto;padding:0 20px 72px;display:grid;grid-template-columns:minmax(0,1fr);row-gap:24px}
.beat-text,.beat-figure{min-width:0}
.beat-figure{container-type:inline-size}
.beat-lead{max-width:16em;font-size:22px;line-height:1.35;color:var(--ink)}
.beat-sub{margin-top:12px;font-family:var(--fm);font-size:12px;letter-spacing:.04em;line-height:1.5;color:var(--mut)}
/* the maze starts well after the hero: more room on a desk than on a phone (Ming, 13 Oct) */
#beat-maze{padding-top:80px}
@media (min-width:900px){
  .beat{padding-bottom:96px;grid-template-columns:440px minmax(0,1fr);column-gap:56px;align-items:start}
  .beat-lead{font-size:26px;line-height:1.3}
  #beat-maze{padding-top:112px}
}

/* the reading bar, as the app draws it: a 3px track, a fill, one mono row.
   [ read on ] is the app's resume mark, plain text and never a button. */
.beat-bar{height:3px;background:var(--line)}
.beat-bar-fill{display:block;height:100%;background:var(--link)}
.beat-bar-row{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-top:8px;font-family:var(--fm);font-size:12px;letter-spacing:.04em;line-height:1.4}
.beat-bar-row .where{color:var(--mut)}
.beat-bar-row .resume{color:var(--link);white-space:nowrap}

/* ---- the maze: a two-column PDF page with the eye's route drawn on it ----
   The page is drawn in its own units (the hero page's px; 294 wide with the
   edges of pages 2 and 3 under it) and --u turns a unit into screen px, so the
   type, the zoom card and the route scale together and cannot drift apart.
   --mock is the drawing's width: the picture's whole width, up to 420px. Where
   the picture's column has 106px to spare beside a 420px page, the four labels
   sit beside their stops; where it does not, they are the plain list under the
   page, as on a phone. The page is a PDF,
   so it stays paper in dark mode; the route and the labels follow the theme. */
.maze{--mock:100cqw;--u:calc(var(--mock) / 294);position:relative}
.maze sub,.maze sup{font-size:.72em;line-height:0}
.maze-page{position:relative;width:var(--mock);aspect-ratio:294 / 448}
.maze-page>*{position:absolute}
.maze-page{--page:var(--sheet);--ink:var(--sheet-ink);--ink2:var(--sheet-ink2);--mut:var(--sheet-mut);--line:var(--sheet-line)}
.maze-sheet{left:calc(var(--u) * var(--x));top:calc(var(--u) * var(--y));width:calc(var(--u) * 286);height:calc(var(--u) * 404);
  background:var(--page);border:1px solid var(--line)}
.maze-sheet>*{position:absolute}
.maze-folio{right:calc(var(--u) * 14);bottom:calc(var(--u) * 5);font-family:var(--fm);font-size:calc(var(--u) * 9);line-height:calc(var(--u) * 11);color:var(--ink2)}
.maze-title{left:0;top:calc(var(--u) * 14);width:100%;text-align:center;font-size:calc(var(--u) * 11.5);font-weight:700;line-height:1.3;color:var(--ink);white-space:nowrap}
.maze-byline{left:0;top:calc(var(--u) * 32);width:100%;text-align:center;font-size:calc(var(--u) * 7.5);line-height:1.3;color:var(--mut);white-space:nowrap}
.maze-sec{left:calc(var(--u) * 15 + 5px);top:calc(var(--u) * 49);font-family:var(--fm);font-size:calc(var(--u) * 6.5);letter-spacing:.08em;line-height:1.3;color:var(--mut);white-space:nowrap}
.maze-ln{left:calc(var(--u) * var(--x) + var(--s));top:calc(var(--u) * var(--y));width:calc(var(--u) * 114);font-size:calc(var(--u) * 9.5);line-height:calc(var(--u) * 15);
  color:var(--ink2);white-space:nowrap;text-align:justify;text-align-last:justify}
.maze-eq{left:calc(var(--u) * 157 - 7px);top:calc(var(--u) * 315);width:calc(var(--u) * 114);font-family:var(--fmath);font-size:calc(var(--u) * 8.5);line-height:calc(var(--u) * 14);
  color:var(--ink);white-space:nowrap;text-align:center}
/* the pinch: the equation, 2.4 times its size, on a card that sits on the equation's row in
   the right half of the page and over nothing but the equation. The enlarged equation is
   wider than a column, so it is set on two lines, broken at its comma. The tag has the
   top of the card to itself. */
.maze-zoom{left:calc(var(--u) * 152);top:calc(var(--u) * 315);width:calc(var(--u) * 130);height:calc(var(--u) * 72);display:flex;align-items:flex-end;
  padding:0 calc(var(--u) * 6) calc(var(--u) * 6);border:1.5px solid var(--ink);border-radius:4px;background:var(--page)}
.maze-zoom-eq{display:block;font-family:var(--fmath);font-size:calc(var(--u) * 20.4);line-height:1.15;color:var(--ink);white-space:nowrap}
.maze-zoom-ln{display:block}
.maze-zoom-tag{position:absolute;top:3px;right:6px;font-family:var(--fm);font-size:10px;letter-spacing:.04em;line-height:1.2;color:var(--mut)}
/* the route: solid where the eye reads, dashed where it only travels. The
   stroke is 2px at every size. The arrowheads and the dot are drawn in screen
   px (6 by 6) and scaled back by the inverse of the drawing's scale, so they
   stay 6px as the page grows and shrinks; tan(atan2(a, b)) is a / b as the
   plain number scale() needs. The two margin legs sit 6px from the text at
   every size because the lines of text carry that 6px themselves (--s on
   .maze-ln), which lets the route stay in plain units. */
.maze-route{left:0;top:0;width:100%;height:100%;overflow:visible;fill:none;stroke:var(--link);stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.maze-route path,.maze-route circle{vector-effect:non-scaling-stroke}
.maze-dash{stroke-dasharray:4 4}
.maze-route .maze-dot{fill:var(--link);stroke:none}
.maze-head,.maze-dot{transform:scale(tan(atan2(294px, var(--mock))))}
/* the four labels: a plain list under the page on a phone */
.maze-labels{list-style:none;margin:16px 0 0;padding:0;display:grid;gap:4px;font-family:var(--fm);font-size:12px;letter-spacing:.04em;line-height:20px;color:var(--link-text)}
/* on a desk, each label beside its stop and outside the text of the page. Needs a column
   of 526px (a 420px page and 106px for labels 3 and 4, which wrap inside it); labels 1 and 2
   sit above the page and grow upward if they have to wrap. A narrower column keeps the list. */
@media (min-width:900px){
  .maze{--mock:min(100cqw,420px)}
  @container (min-width:526px){
    .maze{padding-top:28px}
    .maze-labels{display:block;margin:0}
    .maze-labels li{position:absolute;text-wrap:balance}
    .maze-labels li:nth-child(1),.maze-labels li:nth-child(2){top:20px;transform:translateY(-100%)}
    .maze-labels li:nth-child(1){left:calc(var(--u) * 15);max-width:calc(var(--u) * 124)}
    .maze-labels li:nth-child(2){left:calc(var(--u) * 143)}
    .maze-labels li:nth-child(3),.maze-labels li:nth-child(4){left:calc(var(--mock) + 10px);width:calc(100cqw - var(--mock) - 10px);transform:translateY(-50%)}
    .maze-labels li:nth-child(3){top:calc(28px + var(--u) * 351)}
    .maze-labels li:nth-child(4){top:calc(28px + var(--u) * 438)}
  }
}

/* ---- the turn: one phone-sized screen that flips from the PDF to the column ----
   Both states are always in the page and exactly one shows: the class is-set
   on the screen cross-fades them. Before brand.js runs, with no script, or
   with reduced motion, there is no .is-live on the figure and both states show
   in full, side by side on a desk and stacked on a phone. */
.turn{margin:0}
.turn-screen{display:grid;grid-template-columns:minmax(0,360px);gap:20px;justify-content:start}
.turn-state-pdf,.turn-state-set{position:relative;width:100%;height:600px;overflow:hidden;
  border:1.5px solid var(--ink);border-radius:18px;background:var(--page)}
.turn-toggle{display:none}
.turn.is-live .turn-toggle{display:flex;width:max-content;margin-top:14px}
/* as wide as its longer words, [ show the PDF ] (16 characters of mono, its letter-spacing, padding and border), so
   the row it shares with [ another paper ] wraps or not by the window alone, never when the words change */
.brand-page button.turn-toggle{border:1px solid var(--link);background:var(--page);color:var(--link);cursor:pointer;
  min-width:calc(16 * (1ch + .04em) + 42px)}
.brand-page button.turn-toggle:hover{background:var(--line-hover-bg);color:var(--link-hover)}
/* the caption names both screens until the flip is live, then follows the state */
.turn-above,.turn-below{display:none}
@media (max-width:899px){
  .turn-left,.turn-right{display:none}
  .turn-above,.turn-below{display:inline}
}
@media (min-width:900px){
  .turn-screen{grid-template-columns:repeat(2,minmax(0,300px))}  /* two screens that shrink to the column */
}
/* live: one screen with a frame, two states cross-fading inside it */
/* 600px tall at every width, as the two screens after it: every paper's proof screen fits it from a 320px
   phone up (tools/check_screens.mjs checks each one) */
.turn.is-live .turn-screen{display:block;position:relative;width:min(100%,360px);height:600px;overflow:hidden;
  border:1.5px solid var(--ink);border-radius:18px;background:var(--page)}
.turn.is-live .turn-state-pdf,.turn.is-live .turn-state-set{position:absolute;inset:0;width:auto;height:auto;border:0;border-radius:0;
  transition:opacity .25s ease}
.turn.is-live .turn-state-set,.turn.is-live .turn-screen.is-set .turn-state-pdf{opacity:0;visibility:hidden;transition:opacity .25s ease,visibility 0s linear .25s}
.turn.is-live .turn-screen.is-set .turn-state-set{opacity:1;visibility:visible;transition:opacity .25s ease,visibility 0s}
@media (min-width:900px){
  .turn.is-live .turn-screen{width:360px}
}
@media (prefers-reduced-motion: reduce){
  .turn .turn-state-pdf,.turn .turn-state-set{transition:none!important}
  .turn .turn-toggle,.turn.is-live .turn-toggle,.roll-foot .turn-toggle{display:none}
}
/* the paper switch (brand.js job 9): [ another paper ] leads the row of buttons under the phone, because it
   is there on every screen and the turn's toggle only on the first (under the live frame the toggle fades
   out on screens two and three, and the switch keeps its place). Hidden until brand.js finds two papers or
   more. A switch fades what belongs to the paper (the slots) out, swaps it, and fades it back in. */
.paper-buttons{display:flex;flex-wrap:wrap;column-gap:12px}
.brand-page button.paper-next{margin-top:14px;border:1px solid var(--link);background:var(--page);color:var(--link);cursor:pointer}
.brand-page button.paper-next:hover{background:var(--line-hover-bg);color:var(--link-hover)}
.brand-page button.paper-next[hidden]{display:none}
.with [data-paper-slot]{transition:opacity var(--t-fade) ease}
.with.is-switching [data-paper-slot]{opacity:0}
/* a switch replaces what is inside the slots, and a paper's page 1 can be A4 or US letter, so what follows it in the PDF
   screen moves by up to 34px. Chrome's scroll anchoring picked a node in there and scrolled the page to hold it still,
   so [ another paper ] slid from under the thumb (the review of 23 Oct 2026). Nothing in the frame or a slot anchors. */
.with .roll-frame,.with [data-paper-slot]{overflow-anchor:none}
@media (prefers-reduced-motion: reduce){
  .with [data-paper-slot]{transition:none}
}

/* the PDF state: a viewer bar, then page 1 of the paper's own PDF at the width of the screen,
   drawn from the PDF by tools/pdf_page_image.py. The whole page fits the width, so its text
   comes out near 6px: a phone's view of a PDF. */
.turn-state-pdf{container-type:inline-size;display:flex;flex-direction:column;background:var(--desk)}  /* the viewer: a gutter the page sits on */
.turn-viewer-bar{flex:none;margin:0;padding:8px 0 7px;text-align:center;font-family:var(--fm);font-size:11px;letter-spacing:.06em;line-height:1.4;color:var(--mut)}
/* the page ends before the 600px screen does, so the top of page 2 shows under the gutter, as a viewer would */
.turn-page-next{flex:1 1 auto;margin-top:8px;background:var(--pdf)}
.turn-page{position:relative;flex:none;background:var(--pdf)}
.turn-page img{display:block;width:100%;height:auto}

/* the set state: the same page as the app sets it, with the reading bar pinned
   to the foot of the screen and anything below it cut off at the edge */
.turn-state-set{container-type:inline-size;padding:18px 20px;color:var(--ink)}
.turn-set-head{display:flex;justify-content:space-between;gap:12px;font-family:var(--fm);font-size:11px;letter-spacing:.06em;line-height:1.4;white-space:nowrap}
.turn-set-head .id{color:var(--link)}
.turn-set-head .where{color:var(--mut)}
/* on the narrowest screens the two halves of the header would run into the edge, so they tighten */
@container (max-width:260px){.turn-set-head{letter-spacing:0;gap:8px}}
/* before the flip is live (no script, or reduced motion) both screens show at once and the text is set a size smaller */
.turn:not(.is-live) .turn-set-text{font-size:15px;line-height:1.5}
.turn-set-text{margin-top:16px;font-size:17px;line-height:1.55;color:var(--ink)}
.turn-set-foot{position:absolute;left:0;right:0;bottom:0;padding:12px 20px 16px;background:var(--page)}

/* ---------- with Latently: a paragraph that builds, and the screen beside it ----------
   The stage holds the header, the text on the left and the three screens
   on the right. Live (wide, tall enough, motion allowed, script running) the
   stage is pinned whole, the spacer under it gives the scroll its length, and
   brand.js turns that scroll into four states: each sentence of the
   paragraph rises into place under the last, the phone's column glides to the
   screen for it, and the call with the sample button comes last. The sentences
   are blocks so they can move; stacked, they read as one paragraph. Not live,
   the paragraph stands whole, the screens stack under or beside it, then the
   call, and nothing is pinned. .with-pin holds the stage and its spacer, so the
   stage lets go where the spacer ends and the credit after it scrolls in under
   the phone. */
.with{max-width:1100px;margin:0 auto;padding:48px 20px 96px}
.with-grid{display:grid;grid-template-columns:minmax(0,1fr);row-gap:40px}
.with-text,.with-panels,.with-call{min-width:0}
.with-para{max-width:16em}
.with-step{display:block}
/* not live: no frame on the column (two classes, to beat .screen below), each screen framed on its own
   with its caption under it */
.with-panels{display:grid;row-gap:24px}
.screen.roll-frame{border:0;border-radius:0;height:auto;overflow:visible;background:none;width:auto;container-type:normal}
.screen-roll{display:grid;row-gap:40px}
.roll-seg{margin:0;min-width:0}
.roll-foot{display:none}
.with-call-line{max-width:16em;font-size:22px;line-height:1.35;color:var(--ink)}
.with-call .btn{margin-top:20px}
.with-scroll{height:0}
.proof-credit{font-family:var(--fm);font-size:12px;letter-spacing:.04em;line-height:1.6;color:var(--mut)}
.with-credit{display:none}   /* the live stage's copy of the credit, after the pin */
@media (min-width:900px){
  .with{padding-top:80px;padding-bottom:120px}
  /* the second row takes the height the screens' column adds, so the call follows the paragraph
     instead of being pushed down by the phone */
  .with-grid{grid-template-columns:440px minmax(0,1fr);grid-template-rows:auto 1fr;column-gap:56px;align-items:start;row-gap:48px}
  .with-text{grid-column:1;grid-row:1}
  .with-call{grid-column:1;grid-row:2}
  .with-panels{grid-column:2;grid-row:1 / span 2}
  .with-call-line{font-size:26px;line-height:1.3}
  /* not live on a desk (reduced motion, a short window, no script): the call comes after the screens */
  .with:not(.is-live) .with-call{grid-column:1 / -1;grid-row:3}
  /* live: the stage pins 64px from the top (brand.js reads it from here), for the length of the
     spacer (55vh a state, four states); a sentence or the call fades in once its state is reached */
  .with.is-live .with-stage{position:sticky;top:64px}
  .with.is-live .with-scroll{height:220vh}
  .with.is-live .with-step,.with.is-live .with-reveal{opacity:0;transform:translateY(32px);
    transition:opacity var(--t-reveal) var(--ease-out),transform var(--t-reveal) var(--ease-out)}
  .with.is-live .with-step.is-shown,.with.is-live .with-reveal.is-shown{opacity:1;transform:none}
  .with.is-live .with-panels{row-gap:0}
  /* the phone scrolls: the column of three screens glides up inside the frame, a screen a state,
     over --t-glide (0.9s) on an ease-out curve (brand.js reads it as GLIDE_MS); the frame itself never moves. On a
     shorter window brand.js sets --frame-k under 1 and the whole frame is drawn smaller; the
     negative margin hands the height it no longer uses back to the page */
  .with.is-live .screen.roll-frame{width:360px;height:600px;overflow:hidden;container-type:inline-size;
    border:1.5px solid var(--ink);border-radius:18px;background:var(--page);
    transform-origin:0 0;transform:scale(var(--frame-k,1));margin-bottom:calc(600px * (var(--frame-k,1) - 1))}
  .with.is-live .screen-roll{row-gap:0;transition:transform var(--t-glide) var(--ease-out)}  /* one continuous column */
  .with.is-live .roll-seg{height:600px}
  .with.is-live .roll-seg .screen,.with.is-live .turn.is-live .turn-screen{width:100%;border:0;border-radius:0}
  .with.is-live .seg-only{display:none!important}
  /* under the frame: the caption, then the row of [ another paper ] and the PDF toggle (the toggle only
     while screen one is up). Stacked, both stay inside the phone's width, and a caption of another length
     never moves the buttons */
  .with.is-live .roll-foot{display:flex;flex-direction:column;align-items:flex-start;gap:8px;margin-top:14px}
  .with.is-live .roll-foot .beat-sub{margin-top:0;transition:opacity var(--t-fade) ease}
  .with.is-live .roll-foot.is-swapping .beat-sub{opacity:0}
  .with.is-live .roll-foot .turn-toggle{display:flex;width:max-content;margin-top:0;transition:opacity var(--t-state) ease,visibility 0s}
  .with.is-live .roll-foot .paper-next{margin-top:0}
  /* the toggle fades out as screen one glides away (data-screen) and only then hides; the caption
     changes halfway through the glide (data-caption), as the new screen arrives */
  .with.is-live:not([data-screen="0"]) .roll-foot .turn-toggle{opacity:0;visibility:hidden;transition:opacity var(--t-state) ease,visibility 0s linear var(--t-state)}
  .with.is-live:not([data-caption="0"]) .roll-foot [data-turn-caption]{display:none}
  .with.is-live[data-caption="0"] .roll-foot [data-with-caption]{display:none}
  .with.is-live .with-panels .proof-credit{display:none}
  .with.is-live .with-credit{display:block;margin:18px 0 0 496px}   /* under the phone: the 440px column and the 56px gap */
}
@media (prefers-reduced-motion: reduce){
  .with .with-step,.with .with-reveal,.with .with-panel{transition:none!important}
}

/* a phone-sized screen of the app, the frame the turn draws, for the two screens after it */
.screen{position:relative;width:min(100%,360px);height:600px;overflow:hidden;container-type:inline-size;
  border:1.5px solid var(--ink);border-radius:18px;background:var(--page)}
.screen-body{padding:18px 20px;font-size:17px;line-height:1.55;color:var(--ink)}
.screen-body p{margin:0}
.screen-body p+p{margin-top:12px}
.screen-gap{margin:0 0 10px;font-family:var(--fm);font-size:11px;letter-spacing:.06em;line-height:1.4;color:var(--mut)}
* + .screen-gap{margin-top:18px}

/* Phones and small tablets (Ming, 21 Oct 2026: "clunky", "the look"): the two screens after the turn take the height
   of what they hold. At a fixed 600px a figure or a single card sat in the top of a mostly empty box, 250 to 400px
   of navy under it. The live stage (900px and up) keeps its 600px frame, which the glide needs. The turn keeps 600:
   its PDF page nearly fills it. The two buttons under the turn are one full, equal width, and the three sentences
   above the screens get a line of air between them. */
@media (max-width:899px){
  .with-proof .screen,.with-resume .screen{height:auto}
  .paper-buttons{flex-direction:column;max-width:360px}   /* as wide as the screen above them, also on a tablet */
  .paper-buttons .btn,.turn.is-live .turn-toggle{width:100%}
  .with-step+.with-step{margin-top:.55em}
}

/* The three screens as one swipeable row (Ming, 21 Oct 2026: "I want this"; his notes of 22 Oct: all screens the same
   height, and a swipe that moves one screen, smoothly; the design is in the vault, webapp/Landing sample rotation,
   design). brand.js setupSwipe puts .is-swipe on the section on a phone with motion and script; without them the stacked
   layout above stays. The frame clips the row, which brand.js moves with a transform (not the browser's scrolling: its
   fling could carry past a screen); touch-action: pan-y leaves vertical drags to the page. The frame bleeds 20px into
   both of the page's gutters, so a screen slides off at the glass and the next screen's edge shows: that is the cue to
   swipe (on screens 2 and 3 a sliver of the one before shows at the left; Ming's yes, 23 Oct 2026, where a dragged
   screen used to be cut on an invisible line 20px inside the left edge). All three screens are 600px tall, the desk
   stage's frame, so the row never changes height (a row that followed the screen on show was laid out again in the
   middle of a swipe). Under it: three markers in the reading bar's own look, then the live stage's own pair of buttons
   (.roll-foot); the copy of the pair inside screen one is hidden, and the toggle shows on screen one only, as on the stage. */
.swipe-dots{display:none}
.swipe-dot{position:relative;width:44px;height:44px;padding:0;border:0;background:none;cursor:pointer}
.swipe-dot::before{content:"";position:absolute;left:4px;right:4px;top:50%;height:3px;margin-top:-1.5px;background:var(--line);transition:background var(--t-fade) ease}
.swipe-dot[aria-current="true"]::before{background:var(--link)}
.swipe-dot:focus-visible{outline:2px solid var(--link);outline-offset:-2px}
@media (max-width:899px){
  /* the frame never moves, so it takes the touch (touch-action and brand.js's listeners): Safari ties touch-action to an
     element's own box, and the row inside it moves away from under the finger (Ming, 22 Oct: it stopped after screen one).
     pinch-zoom: two fingers on the row still zoom the page (pan-y alone forbade it, the review of 23 Oct 2026) */
  .with.is-swipe .screen.roll-frame{overflow:hidden;margin-left:-20px;margin-right:-20px;touch-action:pan-y pinch-zoom;cursor:grab;
    user-select:none;-webkit-user-select:none;-webkit-touch-callout:none}
  .with.is-swipe .screen.roll-frame.is-dragging{cursor:grabbing}
  .with.is-swipe .screen.roll-frame:focus-visible{outline:2px solid var(--link);outline-offset:-2px}
  .with.is-swipe .screen-roll{display:flex;align-items:flex-start;gap:12px;padding-left:20px;will-change:transform}   /* screen one starts on the gutter's edge, as before */
  .with.is-swipe .roll-seg{flex:0 0 min(calc(100% - 20px),360px);touch-action:pan-y pinch-zoom}   /* also on each screen's own box, which is always under the finger */
  .with.is-swipe .roll-seg img{-webkit-user-drag:none}
  .with.is-swipe .roll-seg .screen,.with.is-swipe .turn.is-live .turn-screen{height:600px}
  .with.is-swipe .paper-buttons.seg-only{display:none}
  .with.is-swipe .roll-foot{display:flex;flex-direction:column}
  .with.is-swipe .roll-foot .beat-sub{display:none}
  .with.is-swipe .roll-foot .turn-toggle{display:flex;width:100%;margin-top:14px}   /* the stage's rule that shows it needs .is-live */
  .with.is-swipe:not([data-screen="0"]) .roll-foot .turn-toggle{display:none}
  .with.is-swipe .swipe-dots{display:flex;margin-top:6px}
}

/* the paper inside the screens: blocks as our renderer sets them (tools/render.cjs, written in by
   tools/build_with_screens.mjs), in the site's type. The classes are the renderer's own. */
.turn-set-text .doc-title{margin:0;font-size:1.25em;line-height:1.25;font-weight:600}
.turn-set-text .h2{margin:14px 0 6px;font-size:1em;line-height:1.3;font-weight:600}
.turn-set-text .body,.screen-body .body{margin:0}
/* a footnote on the screens (the user-centric ranking paper's title carries a star), small and muted as on /sample */
.turn-set-text .fnote,.screen-body .fnote{margin:6px 0 0;font-size:.85em;line-height:1.45;color:var(--mut)}
.screen-body .body+.eq{margin-top:10px}
.turn-set-text .eq,.screen-body .eq{overflow-x:auto;overflow-y:hidden}
.turn-set-text .katex-display,.screen-body .katex-display{margin:0}
/* KaTeX sets maths at 1.21em of the text; a size smaller keeps the display equation inside the narrowest screen */
.screen-body .eq .katex{font-size:1em}
/* On a phone narrower than 340px a display equation and its number need a size smaller still: the molecule paper's
   equation 1 is 29px too wide for the 238px the screen leaves it at 320px, and .86em brings it to about 230px.
   Ming's ruling of 21 Oct (option C); it shrinks every paper's equations on these phones. */
@media (max-width:340px){
  .screen-body .eq .katex{font-size:.86em}
}
.screen-body .vis{margin:0;background:var(--sheet);border:1px solid var(--line);padding:8px}
.screen-body .vis img{display:block;margin:0 auto;height:auto}
.screen-body .caption{margin-top:8px;font-size:13px;line-height:1.5;color:var(--ink2)}
/* a citation is a link in the reader (to its reference); in these screens there is nothing to go to */
.turn-set-text a.xref,.screen-body a.xref{color:inherit;text-decoration:none;pointer-events:none}
.screen-body .turn-set-head+.resume-card{margin-top:16px}

/* ---- the card of a paper you stopped reading, on the continue-reading screen ---- */
.resume-card{width:100%;background:var(--page);border:1px solid var(--line);border-radius:6px;padding:18px 20px}
@media (min-width:900px){.resume-card{max-width:360px}}
.resume-card-meta{display:flex;justify-content:space-between;gap:12px;font-family:var(--fm);font-size:11px;letter-spacing:.06em;line-height:1.4}
.resume-card-meta .id{color:var(--link)}
.resume-card-meta .cat{color:var(--mut)}
.resume-card-title{margin-top:10px;font-size:20px;font-weight:600;line-height:1.25;color:var(--ink)}
.resume-card-authors{margin-top:6px;font-size:14px;color:var(--mut)}
.resume-card-foot{margin-top:18px}
/* on a 320px phone the screen is 280px and the card's bar row ("54% · §6.4 next" and [ read on ]) would wrap,
   so the card's sides and the row's letter-spacing tighten, as the set page's header does at 260px */
@container (max-width:290px){
  .resume-card{padding-left:14px;padding-right:14px}
  .resume-card .beat-bar-row{letter-spacing:0}
}

/* the reading bar, as the hero phone and the sample papers draw it */
.progress-track{height:3px;background:var(--track)}
.progress-fill{height:3px;background:var(--blue-500)}
.card-foot-row{display:flex;justify-content:space-between;align-items:center;gap:12px;font-family:var(--fm);font-size:10.5px;letter-spacing:.06em}
.card-foot-row .where{color:var(--mut)}
.card-foot-row .resume{color:var(--link);font-size:11px;letter-spacing:.04em;white-space:nowrap}

/* the call: the end of the document, with more air above it than between
   the other passages; the sides come from .passage */
.call{padding-top:32px;padding-bottom:104px}
/* balance: the belief line's lines come out even, so "was." never ends it alone (13 Oct wording) */
/* the statement in the regular weight: beside the position on a desk, bold made it shout over the whole left column
   (Ming, 29 Oct 2026); the hero's headline stays the page's one bold line */
.call h2{font-size:44px;font-weight:400;letter-spacing:-.01em;line-height:1.1;max-width:18ch;text-wrap:balance}
.call-invite{margin-top:28px;font-size:22px;line-height:1.35;color:var(--ink)}
.call .btn{margin-top:32px}
.call .call-invite+.btn{margin-top:20px}
.call .mono{margin-top:14px}
@media (max-width:640px){.call{padding-top:24px;padding-bottom:80px}.call h2{font-size:34px}}

/* ---------- the hero figure (B7): the re-typeset, in motion ----------
   Ported from the vault board Business/Brand Look source (Oct 2026)/v1/project/HeroB7.dc.html.
   The stage is drawn at a fixed design size and scaled to fit by brand.js
   (it sets --k). Two arrangements:
     wide   (desktop and tablet): page left, phone right, 590 x 600; the page is
            drawn at .85 of the board's size and centred on the phone, so
            the page, the arrow and the phone have 22px of air between
            them (2px on the board)
     narrow (phones): page above, phone below, lines travel down, 350 x 676
   Where each page line lands in the phone is measured by brand.js and handed
   to the CSS as --dx/--dy/--s, so both arrangements play the same motion.
   The forward timings (--df) are the board's, written into the markup, and --b7-pace
   stretches all of them (1.6: Ming asked for it slower, 13 Oct; about 3s end to end). Going back
   to the PDF has no flight: the page's text fades in where it sits while the
   phone's fades out (Ming, 13 Oct). */
.b7{--b7-w:590;--b7-h:600;--b7-maxw:590;--page-k:.85;--phone-k:1;--b7-pace:1.6;
  position:relative;width:100%;max-width:calc(var(--b7-maxw) * 1px);aspect-ratio:var(--b7-w) / var(--b7-h)}
.b7-stage{position:absolute;left:0;top:0;width:calc(var(--b7-w) * 1px);height:calc(var(--b7-h) * 1px);
  transform-origin:0 0;transform:scale(var(--k,1))}
.b7-page{position:absolute;left:0;top:138px;width:270px;height:382px;background:var(--pdf);border:1px solid #DDD9CC;
  box-shadow:0 6px 18px rgba(22,21,15,.12);transform-origin:0 0;transform:scale(var(--page-k))}
.b7-page-title{position:absolute;left:0;top:15px;width:268px;text-align:center;font-size:11.5px;font-weight:700;color:#16150F;white-space:nowrap}
.b7-page-byline{position:absolute;left:0;top:33px;width:268px;text-align:center;font-size:7.5px;color:#6E6A5C;white-space:nowrap}
.b7-page-sec{position:absolute;left:14px;top:57px;font-family:var(--fm);font-size:6.5px;letter-spacing:.08em;color:#6E6A5C;white-space:nowrap}
.b7-arrow{position:absolute;left:252px;top:292px;width:16px;height:16px;color:var(--blue-600)}
.b7-phone{position:absolute;left:290px;top:0;width:300px;height:600px;background:var(--page);border:2.5px solid var(--ink);
  border-radius:40px;box-shadow:0 2px 6px rgba(22,21,15,.08),0 30px 70px rgba(22,21,15,.22);
  transform-origin:0 0;transform:scale(var(--phone-k));color:var(--ink)}
.b7-phone .b7-id{position:absolute;left:21.5px;top:27.5px;font-family:var(--fm);font-size:10px;letter-spacing:.06em;color:var(--link)}
.b7-phone .b7-cat{position:absolute;right:21.5px;top:27.5px;font-family:var(--fm);font-size:10px;letter-spacing:.06em;color:var(--mut)}
.b7-phone .b7-title{position:absolute;left:21.5px;top:49.5px;width:252px;font-size:21px;font-weight:700;line-height:1.15}
.b7-phone .b7-byline{position:absolute;left:21.5px;top:105.5px;font-size:12px;color:var(--mut);white-space:nowrap}
.b7-phone .b7-sec{position:absolute;left:21.5px;top:135.5px;font-family:var(--fm);font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--mut)}
.b7-phone .b7-eq{position:absolute;left:21.5px;top:502.5px;width:252px;text-align:center;font-size:13.5px;line-height:1.5;white-space:nowrap}
.b7-foot{position:absolute;left:21.5px;right:21.5px;bottom:19.5px;display:flex;flex-direction:column;gap:8px}
.b7-foot .progress-fill{width:67%}
.b7-foot .card-foot-row .where{font-size:10px}
.b7-toggle{position:absolute;left:0;bottom:0;min-height:44px;padding:8px 12px;font-family:var(--fm);font-size:11px;letter-spacing:.05em;
  border:1px solid var(--line);background:var(--page);color:var(--mut);cursor:pointer;display:none;white-space:nowrap}
.b7-toggle:hover{color:var(--link);border-color:var(--link)}
.b7.is-live .b7-toggle{display:block}
/* the lines: .ml page halves, .me page equation, .pl phone lines, .pe phone heading and equation */
.b7 .ml{position:absolute;display:block;width:114px;font-size:9.5px;line-height:15px;color:#3D3B33;white-space:nowrap;
  text-align:justify;text-align-last:justify;transform-origin:left top}
.b7 .me{position:absolute;display:block;width:114px;font-size:8.5px;line-height:14px;color:#16150F;white-space:nowrap;
  text-align:center;transform-origin:left top}
.b7 .pl{position:absolute;left:21.5px;font-size:10.5px;line-height:19px;color:var(--ink2);white-space:nowrap}
.b7 .me,.b7 .b7-eq{font-family:var(--fmath)}
.b7 sub,.b7 sup{font-size:.72em;line-height:0}
/* Before brand.js runs, or with no script, both states show full, as with
   reduced motion. .is-live (added by brand.js) starts the figure on the PDF page. */
.b7.is-live .pl,.b7.is-live .pe{opacity:0}
.b7.is-live .fwd .ml,.b7.is-live .fwd .me{animation:b7-lift calc(.55s * var(--b7-pace)) var(--ease-lift) both;animation-delay:calc(var(--df) * var(--b7-pace))}
.b7.is-live .back .ml,.b7.is-live .back .me{animation:b7-show .35s ease both}
.b7.is-live .fwd .pl,.b7.is-live .fwd .pe{animation:b7-ink-in calc(.12s * var(--b7-pace)) ease-out both;animation-delay:calc(var(--df) * var(--b7-pace))}
.b7.is-live .back .pl,.b7.is-live .back .pe{animation:b7-ink-out .35s ease both}
@keyframes b7-lift{0%{transform:translate(0,0) scale(1);opacity:1}55%{opacity:1}100%{transform:translate(var(--dx),var(--dy)) scale(var(--s));opacity:0}}
@keyframes b7-show{from{transform:none;opacity:0}to{transform:none;opacity:1}}   /* home, from wherever the lift left it */
@keyframes b7-ink-in{from{opacity:0}to{opacity:1}}
@keyframes b7-ink-out{from{opacity:1}to{opacity:0}}

/* ---------- the opening (the landing page at 1060px and wider, motion allowed) ----------
   The hero plays alone first: the headline, its line and the re-typeset, large in the middle of
   the window with nothing else on screen, the way the app hides its chrome while you read (Ming,
   13 Oct; the headline joined it that night, so the motion comes with its words).
   index.html's head sets .is-opening before the first paint, and a few lines of script right after
   the hero's markup measure where the layout put the headline, its line and the figure, set
   --opening-x, -y and -s on the hero to draw them large in the middle of the window, and add
   .is-staged, also before the first paint (until 23 Oct 2026 brand.js did this, and a first paint
   that came before it showed only the logo). The buttons and notes under the line, the PDF toggle,
   the nav's links, the theme toggle and the page under the hero wait; the lockup stays. The page
   under the hero waits too because a window taller than the hero (820px) shows the maze under it.
   brand.js (setupOpening) adds .is-driven: it is in charge now. When the re-typeset has finished,
   or the reader scrolls, taps, types, points at the nav or switches reduced motion on, .is-opening
   comes off: the hero glides back into its place and the rest fades in. Only transforms and
   opacity change, so nothing in the layout moves. If brand.js never takes charge, the fallbacks
   show everything 2.5s after the first paint; once it has, 9s at the latest (OPENING_MAX_MS). */
.hero{transform-origin:0 0;transition:transform var(--t-glide) var(--ease-out),opacity .4s ease}
/* while opening, only opacity transitions: the hero appears in the middle, it does not slide there */
.is-opening .hero{opacity:0;transition:opacity .4s ease;transform:translate(var(--opening-x,0px),var(--opening-y,0px)) scale(var(--opening-s,1))}
.is-opening.is-staged .hero{opacity:1}
/* what waits for the page to come in (the theme toggle's fade is in its own rule, with its colours) */
.hero-actions,.b7-toggle,.site-nav-links,.site-nav-burger,main>section:not(.hero),.site-footer{transition:opacity var(--t-reveal) ease .3s}
.is-opening .hero-actions,.is-opening .b7-toggle,.is-opening .site-nav-links,.is-opening .site-nav .theme-toggle,.is-opening .site-nav-burger,
.is-opening main>section:not(.hero),.is-opening .site-footer{opacity:0;pointer-events:none}
.is-opening .site-nav:hover .site-nav-links,.is-opening .site-nav:hover .theme-toggle,.is-opening .site-nav:hover .site-nav-burger,
.is-opening .site-nav:focus-within .site-nav-links,.is-opening .site-nav:focus-within .theme-toggle,.is-opening .site-nav:focus-within .site-nav-burger{opacity:1;pointer-events:auto}
/* until brand.js takes charge, the staged figure shows the PDF state: its phone is empty, as .is-live draws it. Since
   the staging moved before the first paint, the figure was on screen before brand.js and its phone showed the finished
   text, then emptied and filled again as the re-typeset played (found the same evening, 23 Oct 2026) */
.is-opening .b7 .pl,.is-opening .b7 .pe{opacity:0}
/* the fallbacks: 2.5s after the first paint if brand.js never takes charge (.is-driven), or 9s after it did, the hero
   glides home and everything that waits fades in, as letting the page in does (a 0s fallback made the staged hero jump);
   without brand.js the phone's lines come in with the rest, so the figure shows both states, as without script */
.is-opening:not(.is-driven) .hero{animation:opening-back var(--t-glide) var(--ease-out) 2.5s forwards}
.is-opening.is-driven .hero{animation:opening-back var(--t-glide) var(--ease-out) 9s forwards}
.is-opening:not(.is-driven) .hero-actions,.is-opening:not(.is-driven) .b7-toggle,
.is-opening:not(.is-driven) .site-nav-links,.is-opening:not(.is-driven) .site-nav .theme-toggle,.is-opening:not(.is-driven) .site-nav-burger,
.is-opening:not(.is-driven) main>section:not(.hero),.is-opening:not(.is-driven) .site-footer,
.is-opening:not(.is-driven) .b7 .pl,.is-opening:not(.is-driven) .b7 .pe{animation:opening-in var(--t-reveal) ease 2.5s forwards}
.is-opening.is-driven .hero-actions,.is-opening.is-driven .b7-toggle,
.is-opening.is-driven .site-nav-links,.is-opening.is-driven .site-nav .theme-toggle,.is-opening.is-driven .site-nav-burger,
.is-opening.is-driven main>section:not(.hero),.is-opening.is-driven .site-footer{animation:opening-in var(--t-reveal) ease 9s forwards}
@keyframes opening-back{to{opacity:1;transform:none}}
@keyframes opening-in{to{opacity:1;pointer-events:auto}}
@media (prefers-reduced-motion: reduce){
  .b7 .ml,.b7 .me,.b7 .pl,.b7 .pe,.b7.is-live .pl,.b7.is-live .pe{animation:none!important;opacity:1}
  .b7.is-live .b7-toggle{display:none}
  .hero{transition:opacity .4s ease}   /* reduced motion switched on mid-opening: the hero jumps home, it does not glide */
}
/* narrow: the page above, small, as a PDF is on a phone; the phone below it */
/* Phones (Ming, 21 Oct 2026: the landing "feels clunky", "the look"): one centred column, the small page, the
   arrow, a bigger phone (.9, its text was too small at .76), then the toggle as a full-width button under it. It
   was a diagonal stagger with the toggle floating in the gap beside the emptied page. The phone is full size (1:
   its text is 10.5px and must not shrink further). brand.js measures the travel of the lines from wherever the
   layout puts them, so the motion is the same. */
@media (max-width:640px){
  .b7{--b7-w:350;--b7-h:898;--b7-maxw:392;--page-k:.5;--phone-k:1}
  .b7-page{top:0;left:107px}
  .b7-arrow{left:167px;top:203px;transform:rotate(90deg)}
  .b7-phone{left:25px;top:230px}
  .b7-toggle{left:0;top:854px;bottom:auto;width:350px;text-align:center}
}

/* ---------- /why: the post ---------- */
/* as wide as the legal pages: 980px of text just inside the nav's 1060px (Ming, 13 Oct) */
.post{max-width:1020px;margin:0 auto;padding:40px 20px 88px}
.post h1{font-size:44px;font-weight:700;letter-spacing:-.015em;line-height:1.1}
.post-byline{margin-top:16px}
.post-body{margin-top:40px}
.post-body p{font-size:19px;line-height:1.72;color:var(--ink)}
.post-body p+p{margin-top:20px}
@media (max-width:640px){.post h1{font-size:34px}.post-body p{font-size:18px}}

/* ---------- /pricing ----------
   The two prices are the headline, set inside a white card with an ink top rule (variant D, Ming's
   go on 20 Oct); three columns under it, one on a phone; then why it is a subscription. */
.pricing{max-width:1100px;margin:0 auto;padding:40px 20px 96px}
.pricing-card{margin-top:16px;padding:36px 44px 40px;background:var(--page);border:1px solid var(--line);border-top:2px solid var(--ink)}
.pricing h1{font-size:64px;font-weight:700;letter-spacing:-.02em;line-height:1.05}
.pricing-std{display:block}
.pricing-alpha{display:block;max-width:820px;margin-top:12px;font-size:36px;font-weight:500;letter-spacing:-.01em;line-height:1.2;color:var(--link)}
.pricing-sub{margin-top:18px;max-width:620px;font-size:18px;line-height:1.6;color:var(--ink2)}
.pricing-card .btn{margin-top:26px}
/* two ways in, side by side (Ming, 29 Oct 2026: the waitlist is a path of its own, beside the alpha); the sentence
   above them says which is which. The waitlist button opens the pass on the waitlist (/alpha#waitlist, script.js).
   On a phone the row wraps */
.pricing-actions{margin-top:26px;display:flex;flex-wrap:wrap;gap:12px}
.pricing-card .pricing-actions .btn{margin-top:0}
.pricing-cols{margin-top:56px;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:0 40px}
.pricing-cols h2.label{font-weight:400}
.pricing-cols p{margin-top:10px;font-size:17px;line-height:1.6;color:var(--ink)}
.pricing-cols .pricing-small{font-size:15.5px;color:var(--ink2)}
.pricing-cols a,.pricing-why a{text-decoration-thickness:1px;text-underline-offset:3px}
.pricing .nw{white-space:nowrap}
.pricing-why{max-width:760px;margin-top:56px;padding-top:22px;border-top:1px solid var(--line)}
.pricing-why h2{font-size:22px;font-weight:600;line-height:1.3}
.pricing-why p{margin-top:10px;font-size:18px;line-height:1.6;color:var(--ink2)}
@media (max-width:820px){
  .pricing-card{padding:26px 22px 28px}
  .pricing h1{font-size:44px}
  .pricing-alpha{font-size:26px}
  .pricing-cols{grid-template-columns:1fr;gap:28px;margin-top:44px}
}
@media (max-width:400px){
  .pricing-card{padding:22px 18px 24px}
  .pricing h1{font-size:36px}
  .pricing-alpha{font-size:23px}
}

/* ---------- where an in-page link lands (Ming, 22 Oct 2026) ----------
   The jump itself stays instant: a glide over a whole paper is a blur. brand.js (job 8) puts .landed on the place a
   link goes to, in a paper or a legal page only: it holds a stronger blue for a beat, then fades over --t-landed to
   what the place rests on (a reference or a footnote keeps its :target tint, the rest has none). Colour only, no
   movement, so reduced motion keeps it. A heading a contents entry lands on stops 24px under the window's edge. */
.landed{animation:landed var(--t-landed) ease-out}
@keyframes landed{
  0%,25%{background-color:var(--landed);box-shadow:0 0 0 6px var(--landed)}
  100%{box-shadow:0 0 0 6px transparent}
}
/* a link landed on (the citation the way back returns to) flashes its own box only: on an inline link the 6px halo
   painted over the bracket before it. Its link-blue text darkens for the flash, so it stays readable: about 7.4:1 in
   light and 5.4:1 in dark, where it read 3.7:1 (the motion review of 23 Oct 2026). brand.js removes .landed at the end
   of either animation. */
a.landed{animation-name:landed-inline}
@keyframes landed-inline{
  0%,25%{background-color:var(--landed);color:var(--link-hover)}
}

/* ---------- /sample: the list of samples, one card per paper ----------
   The card is the app's paper card made into a link: id and field, title, authors, then what is
   inside and where it goes. The whole card is the link; its border turns blue on hover. */
/* as wide as the legal pages (Ming, 13 Oct); cards sit two to a row there, one on a phone */
.sample-list{list-style:none;margin:36px 0 0;padding:0;display:grid;gap:16px;grid-template-columns:repeat(auto-fill,minmax(min(100%,400px),1fr))}
.brand-page a.sample-card,.brand-page a.sample-card:hover{display:block;padding:20px 22px;background:var(--page);
  border:1px solid var(--line);border-radius:6px;color:var(--ink);text-decoration:none;transition:border-color var(--t-feedback) ease}
.brand-page a.sample-card:hover{border-color:var(--link)}
.sample-card-meta{display:flex;justify-content:space-between;gap:12px;font-family:var(--fm);font-size:11px;letter-spacing:.06em;line-height:1.4}
.sample-card-meta .id{color:var(--link)}
.sample-card-meta .cat{color:var(--mut)}
.sample-card-title{display:block;margin-top:10px;font-size:22px;font-weight:600;line-height:1.25;color:var(--ink)}
.sample-card-authors{display:block;margin-top:6px;font-size:15px;line-height:1.5;color:var(--mut)}
.sample-card-foot{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px 16px;margin-top:18px;
  font-family:var(--fm);font-size:12px;letter-spacing:.04em;line-height:1.5}
.sample-card-foot .what{color:var(--mut)}
.sample-card-foot .go{margin-left:auto;color:var(--link);white-space:nowrap}   /* stays at the right when the row wraps */

/* ---------- /sample/<name>: the paper, re-typeset ----------
   The paper is a sheet on the desk, as the app shows a paper: page colour, one column, about 62ch. What is
   inside is our renderer's (tools/render.cjs, written in by tools/build_samples.mjs), so the classes are its
   own: doc-title, h2 and h3 (both are <h2> tags; the parse sets the level), body (li for a bullet), eq,
   tbl with tblwrap, vis, caption, fnote, refs and xref. The landing page's phone screens set the same classes
   (the .screen-body rules above), and these follow them where a page can. A wide equation or table scrolls
   inside its own box, never the page. */
.paper{max-width:calc(62ch + 96px);margin:8px auto 0;padding:44px 48px 56px;font-size:18px;line-height:1.7;color:var(--ink);
  background:var(--page);box-shadow:0 2px 6px rgba(22,21,15,.08),0 30px 70px rgba(22,21,15,.14)}
.paper-meta{display:flex;justify-content:space-between;gap:12px;font-family:var(--fm);font-size:11px;letter-spacing:.06em}
.paper-meta .id{color:var(--link)}
.paper-meta .cat{color:var(--mut)}
.paper .doc-title{margin-top:14px;font-size:34px;font-weight:700;letter-spacing:-.01em;line-height:1.15}
/* the line under the title is the authors, as the parse gives it */
.paper .doc-title+.body{margin-top:16px;font-size:16.5px;line-height:1.55;color:var(--ink2)}
.paper .h2{margin-top:48px;font-size:25px;font-weight:700;line-height:1.25;scroll-margin-top:20px}
.paper .h3{margin-top:34px;font-size:20px;font-weight:700;line-height:1.3;scroll-margin-top:20px}
.paper .body{margin-top:16px;hyphens:auto;overflow-wrap:break-word}
/* a bullet or a numbered line arrives as plain text, so a hanging indent is all the list there is */
.paper .li{margin-top:8px;padding-left:.9em;text-indent:-.9em}
.paper .body:not(.li)+.li{margin-top:12px}
.paper strong{font-weight:700}
/* KaTeX sets maths at 1.21em of the text: a touch smaller beside STIX, as in the phone screens */
.paper .katex{font-size:1.08em}
/* position:relative on the two scroll boxes: KaTeX's hidden MathML copy is position:absolute, and without a
   positioned box around it the copy escapes the scrolling and widens the page */
.paper .eq{position:relative;margin:18px 0;padding:4px 0;overflow-x:auto;overflow-y:hidden}
.paper .eq .katex-display{margin:0}
.paper .eq .katex{font-size:1.15em}
/* A numbered equation, here and in the phone screens: KaTeX pins the number to the right edge (position:absolute),
   so an equation as wide as the column runs under it, and one wider scrolls under it. The number goes in the row
   after the equation instead, an em clear of it, and scrolls with it. The empty ::before and the number's margin
   share the spare room, so the equation stays near the middle. */
.eq .katex-display>.katex>.katex-html:has(>.tag){display:flex;align-items:baseline;width:max-content;min-width:100%}
.eq .katex-display>.katex>.katex-html:has(>.tag)::before{content:"";margin-right:auto}
.eq .katex-display>.katex>.katex-html>.tag{position:static;margin-left:auto;padding-left:1em}
.paper .vis{margin:26px 0 0;padding:12px;background:var(--sheet);border:1px solid var(--line)}
.paper .vis img{display:block;margin:0 auto;height:auto;max-width:100%}
.paper .caption{margin-top:10px;font-size:14.5px;line-height:1.55;color:var(--ink2)}
.paper .caption+.tbl{margin-top:8px}
.paper .tbl{margin-top:20px}
.paper .tblwrap{position:relative;overflow-x:auto}
.paper .tbl table{border-collapse:collapse;margin:0 auto;font-size:15px;line-height:1.4;font-variant-numeric:tabular-nums;
  border-top:1.5px solid var(--ink2);border-bottom:1.5px solid var(--ink2)}
.paper .tbl th,.paper .tbl td{padding:7px 12px;text-align:left;vertical-align:top;border-bottom:1px solid var(--line)}
.paper .tbl thead th{font-weight:700;border-bottom-color:var(--ink2)}
.paper .tbl tbody tr:last-child td{border-bottom:0}
.paper .fnote{margin-top:10px;font-size:14px;line-height:1.55;color:var(--mut);overflow-wrap:anywhere;scroll-margin-top:20px}
.paper .fnote:target{background:var(--line-hover-bg)}
/* a footnote marker, raised as the reader raises it (tools/build_samples.mjs points it at its footnote) */
.paper a.fnref{font-size:.68em;vertical-align:super;line-height:0}
.paper a.xref{text-decoration:none}
.paper a.xref:hover{text-decoration:underline}
/* the references stay shut until asked for; a citation opens them (brand.js setupReferenceLinks) */
.paper .refs{margin-top:40px;padding-top:6px;border-top:1px solid var(--line)}
.paper .refs summary{padding:10px 0;cursor:pointer;font-family:var(--fm);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--link)}
.paper .refs .body{margin-top:10px;padding-left:1.6em;text-indent:-1.6em;font-size:15px;line-height:1.55;color:var(--ink2);overflow-wrap:anywhere;scroll-margin-top:20px}
.paper .refs .body:target{background:var(--line-hover-bg)}
.paper-credit{margin-top:48px;padding-top:18px;border-top:2px solid var(--ink);font-size:15px;line-height:1.6;color:var(--ink2)}
.paper-credit p{margin-top:8px;overflow-wrap:anywhere}
/* the reading bar, like the app's paper card: how far in, and the next section (brand.js); no [ read on ] here */
.reading-bar{position:sticky;bottom:0;z-index:10;background:var(--page);border-top:1px solid var(--line)}
.reading-bar-inner{max-width:calc(62ch + 96px);margin:0 auto;padding:10px 48px 12px;display:flex;flex-direction:column;gap:8px}
.reading-bar .progress-fill{width:0}
.reading-bar .where{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* the way back after a citation jump (brand.js job 11): in the bar's own row, so the bar does not grow while it shows */
.return-link{position:relative;flex:none;margin:0;padding:0;border:0;background:none;font:inherit;font-size:11px;letter-spacing:.04em;white-space:nowrap;color:var(--link);cursor:pointer}
.return-link::after{content:"";position:absolute;inset:-15px -10px}   /* a 44px tap area without 44px of height (the button is 14px) */
.return-link:hover{color:var(--link-hover)}
.return-link:focus-visible{outline:2px solid var(--link);outline-offset:2px}
/* its twin for a keyboard, put by brand.js right after the place a citation jumped to: seen only while it has the focus,
   like a skip link, so a reader on a keyboard or a screen reader finds the way back where they are */
.return-nearby{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;
  border:0;background:none;font-family:var(--fm);font-size:11px;letter-spacing:.04em;color:var(--link);cursor:pointer}
.return-nearby:focus{position:static;width:auto;height:auto;margin:8px 0 0;padding:4px 0;overflow:visible;clip:auto}
/* Tab scrolls a focused link into view at the window's foot, which is under the sticky reading bar: its height (about
   48px) and some air keep the focus in sight (WCAG 2.4.11; 11 of 115 links were hidden, the review of 23 Oct 2026) */
html:has(.reading-bar){scroll-padding-bottom:64px}
@media (max-width:640px){
  .reading-bar-inner{padding:10px 20px 12px}
  .paper{margin-top:0;padding:28px 20px 40px;font-size:17px;box-shadow:none;border-top:1px solid var(--line)}
  .paper .doc-title{font-size:28px}
  .paper .h2{font-size:22px}
}

/* ---------- documents: /privacy, /terms, /dmca, /feature-request and the 404 ----------
   One reading column on the desk, like /why. No sheet, no side id, nothing from styles.css.
   The legal three are written by tools/legal_from_vault.py; the other two are by hand. */
.doc{max-width:calc(62ch + 40px);margin:0 auto;padding:40px 20px 88px}
/* doc-wide: the legal three (written by tools/legal_from_vault.py) and the samples list run just
   inside the nav, 980px of text under the nav's 1060px (Ming, 13 Oct); the form and the 404 keep the
   reading column */
.doc.doc-wide{max-width:1020px}
.doc h1{font-size:40px;font-weight:700;letter-spacing:-.015em;line-height:1.1}
.doc-eyebrow{font-family:var(--fm);font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--mut)}
.doc-eyebrow+h1{margin-top:10px}
.doc-subtitle{margin-top:12px;font-size:20px;font-style:italic;line-height:1.45;color:var(--ink2)}
.doc-meta{margin-top:16px;font-family:var(--fm);font-size:11.5px;letter-spacing:.04em;line-height:1.7;color:var(--mut)}
.doc-note{margin-top:14px;font-size:16.5px;line-height:1.6;color:var(--ink2)}
.doc-toc{margin-top:28px;padding-top:18px;border-top:1px solid var(--line)}
.doc-toc-title{font-family:var(--fm);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--mut)}
.doc-toc ol{margin-top:10px;padding-left:1.5em;font-size:15.5px;line-height:1.65}
.doc-toc ol.own-numbers{list-style:none;padding-left:0}   /* the headings carry their own numbers (tools/legal_from_vault.py) */
.doc-toc li{margin-top:2px}
.doc-toc a{text-decoration:none}
.doc-body{margin-top:36px;font-size:18px;line-height:1.7;color:var(--ink)}
.doc-body h2{margin-top:44px;scroll-margin-top:24px;font-size:24px;font-weight:600;line-height:1.25;letter-spacing:-.01em}
.doc-body h2 .n{margin-right:.45em;font-weight:500;color:var(--link)}
.doc-body h3{margin-top:28px;font-size:19px;font-weight:600}
.doc-body p{margin-top:16px}
.doc-body p.lbl{margin-top:24px;font-weight:600}
.doc-body p.short{color:var(--ink2)}
.doc-body p.agent{margin-top:18px;padding:14px 18px;border-left:2px solid var(--link);background:var(--page);font-size:16.5px;line-height:1.6}
.doc-body ul,.doc-body ol{margin-top:14px;padding-left:1.5em}
.doc-body li{margin-top:8px}
.doc-body li::marker{color:var(--mut)}
.doc-body b{font-weight:600}
.doc-body code{font-family:var(--fm);font-size:.88em}
.doc-body .tablewrap{margin-top:18px;overflow-x:auto}
.doc-body table{width:100%;border-collapse:collapse;font-size:15.5px;line-height:1.5}
.doc-body th,.doc-body td{padding:9px 10px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}
.doc-body th{font-family:var(--fm);font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--mut);font-weight:400}
.doc-body a{text-decoration:underline;text-decoration-color:var(--line);text-underline-offset:3px}
.doc-body a:hover{text-decoration-color:currentColor}
.doc-actions{margin-top:28px;display:flex;flex-wrap:wrap;gap:12px}
/* a form on a document page: the feature request. The fr- classes are what its script expects. */
.doc-form{position:relative;margin-top:32px;display:flex;flex-direction:column;gap:20px}
.doc-form .fr-hp{position:absolute;left:-9999px;width:1px;height:1px;opacity:0}
.fr-field{display:flex;flex-direction:column;gap:7px}
.fr-field label{font-family:var(--fm);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--mut)}
.fr-field input,.fr-field textarea{font:inherit;font-size:17px;line-height:1.5;padding:12px 14px;min-height:44px;
  border:1px solid var(--line);border-radius:0;background:var(--page);color:var(--ink);-webkit-appearance:none}
.fr-field textarea{min-height:170px;resize:vertical}
.fr-field input::placeholder,.fr-field textarea::placeholder{color:var(--mut);opacity:1}
.fr-field input:focus,.fr-field textarea:focus{outline:2px solid var(--link);outline-offset:1px;border-color:var(--link)}
/* a field that failed the check (the page's own script, showFieldError): a red border, the message under it says why */
.fr-field.is-invalid input,.fr-field.is-invalid textarea,.fr-field.is-invalid input:focus,.fr-field.is-invalid textarea:focus{border-color:var(--err)}
.field-err{font-family:var(--fm);font-size:11.5px;letter-spacing:.03em;line-height:1.6;color:var(--err)}
/* the form's own button is solid; scoped to the form so it cannot override an outlined button
   elsewhere (it used to fill the landing page's PDF toggle) */
.doc-form button.btn{border-color:var(--link);cursor:pointer;align-self:flex-start;background:var(--link);color:var(--on-solid)}
.doc-form button.btn:hover{background:var(--solid-hover)}
.doc-form button.btn[disabled]{opacity:.6;cursor:default}
.fr-fine{font-family:var(--fm);font-size:11px;letter-spacing:.04em;line-height:1.6;color:var(--mut)}
.fr-fine.err{color:var(--err)}
.doc-form.sent .fr-fine{color:var(--ink)}
