/* ==================================================================================================
   VEIZIK — preopen site stylesheet (2026-09-20).

   DEPARTURE, STATED: the archived v1 site carried an inline <style> in every page and had no shared
   sheet. That was right for a 25-page tree whose pages were edited one at a time; it is wrong for
   this one, where nine pages share one header, one footer and one table. One immutable, CDN-cached
   file is served once and reused, and a token change lands in nine places at once instead of none.
   The archive under /v1/ keeps its inline styles untouched.

   NOTHING NEW WAS INVENTED HERE. Palette, type scale, spacing and radii are the released site's
   tokens verbatim (the brand tokens, contrast recomputed 2026-09-10). .vz-strip and .vz-panel
   are brand/layout-frames.css, which is where the last design round put the status strip (R-02) and
   the titled box (R-27) after they were rejected as marks. The wordmark rule is the confirmed R-23
   mark: monospace lowercase, z in the signal colour, raised, with its own bar above it.
   ================================================================================================== */

@font-face{font-family:'Red Hat Display';font-style:normal;font-weight:300 900;font-display:swap;src:url(/fonts/RedHatDisplay.woff2) format('woff2')}
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:100 900;font-display:swap;src:url(/fonts/JetBrainsMono.woff2) format('woff2')}

:root{
  --bg:#0d0f13; --bg1:#12151a; --bg2:#161a20; --bg3:#1a1e26; --code:#090b0e; --line:#262b33; --line2:#343a44;
  --ink:#edeff2;   /* 16.7:1 on --bg */
  --mut:#9ba3ae;   /* 7.5:1  on --bg */
  --dim:#8d949f;   /* 6.27:1 on --bg, 5.46:1 on --bg3 */
  --faint:#848b96; /* 5.58:1 on --bg, 4.86:1 on --bg3 — non-text or >=18px only, per the charter */
  --amber:#da5b75; --amber2:#de8a9c; --amber-d:#6e2938;
  --sig:var(--amber); --sig2:var(--amber2); --sig-d:var(--amber-d);
  --ok:#8fb98a; --ok-d:#3c4b39; --warn:#c9924e; --bad:#c46a5a;
  --on-sig:#130407; --on-ok:#0b1209; --on-warn:#170f04; --on-bad:#1a0805;
  --grad: radial-gradient(1200px 520px at 72% -8%, rgba(218,91,117,.07), transparent 60%);
  /* Korean. The site carries Korean prose on /legal/seller-information and /youth-protection, and
     until 2026-09-25 only those pages' own sheets named a Korean face -- so Korean in a generated
     page fell to the platform default. It belongs in the shared stack, not in twelve copies. */
  --ko:'Apple SD Gothic Neo','Malgun Gothic','Noto Sans KR';
  /* Reading measure for long-form legal prose, which is set wider than the product pages' 66ch. */
  --measure:68ch; --measure-wide:78ch;
  /* Section and list numbers. Not a warning -- deliberately not --warn. */
  --num:#b98a35;
  --mono:'JetBrains Mono',ui-monospace,'SF Mono',Menlo,Consolas,'Apple SD Gothic Neo','Malgun Gothic';
  --sans:'Red Hat Display',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,'Apple SD Gothic Neo','Malgun Gothic','Noto Sans KR';
  --r:12px; --rad-s:8px; --hair:1px;
  --s-1:4px; --s-2:8px; --s-3:12px; --s-4:16px; --s-5:24px;
  /* ★THE PAGE GUTTER, WHICH HAD NO NAME. Every column on this site starts 22px from the
     viewport edge, and that 22 lived only as a literal inside .wrap. A value with no owner
     becomes whatever literal is nearest: .auth-wrap reached for --s-4 (16px) because it was
     the token that looked closest, and /signin, /activate and /account then drew their own
     content on a line 6px inside the header wordmark above it and the footer below it --
     three vertical edges stacked on one phone screen. Naming it is the fix; changing 16 to 22
     would only move the next rule's guess. */
  --gutter:22px;
  /* TYPE SCALE. Audited 2026-09-24 across seven pages: SIXTEEN distinct computed sizes
     (54 42 30 28 26 23 20 19 18 17 16 15 14 13 12 11), and <p> alone using eleven different
     size/line-height pairs -- including 14/22.4, 14/23.8 and 14/21.7, the same size with three
     line-heights. Only the bottom four steps had tokens; everything above 14px had accumulated as
     literals in rules and in inline styles. That is not a scale, it is a pile.

     Seven reading steps, each a deliberate ratio from the one below. Two things are deliberately
     NOT in this scale: the wordmark, which is a mark with its own rule and must
     not move when body copy is retuned; and icon sizes, which are a separate family below -- the
     audit's count of sixteen was inflated by reading those as type. */
  /* TRACKING. The one axis of the type system with no token and no gate: the 11px uppercase mono
     micro-label role shipped SEVEN different trackings (.16 .14 .14 .12 .10 .08 .06em) across the
     sheet, so a reader met the same role set five different ways. --ls-label is that role.
     --ls-eyebrow and --ls-th stay separate because they are separate roles, documented here rather
     than left as literals somebody has to reverse-engineer. */
  --ls-label:.14em; --ls-eyebrow:.16em; --ls-th:.06em;
  --ls-mono:.02em; --ls-tight:-.02em; --ls-display:-.035em;
  --fs-micro:12px;   /* eyebrow, credit line, pill, table micro */
  --fs-small:12.5px;   /* caption, footnote */
  --fs-meta:13px;    /* small control, chip, table meta */
  --fs-body:14px;    /* body copy, table body, button */
  --fs-read:16px;    /* reading copy: lede, prose, card heading */
  --fs-sub:20px;     /* sub-heading and displayed figures */
  --fs-h2:30px;      /* section heading (the clamp ceiling) */
  --fs-h1:42px;      /* page heading (the clamp ceiling) */
  --fs-hero:54px;    /* the home headline only */
  --fs-deck:23px;    /* the line under a page headline -- its own role, between sub and h2 */
  --fs-mark:18px;    /* the wordmark. Not a step of the reading scale. */
  /* Icon sizes, counted from the call sites rather than invented: 13 (x11), 14 (x3), 16 (x11),
     17 (x1), 18 (x16), 30 (x1). Four steps; 17 folds into 18 and 14 into 13. */
  --ic-xs:13px; --ic-sm:13px; --ic-md:16px; --ic-lg:18px; --ic-xl:30px;
  /* One line-height per band, so a size cannot arrive with three different ones. */
  --lh-display:1.08;  /* hero and page headlines -- set tight on purpose */
  --lh-tight:1.15;    /* section headings */
  --lh-snug:1.35;     /* labels and figures */
  --lh-body:1.6;      /* everything you read in sentences */
  --lh-code:1.85;     /* a terminal block, which needs the air */
  --tap:44px;
}

*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
body{background:var(--bg);color:var(--ink);font-family:var(--sans),sans-serif;line-height:var(--lh-body);font-size:var(--fs-read);
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale}
body::before{content:"";position:fixed;inset:0;background:var(--grad);pointer-events:none;z-index:0}
a{color:var(--amber2);text-decoration:none}@media(hover:hover) and (pointer:fine){
a:hover{color:var(--amber)}}
a:focus-visible,button:focus-visible,input:focus-visible,summary:focus-visible{outline:2px solid var(--amber2);outline-offset:2px;border-radius:3px}
.wrap{max-width:1120px;margin:0 auto;padding:0 var(--gutter);position:relative;z-index:1}
/* <code> carries the same look the .mono span used to, so the semantics change costs no pixels. */
code{font-family:var(--mono),var(--ko),monospace;letter-spacing:var(--ls-mono)}
.mono{font-family:var(--mono),monospace}
/* A quantity column is read by comparing magnitudes down the column, so the digits have to
   line up: 327 MiB against 3,923 MiB is unreadable ragged-left. Right-aligned, tabular, and
   never broken between the number and its unit (at 860px "327 MiB" was wrapping onto two
   lines). A date is an identifier, not a magnitude -- it keeps .dt and stays left. */
.num{font-variant-numeric:tabular-nums;font-family:var(--mono),monospace;
  text-align:right;white-space:nowrap}
th.num{text-align:right}
.dt{font-variant-numeric:tabular-nums;font-family:var(--mono),monospace;white-space:nowrap}
/* An icon belongs to the word beside it. Without this the glyph sat on one line and
   "Language" / "Apple Silicon" dropped to the next, in a cell wide enough for both. */
.nw{white-space:nowrap}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
svg.i{width:1em;height:1em;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;vertical-align:-.14em}

/* ---- section furniture ---- */
section{padding:56px 0;border-top:var(--hair) solid var(--line);position:relative;scroll-margin-top:72px}
/* 72px = the sticky header (60px) plus a little air. Without it an anchor lands with its heading
   tucked under the header, which the first render review caught on /docs.html#install. */
.eyebrow{font-family:var(--mono),monospace;font-size:var(--fs-micro);letter-spacing:.16em;text-transform:uppercase;color:var(--amber);
  display:inline-flex;align-items:center;gap:8px;margin-bottom:12px}
.eyebrow::before{content:"";width:18px;height:1px;background:var(--amber)}
.eyebrow svg.i{margin-right:-2px}  /* the flex gap already spaces it; the icon only needs to not double it */
h1.page{font-size:clamp(28px,4.2vw,var(--fs-h1));line-height:var(--lh-display);letter-spacing:-.03em;font-weight:700}
h2.sec{font-size:clamp(22px,3.2vw,var(--fs-h2));letter-spacing:-.02em;font-weight:700;line-height:var(--lh-tight);margin-bottom:10px}
h3.sub{font-size:var(--fs-sub);line-height:var(--lh-snug);font-weight:600;letter-spacing:-.01em;margin-bottom:6px}
.lede{color:var(--mut);font-size:var(--fs-read);line-height:var(--lh-body);max-width:66ch}
p+p{margin-top:12px}
/* Long-form doc body lifted toward --ink for magazine-grade legibility (was --mut 7.5:1); the measure
   is tightened to ~68ch, the comfortable reading column. */
.prose p{color:color-mix(in srgb,var(--ink) 78%,var(--mut));font-size:var(--fs-read);line-height:var(--lh-body);max-width:68ch}
.prose ul{color:color-mix(in srgb,var(--ink) 78%,var(--mut));font-size:var(--fs-read);line-height:var(--lh-body);max-width:68ch;margin:10px 0 0 18px}
.prose li{margin-bottom:6px}
.prose b,.prose strong{color:var(--ink);font-weight:600}

/* ---- pills, buttons ---- */
.pill{display:inline-flex;align-items:center;gap:5px;font-family:var(--mono),monospace;font-size:var(--fs-micro);letter-spacing:.03em;
  padding:3px 9px;border-radius:999px;border:var(--hair) solid var(--line2);color:var(--mut);white-space:nowrap}
.pill.ok{color:var(--ok);border-color:var(--ok-d);background:rgba(143,185,138,.07)}
.pill.am{color:var(--amber);border-color:var(--amber-d);background:rgba(218,91,117,.08)}
.pill.warnp{color:var(--warn);border-color:#6a4a14;background:rgba(201,146,78,.08)}
.pill.soon{color:var(--dim);border-color:var(--line)}
.pill svg.i{opacity:.9}
.btn{display:inline-flex;align-items:center;gap:8px;background:var(--amber);color:var(--on-sig);border:0;border-radius:9px;
  padding:11px 17px;font-size:var(--fs-body);font-weight:600;cursor:pointer;font-family:var(--mono),monospace;letter-spacing:-.01em;
  white-space:nowrap;transition:.15s}@media(hover:hover) and (pointer:fine){
.btn:hover{background:var(--amber2);color:#0d0205}}
.btn.ghost{background:transparent;border:var(--hair) solid var(--line2);color:var(--ink)}@media(hover:hover) and (pointer:fine){
.btn.ghost:hover{border-color:var(--amber);color:var(--amber2)}}
.btn.sm{padding:8px 13px;font-size:var(--fs-meta)}

/* ---- header / nav ---- */
header.site{position:sticky;top:0;z-index:30;background:rgba(13,15,19,.82);backdrop-filter:blur(10px);border-bottom:var(--hair) solid var(--line)}
.nav{display:flex;align-items:center;justify-content:space-between;height:60px;gap:16px;min-width:0}
.nav-cta{flex:0 0 auto}
.brand{min-width:0;flex:0 1 auto}
.brand{display:flex;align-items:baseline;gap:10px;color:var(--ink)}@media(hover:hover) and (pointer:fine){
.brand:hover{color:var(--ink)}}
/* R-23, the confirmed mark: the six-letter wordmark alone. No icon sits beside it.
   The mark is defined ONCE, canonically, in build/v3/base.css (.wm / .wm i / .wm i::before, the
   token z-raise + rose-bar construction) and that global rule loads on legacy pages too. The former
   .vzl-scoped copy here diverged (z top:-.13em vs canonical -.26em, bar .11em/-.07em vs .105em/-.05em)
   and, being more specific, would have overridden the canonical mark the moment any .wm rendered inside
   a .vzl body — a latent consistency hazard. Removed so the mark can never render off-spec on a legacy
   page. (Today every wordmark sits in the v3 nav/footer, outside .vzl, so this is hygiene + future-proofing.) */
.brand .tagline{font-family:var(--mono),monospace;font-size:var(--fs-micro);color:var(--dim);letter-spacing:.08em}
.nav-links{display:flex;align-items:center;gap:22px}
.nav-links a{color:var(--mut);font-size:var(--fs-body)}@media(hover:hover) and (pointer:fine){
.nav-links a:hover,.nav-links a[aria-current="page"]{color:var(--ink)}}
.nav-links a[aria-current="page"]{box-shadow:0 2px 0 -1px var(--amber)}
.nav-cta{display:flex;align-items:center;gap:12px}
.burger{display:none;background:none;border:var(--hair) solid var(--line2);border-radius:8px;color:var(--ink);padding:7px;cursor:pointer}
/* Icon-only control: it needs a hit area and a border like the burger, or it reads as a stray glyph. */
.gh{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border-radius:8px;
  border:var(--hair) solid var(--line2);color:var(--mut);flex:0 0 auto}@media(hover:hover) and (pointer:fine){
.gh:hover{color:var(--ink);border-color:var(--amber-d)}}
.mobile{display:none;border-bottom:var(--hair) solid var(--line);background:var(--bg1)}
.mobile a{display:block;padding:12px var(--gutter);color:var(--mut);border-top:var(--hair) solid var(--line);font-size:var(--fs-read)}
.mobile.open{display:block}
/* `.mobile.open` was unqualified while the ONLY control that closes it -- the burger -- lives in
   @media(max-width:680px). Open the menu on a phone, rotate or widen past 681px, and the panel stays
   open forever with no visible way to dismiss it: the close button is display:none. The panel is a
   phone affordance, so above the breakpoint it is not open, whatever the class says. vz.js also
   clears the class and the aria state on the media-query change so the DOM agrees with the paint. */
@media(min-width:681px){.mobile.open{display:none}}

/* ---- hero ---- */
/* The two-column hero belongs to the SHAPE that has a second column, not to the word "hero".
   Seven served pages -- /security, /terms, /notices, /model-licenses and the three under /legal --
   use .hero as a stacked content header: eyebrow, h1, meta line, notice. Under a blanket
   `display:grid` those four auto-placed into two columns, so /security rendered its title in the
   right-hand column with an empty space beside it and the eyebrow stranded on the left. Scoping the
   grid to `:has(.term)` needs no edit to any page, and a browser without :has() falls back to the
   stacked layout, which is the correct one for every page but the home page. */
.hero{padding:74px 0 56px;border-top:none}
/* The COLUMNS live behind a min-width, not just behind :has(). Written without it, this rule
   (specificity 0,2,0) out-specified the `@media(max-width:680px){.hero{grid-template-columns:1fr}}`
   below (0,1,0), and the home page kept two columns down to 375px -- a 92px terminal beside a
   183px headline. Nothing overflowed, so the viewport sweep stayed green; it only shows in a
   picture. A narrower override has to be at least as specific as what it overrides. */
.hero:has(.term){display:grid;gap:28px;align-items:start}
/* 941, not 681: the sheet already decided at `@media(max-width:940px){.hero{...1fr}}` that the
   split stops working below 940. Scoping this at 681 did not disagree with that decision --
   it silently outranked it for 260px of width, which is the same specificity accident this
   rule was written to stop. The two now meet instead of overlapping. */
@media(min-width:941px){ .hero:has(.term){grid-template-columns:1.4fr .6fr;gap:44px} }
.hero h1{font-size:clamp(34px,5.2vw,var(--fs-hero));line-height:var(--lh-display);letter-spacing:-.035em;font-weight:700}
.hero h1 .am{color:var(--amber2)}
/* The line directly under the slogan. It carries the product definition, so it is nearer the
   headline in weight than the body copy that follows it. */
.hero p.deck{color:var(--ink);font-size:clamp(var(--fs-sub),2.4vw,var(--fs-deck));line-height:var(--lh-tight);letter-spacing:-.015em;
  font-weight:600;margin:20px 0 0;max-width:30ch}
.hero p.sub{color:var(--mut);font-size:var(--fs-read);line-height:var(--lh-body);margin:6px 0 0;max-width:52ch}
.hero .cta{display:flex;gap:12px;flex-wrap:wrap;align-items:center;margin-top:24px}
.microcopy{color:var(--dim);font-size:var(--fs-meta);margin-top:14px;display:flex;gap:14px;flex-wrap:wrap}
/* In the hero the microcopy is one sentence, not a row of chips: side-by-side it read as two columns. */
.hero .microcopy{display:block;line-height:var(--lh-body)}
/* .microcopy span is inline-flex for the icon rows elsewhere; in the hero the span holds a sentence
   with a link inside it, and inline-flex turned that into two columns. */
.hero .microcopy span{display:inline}
.microcopy span{display:inline-flex;align-items:center;gap:6px}
/* A sentence is not an icon row. `display:inline-flex` makes the text one unbreakable flex
   item, and the docs sentence ran 503px wide inside a 390px viewport (WebKit sweep, 4 of 143
   page/width pairs). Sentences opt out and lay out as text. */
.microcopy span.say{display:inline;align-items:initial;gap:0}
.phon{font-family:var(--mono),monospace;font-size:var(--fs-small);color:var(--dim);letter-spacing:.04em;text-transform:none}
/* The brand line. It sits under the hero rather than in it: the H1's job is to say what the product
   is, and this line's job is to be remembered. One per page, never both jobs at once. */
.brandline{font-family:var(--mono),monospace;font-size:clamp(var(--fs-read),2vw,var(--fs-sub));letter-spacing:-.01em;
  color:var(--amber2);margin:0 0 22px;font-weight:600}

/* ---- terminal card ---- */
/* Quickstart stacks: the terminal at full measure first, the prose under it. The two-column
   grid it used to sit in cut the longest command in half, and a wrapped command is a command that
   does not paste. */
.qs{display:block}
.qs > .term{width:100%}
.qs > :not(.term){margin-top:20px}
.term--wide pre{white-space:pre;overflow-x:auto}
.term{background:var(--code);border:var(--hair) solid var(--line2);border-radius:12px;overflow:hidden;box-shadow:0 24px 60px -30px rgba(0,0,0,.8)}
.term .bar{display:flex;align-items:center;gap:7px;padding:10px 13px;border-bottom:var(--hair) solid var(--line);background:var(--bg1)}
.term .bar i{width:10px;height:10px;border-radius:50%;background:var(--line2);display:inline-block}
.term .bar i:first-child{background:var(--amber-d)}
.term .bar .t{margin-left:8px;font-family:var(--mono),monospace;font-size:var(--fs-micro);color:var(--dim)}
/* The hero's one job is showing what running Veizik looks like, and `white-space:pre` cut the
   command at the box edge at EVERY width -- 331px of it missing at 375, still 11px at 1024 --
   behind a scrollbar nobody sees on a touch device. It wraps at spaces now; overflow-x stays
   for a single token longer than the box. */
.term pre{overflow-x:auto;margin:0;padding:15px 16px;font-family:var(--mono),monospace;font-size:var(--fs-meta);line-height:var(--lh-code);color:var(--mut);white-space:pre-wrap}
.term .c{color:var(--faint)} .term .p{color:var(--amber2)} .term .g{color:var(--ok)} .term .a{color:var(--amber)}
.term .k{color:var(--ink)}

/* ---- grids and cards ---- */
/* A two-column grid holding ONE card leaves half the row empty and reads as a layout that failed
   to load. /benchmarks has exactly that where an axis carries a single result. The container
   should follow its contents: one child means one full-width panel. */
.grid2:has(> :only-child){grid-template-columns:1fr}
.grid2{display:grid;grid-template-columns:repeat(2,1fr);gap:16px}
.grid3{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}
.grid4{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}
.card{background:var(--bg1);border:var(--hair) solid var(--line);border-radius:var(--r);padding:22px 20px;position:relative;transition:.15s}@media(hover:hover) and (pointer:fine){
.card:hover{border-color:var(--line2)}}@media(hover:hover) and (pointer:fine){
a.card:hover{border-color:var(--amber-d)}}
.card h3{font-size:var(--fs-read);margin-bottom:6px;font-weight:600;color:var(--ink)}
.card p{color:var(--mut);font-size:var(--fs-body)}
.ic{width:38px;height:38px;border-radius:9px;display:inline-flex;align-items:center;justify-content:center;
  background:rgba(218,91,117,.09);border:1px solid var(--amber-d);color:var(--amber2);font-size:var(--fs-sub);margin-bottom:13px}

/* ---- stat strip ---- */
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:0;border:var(--hair) solid var(--line);border-radius:var(--r);overflow:hidden;background:var(--bg1)}
/* Column counts belong in the stylesheet, not in a style attribute: an inline grid-template beats
   every media query, and at 390px it held three columns and crushed each tile to a word per line. */
.stats.s3{grid-template-columns:repeat(3,1fr)}
.stat{padding:18px 16px;border-right:var(--hair) solid var(--line)}
.stat:last-child{border-right:none}
.stat .n{font-size:var(--fs-sub);line-height:var(--lh-snug);font-weight:700;letter-spacing:-.02em;font-family:var(--mono),monospace}
.stat .n .am{color:var(--amber2)}
.stat .l{color:var(--dim);font-size:var(--fs-small);margin-top:3px;line-height:var(--lh-snug)}

/* ---- tables ---- */
/* A table that is 731px wide inside a 329px viewport scrolls sideways, and at 375px nothing on
   screen said so. These are Komarov's scrolling shadows: the two `local` gradients ride with the
   content and paint over the two `scroll` shadows, so a shadow is visible only on a side that has
   more table left. Pure CSS — it is correct with JS off, and it disappears by itself on a wide
   screen where the table already fits. */
/* The scroller is focusable now (build.py names it at emit time), so it must show where focus is.
   Without this the keyboard user the tabindex was added for gets a silent stop. */
.tw:focus-visible{outline:2px solid var(--sig);outline-offset:2px;border-radius:4px}
.tw{overflow-x:auto;border:var(--hair) solid var(--line);border-radius:var(--r);
  background:
    linear-gradient(to right,  var(--bg) 42%, rgba(13,15,19,0)) 0 0 / 34px 100% no-repeat local,
    linear-gradient(to left,   var(--bg) 42%, rgba(13,15,19,0)) 100% 0 / 34px 100% no-repeat local,
    radial-gradient(farthest-side at 0    50%, rgba(0,0,0,.55), rgba(0,0,0,0)) 0 0 / 16px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, rgba(0,0,0,.55), rgba(0,0,0,0)) 100% 0 / 16px 100% no-repeat scroll;}
.tw table{background:transparent}
table{border-collapse:collapse;width:100%;font-size:var(--fs-body);min-width:560px}
th,td{text-align:left;padding:11px 15px;border-bottom:var(--hair) solid var(--line);vertical-align:middle}
th{font-family:var(--mono),monospace;font-size:var(--fs-micro);letter-spacing:.06em;text-transform:uppercase;color:var(--mut);
  background:var(--bg1);font-weight:500;white-space:nowrap}
tbody tr:last-child td{border-bottom:none}@media(hover:hover) and (pointer:fine){
tbody tr:hover{background:rgba(255,255,255,.015)}}
td b{color:var(--ink);font-weight:600}
.cell-y{color:var(--ok)} .cell-n{color:var(--dim)} .cell-w{color:var(--warn)}
/* `.cmp` was dead CSS — no generated page carries that class, so this rule had never applied.
   It matters most where it never ran: at 375px the comparison board is 731px wide, and a reader
   who scrolls right to the ratio loses the Axis cell that says what the row is. The first column
   now stays put, so the row keeps its label. */
.tw td:first-child,.tw th:first-child{position:sticky;left:0;z-index:1;
  background:var(--bg);box-shadow:1px 0 0 var(--line)}
.tw th:first-child{background:var(--bg1)}

/* ---- frames, from brand/layout-frames.css (R-02 status strip, R-27 titled box) ---- */
/* Ratios below recomputed 2026-09-20 from the tokens in this file (WCAG 2.x relative luminance).
   layout-frames.css carried 5.55 / 9.10 / 7.66 for the first three; the values here are what the
   current token set actually produces, which is the number that matters. */
.vz-strips{display:flex;flex-direction:column;gap:var(--s-2)}
.vz-strip{display:flex;align-items:stretch;border:var(--hair) solid var(--line);border-radius:var(--rad-s);overflow:hidden;
  background:var(--bg1);font-family:var(--mono),monospace;font-size:var(--fs-small);line-height:var(--lh-body)}
.vz-strip>.s{display:inline-flex;align-items:center;white-space:nowrap;font-weight:600;letter-spacing:.02em;
  padding:var(--s-2) var(--s-3);background:var(--sig);color:var(--on-sig)}            /* 5.47:1 */
.vz-strip>.l{display:flex;align-items:center;flex:1;min-width:0;color:var(--mut);padding:var(--s-2) var(--s-3);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.vz-strip>.m{display:inline-flex;align-items:center;white-space:nowrap;color:var(--dim);font-variant-numeric:tabular-nums;
  padding:var(--s-2) var(--s-3);border-left:var(--hair) solid var(--line)}
.vz-strip.is-ok>.s{background:var(--ok);color:var(--on-ok)}                            /* 8.58:1 */
.vz-strip.is-warn>.s{background:var(--warn);color:var(--on-warn)}                      /* 6.96:1 */
.vz-strip.is-bad>.s{background:var(--bad);color:var(--on-bad)}                         /* 5.14:1 */
.vz-panel{margin:0;border:2px solid var(--sig-d);border-radius:var(--rad-s);background:var(--bg1);overflow:hidden}
.vz-panel>figcaption{background:var(--sig-d);color:var(--ink);font-family:var(--mono),monospace;font-size:var(--fs-micro);
  letter-spacing:.10em;padding:var(--s-1) var(--s-3)}                                  /* 8.95:1 */
.vz-panel>.b{padding:var(--s-4) var(--s-3);font-family:var(--mono),monospace;font-size:var(--fs-body);color:var(--ink);
  letter-spacing:.05em;text-align:center}
.vz-panel>.b em{font-style:normal;color:var(--sig2)}                                   /* 7.16:1 on --bg1 */
.vz-panel>.b.left{text-align:left;letter-spacing:0;line-height:var(--lh-body);font-family:var(--sans),sans-serif;font-size:var(--fs-body);color:var(--mut)}
.vz-panel>.b.left b{color:var(--ink)}
.vz-panel.is-warn{border-color:#6a4a14} .vz-panel.is-warn>figcaption{background:#6a4a14}
.vz-panel.is-ok{border-color:#2c4029}   .vz-panel.is-ok>figcaption{background:#2c4029}

/* ---- release / changelog feed ---- */
.feed{display:flex;flex-direction:column;gap:14px}
.entry{background:var(--bg1);border:var(--hair) solid var(--line);border-radius:var(--r);padding:18px 20px}
.entry .top{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-bottom:8px}
.entry time{font-family:var(--mono),monospace;font-size:var(--fs-small);color:var(--dim);font-variant-numeric:tabular-nums}
.entry h3{font-size:var(--fs-read);font-weight:600;margin:0}
.entry p{color:var(--mut);font-size:var(--fs-body);margin-top:6px}
.entry ul{color:var(--mut);font-size:var(--fs-body);margin:8px 0 0 18px}
.entry li{margin-bottom:4px}
.tags{display:flex;gap:6px;flex-wrap:wrap}
.tag{font-family:var(--mono),monospace;font-size:var(--fs-micro);letter-spacing:.06em;color:var(--dim);
  border:var(--hair) solid var(--line);border-radius:5px;padding:2px 7px;background:var(--bg2)}
.tag.sig{color:var(--amber2);border-color:var(--amber-d)}

/* ---- definition rows (model spec sheets) ---- */
.spec{display:grid;grid-template-columns:200px 1fr;gap:0;border:var(--hair) solid var(--line);border-radius:var(--r);overflow:hidden;background:var(--bg1)}
.spec dt{font-family:var(--mono),monospace;font-size:var(--fs-small);letter-spacing:.04em;color:var(--dim);
  padding:11px 15px;border-bottom:var(--hair) solid var(--line);background:var(--bg2)}
.spec dd{padding:11px 15px;border-bottom:var(--hair) solid var(--line);color:var(--ink);font-size:var(--fs-body)}
.spec dt:last-of-type,.spec dd:last-of-type{border-bottom:none}

/* ---- footer ---- */
footer.site{border-top:var(--hair) solid var(--line);padding:40px 0 56px;margin-top:0;background:var(--bg1)}
footer.site .cols{display:grid;grid-template-columns:1.4fr 1fr 1fr 1fr;gap:26px}
footer.site h4{font-family:var(--mono),monospace;font-size:var(--fs-micro);letter-spacing:var(--ls-label);text-transform:uppercase;
  color:var(--dim);margin-bottom:10px}
footer.site a{display:block;color:var(--mut);font-size:var(--fs-meta);margin-bottom:7px}@media(hover:hover) and (pointer:fine){
footer.site a:hover{color:var(--ink)}}
footer.site .fine{color:var(--dim);font-size:var(--fs-small);margin-top:28px;border-top:var(--hair) solid var(--line);padding-top:18px;line-height:var(--lh-body)}

@media(max-width:940px){
  .hero{grid-template-columns:1fr;gap:28px;padding-top:52px}
  .grid3,.grid4{grid-template-columns:repeat(2,1fr)}
  .stats,.stats.s3{grid-template-columns:repeat(2,1fr)}
  /* Three tiles in two columns leaves the third alone beside a hole -- visible on the home page at
     768 and 834, the two commonest tablet widths. It spans instead, so the block reads two-over-one
     rather than as a row that failed to fill. Four tiles divide evenly and are left alone. */
  .stats.s3 > .stat:last-child{grid-column:1/-1;border-right:none}
  .stat:nth-child(2n){border-right:none}
  .stat{border-bottom:var(--hair) solid var(--line)}
  footer.site .cols{grid-template-columns:1fr 1fr}
}
/* The header squeeze, measured rather than guessed. Between 760 and 940px the tagline wrapped to
   two and three lines and pushed the brand block to 38px then 56px tall; below 760px the CTA button
   (195px) plus the GitHub control pushed the row past the viewport and the whole page scrolled
   sideways. Three steps, each at the width where the measurement said it breaks. */
@media(max-width:940px){ .brand .tagline{display:none} }
@media(max-width:820px){ .nav-links{gap:16px} .nav-links a{font-size:var(--fs-meta)} }
@media(max-width:760px){
  /* Was: font-size:0 plus ::after{content:"Get access"} — a mobile stand-in from when the label
     was "Request preview access" and genuinely did not fit. It is "Sign in" now, which is SHORTER
     than the substitute, so the override only ever replaced the real label with a different word.
     Worse, the selector also matched the signed-in account button, so a phone showed "Get access"
     where the desktop showed the person's own address. Measured on the live page at 375px. */
  .nav-cta .btn.sm{padding:9px 12px;font-size:var(--fs-meta)}
}
/* The signed-in header button carries an address, which is as long as the person's address is.
   Measured at 375px it was ~226px wide and the header lost its wordmark and its burger to it.
   The address ellipsises, the device count drops on a phone, and the whole control is capped
   against the viewport rather than against a character count nobody can see. */
/* ★THE ACCOUNT CONTROL IS A CIRCLE, THE SAME SIZE AS THE OTHER TWO HEADER CONTROLS. It used to be
   a wide ghost button carrying the signed-in address and a device count, which is the one thing a
   reader already knows plus a number that belongs on the page that can act on it -- and at 375px
   the pair pushed the wordmark and the burger around, which is why the count had a media query
   hiding it. A 34px circle needs no media query.
   The mark is the vz monogram. A Google or GitHub profile photo is deliberately NOT drawn: nothing
   in this system stores one (/api/account/me returns signed_in, account_id, email, methods and
   activation, and the identity table keeps no picture), and hotlinking the provider's avatar would
   report the IP of every signed-in page load to that provider. Showing one needs a decision from
   the account lane about scope and storage, not a URL pasted in here. */
.avatar{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;
  border-radius:50%;flex:0 0 auto;background:var(--bg3);border:var(--hair) solid var(--line2);
  color:var(--ink);overflow:hidden;text-decoration:none}
.avatar .av-mark{font-family:var(--mono),monospace;font-size:var(--fs-micro);font-weight:700;
  letter-spacing:-.02em;line-height:1;color:var(--amber)}
.avatar img{width:100%;height:100%;object-fit:cover;display:block}
@media(hover:hover) and (pointer:fine){ .avatar:hover{border-color:var(--amber-d)} }
/* The sheet declares --tap:44px and the header then ignored it: measured on the live page at
   375px the GitHub link and the burger were 34px and the CTA 39px, all under the 44px minimum
   these controls exist to meet. Coarse pointers only, so the desktop header keeps its proportions. */
@media(pointer:coarse),(max-width:680px){
  /* The floor belongs to the ROLE, not to a list of places. The enumeration under this comment
     names nine selectors and omitted both sets of links that are the site's actual navigation:
     measured, every footer link and every header link is 20.8px tall on a coarse pointer, against
     a --tap of 44px that the burger beside them honours exactly. */
  header nav a,.nav-links a{min-height:var(--tap);display:inline-flex;
    align-items:center;margin-bottom:0}
  /* ★THE SAME FLOOR, BUT NOT THE SAME FLOW. Footer links were in the rule above, and
     `display:inline-flex` overrode the `display:block` they are laid out with -- so on a phone
     every column's links flowed onto one line and touched: "ProductModels", "BenchmarksSign in".
     A rule written for a ROLE (a 44px tap floor) still has to respect that the role lives in two
     different flows: the header nav is a row, the footer column is a list. `flex` is block-level,
     so the link fills its column and stacks, and the floor and centring are unchanged. */
  footer.site a{min-height:var(--tap);display:flex;align-items:center;margin-bottom:0}
  .nav-cta .gh,.burger{width:var(--tap);height:var(--tap)}
  /* Was `.nav-cta .btn.sm`. The same button off the header -- "Cookie settings" on /privacy,
     "Sub-processor list", "Email a data request" -- measured 35px, because the selector named a
     place rather than the control. A rule that declares a minimum for a control has to reach the
     control wherever it is drawn. */
  .btn.sm{min-height:var(--tap);display:inline-flex;align-items:center}
  .mobile a{min-height:var(--tap);display:flex;align-items:center}
  /* The wordmark is the way back to the home page and it measured 64x18 on all 31 pages. Padding
     rather than min-height: the brand is a baseline-aligned flex box, and growing it with
     min-height would push the mark to the top of its own box instead of centring it. */
  /* padding, not min-height alone: the brand is a baseline-aligned flex box and min-height on
     its own would push the mark to the top of its own box. The generated pages set the
     wordmark at 18px and the hand-written ones at 16px, so padding alone landed on 44 and 42
     -- the floor has to be stated as a floor. */
  .brand{padding-block:13px;min-height:var(--tap)}
  /* A standalone row of document links is navigation, not prose: /legal/seller-information
     offers five policy documents in a flex row that measured 24px. */
  .linkrow a{display:inline-flex;align-items:center;min-height:var(--tap)}
  /* The /models facet buttons were 27px -- the only way to filter that page on a phone. */
  .chip{min-height:var(--tap);display:inline-flex;align-items:center}
  /* /legal's index is four cards whose heading is the link. At 20px the target was the words. */
  .card h3 a{min-height:var(--tap);display:flex;align-items:center}
  /* The <details> disclosure on /signin ("What signing in with a provider does") was 21px. */
  .auth summary{min-height:var(--tap);display:flex;align-items:center}
  /* An eyebrow is an 11px kicker, not a button -- it keeps its type size and takes the 24px floor
     that applies to a label-sized link, not the 44px one that applies to a control. */
  .eyebrow a,.pill a{display:inline-flex;align-items:center;min-height:24px}
}
@media(max-width:680px){
  .stats,.stats.s3{grid-template-columns:1fr}
  .stat{border-right:none}
  .nav-links{display:none}
  .burger{display:inline-flex}
  .grid2,.grid3,.grid4{grid-template-columns:1fr}
  .spec{grid-template-columns:1fr}
  .spec dt{border-bottom:none;padding-bottom:0;background:transparent}
  .spec dd{padding-top:4px}
  section{padding:42px 0}
}

/* ==================================================================================================
   WRAPPING, RESOLUTION AND SCROLL — added 2026-09-20 after measuring, not after looking.

   The audit ran 9 pages x 10 widths (1440 down to 360) and read every block's real line boxes,
   taking the line count from height / line-height so that a 13px span sitting beside an 18px
   heading is not miscounted as a second line. 1,214 multi-line blocks; 167 of them ended on a
   line under 20% of the measure — "of AI.", "stack.", "command.", "own.", "hardware." alone on
   a line. A last line that short reads as a fragment, and for a heading it splits the phrase the
   heading exists to say.
   ================================================================================================== */

/* Body copy: let the engine pull a word down rather than leave one behind. */
p,li,dd,td,th,figcaption{text-wrap:pretty}

/* Headings and ledes: even lines, so the break lands between phrases instead of after the last
   word that happened to fit. Declared after the rule above so .lede (a <p>) takes this one. */
h1,h2,h3,h4,.deck,.lede,.eyebrow{text-wrap:balance}
/* `balance` evens the line lengths, which is what a heading wants. A .lede is one or two
   lines on a desktop and five on a phone, and evening five lines pulls the paragraph into a
   narrow ragged column beside a full-width paragraph directly under it -- measured on
   /models at 375px, where the lede filled 300 of its 331px box. Headings stay balanced
   because they stay short; the lede switches to the body-text setting. */
@media(max-width:680px){ .lede{text-wrap:pretty} }

/* Both properties are ignored by browsers that do not have them (Safari before 17.5 for balance,
   before 26 for pretty), which leaves those readers exactly today's rendering — no regression. */

/* Apple displays: a 1px rule is two device pixels at 2x and reads as a drawn line rather than a
   separator. Halve it where there are pixels to spare; the token keeps every border in step. */
@media (-webkit-min-device-pixel-ratio:1.5),(min-resolution:144dpi){
  :root{--hair:.5px}
}

/* Scroll ----------------------------------------------------------------------------------------
   A horizontal fling at a scroller's edge is history-back on a macOS trackpad. The board is wider
   than the phone viewport by design, so a reader who swipes past the last column would leave the
   page. Contain it. */
.tw,.term pre{overscroll-behavior-x:contain}

/* Anchors land under the sticky header unless the offset is the header's own height. */
:root{--header-h:61px}
section,h2[id],h3[id],[id^="sec-"]{scroll-margin-top:calc(var(--header-h) + 14px)}

/* Scroll position, shown where the eye already is. Runs on the compositor off the main thread,
   costs no JavaScript, and simply does not appear where scroll-driven animation is unsupported. */
@supports (animation-timeline:scroll()){
  header.site::after{content:"";position:absolute;left:0;right:0;bottom:calc(-1 * var(--hair));
    height:2px;transform:scaleX(0);transform-origin:0 50%;
    background:linear-gradient(90deg,var(--sig),var(--sig2));
    animation:vz-progress linear both;animation-timeline:scroll(root block)}
  @keyframes vz-progress{from{transform:scaleX(0)}to{transform:scaleX(1)}}
}

/* A number on its own invites the question it exists to answer. The facts under it are what the
   10.1 MB means: nothing else to install, and no converted copy of the model. */
.facts{list-style:none;margin-top:14px;display:grid;gap:8px}
.facts li{display:flex;align-items:flex-start;gap:0;color:var(--mut);font-size:var(--fs-meta);
  line-height:var(--lh-body)}
.facts li svg{margin-top:2px}

@media (prefers-reduced-motion:reduce){
  header.site::after{display:none}
  html{scroll-behavior:auto}
  *{transition:none!important;animation:none!important}
}

/* ---- catalogue facets (/models) --------------------------------------------------------------
   The chips filter rows that are already in the page. With scripting off they simply do nothing,
   which is why the table must never depend on them. */
.facets{display:flex;flex-direction:column;gap:10px;margin-top:8px}
.facet{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.facet-l{font-family:var(--mono),monospace;font-size:var(--fs-micro);letter-spacing:var(--ls-label);
  text-transform:uppercase;color:var(--faint);width:104px;flex:0 0 auto}
.chip{font-family:var(--mono),monospace;font-size:var(--fs-small);color:var(--mut);cursor:pointer;
  background:var(--bg1);border:var(--hair) solid var(--line);border-radius:999px;padding:5px 12px;transition:.12s}@media(hover:hover) and (pointer:fine){
.chip:hover{border-color:var(--line2);color:var(--ink)}}
.chip.on{color:var(--on-sig);background:var(--amber);border-color:var(--amber);font-weight:600}
.chip.reset{align-self:flex-start;margin-left:112px;border-style:dashed;color:var(--dim)}
tbody tr[hidden]{display:none}
@media(max-width:680px){.facet-l{width:100%}.chip.reset{margin-left:0}}

/* ==================================================================================================
   DATA MARKS. Every one of these encodes a number that is already in the ledger — a ratio, a
   fraction, a count. None of them is decoration with a shape: if the datum goes away the mark goes
   with it. That is the difference between a dense page and a busy one.
   ================================================================================================== */

/* Ratio bar: centred on parity. Right of the line is faster, left is slower, and the line itself
   is drawn so "about the same" looks like about the same instead of like a win. */
.rbar{display:flex;align-items:center;gap:9px;white-space:nowrap}
.rbar u{position:relative;display:block;width:74px;height:14px;flex:0 0 auto;text-decoration:none;
  background:var(--bg3);border-radius:3px}
.rbar u::before{content:"";position:absolute;left:50%;top:-3px;bottom:-3px;width:1px;background:var(--line2)}
.rbar i{position:absolute;top:2px;height:10px;border-radius:2px;display:block}
.rbar i.up{left:50%;background:linear-gradient(90deg,var(--sig-d),var(--sig))}
.rbar i.dn{right:50%;background:var(--warn);opacity:.75}
.rbar b{font-family:var(--mono),monospace;font-size:var(--fs-small);font-variant-numeric:tabular-nums}

/* Proportion bar: what is left after a transformation, drawn against what went in. */
.pbar{display:block;height:8px;border-radius:99px;background:var(--bg3);overflow:hidden;margin-top:6px}
.pbar i{display:block;height:100%;background:linear-gradient(90deg,var(--sig),var(--sig2));border-radius:99px}
.pbar-l{display:flex;justify-content:space-between;font-family:var(--mono),monospace;
  font-size:var(--fs-micro);color:var(--dim);margin-top:5px}

/* Section rhythm. Every section looked the same, so the page read as one long column of text.
   Alternating the ground gives the eye a place to rest and makes the page feel built rather than
   typed. Contrast stays within the ladder: --bg1 is 15.88:1 against --ink. */
/* ★A FILLED BAND WITH NO SIDE PADDING PUTS THE TEXT ON ITS OWN EDGE. Operator, 2026-10-05, twice
   and with screenshots: every layout box should follow the rounded-rectangle frame, and none of
   them should have zero padding. A band was the exception on every page -- `section` gives
   `padding:56px 0`, so the vertical air was there and the horizontal was ZERO, and `.band` adds a
   background on top of that. The result is a square-cornered filled panel whose eyebrow, heading
   and paragraphs all begin exactly at its left edge, sitting directly under stat panels and tables
   that ARE rounded and padded. Two frame languages on one page, and the wrong one was the one
   wrapping the headings.
   Now it is the same box as everything else: a full hairline border (not only the top one), the
   shared 12px radius, and side padding that scales with the viewport. The ::before wash inherits
   the radius or it paints square corners over round ones. Nothing here is overflow:hidden on
   purpose -- a .bleed child escapes with a negative margin and clipping it would be a silent
   change to a different component. (Checked: no .band on this site currently contains one.) */
section.band{background:var(--bg1);border:var(--hair) solid var(--line);border-radius:var(--r);
  padding-left:clamp(20px,3vw,34px);padding-right:clamp(20px,3vw,34px)}
/* Consecutive bands are separate panels now, so they need air between them; before this they were
   one continuous fill divided by a hairline and nothing had to separate them. */
section.band + section.band,section.band + section,section + section.band{margin-top:18px}
section.band::before{content:"";position:absolute;inset:0;pointer-events:none;border-radius:inherit;
  background:linear-gradient(180deg,rgba(255,255,255,.012),transparent 40%)}
section.band > *{position:relative}

/* A measured grid behind the hero: present but imperceptible, per the de-glare rule. */
/* The horizontal bleed was -40px on each side and the hero does not clip, so in WebKit the
   decoration pushed document scrollWidth to 408 against a 390 viewport. The page could not
   actually be panned (scrollX stayed 0), but an inflated scroll width is a rubber-band and a
   stray scrollbar waiting to happen. The grid keeps its vertical bleed and stops at the box. */
.hero::before{content:"";position:absolute;inset:-40px 0 auto 0;height:420px;pointer-events:none;
  background-image:linear-gradient(var(--line) 1px,transparent 1px),linear-gradient(90deg,var(--line) 1px,transparent 1px);
  background-size:52px 52px;opacity:.20;
  -webkit-mask-image:radial-gradient(660px 300px at 18% 30%,#000,transparent 72%);
  mask-image:radial-gradient(660px 300px at 18% 30%,#000,transparent 72%)}
.hero{position:relative}
.hero > *{position:relative}

/* Stat tiles carried a number and a caption and nothing else. A tile that also shows its own scale
   reads as a measurement rather than a claim. */
.stat .bar{margin-top:10px}
.stat.hl{background:linear-gradient(180deg,rgba(218,91,117,.06),transparent 70%)}

/* ============================================================ auth surfaces
   Sign-in, activation and the account page are the only screens a person arrives at to DO
   something rather than to read. They were built as prose columns like every other page, which
   left the eye with no anchor: a heading, a field and two unbranded buttons stretched across a
   full-width measure. These give them a card, a centre, and a density that matches the task. */
/* overflow-x:clip, not hidden: the ::before below is a glow inset -20% on each side, which at
   375px puts it 75px past both edges. Nothing CLIPPED it, so the document scrolled 75px to the
   right into empty space on /signin, /account and /activate. A sweep that enumerates elements
   cannot see this -- a pseudo-element is not in querySelectorAll('*') -- and the signal that did
   report it, documentElement.scrollWidth, I had earlier written off as unreliable because it lies
   under the browser pane's viewport EMULATION. Inside a real 375px frame it was telling the truth.
   `clip` rather than `hidden` because hidden would make this a scroll container and break
   position:sticky for anything inside it. */
.auth-wrap{min-height:calc(100vh - var(--header-h) - 190px);display:grid;place-items:start center;
  padding:clamp(28px,7vh,72px) var(--gutter) 64px;position:relative;overflow-x:clip}
.auth-wrap::before{content:"";position:absolute;inset:-10% -20% auto;height:420px;pointer-events:none;
  background:radial-gradient(520px 260px at 50% 0,rgba(218,91,117,.10),transparent 70%)}
.auth{width:100%;max-width:27rem;position:relative}
.auth .card{background:linear-gradient(180deg,var(--bg2),var(--bg1));border:var(--hair) solid var(--line);
  border-radius:var(--r);padding:clamp(22px,4vw,32px);box-shadow:0 1px 0 rgba(255,255,255,.03) inset,0 18px 50px -28px #000}
.auth h1{font-size:clamp(24px,5vw,var(--fs-h2));line-height:var(--lh-tight);letter-spacing:-.01em;margin:0}
/* No second measure. .auth is 432px wide, which at 14px is ~52ch -- already inside the
   comfortable range. The 34ch that used to be here wrapped the intro at 300px under a 432px
   card, so the paragraph read as a mistake rather than as typography: two numbers on one
   axis, and the narrower one wins by accident. The container is the measure. */
.auth .sub{color:var(--mut);font-size:var(--fs-body);line-height:var(--lh-body);margin:10px 0 0}
.auth label{display:block;font-size:var(--fs-meta);color:var(--mut);margin-bottom:7px}
/* `input[type=text]` matches the ATTRIBUTE, not the effective type. The activation code field
   carries inputmode/autocomplete but no `type`, so it matched neither selector and fell back
   to its size="20" intrinsic width: 277px under a 367px button, in the middle of a card.
   The card owns one field, whatever it is spelled as. */
.auth input:not([type=checkbox]):not([type=radio]):not([type=submit]){width:100%;padding:12px 14px;background:var(--code);color:var(--ink);
  border:var(--hair) solid var(--line2);border-radius:var(--rad-s);font:inherit;font-size:var(--fs-read);
  transition:border-color .15s ease,box-shadow .15s ease}
.auth input:focus-visible{outline:none;border-color:var(--sig);box-shadow:0 0 0 3px rgba(218,91,117,.22)}
.auth .btn{width:100%;justify-content:center;padding:12px 16px;font-size:var(--fs-read)}

/* Divider that reads as a step, not a rule. */
.auth .or{display:flex;align-items:center;gap:12px;margin:22px 0 16px;color:var(--dim);font-size:var(--fs-small);
  text-transform:uppercase;letter-spacing:.08em}
.auth .or::before,.auth .or::after{content:"";flex:1;height:var(--hair);background:var(--line)}

/* Provider buttons. The marks are the providers' own and are never recoloured or redrawn; the
   button chrome is ours and stays inside our palette so the two do not fight. Full width and
   stacked, because a row of two at 375px gives each one a target narrower than a thumb. */
.prov{display:grid;gap:10px}
.prov a{display:flex;align-items:center;justify-content:center;gap:10px;min-height:var(--tap);
  padding:11px 16px;border:var(--hair) solid var(--line2);border-radius:var(--rad-s);
  background:var(--bg3);color:var(--ink);font-family:var(--sans);font-size:var(--fs-read);font-weight:500;
  text-decoration:none;transition:border-color .15s ease,background .15s ease,transform .12s ease}@media(hover:hover) and (pointer:fine){
.prov a:hover{border-color:var(--line2);background:#1e232b}}
.prov a:active{transform:translateY(1px)}
.prov a:focus-visible{outline:2px solid var(--sig);outline-offset:2px}
.prov .i{font-size:var(--ic-lg);flex:none}

/* Fine print that stays available without competing with the task. */
.auth details{margin-top:18px;border-top:var(--hair) solid var(--line);padding-top:14px}
.auth details summary{cursor:pointer;color:var(--dim);font-size:var(--fs-meta);list-style:none}
.auth details summary::-webkit-details-marker{display:none}
.auth details summary::before{content:"＋ ";color:var(--sig)}
.auth details[open] summary::before{content:"－ "}
.auth details p{color:var(--mut);font-size:var(--fs-meta);line-height:var(--lh-body);margin:10px 0 0}

/* One entrance, once. Anything that moves on every interaction becomes furniture. */
@keyframes auth-rise{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.auth .card,.auth .above{animation:auth-rise .32s cubic-bezier(.2,.7,.3,1) both}
.auth .card{animation-delay:.04s}
@media (prefers-reduced-motion:reduce){.auth .card,.auth .above{animation:none}}

/* Numbered steps. Used where a screen has to explain a sequence before it asks for anything —
   the signed-out activation page was a heading, one line and a button on an empty page. */
.steps{list-style:none;counter-reset:s;margin:0;padding:0;display:grid;gap:12px}
.steps li{counter-increment:s;display:grid;grid-template-columns:26px 1fr;gap:12px;align-items:baseline}
.steps li::before{content:counter(s);grid-row:span 2;justify-self:start;
  width:26px;height:26px;border-radius:50%;display:grid;place-items:center;
  background:var(--sig-d);color:var(--sig2);font-size:var(--fs-small);font-weight:600;
  font-family:var(--mono);align-self:start}
.steps li b{font-size:var(--fs-body);color:var(--ink);font-weight:600}
.steps li span{grid-column:2;color:var(--mut);font-size:var(--fs-meta);line-height:var(--lh-body)}

/* A management screen needs more room than a sign-in form but not a full page measure. */
.auth.wide{max-width:40rem}

/* Device and token rows. The old shape was a .panel per item with everything in one text run;
   at 375px the label, the key type, the state and the button all wrapped into a paragraph. */
.row{display:grid;grid-template-columns:auto 1fr auto auto;gap:12px;align-items:center;
  padding:14px;margin-top:10px;background:var(--bg2);border:var(--hair) solid var(--line);border-radius:var(--rad-s)}
.row-ic{width:34px;height:34px;border-radius:9px;display:grid;place-items:center;
  background:var(--bg3);border:var(--hair) solid var(--line);color:var(--mut)}
.row-ic .i{font-size:var(--ic-lg)}
.row-main{min-width:0}
.row-main b{display:block;font-size:var(--fs-body);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.row-sub{display:block;color:var(--dim);font-size:var(--fs-small);margin-top:3px}
/* Four children, four tracks. With three the act cell was pushed onto an implicit second
   row: the Release button sat under the state pill and the row stood 130px tall. */
/* Two controls now (Rename, Release), so the slot lays them out instead of relying on inline
   flow. nowrap: at the account page's width a wrapping row-act stacked them into a two-storey
   column beside a single-line name, which reads as two separate rows. */
.row-act{grid-column:4;justify-self:end;display:flex;align-items:center;gap:8px;flex-wrap:nowrap}
.row-act:empty{display:none}
@media(max-width:560px){
  /* The state pill moves under the label rather than squeezing the label to nothing. */
  .row{grid-template-columns:auto 1fr;row-gap:10px}
  .row>.pill{grid-column:2;justify-self:start}
  /* The desktop rule pins this cell to the right (justify-self:end). Stacked, the button is
     meant to span the row, and width:100% on a shrink-wrapped cell spans nothing. */
  .row-act{grid-column:1/-1;justify-self:stretch}
  .row-act .btn{width:100%;justify-content:center}
}
.prov-btn .i{font-size:var(--ic-md);flex:none}

/* ---- copy affordance on every code block ----------------------------------------------------
   Stripe, Vercel, Supabase and OpenAI all put a copy button on every fenced block; a visitor who
   has to select a shell command by hand is being asked to do the one thing the page exists to save
   them. Measured on /docs 2026-09-24: zero buttons on the page. The button is injected by vz.js
   rather than emitted per call site, so every <pre> the generator already writes gets one. */
.codewrap{position:relative}
.copybtn{position:absolute;top:8px;right:8px;z-index:2;display:inline-flex;align-items:center;
  gap:6px;padding:6px 9px;border-radius:7px;cursor:pointer;font-family:var(--mono),monospace;
  font-size:var(--fs-micro);letter-spacing:.02em;color:var(--dim);background:rgba(13,15,19,.72);
  border:var(--hair) solid var(--line);backdrop-filter:blur(6px);opacity:0;transition:.14s}
.codewrap:hover .copybtn,.copybtn:focus-visible{opacity:1}@media(hover:hover) and (pointer:fine){
.copybtn:hover{color:var(--ink);border-color:var(--line2)}}
.copybtn.done{color:var(--ok);border-color:var(--ok-d)}
.copybtn .i{font-size:var(--ic-sm)}
/* A touch device has no hover, so the affordance has to be visible without one. */
@media(pointer:coarse){.copybtn{opacity:1}}

/* ---- hero art -------------------------------------------------------------------------------
   Measured 2026-09-24 at 1024px: the hero was 74.5% empty background, and its right column held a
   small terminal mock floating in that void. The engine's own render now fills the column and the
   terminal sits on it, so the fold carries both halves of what this product is -- a machine you
   own, and a command you type. The credit line under it names the engine, model and hardware that
   made the picture; a picture with no provenance is a claim the page cannot support. */
.heroart{position:relative;border-radius:var(--r);overflow:hidden;border:var(--hair) solid var(--line);
  background:var(--bg1)}
.heroart-img{display:block;width:100%;height:auto;max-height:300px;object-fit:cover;--tone:.82}
/* The terminal used to float ON the render: position:absolute, bottom:38px, over a 453px-tall
   image -- it covered the bottom 32% of the picture and the provenance credit was laid under it,
   clipped and unreadable. The overlap was the whole point of the composition and it was also the
   thing that made the picture not worth including. Stacked now: render, then terminal, then
   credit. Nothing covers anything, the card gains real vertical presence, and the line that has to
   name the engine, model and hardware is a line you can actually read. */
.heroart{display:flex;flex-direction:column}
.heroart .term{position:static;left:auto;right:auto;bottom:auto;margin:0;
  border-radius:0;border-left:0;border-right:0;border-bottom:0;box-shadow:none}
.heroart .credit{position:static;margin:0;padding:10px 14px 12px;background:var(--bg1);
  font-family:var(--mono),monospace;font-size:var(--fs-micro);letter-spacing:.02em;color:var(--faint);
  background:linear-gradient(0deg,rgba(13,15,19,.92),transparent);text-align:right}
/* Below the split the art stacks under the copy; the terminal keeps its own flow position rather
   than floating over a picture that is now the full width of the screen. */
/* The cap was 300 PIXELS against a FLUID width, so the crop grew with the viewport -- measured
   0% at 320, 9.1% at 375, 41.7% at 560, 58.5% at 768, 66.5% at 940 -- and then snapped back to 0%
   at 941 where the two-column layout takes over. Two-thirds of a square picture disappeared one
   pixel before the breakpoint that makes it whole. Constrain the ART's width instead: the crop
   stops being a function of the viewport. */
@media(max-width:940px){.heroart{max-width:clamp(320px,62vw,460px);margin-inline:auto}
  .heroart-img{max-height:none}}

/* ---- a full-bleed band ----------------------------------------------------------------------
   The home page was six consecutive card grids with identical treatment and no visual break --
   3,495px of the same thing. One band interrupts it. */
/* It has to leave the 1120px wrapper to be a bleed at all: inside .wrap it was 1076px with the
   page gutter on both sides, which is a wide picture, not a break in the page. */
.bleed{position:relative;width:100vw;margin-left:calc(50% - 50vw);padding:0;
  border-top:var(--hair) solid var(--line);border-bottom:var(--hair) solid var(--line);
  overflow:hidden;background:var(--bg1)}
.bleed .bleed-copy .wrap{max-width:1120px;margin:0 auto;padding:0 var(--gutter)}
.bleed img{display:block;width:100%;height:clamp(180px,26vw,320px);object-fit:cover;
  /* Exposure is per asset and lives in data/media.json -- one brightness() here was applied to
     assets whose measured luminance differs by 7x, which pushed the darkest two below the page
     background. The saturate stays: it is a house tone, the same for every asset. An asset the
     ledger leaves at 1.0 gets this rule and nothing else. */
  --tone:.72}
/* ★The gutter belongs to the .wrap, not to this box. Measured at 1920: every line of type on the
   page starts at x=422 -- header wordmark, h1, eyebrow, section h2, cards, footer -- and the three
   lines inside a full-bleed band started at 400. The page's alignment broke for exactly the height
   of the band and nowhere else.
   The cause is that this box carried `padding:0 22px` and then zeroed the inner .wrap's own
   padding. At phone width the two are indistinguishable. At 1920 they are not: the inner 1120px
   column then centres inside a box already inset by 44px, which lands it 22px left of every other
   1120px column on the page. Give the padding back to .wrap and it centres against the same
   viewport as everything else. */
.bleed .bleed-copy{position:absolute;inset:auto 0 0 0;padding:26px 0;
  background:linear-gradient(0deg,rgba(13,15,19,.94),rgba(13,15,19,.55) 55%,transparent)}
.bleed h2{font-size:clamp(var(--fs-sub),3vw,var(--fs-h2));letter-spacing:-.02em;font-weight:700;margin:0}
.bleed p{color:var(--mut);font-size:var(--fs-body);margin:6px 0 0;max-width:60ch}
.bleed .credit{margin:10px 0 0;font-family:var(--mono),monospace;font-size:var(--fs-micro);
  color:var(--faint);letter-spacing:.02em}

/* A quantity column is read by comparing magnitudes down the column, and until 2026-09-24 the
   /models DEVICE MEMORY column asked the reader to do that arithmetic in their head across a 12x
   range (327 MiB .. 3,923 MiB) while /benchmarks drew the same metric as a bar. Same magnitude,
   one treatment. The bar sits under the figure and never replaces it -- the number is the value,
   the bar is only its size. */
/* Accuracy against the chance floor, with the 95% interval drawn over it. The track starts at
   chance because a 0-100 track flattens the only difference the reader cares about; the interval
   is drawn because two of the four rows overlap and a bare bar would imply a ranking the
   measurement does not support. Both numbers come from the same record that prints the figure. */
.accbar{position:relative;display:block;height:7px;margin:7px 0 4px;border-radius:4px;
  background:linear-gradient(90deg,var(--line) 0,var(--line2) 100%);overflow:hidden}
.accbar i{position:absolute;left:0;top:0;bottom:0;background:var(--sig);border-radius:4px}
.accbar u{position:absolute;top:0;bottom:0;background:rgba(237,239,242,.34);
  border-left:1px solid var(--ink);border-right:1px solid var(--ink);text-decoration:none}
.accbar-l{display:block;font-family:var(--mono),var(--ko),monospace;font-size:var(--fs-micro);
  color:var(--mut);letter-spacing:var(--ls-mono,.02em);white-space:nowrap}
@media(prefers-reduced-motion:no-preference){
  .accbar i{transition:width .5s cubic-bezier(.22,.61,.36,1)}}
.magbar{display:block;height:3px;margin-top:5px;border-radius:2px;background:var(--line);overflow:hidden}
.magbar i{display:block;height:100%;background:var(--sig);opacity:.62}

/* ---- a compact stat line -------------------------------------------------------------------
   /benchmarks opened with three .stat tiles at 326x105px carrying "11 | published results" and
   two more like it -- under 1% character density per box, measured 2026-09-24. Three numbers that
   belong to one sentence read as one line, not as three cards pretending to be a dashboard. */
.statline{display:flex;flex-wrap:wrap;gap:0;border:var(--hair) solid var(--line);
  border-radius:var(--r);background:var(--bg1);overflow:hidden}
.statline>div{flex:1 1 180px;padding:14px 18px;border-right:var(--hair) solid var(--line);
  display:flex;flex-direction:column-reverse;gap:2px;min-width:0}
.statline>div:last-child{border-right:none}
.statline dt{color:var(--dim);font-size:var(--fs-small);line-height:var(--lh-snug)}
.statline dd{margin:0;font-family:var(--mono),monospace;font-size:var(--fs-sub);font-weight:700;
  letter-spacing:-.02em;color:var(--ink)}
.statline dd.am{color:var(--amber2)}
@media(max-width:620px){.statline>div{flex:1 1 100%;border-right:none;
  border-bottom:var(--hair) solid var(--line)}.statline>div:last-child{border-bottom:none}}

/* The facet rows stacked one group per line and pushed the /models table 508px down the page --
   66% of the opening viewport before any data. They wrap as one run instead. */
@media(min-width:681px){
  .facets{flex-direction:row;flex-wrap:wrap;gap:8px 22px;align-items:center}
  .facet{gap:6px}
  .facet-l{width:auto}
}@media(hover:hover) and (pointer:fine){
/* A card that lifts on hover tells a pointer it is a target; the rest of the component library
   already does this and the cards only changed their border colour. */
.card:hover{border-color:var(--line2);transform:translateY(-1px);
  box-shadow:0 18px 40px -28px rgba(0,0,0,.9)}}@media(hover:hover) and (pointer:fine){
a.card:hover{color:inherit}}
@media(prefers-reduced-motion:reduce){.card:hover{transform:none}}

/* A divider band: thinner than a hero bleed, and quiet enough that it separates sections instead
   of competing with them. The credit sits at the edge because the band carries no headline of its
   own -- but it still has to say what made the picture. */
/* The `layers` render is 2048x440 -- a 4.65:1 band. This rule used to force it into
   clamp(96px,10vw,140px), so at a 1440px viewport a 309px-tall picture was cropped to 140 and what
   reached the reader was a featureless smear with a caption beside it. A texture still has to be
   legible AS a texture. The cap is now the asset's own height at a wide viewport, so nothing is
   cropped on a desktop; narrow screens still crop, which is what a band is for. */
.bleed.thin img{height:clamp(160px,21.5vw,310px)}
/* This line names the engine, the model and the hardware for the picture it sits on -- the one
   piece of provenance the reader is asked to check -- and it was set in --faint at 11px with
   opacity:.72 ON TOP OF THE IMAGE. Measured: --faint alone is 5.58:1 against the page, but .72
   opacity drops it to 3.42:1 on the flat background and 2.31:1 over the lighter part of the band.
   AA for 11px text is 4.5:1. Opacity is gone, the colour is --mut (7.53:1), and a scrim makes the
   ratio a property of the rule instead of a property of whichever pixel is underneath. */
/* ★A BAND THAT CARRIES COPY PUTS THE CREDIT AT THE TOP. Both sit at the BOTTOM of the band --
   the copy bottom-left, the credit bottom-right -- and whether they touch depends on how long the
   sentence happens to be. Measured on /releases: its line runs 22..437 at every width, and the
   credit starts at 219 (621px), 366 (768px), 418 (820px). They overlapped across the entire
   tablet range, 621 to about 880, and the page's own sentence was printed through the provenance
   line. /models and /product escaped only because their sentences are shorter.
   A layout that works because of the length of a string is not a layout. The credit moves to the
   top of the band where there is nothing but image, and the two can no longer reach each other at
   any width or any sentence length. */
.bleed.has-copy .credit-edge{top:8px;bottom:auto}
.bleed .credit-edge{position:absolute;right:14px;bottom:8px;margin:0;
  font-family:var(--mono),monospace;font-size:var(--fs-micro);color:var(--mut);
  letter-spacing:.02em;background:rgba(13,15,19,.78);border-radius:6px;padding:4px 8px;backdrop-filter:saturate(.6) blur(2px)}
/* ★THE ALIGNMENT LINE, SECOND INSTANCE. §2 of the lane document says every text element on a
   page shares one left edge, and records the full-bleed band as the one thing that was off it.
   The band's COPY was fixed; this line was not. Below 620px the credit stops being an overlay
   and becomes a static block, and it carried its overlay padding -- 14px -- into the flow. On a
   phone and on a folded Fold every line of type sits at 22 and this one sat at 14. The number
   was a literal beside a token that already existed. */
@media(max-width:620px){.bleed .credit-edge{position:static;padding:8px var(--gutter);text-align:left;opacity:1}
  /* The heading block stops overlaying at the same width the credit line already does, and for a
     harder reason than tidiness: measured on /index at 320px the band image is 180px tall while
     .bleed-copy is 256.8px, so its <h2> sat at top:-50.3px inside `.bleed{overflow:hidden}` and the
     whole first line of "Your hardware is the datacentre." was CLIPPED AWAY. At 360 the glyphs were
     cut mid-line. That is served content a reader never receives. Below the crossover the copy sits
     under the picture, so 180px of band is 180px of picture and the heading is whole. */
  .bleed .bleed-copy{position:static;background:var(--bg1);padding:18px 0 20px}}

/* ---- /docs: a nav rail beside the prose ------------------------------------------------------
   Measured 2026-09-24 at 1440px: /docs was 5,487px -- about six screens -- of nine well-formed
   sections with no index, no anchors and nothing sticky. The page was not disorganised; it had no
   way to move through it. Stripe, Vercel, Supabase and Cloudflare all answer this with a rail
   beside the content, and that shape needs no build step a static generator does not have.

   Below the split the rail becomes a plain list under the intro rather than a sticky panel: a
   sticky sidebar on a phone is a sticky sidebar covering the thing you came to read. */
.doclayout{display:block}
.docnav{margin:0 0 8px}
.docnav-h{font-family:var(--mono),monospace;font-size:var(--fs-micro);letter-spacing:var(--ls-label);
  text-transform:uppercase;color:var(--faint);margin:0 0 10px}
.docnav ol{list-style:none;counter-reset:dn;margin:0;padding:0;
  display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:2px 20px}
.docnav li{counter-increment:dn}
.docnav a{display:flex;align-items:center;gap:9px;min-height:36px;padding:4px 10px;
  color:var(--mut);font-size:var(--fs-meta);border-radius:var(--rad-s);
  border-left:2px solid transparent;transition:.12s}
.docnav a::before{content:counter(dn,decimal-leading-zero);font-family:var(--mono),monospace;
  font-size:var(--fs-micro);color:var(--faint);flex:none}@media(hover:hover) and (pointer:fine){
.docnav a:hover{color:var(--ink);background:var(--bg1)}}
.docnav a[aria-current="true"]{color:var(--ink);border-left-color:var(--sig);background:var(--bg1)}
.docnav a[aria-current="true"]::before{color:var(--sig2)}

@media(min-width:1101px){
  /* The rail and the prose sit side by side, and the rail holds its place while the page scrolls. */
  .doclayout{display:grid;grid-template-columns:228px minmax(0,1fr);gap:0 46px;align-items:start}
  .doclayout > .docintro,.doclayout > .bleed{grid-column:1/-1}
  .docnav{position:sticky;top:calc(var(--header-h) + 22px);align-self:start;
    max-height:calc(100vh - var(--header-h) - 44px);overflow:auto;
    padding-right:6px;border-right:var(--hair) solid var(--line)}
  .docnav ol{grid-template-columns:1fr;gap:1px}
  .docbody{min-width:0}
  /* The first section under the rail should not re-draw the rule the rail already carries. */
  .docbody > section:first-child{border-top:none;padding-top:8px}
}
/* A heading you can link to. The anchor only appears on hover or focus, so it is there when it is
   wanted and invisible when it is not. */
.docbody h2.sec{position:relative}

/* ---- a four-step flow ------------------------------------------------------------------------
   /docs described activation as a four-row table: step number, sentence, where. What a table
   cannot show is the thing a reader most needs here -- that the sequence CHANGES HANDS: browser,
   then the machine, then the browser again, then the server on its own. Each step names its actor,
   and the arrow between steps is the handoff. The security sentence stays as prose below; a
   picture must not be asked to carry a claim. */
/* Stacked by default; four across only where four actually fit (4x208 + 3x14 = 874). auto-fit was
   wrong here: a wrapped row leaves its last card with an arrow pointing sideways at a card that is
   now on the line below -- the exact lie this comment warns about two rules down. With a fixed
   count the arrow is drawn between real neighbours or not at all. */
.flow{list-style:none;counter-reset:fl;display:grid;gap:14px;margin:0;padding:0}
@media(min-width:900px){ .flow{grid-template-columns:repeat(4,1fr)} }
.flow li{counter-increment:fl;position:relative;display:flex;flex-direction:column;gap:4px;
  background:var(--bg1);border:var(--hair) solid var(--line);border-radius:var(--r);
  padding:16px 16px 18px}
.flow li::before{content:counter(fl,decimal-leading-zero);position:absolute;top:14px;right:14px;
  font-family:var(--mono),monospace;font-size:var(--fs-micro);color:var(--faint)}
/* The handoff mark sits INSIDE the card, at its right edge. Drawn in the gap (right:-14px) it
   reached outside its own box, which check_decoration_is_clipped refuses and is right to: nothing
   in the chain clipped it. Inside the box it says the same thing and overhangs nothing. */
.flow li:not(:last-child)::after{content:"";position:absolute;top:50%;right:8px;width:12px;
  height:var(--hair);background:var(--line2)}
@media(max-width:899px){ .flow li:not(:last-child)::after{display:none} }
.flow-who{font-family:var(--mono),monospace;font-size:var(--fs-micro);letter-spacing:var(--ls-label);
  text-transform:uppercase;color:var(--sig2)}
.flow-ic{color:var(--mut);margin:2px 0 2px}
.flow li b{font-size:var(--fs-body);font-weight:600;color:var(--ink)}
.flow-sub{color:var(--dim);font-size:var(--fs-meta);line-height:var(--lh-body)}
.flow li a{font-size:var(--fs-meta);margin-top:2px}

/* ---- the support matrix ----------------------------------------------------------------------
   Three model classes against two execution targets. The three cell states are deliberately NOT
   one axis with three values -- they answer two different questions, and the colour says which:
     yes   the catalogue carries measured rows      (the signal colour, a count)
     runs  the engine executes it, nothing published (muted, no number to give)
     none  not on this target at all                (an em dash, and nothing else)
   A muted "runs, not published" must never read as a weaker version of a published row; it is a
   different sentence. */
/* Its own minimum. The shared `table{min-width:560px}` is sized for the wide data tables
   (/models is 871px of intrinsic width, /benchmarks 852); this grid is three columns of short
   cells and does not need it. Measured at 375px: with the inherited 560 the document scrolled
   sideways 19px -- 394 against a 375 viewport -- while every value from 0 up to 520 left it at
   375 exactly. The .tw wrapper scrolls the table either way; what the inherited minimum added was
   19px of page rock, which is a defect a reader feels and no element rect reports.
   460 keeps the three columns readable before the wrapper starts scrolling. */
.matrix{min-width:min(460px,100%)}
.matrix th[scope="row"]{white-space:nowrap;color:var(--ink);font-weight:600;text-align:left}
.matrix td.mx{white-space:nowrap;font-size:var(--fs-meta)}
.matrix td.mx.yes{color:var(--ok)}
.matrix td.mx.yes b{font-family:var(--mono),monospace;font-size:var(--fs-body);font-weight:700}
.matrix td.mx.runs{color:var(--mut)}
.matrix td.mx.none{color:var(--faint);font-family:var(--mono),monospace}

/* ---------------------------------------------------------------- press, focus and prose links
   Measured before this block: 18 :hover rules, ONE :active rule, ZERO hover guards. On a touch
   screen a tap latches the hover state until the next tap somewhere else, so a phone got a whole
   sheet's worth of "hovered" chrome it never asked for and no press feedback at all. The hover
   rules are now behind a real hover device; the press state is outside that query because a finger
   is the pointer that needs it most.

   And in-prose links were distinguished from body text by HUE ALONE: --amber2 against --mut is
   1.00:1 in luminance, with no underline anywhere in the sheet. That is WCAG 1.4.1 -- a reader who
   does not separate those hues cannot find a link in a paragraph. The underline is thin and offset
   so it reads as typography rather than as a 1995 hyperlink. */
.btn:active,.chip:active,.copybtn:active,.pill a:active,.nav-links a:active,
.mobile a:active,.card h3 a:active,.tw:active{transform:translateY(1px)}
@media(prefers-reduced-motion:reduce){
  .btn:active,.chip:active,.copybtn:active,.pill a:active,.nav-links a:active,
  .mobile a:active,.card h3 a:active,.tw:active{transform:none;opacity:.82}}

.prose a:not(.btn):not(.chip),p a:not(.btn):not(.chip),li a:not(.btn):not(.chip),
.lede a:not(.btn):not(.chip){
  text-decoration:underline;
  text-decoration-color:color-mix(in srgb,var(--amber2) 48%,transparent);
  text-decoration-thickness:1px;text-underline-offset:.18em}
.prose a:not(.btn):not(.chip):hover,p a:not(.btn):not(.chip):hover,
li a:not(.btn):not(.chip):hover,.lede a:not(.btn):not(.chip):hover{
  text-decoration-color:var(--amber2)}

a:focus-visible,button:focus-visible,summary:focus-visible,[tabindex]:focus-visible{
  outline:2px solid var(--sig);outline-offset:2px;border-radius:3px}

/* ---------------------------------------------------------------- the journey rail  (.jr)
   The five steps from nothing to a running model, drawn by journey() from data/journey.json.

   It is a rail and not five cards because the reader's question is ordinal -- what comes first,
   and where does this stop needing the network. Cards answer neither: the home page carried three
   of them for weeks and a reader following them installed the package and then found the device
   was not activated, because "create an account" and "activate this device" were in no card.

   The two chips are the data: three of the five steps reach the network and generation is not one
   of them. `--ok` for on-your-machine, `--sig` for needs-the-network -- the same two colours the
   rest of the site already uses for "this is fine" and "look here", not a third palette. */
.jr{list-style:none;margin:26px 0 0;padding:0;display:grid;gap:var(--s-5);
    grid-template-columns:repeat(5,1fr);counter-reset:none}
.jr-s{position:relative;padding-top:var(--s-5);display:flex;flex-direction:column}
/* The rail. A border on the item would draw a segment per item including after the last one; a
   pseudo-element lets the final step end the line instead of trailing off the grid. */
.jr-s::before{content:"";position:absolute;top:13px;left:0;right:0;height:var(--hair);
              background:var(--line2)}
.jr-s:first-child::before{left:13px}
.jr-s:last-child::before{right:calc(100% - 13px)}
.jr-n{position:absolute;top:0;left:0;width:26px;height:26px;border-radius:50%;
      display:grid;place-items:center;background:var(--bg2);border:var(--hair) solid var(--line2);
      color:var(--mut);font:600 var(--fs-micro)/1 var(--mono);letter-spacing:0}
.jr-s[data-net="no"] .jr-n{border-color:var(--ok-d);color:var(--ok)}
.jr-t{margin:var(--s-3) 0 0;font-size:var(--fs-read);line-height:var(--lh-snug);letter-spacing:var(--ls-tight)}
.jr-net{display:inline-flex;align-items:center;margin-top:var(--s-2);
        font-size:var(--fs-micro);letter-spacing:var(--ls-label);text-transform:uppercase;
        color:var(--sig2)}
.jr-s[data-net="no"] .jr-net{color:var(--ok)}
.jr-line{margin:var(--s-2) 0 var(--s-3);font-size:var(--fs-body);line-height:var(--lh-body);
         color:var(--mut);flex:1}
.jr-cmd{margin:var(--s-3) 0 0;align-self:stretch;padding:6px 9px;border-radius:var(--rad-s);
        background:var(--code);border:var(--hair) solid var(--line);
        font-size:var(--fs-micro);letter-spacing:var(--ls-mono);color:var(--ink);
        overflow-x:auto;white-space:nowrap}
.jr-cap{margin:var(--s-5) 0 0;font-size:var(--fs-meta);color:var(--dim)}
/* Progress-rail mode on /activate and /account: the step you are on, and the ones behind you. */
.jr--compact{gap:var(--s-4);margin-top:var(--s-4)}
.jr--compact .jr-t{font-size:var(--fs-meta);color:var(--dim);letter-spacing:0}
.jr--compact .jr-net{display:none}
.jr .is-done .jr-n{background:var(--ok-d);border-color:var(--ok-d);color:var(--ink)}
.jr .is-now .jr-n{background:var(--sig);border-color:var(--sig);color:var(--on-sig)}
.jr .is-now .jr-t{color:var(--ink)}
@media(max-width:940px){
  .jr{grid-template-columns:repeat(2,1fr);gap:var(--s-5) var(--s-4)}
  /* Two columns make the rail lie about the order -- step 3 sits under step 1. Drop it. */
  .jr-s::before{display:none}
  .jr-s{padding-top:0;padding-left:38px;min-height:26px}
  .jr-n{top:1px}
}
@media(max-width:560px){ .jr{grid-template-columns:1fr} }

/* A picture that belongs to the copy beside it, rather than a full-bleed band between sections.
   /product carried no image at all and ran 1,100px of type before its first table; the band was
   the only visual device the site had, and a band cannot sit next to a paragraph. */
.figure{margin:0;border:var(--hair) solid var(--line);border-radius:var(--r);overflow:hidden;
        background:var(--bg1)}
.figure img{display:block;width:100%;height:auto}

/* ------------------------------------------------- one consumer for every picture
   Exposure is emitted by build.py onto the <img> itself, from data/media.json. It was READ by two
   rules -- .bleed img and .heroart-img -- and /product's hero is in neither: it sits in .figure,
   so it carried --exposure:1.25 and computed filter:none. Declared, never applied, and invisible
   to every check that reads the ledger or the HTML rather than the rendered page.
   Adding `.figure img` would have fixed today and left the next container to be missed the same
   way, so the consumer is bound to the ELEMENT instead: any <img> reads the variables, and a
   container's only job is to say what tone it wants. Icons are <svg><use>, not <img>, so nothing
   else is touched.
   `body`, not `main`: /refund, /legal/ and /legal/ai-transparency have no <main> at all -- they
   lay out with .wrap directly. They carry no picture today, and `main img` would have skipped the
   first one added, which is the same failure one level out. */
body img{filter:saturate(var(--tone,.78)) brightness(var(--exposure,1)) contrast(var(--exposure-c,1))}
.figure figcaption{padding:10px 12px;border-top:var(--hair) solid var(--line);
                   font-size:var(--fs-micro);letter-spacing:var(--ls-label);color:var(--dim)}
/* ★A SQUARE IN ONE COLUMN IS A SCREEN-HIGH HOLE. Measured on a Galaxy Fold 7 (inner screen, CSS
   width just under the 940px split) and reproduced at 860/900/940: the hero collapses to one
   column and the figure becomes 896x934 -- a near-square block almost a full viewport tall. The
   enclosure render is deliberately dark, so at that size it reads as an empty void rather than a
   picture, which is exactly what the operator saw.
   Two columns can afford a square because it sits beside the copy. One column cannot, so the
   figure becomes a band there: crop instead of scale, and hold the crop low, because the subject
   sits in the lower half of the frame and a centred crop would show the empty black above it. */
@media(max-width:940px){
  .prodhero{grid-template-columns:1fr}
  .prodhero .figure img{aspect-ratio:16/9;object-fit:cover;object-position:50% 68%;
                        max-height:min(46vh,340px)}
}

/* ---------------------------------------------------- the signed-in header, before first paint
   `acct-known` is set by a tiny script in <head>, from a flag the browser kept after the last
   successful account check. It hides the sign-in button for the one frame before
   /api/account/me answers, and shows a neutral "Account" in its place.

   It is scoped to [data-cta="signin"] on purpose: when the build ships a download button, vz.js
   leaves the header alone and hiding it here would hide the download from exactly the people who
   came for it. The hint says nothing about who is reading -- the address and the device count are
   still drawn only from the answer. */
html.acct-known[data-cta="signin"] #nav-cta,
html.acct-known[data-cta="signin"] #mob-cta{display:none!important}
html.acct-known[data-cta="signin"] #nav-acct{display:inline-flex!important}
html.acct-known[data-cta="signin"] #mob-acct{display:block!important}
/* ★AND THE DOWNLOAD CASE, WHICH HAD NO RULE AT ALL. When the build offers a download, #nav-cta is
   that download button and the header carries a separate #nav-signin; the rules above are scoped to
   [data-cta="signin"] and so did nothing here. A reader who was signed in still saw "Sign in".
   Same mechanism, same pre-paint hint, different element -- deliberately NOT a second mechanism
   (see the note on .acct-cta below for why that matters). The download button is untouched: it is
   about the product, not the reader, and both controls ship side by side. */
html.acct-known[data-cta="download"] #nav-signin,
html.acct-known[data-cta="download"] #mob-signin{display:none!important}
html.acct-known[data-cta="download"] #nav-acct{display:inline-flex!important}
html.acct-known[data-cta="download"] #mob-acct{display:block!important}

/* ------------------------------------------------------------------ the activation success view
   The end of the device flow. GitHub, Google and Auth0 all land the same way and for the same
   reason: a person who has just approved something needs to know it worked WITHOUT READING. A
   large mark, a headline at page-title size, one line naming the device, and an explicit
   instruction to go back to the terminal.

   What this replaced was one line of 14px grey body text in a bordered card -- the same weight as
   the warning above it and the error below it, so the three most different outcomes on the page
   all looked alike. */
.doneview{margin-top:var(--s-5);padding:40px 0 8px;text-align:center}
.donemark{display:grid;place-items:center;width:64px;height:64px;margin:0 auto;border-radius:50%;
          background:var(--ok-d);color:var(--ok);border:var(--hair) solid var(--ok)}
.donemark .i{font-size:var(--ic-xl);stroke-width:2.4}
.doneh{margin:var(--s-5) 0 0;font-size:var(--fs-h1);line-height:var(--lh-display);
       letter-spacing:var(--ls-display);text-wrap:balance}
.donep{margin:var(--s-4) auto 0;max-width:46ch;font-size:var(--fs-read);
       line-height:var(--lh-body);color:var(--mut)}
.donenote{margin:var(--s-5) auto 0;max-width:46ch;font-size:var(--fs-meta);color:var(--dim)}
/* Once it has worked, the page's own opening block is asking for a decision that is already made
   -- and "Activate this device" sitting above "This device is activated" is two headlines
   competing to be the answer. The success view becomes the page; the rail stays, because now it
   reads as a finished sequence rather than a prompt. */
.auth.is-done .above .eyebrow,
.auth.is-done .above h1,
.auth.is-done .above .sub{display:none}
@media(max-width:560px){
  .doneview{padding:28px 0 4px}
  .donemark{width:52px;height:52px}
  .donemark .i{font-size:var(--ic-xl)}
}
@media(prefers-reduced-motion:no-preference){
  .doneview{animation:donein .28s ease-out both}
  @keyframes donein{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
}

/* The rail after success, on a phone.
   Measured at 375x812 before this rule: the five stacked steps are 244px, the success mark lands
   at y=429 and the headline at y=505 -- 62% of the screen spent before the page says the one thing
   it exists to say, and on a 667px phone the closing instruction is off the bottom. A progress
   rail that pushes the outcome below the fold is not orienting anybody.

   It stays on wider screens, where it is a single ~90px row and reads as a finished sequence.
   What it was actually worth on a phone -- which steps are left -- is carried by .donenext, one
   line, derived from the same ledger the rail is drawn from. */
.donenext{margin:var(--s-4) auto 0;max-width:46ch;font-size:var(--fs-body);
          line-height:var(--lh-body);color:var(--mut)}
.donenext:empty{display:none}
@media(max-width:700px){
  .auth.is-done .jr{display:none}
}

/* ------------------------------------------------- the tap floor, third pass: two more controls
   The rule at the top of this block says the floor belongs to the ROLE and not to a list of
   places -- and it was still written as a list of places, so everything added afterwards missed
   it. Rendered at 375px with pointer:coarse, two controls were under the floor:

     .docnav a   36px   the section nav on /docs -- navigation, the exact role the rule names
     .copybtn    28px   the copy control on every code block

   .docnav carried `min-height:36px` written out, a number below the --tap token it should have
   been. It gets the token.

   .copybtn keeps its visual size. It is pinned to the top-right of a terminal frame, and a 44px
   slab there covers the first line of the command it is offering to copy -- so the HIT AREA grows
   and the button does not. That is the honest fix for a control that must stay small: the finger
   target is what the floor is about, not the ink. */
@media(pointer:coarse),(max-width:680px){
  .docnav a{min-height:var(--tap)}
  .copybtn::before{content:"";position:absolute;left:50%;top:50%;
                   transform:translate(-50%,-50%);
                   width:100%;min-width:var(--tap);height:var(--tap)}
}

/* --------------------------------------------------- the call to action, for whoever is reading
   Ships both readings; the stylesheet picks one before first paint from the same `acct-known`
   hint the header uses. A signed-in reader was seeing their own address in the header and a
   bright "Sign in" button 900px below it -- the page contradicting itself twice on one screen.
   Scoped to [data-cta="signin"]: when the build ships a download button there is nothing to
   swap, because a download is about the product and not about the reader. */
.acct-cta .ca-in{display:none}
.acct-cta .ca-out,.acct-cta .ca-in{display:inline-flex;align-items:center}
.acct-cta .ca-in{display:none}
html.acct-known[data-cta="signin"] .acct-cta .ca-out{display:none}
html.acct-known[data-cta="signin"] .acct-cta .ca-in{display:inline-flex}

/* "In preparation" — a target the engine runs on with no packaged download yet.
   Drawn from data/targets.json `availability`, never typed into a page. It reads as a state, not
   a warning: the measurements below it are real, and what is missing is the package. */
.pill.prep{margin-left:12px;vertical-align:middle;color:var(--warn);border-color:var(--warn)}
.prepnote{margin:var(--s-4) 0 0;max-width:var(--measure);font-size:var(--fs-body);
          line-height:var(--lh-body);color:var(--mut);
          border-left:2px solid var(--warn);padding-left:14px}

/* ------------------------------------------------- the account surfaces on a screen with room
   Measured on the live site: the auth column is a fixed 27rem card. That is right on a phone and
   wasteful on a desktop -- 432px of a 1920px viewport, 23% of the width, with the heading, the
   progress rail and the card stacked one above the other and the page running past the fold.
   The card was laid out for the narrowest screen and never told what to do with a wider one.

   So above 1024px the column becomes two: what ORIENTS you (eyebrow, heading, the sentence under
   it, the rail showing where you are) on the left, and what you ACT on (the card, the form, the
   device detail) on the right. The form itself does not get wider -- a text field 900px across is
   worse, not better -- the width goes to putting two things side by side instead of stacked.

   The success view is the exception: when it appears it IS the page, so it spans both columns and
   stays centred. */
@media(min-width:1024px){
  /* max-width:27rem from the phone rule is what actually sizes this box; a `width` alone loses to
     it and the grid then squeezes the card to 65px wide and 990px tall -- worse than the stack it
     replaced. Both have to move. The right column is capped too: the gain is two things beside
     each other, not a 700px-wide text field. */
  .auth{width:min(100%,54rem);max-width:none;display:grid;
        grid-template-columns:22rem minmax(0,1fr);
        gap:clamp(28px,4vw,56px);align-items:start}
  .auth.wide{width:min(100%,64rem);max-width:none}
  .auth > .above{grid-column:1;position:sticky;top:calc(var(--header-h) + 24px)}
  .auth > :not(.above){grid-column:2}
  /* Success. On a phone the rail is hidden here already, because a person who is done does not
     need to be told where they are. The two-column desktop grid would otherwise leave the rail
     alone in column 1, column 2 empty, and the confirmation pushed onto a row underneath -- so the
     whole left column goes, the grid collapses to one, and the mark is the page. */
  .auth.is-done{grid-template-columns:minmax(0,1fr);width:min(100%,34rem)}
  .auth.is-done > .above{display:none}
  .auth > #done{grid-column:1 / -1}
  /* The rail reads as a column here, beside the copy it belongs to, rather than as a wide row. */
  .auth .jr--compact{grid-template-columns:1fr;gap:var(--s-3)}
  .auth .jr--compact .jr-s::before{display:none}
  .auth .jr--compact .jr-s{padding-top:0;padding-left:38px;min-height:26px}
  .auth .jr--compact .jr-n{top:1px}
}

/* ------------------------------------------------- a row a later measurement replaced
   Both rows are published on purpose: a number stays true of the build it was taken on. But when
   the figure did not move between builds, the two rows differ only in the last two columns, which
   are also the dimmest -- three of the four re-measured rows came back identical, and the table
   read as one row printed twice. Recessed rather than hidden, and labelled, so the pair reads as
   "this was re-taken" instead of as a mistake. */
tbody tr.r-sup > td{color:var(--dim);opacity:.62}
tbody tr.r-sup > td b,
tbody tr.r-sup > td a{color:inherit;font-weight:500}
.sup-tag{display:inline-block;margin-top:3px;font-size:var(--fs-micro);letter-spacing:.04em;
  text-transform:uppercase;color:var(--mut);border:var(--hair) solid var(--line);
  border-radius:4px;padding:1px 5px;opacity:.9}

/* Progressive disclosure. A claim stays one line; the evidence is one click away, on the same page
   and in the same words. No script and no state -- the element already does this, including for a
   reader who navigates by keyboard, and it prints open. */
.disclose{border-top:var(--hair) solid var(--line);margin-top:14px;padding-top:12px}
.disclose>summary{cursor:pointer;list-style:none;color:var(--dim);font-size:var(--fs-meta);
  display:flex;align-items:center;gap:8px;transition:color .15s}
.disclose>summary::-webkit-details-marker{display:none}
.disclose>summary::before{content:"";width:6px;height:6px;border-right:2px solid currentColor;
  border-bottom:2px solid currentColor;transform:rotate(-45deg);transition:transform .15s;flex:none}
.disclose[open]>summary::before{transform:rotate(45deg)}
.disclose>summary:hover{color:var(--fg)}
.disclose-b{margin-top:10px;color:var(--dim);font-size:var(--fs-meta);line-height:var(--lh-body)}
.disclose-b>p+p{margin-top:8px}
@media print{.disclose>.disclose-b{display:block}}

/* The hero's "what runs today / what is next" line, and the card that gives the same fact room.
   Both are read from the ledger; these rules only decide how loudly they are said. */
/* The first screen states four things in a fixed order, and the only way a reader receives them
   in that order is if the type says so: the claim (.deck), the engine (.deck2), what runs today
   and what is next (.nownext), then the evidence below the fold. Each step down is a step down
   in size or in weight; nothing below rank 1 competes with it.
   No new size is minted for this: the ladder is built from steps the scale already has, and rank 3
   separates from the supporting copy by weight and colour at the same size rather than by a step
   invented to sit between two existing ones. */
.hero p.deck2{margin:22px 0 0;font-size:var(--fs-sub);font-weight:600;color:var(--ink);
  line-height:var(--lh-tight);letter-spacing:-.015em;max-width:24ch}
.hero p.nownext{margin:22px 0 0;font-size:var(--fs-read);font-weight:600;color:var(--ink);
  line-height:var(--lh-body);letter-spacing:-.01em}
.card.coming{display:block;margin-top:18px;border-color:var(--am);background:
  linear-gradient(180deg,color-mix(in oklab,var(--am) 7%,transparent),transparent)}
.card.coming .eyebrow{color:var(--am)}
.card.coming h3{margin-top:6px}
.coming-status{margin-top:10px;color:var(--dim);font-size:var(--fs-meta)}

/* Announced models. The licence line is not a footnote: a reader planning around a model needs to
   know what its publisher permits before they plan, so it sits in the card with the name. */
.coming-row{border-color:color-mix(in oklab,var(--am) 30%,var(--line))}
.coming-row .lic{margin-top:12px;padding-top:12px;border-top:1px solid var(--line);
  font-size:var(--fs-meta);color:var(--dim);line-height:var(--lh-body)}
.coming-row .lic b{color:var(--mut)}
.lic-dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:7px;
  vertical-align:middle;background:var(--dim)}
.lic-green{background:#3fb950} .lic-amber{background:#d29922} .lic-red{background:#f85149}

/* The device rename field. It replaces the name in place, so it takes the name's own type and sits
   on the row's background rather than looking like a form dropped into a list. A bare <input> here
   inherits the browser's 13px sans and 2px padding -- text against its own border, which is the
   thing this stylesheet is not allowed to do. */
.dv-in{font:inherit;font-size:var(--fs-body);font-weight:600;color:var(--ink);
  background:var(--bg0);border:var(--hair) solid var(--line2);border-radius:7px;
  padding:6px 10px;min-width:0;width:min(28ch,100%);max-width:100%}
.dv-in:focus{outline:2px solid var(--amber);outline-offset:1px;border-color:transparent}

/* The account page sits on the shared page shell (main.wrap) like every other content page rather
   than on the sign-in card, so its heading is full width and its panels start below it instead of
   beside it. It keeps a reading measure of its own: a settings list at 1120px puts the Release
   button a hand's width from the device it releases. */
.acctpage{max-width:56rem;margin-inline:auto;padding-bottom:72px}

/* ★ACCOUNT-PAGE FORM CONTROLS. The account page sits on main.wrap, not the .auth sign-in card, so
   a bare <input>/<select> here fell back to the browser's WHITE box on the dark page -- the CLI
   password field, the key-count numbers and the key-kind select all rendered white (reported
   2026-10-11). They get the .auth field styling; .dv-in styles itself and checkboxes/radios keep
   the native control. */
.acctpage input:not([type=checkbox]):not([type=radio]):not(.dv-in),
.acctpage select,.acctpage textarea{
  background:var(--code);color:var(--ink);border:var(--hair) solid var(--line2);
  border-radius:var(--rad-s);font:inherit;font-size:var(--fs-read);padding:12px 14px;
  transition:border-color .15s ease,box-shadow .15s ease}
.acctpage input[type=number]{padding:8px 10px;text-align:center;-moz-appearance:textfield}
/* ★NUMBER-INPUT SPINNERS. The native WebKit stepper on `Machines` and `Expires in` rendered as a
   white up/down box on the dark form (reported 2026-10-13) -- the one control the account styling
   did not reach, because the spinner is a shadow-DOM pseudo-element, not the input box itself. Hidden
   on both engines; min/max and typed entry are unchanged. */
.acctpage input[type=number]::-webkit-outer-spin-button,
.acctpage input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
/* ★CUSTOM NUMBER STEPPER (operator, 2026-10-13). Earlier tries -- the native white spinner, then tiny
   corner triangles -- did not land: the click target was a few pixels and the proportions read wrong.
   This is a segmented [ - value + ]: one bordered control with the input centred between two wide,
   full-height buttons, so the whole of each button answers the click. A disabled field (single-use
   Machines) dims the whole control so it reads as locked, not as a second editable box. */
.acctpage .stepper{display:inline-flex;align-items:stretch;vertical-align:middle;overflow:hidden;
  border:var(--hair) solid var(--line2);border-radius:var(--rad-s);background:var(--code)}
.acctpage .stepper input[type=number]{width:46px;padding:11px 4px;border:0;border-radius:0;background:none;
  border-left:var(--hair) solid var(--line);border-right:var(--hair) solid var(--line);box-shadow:none}
.acctpage .stepper input[type=number]:focus-visible{outline:none;box-shadow:inset 0 0 0 2px rgba(218,91,117,.38)}
.acctpage .stepper .step{display:grid;place-items:center;width:34px;border:0;background:none;color:var(--dim);
  cursor:pointer;font-size:var(--fs-read);line-height:1;padding:0;font-family:var(--sans),sans-serif;
  transition:background .1s ease,color .1s ease}
@media(hover:hover) and (pointer:fine){ .acctpage .stepper .step:hover{color:var(--ink);background:var(--bg3)} }
.acctpage .stepper .step:active{background:var(--line)}
.stepper.is-off{opacity:.5}
.stepper.is-off .step{pointer-events:none}
/* ★A LOCKED FIELD must look locked, and controls should answer the pointer (operator, 2026-10-13). */
.acctpage input:disabled{opacity:.55;cursor:not-allowed}
@media(hover:hover) and (pointer:fine){
  .acctpage input:not([type=checkbox]):not([type=radio]):not(.dv-in):not(:disabled):hover,
  .acctpage select:hover{border-color:var(--mut)} }
/* ★INLINE RENAME input + its Save/Cancel. The account input rule excludes .dv-in (meant to style
   itself) and it had none, so the edit fell back to a white browser box -- the broken rename seen
   2026-10-13. Styled to match the form; the name's pencil and id hide while it is open. */
.dv-in{background:var(--code);color:var(--ink);border:var(--hair) solid var(--line2);border-radius:var(--rad-s);
  font:inherit;font-size:var(--fs-body);padding:5px 9px;width:190px;max-width:100%;min-width:0}
.dv-in:focus-visible{outline:none;border-color:var(--sig);box-shadow:0 0 0 3px rgba(218,91,117,.22)}
.dv-head .dv-save,.dv-head .btn.sm{padding:5px 11px;font-size:var(--fs-micro)}
/* The id sits clear of the rename pencil so it is obvious the pencil edits the NAME, not the id. */
.dv-head .dv-id{margin-left:10px}
.acctpage input:not([type=checkbox]):not([type=radio]):not(.dv-in):focus-visible,
.acctpage select:focus-visible,.acctpage textarea:focus-visible{
  outline:none;border-color:var(--sig);box-shadow:0 0 0 3px rgba(218,91,117,.22)}

/* ★THE ROW CONTROLS WERE SHOUTING. Operator, 2026-10-05: Rename and Release stand out from
   everything around them and the row is hard to scan. `.btn.sm` is 600-weight monospace at meta
   size -- correct for a page's call to action, wrong for the second and third control on every
   line of a list, where the thing that should carry the eye is the device name. Lighter weight,
   micro size, the row's dim colour until hover, and a glyph so the two are told apart before the
   words are read. */
.row-act .rowbtn,.row-act .dv-ren{font-weight:500;font-size:var(--fs-micro);padding:6px 10px;
  color:var(--dim);gap:6px}
.row-act .rowbtn .i,.row-act .dv-ren .i{font-size:var(--ic-sm);opacity:.8}
@media(hover:hover) and (pointer:fine){
  .row-act .rowbtn:hover,.row-act .dv-ren:hover{color:var(--ink);border-color:var(--line2)} }

/* ★DEVICE ROWS -- COMPACT (operator, 2026-10-13). A long list was hard to scan: three lines per
   device plus three text buttons, each row ~3 lines tall. Now two lines -- name, its rename icon and
   the short id on the first; key type and last-used on the second -- with the controls as icons.
   Scoped to #devs so the CLI-key rows (which share .row) are untouched. */
#devs .row{padding:11px 13px;gap:10px;margin-top:8px}
.dv-head{display:flex;align-items:center;gap:7px;min-width:0;flex-wrap:wrap}
.dv-head .dv-nm{flex:0 1 auto;min-width:0}          /* shrinks and ellipsises before the id wraps */
#devs .row .row-sub{margin-top:2px;font-size:var(--fs-micro)}
/* The short id on the primary line: quiet label, first group brightest, the rest dim. */
.dv-head .dv-id{flex:none;color:var(--dim);font-size:var(--fs-small);letter-spacing:.02em;white-space:nowrap}
.dv-head .dv-id .dv-id-lbl{font-size:var(--fs-micro);opacity:.65;margin-right:1px}
.dv-head .dv-id .dv-id-k{color:var(--ink);font-weight:600}
.dv-head .dv-id .dv-id-x{color:var(--dim);font-weight:400}
/* ★ICON-ONLY ROW CONTROLS. Rename beside the name, Release in the action cell -- a glyph each with a
   tooltip, so the row carries them without a line of words. Revoke keeps its word: it is destructive
   and asks you to type the device name, and a bare glyph for that is a trap, not a space saving. */
.iconbtn{display:inline-grid;place-items:center;width:28px;height:28px;border-radius:8px;
  border:var(--hair) solid transparent;background:none;color:var(--dim);cursor:pointer;padding:0;flex:none}
.iconbtn .i{font-size:var(--ic-sm)}
@media(hover:hover) and (pointer:fine){
  .iconbtn:hover{color:var(--ink);border-color:var(--line2);background:var(--bg3)} }
.dv-head .dv-ren{margin-left:-1px}
#devs .row-act{gap:4px}
@media(max-width:560px){ #devs .row-act{grid-column:2;justify-self:start;width:auto}
  #devs .row-act .btn{width:auto} }
/* ★LOADING SKELETON. Shown in #devs until /v1/devices/list answers, so the section is not an empty
   gap on a slow fetch; replaced by the real list (or a note) the moment the response lands. */
.dv-skel{height:56px;border-radius:var(--rad-s);border:var(--hair) solid var(--line);
  background:linear-gradient(90deg,var(--bg2) 25%,var(--bg3) 50%,var(--bg2) 75%);
  background-size:200% 100%;animation:dvskel 1.3s ease-in-out infinite}
.dv-skel + .dv-skel{margin-top:8px}
@keyframes dvskel{0%{background-position:200% 0}100%{background-position:-200% 0}}
@media(prefers-reduced-motion:reduce){.dv-skel{animation:none}}

/* ---- resolution audit fixes, 2026-10-05 -------------------------------------------------------
   Measured at 375 / 768 / 1280 across ten pages rather than eyeballed. Three distinct causes of a
   page that scrolls sideways on a phone, and a set of boxes whose text sat within a few pixels of
   their own edge. */

/* A 64-character digest is one unbroken word, so it set a 422px minimum on a 375px screen and the
   whole of /benchmarks scrolled. Only mono runs inside prose: a table cell may still be wide,
   because .tw scrolls and is meant to. */
/* Widened after re-measuring: scoping this to prose containers missed the digests that sit in a
   <span class="mono"> directly inside a section. Every mono run may break anywhere EXCEPT inside
   .tw, where the table is meant to be wider than the screen and scroll. */
.mono{overflow-wrap:anywhere}
.tw .mono{overflow-wrap:normal}

/* A button whose label is a filename ("Download veizik-metal-2026.10.02-r5.dmg") is 380px wide and
   .btn is nowrap, so /download pushed itself off a phone screen. Buttons may wrap below 560px --
   two short lines inside the button beat a page that pans. */
@media(max-width:560px){ .btn{white-space:normal;text-align:left} }

/* Text was sitting 1px from this box's left border: `.term` is the terminal card on the home page
   and its <pre> carried the padding while the bar above it did not. */
.term .bar{padding-left:12px;padding-right:12px}

/* 5-6px of air above the words in a pill or a chip reads as a vertical squeeze next to the 12-16px
   everything else gets. These are small controls, so they do not need much -- they need some. */
.pill{padding-top:5px;padding-bottom:5px}
.chip{padding-top:7px;padding-bottom:7px}
.credit-edge{padding-top:9px}
.vz-panel figcaption{padding-top:7px;padding-bottom:7px}

/* ===== loading state + profile menu (shared by vz.css pages and v3 pages; vz.js builds the menu) ===== */
/* A spinner and a sentence instead of grey placeholder bars: the bars read as a broken page and ran
   into the footer while the account answer was in flight (operator, 2026-10-20). */
.vz-loading{display:flex;align-items:center;justify-content:center;gap:12px;min-height:240px;margin:22px 0 48px;
  color:var(--dim,#8b909c);font-size:var(--fs-body)}
.vz-loading.sm{min-height:96px;margin:0;justify-content:flex-start;font-size:var(--fs-body)}
.vz-spin{width:22px;height:22px;border-radius:50%;border:2px solid rgba(218,91,117,.22);border-top-color:#da5b75;
  animation:vzspin .8s linear infinite;flex:none}
@keyframes vzspin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){.vz-spin{animation-duration:2.4s}}
.vz-pm{position:fixed;z-index:200;min-width:248px;padding:6px;border-radius:14px;
  background:var(--bg1,#15171c);border:1px solid var(--line2,rgba(255,255,255,.12));
  box-shadow:0 18px 48px rgba(0,0,0,.45);font-size:var(--fs-body)}
.vz-pm[hidden]{display:none}
.vz-pm .who{padding:10px 12px 12px;border-bottom:1px solid var(--line,rgba(255,255,255,.08));margin-bottom:6px}
.vz-pm .who b{display:block;color:var(--ink,#eef0f4);font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.vz-pm .who span{display:block;margin-top:2px;color:var(--dim,#8b909c);font-size:var(--fs-meta)}
.vz-pm a,.vz-pm button{display:flex;align-items:center;width:100%;min-height:38px;padding:0 12px;border:0;border-radius:9px;
  background:none;color:var(--ink2,#c9ccd4);font:inherit;text-align:left;cursor:pointer;text-decoration:none}
.vz-pm a:hover,.vz-pm a:focus-visible,.vz-pm button:hover,.vz-pm button:focus-visible{background:rgba(255,255,255,.06);color:var(--ink,#eef0f4);outline:none}
.vz-pm hr{border:0;border-top:1px solid var(--line,rgba(255,255,255,.08));margin:6px 0}
.vz-pm .out{color:#e57f93}
#nav-acct[aria-expanded="true"]{box-shadow:0 0 0 2px rgba(218,91,117,.55)}
